Merge changes from topic "am-001bee2d-9835-47fb-8025-42a04afc6478" into oc-dev am: 9431523eca am: e7f9d8c8cc am: 8e6e2df7af
am: 41c633f611

Change-Id: I6e1d6da7315f3bcca1b642d67f023d49aee6565b
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..66d28c2 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -207,7 +207,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.
diff --git a/apps/CameraITS/pymodules/its/image.py b/apps/CameraITS/pymodules/its/image.py
index 24b48bb..3fe7f15 100644
--- a/apps/CameraITS/pymodules/its/image.py
+++ b/apps/CameraITS/pymodules/its/image.py
@@ -646,7 +646,10 @@
     ytile = math.ceil(ynorm * hfull)
     wtile = math.floor(wnorm * wfull)
     htile = math.floor(hnorm * hfull)
-    return img[ytile:ytile+htile,xtile:xtile+wtile,:].copy()
+    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..7594f9f 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,16 @@
         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']
+            s_values = 's_write: %d, s_read: %d' % (s_req, s_res)
+            assert s_req >= s_res, s_values
+            assert s_res/float(s_req) > ERROR_TOLERANCE, ERROR_TOLERANCE
 
 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_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_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..5e7128b
--- /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/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..17d49ec 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):
@@ -79,6 +108,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 +130,7 @@
             ],
         "scene3": [
             "test_3a_consistency",
+            "test_flip_mirror",
             "test_lens_movement_reporting",
             "test_lens_position"
             ],
@@ -139,6 +170,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 +190,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 +220,7 @@
                     break
 
         if not valid_scenes:
-            print "Unknown scene specifiied:", s
+            print 'Unknown scene specified:', s
             assert False
         scenes = temp_scenes
 
@@ -245,6 +282,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 +318,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 +339,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 +382,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..10c33ba 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -38,9 +38,14 @@
                                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_PACKAGE_NAME := CtsVerifier
 
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 4e885ad..779725a 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"
@@ -1099,6 +1115,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 +1882,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 +1951,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 +2212,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 +2349,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 +2370,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 +2378,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 +2413,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 +2749,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 db4ff1d..ff340b4 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -566,10 +566,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 +948,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>
@@ -1540,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>
@@ -1579,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\"
@@ -1597,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>
@@ -1894,7 +1936,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>
 
@@ -1923,6 +1966,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">
@@ -2410,9 +2482,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>
@@ -2473,57 +2545,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>
@@ -2683,6 +2750,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">
@@ -2956,6 +3159,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_mode">Disallow config location mode</string>
+    <string name="disallow_config_location_mode_action">Toggling location switch bar or changing location mode state in quick settings</string>
+    <string name="disallow_airplane_mode">Disallow airplane mode</string>
+    <string name="disallow_airplane_mode_action">Toggling airplane mode switch bar or changing airplane mode state in quick settings</string>
+    <string name="disallow_config_screen_timeout">Disallow config sleep options settings</string>
+    <string name="disallow_config_screen_timeout_action">Configuring sleep options in Display or Battery page.</string>
+    <string name="disallow_config_brightness">Disallow config brightness settings</string>
+    <string name="disallow_config_brightness_action">Configuring brightness level or adaptive brightness in Display or Battery page, or toggling brightness slider in quick settings</string>
+    <string name="disallow_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>
@@ -2989,7 +3204,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>
@@ -3035,7 +3252,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:
@@ -3347,6 +3564,23 @@
     <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>
+
     <!-- 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/camera/its/ItsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
index 697ad93..febaf08 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;
@@ -59,6 +62,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;
@@ -93,6 +97,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;
@@ -270,6 +277,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 +301,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);
         }
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..d7a5033 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
@@ -101,6 +101,7 @@
     private DialogTestListItem mConfirmWorkCredentials;
     private DialogTestListItem mParentProfilePassword;
     private TestListItem mVpnTest;
+    private TestListItem mKeyChainTest;
     private TestListItem mAlwaysOnVpnSettingsTest;
     private TestListItem mRecentsTest;
     private TestListItem mDisallowAppsControlTest;
@@ -415,6 +416,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",
@@ -563,6 +570,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..c62f111 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,7 @@
             "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 EXTRA_USER_RESTRICTION =
             "com.android.cts.verifier.managedprovisioning.extra.USER_RESTRICTION";
@@ -171,9 +180,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 +228,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 +472,20 @@
                         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;
             }
         } catch (Exception e) {
             Log.e(TAG, "Failed to execute command: " + intent, e);
@@ -562,4 +589,26 @@
             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();
+    }
 }
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..974b063 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,9 @@
             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 AFFILIATION_ID = "affiliationId";
 
     public static ComponentName getReceiverComponentName() {
         return RECEIVER_COMPONENT_NAME;
@@ -76,6 +89,40 @@
                 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);
+                }
+            });
+        }
+    }
+
     private void setupProfile(Context context) {
         DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
         dpm.setProfileEnabled(new ComponentName(context.getApplicationContext(), getClass()));
@@ -117,6 +164,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 +191,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..6c87b84 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,8 @@
     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 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
@@ -171,8 +170,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 +184,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 +201,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 +215,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 +226,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 +240,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 +278,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 +324,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 +333,18 @@
                     compIntent));
         }
 
+        // Managed user
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)
+                && UserManager.supportsMultipleUsers()) {
+            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())}));
+        }
+
         // Network logging UI
         adapter.add(createInteractiveTestItem(this, NETWORK_LOGGING_UI_TEST_ID,
                 R.string.device_owner_network_logging_ui,
@@ -365,14 +385,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 +403,12 @@
                         CommandReceiverActivity.COMMAND_DISABLE_NETWORK_LOGGING);
     }
 
+    private Intent createCreateManagedUserIntent() {
+        return new Intent(this, CommandReceiverActivity.class)
+                .putExtra(CommandReceiverActivity.EXTRA_COMMAND,
+                        CommandReceiverActivity.COMMAND_CREATE_MANAGED_USER);
+    }
+
     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..630fe1e
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ICrossUserService.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.managedprovisioning;
+
+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..a6e6b2f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/LockTaskUiTestActivity.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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_RECENTS;
+import static android.app.admin.DevicePolicyManager.LOCK_TASK_FEATURE_SYSTEM_INFO;
+
+import static com.android.cts.verifier.managedprovisioning.Utils.createInteractiveTestItem;
+
+import android.app.Activity;
+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_RECENTS,
+                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..66f6b61 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_MODE,
+        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_mode,
+            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_mode_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_MODE);
     }
 
     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..94730ec
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BlockChangeReceiver.java
@@ -0,0 +1,32 @@
+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);
+        if (ACTION_NOTIFICATION_CHANNEL_GROUP_BLOCK_STATE_CHANGED.equals(intent.getAction())
+                || ACTION_NOTIFICATION_CHANNEL_BLOCK_STATE_CHANGED.equals(intent.getAction())
+                || ACTION_APP_BLOCK_STATE_CHANGED.equals(intent.getAction())) {
+            SharedPreferences.Editor editor = prefs.edit();
+            String id = intent.getStringExtra(NotificationManager.EXTRA_BLOCK_STATE_CHANGED_ID);
+            if (id == null) {
+                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/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_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..42f1be2
--- /dev/null
+++ b/common/device-side/device-info/src_stub/android/os/VintfObject.java
@@ -0,0 +1,15 @@
+/**
+ * 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; }
+}
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..bc54871 100644
--- a/common/device-side/util/Android.mk
+++ b/common/device-side/util/Android.mk
@@ -23,10 +23,11 @@
     compatibility-common-util-devicesidelib \
     android-support-test \
     ub-uiautomator \
-    mockito-target-minus-junit4 \
-    legacy-android-test
+    mockito-target-minus-junit4
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := \
+    android.test.runner.stubs \
+    android.test.base.stubs
 
 LOCAL_MODULE_TAGS := optional
 
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/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/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/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/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..ad5e60f 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,6 +28,8 @@
 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;
 
@@ -42,7 +43,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";
@@ -75,9 +76,6 @@
     @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/"
@@ -88,6 +86,8 @@
             importance = Importance.ALWAYS)
     private String mSuitePlan;
 
+    private String mTestTag;
+
     /**
      * Util method to inject build attributes into supplied {@link IBuildInfo}
      * @param buildInfo
@@ -102,6 +102,14 @@
      * {@inheritDoc}
      */
     @Override
+    public void setInvocationContext(IInvocationContext invocationContext) {
+        mTestTag = invocationContext.getTestTag();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public IBuildInfo getBuild() {
         // Create a blank BuildInfo which will get populated later.
         String version = null;
@@ -113,7 +121,7 @@
                 version = IBuildInfo.UNKNOWN_BUILD_ID;
             }
         }
-        IBuildInfo ctsBuild = new BuildInfo(version, mTestTag);
+        IBuildInfo ctsBuild = new DeviceBuildInfo(version, mTestTag);
         if (mBranch  != null) {
             ctsBuild.setBuildBranch(mBranch);
         }
@@ -208,7 +216,7 @@
         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");
         }
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..fe29c7f 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
@@ -195,7 +195,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(
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..11a7466 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;
@@ -39,6 +40,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;
@@ -49,6 +51,7 @@
 import com.android.tradefed.result.LogFile;
 import com.android.tradefed.result.LogFileSaver;
 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)
@@ -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/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 3d34f3e..0e23165 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
@@ -66,6 +70,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) {
@@ -81,13 +97,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();
@@ -148,4 +158,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/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/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..a95ebfb 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,7 +16,6 @@
 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;
@@ -41,13 +40,14 @@
 
 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)
@@ -57,50 +57,58 @@
         ISystemStatusCheckerReceiver, IInvocationContextReceiver, IShardableTest {
 
     /**
-     * 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;
@@ -176,11 +184,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 +197,17 @@
         test.setBuild(mBuildInfo);
         test.setSystemStatusChecker(mStatusCheckers);
         test.setInvocationContext(mContext);
+        // reset the retry id - Ensure that retry of retry does not throw
+        test.resetRetryId();
         // 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 +216,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/CompatibilityTestSuite.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/suite/CompatibilityTestSuite.java
index bb74f23..6ce71df 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,33 @@
 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.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.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.testtype.suite.BaseTestSuite;
 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.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 +52,34 @@
             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;
 
-    /**
-     * {@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 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.");
+        }
+        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 +90,21 @@
                 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)));
-            } 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
-            }
-        }
-        filters.clear();
-        filters.addAll(cleanedFilters);
+        super.setupFilters(testDir);
     }
 
     /**
-     * Sets include-filters for the compatibility test
+     * Allow to reset the requested session id for retry.
      */
-    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);
+    public final void resetRetryId() {
+        mRetrySessionId = null;
     }
 }
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..1a3c723 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
@@ -178,6 +178,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..09e16a6 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());
     }
 
     /**
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 bcc82ac9..644ae22 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
@@ -32,6 +32,8 @@
 
 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 +45,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";
@@ -69,6 +72,7 @@
             "neuralnetworks",
             "renderscript",
             "security",
+            "statsd",
             "systems",
             "sysui",
             "telecom",
@@ -79,6 +83,22 @@
     ));
 
     /**
+     * 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"
+    ));
+
+    /**
      * Test that configuration shipped in Tradefed can be parsed.
      * -> Exclude deprecated ApkInstaller.
      * -> Check if host-side tests are non empty.
@@ -121,6 +141,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.
@@ -148,6 +174,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/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..1178a2f 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(), 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(), 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/retry/RetryFactoryTestTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/retry/RetryFactoryTestTest.java
index d67619a..682088c 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,10 +15,9 @@
  */
 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.config.IConfiguration;
@@ -52,7 +51,7 @@
     private RetryFilterHelper mSpyFilter;
 
     /**
-     * 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 {
@@ -90,7 +89,7 @@
         };
         mFactory = new RetryFactoryTest() {
             @Override
-            RetryFilterHelper createFilterHelper(CompatibilityBuildHelper buildHelper) {
+            protected RetryFilterHelper createFilterHelper(CompatibilityBuildHelper buildHelper) {
                 return mSpyFilter;
             }
             @Override
@@ -108,6 +107,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);
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..c394b8d 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));
@@ -491,8 +494,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 +502,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 +534,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_tests.mk b/error_prone_rules_tests.mk
index d17828d..7c729ec 100644
--- a/error_prone_rules_tests.mk
+++ b/error_prone_rules_tests.mk
@@ -17,9 +17,13 @@
 # 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: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..2001880
--- /dev/null
+++ b/hostsidetests/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 := 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
+
+# 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..636ee32
--- /dev/null
+++ b/hostsidetests/api/src/com/android/cts/api/UnofficialApisUsageTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package 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.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 {
+        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/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..5564125 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/EphemeralTest.java
@@ -219,6 +219,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 +227,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) {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
index 87d8bd6..38e4dc2 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,30 +233,30 @@
             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 (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);
@@ -271,13 +266,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));
@@ -301,6 +297,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();
@@ -324,11 +359,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..02187fa 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.TestResult;
+import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
 
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/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..b797b32 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,30 @@
                 "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 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..d64cfd4 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/PkgInstallSignatureVerificationTest.java
@@ -234,6 +234,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 +479,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",
+                "No APK Signature Scheme v2 signature");
         assertInstallEphemeralSucceeds("v2-only-ephemeral.apk");
         assertInstallEphemeralSucceeds("v1-v2-ephemeral.apk"); // signed with both schemes
     }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
index 889b20b..de41603 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
@@ -58,4 +58,8 @@
     public void testDeniesOnceForAll() throws Exception {
         runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest", "testDeniesOnceForAll");
     }
+
+    public void testResetDoNotAskAgain() throws Exception {
+        runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest", "testResetDoNotAskAgain");
+    }
 }
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 fa0120d..6c0efd5 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.TestIdentifier;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
 import com.android.tradefed.device.DeviceNotAvailableException;
+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
@@ -236,7 +240,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<TestIdentifier, 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..3cde96f 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/Utils.java
@@ -18,12 +18,12 @@
 
 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.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);
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.mk b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/Android.mk
new file mode 100644
index 0000000..e4b7962
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialLegacy/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_TAGS := tests
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsAccessSerialLegacy
+
+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..20d51b6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialModern/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 := \
+    compatibility-device-util \
+    android-support-test \
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsAccessSerialModern
+
+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..8c5b768 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,8 @@
      */
     private static final String PUBLIC_FILE_NAME = "public_file.txt";
 
+    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 +78,6 @@
         } catch (FileNotFoundException | SecurityException e) {
             // expected
         }
-        accessPrivateTrafficStats();
     }
 
     private ApplicationInfo getApplicationInfo(String packageName) {
@@ -97,7 +106,7 @@
         }
     }
 
-    private void accessPrivateTrafficStats() throws IOException {
+    public void testAccessPrivateTrafficStats() throws IOException {
         int otherAppUid = -1;
         try {
             otherAppUid = getContext()
@@ -121,4 +130,46 @@
             fail("Was not able to access qtaguid/stats: " + e);
         }
     }
+
+    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 {
+            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/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..576f544 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);
     }
@@ -317,6 +320,55 @@
         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 settings.
+        final Intent intent = new Intent(Settings.ACTION_STORAGE_VOLUME_ACCESS_SETTINGS)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK );
+        final Context context = getInstrumentation().getContext();
+        final String appLabel = context.getPackageManager().getApplicationLabel(
+                context.getApplicationInfo()).toString();
+        context.startActivity(intent);
+
+        // Select app.
+        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();
+
+        // Toggle permission for dir.
+        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.
+        // TODO(b/63720392): currently it only resets the DocumentsUI preference, but it will
+        // eventually grant the permission directly from Settings, in which case we'll need to
+        // change this assertion.
+        userAcceptsTest(volume, dir);
+    }
+
     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/EphemeralTestApp/EphemeralApp1/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/EphemeralApp1/Android.mk
index aaeb8c0..99ee67c 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..0db23d4 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
@@ -994,6 +1010,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/ImplicitlyExposedApp/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/Android.mk
index 31a45b0..6309704 100644
--- a/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/ImplicitlyExposedApp/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/NormalApp/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/NormalApp/Android.mk
index 43deb82..bd1b2d8 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/UserApp/Android.mk b/hostsidetests/appsecurity/test-apps/EphemeralTestApp/UserApp/Android.mk
index 1206e56..35c089f 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 f446140..cfa0b55 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/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/PermissionPolicy25/Android.mk b/hostsidetests/appsecurity/test-apps/PermissionPolicy25/Android.mk
index 9206b6f..0d53ac8 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 e86fae9..5a584c1 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..7c642b1 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
@@ -20,7 +20,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)
 
@@ -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/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 713ba17..351c493 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 2b6c2b0..e1395bb 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 305359c..74f6e6c 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..0d3978c 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
@@ -278,6 +278,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 +359,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 b4ed09f..9e408b3 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
@@ -450,7 +450,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 {
@@ -463,9 +463,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
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp25/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionApp25/Android.mk
index ac4f272..82d69ba 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..4605cfb 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk
@@ -22,7 +22,9 @@
     ../PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.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/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/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 314a92e3..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/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 8dd589a..e137e3e 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;
 
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 116da94..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/Android.mk
+++ /dev/null
@@ -1,45 +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_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/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 c2557ef..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java
+++ /dev/null
@@ -1,230 +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()) {
-            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());
-    }
-}
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/Android.mk b/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/Android.mk
deleted file mode 100644
index 0979e96..0000000
--- a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/Android.mk
+++ /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.
-#
-
-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_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/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..df16a81
--- /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 com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import org.junit.Assume;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests that it is possible to remove apps from the system whitelist
+ */
+public class DeviceIdleWhitelistTest extends DeviceTestCase implements IBuildReceiver {
+
+    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;
+    protected IBuildInfo mCtsBuild;
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        getDevice().executeShellCommand(RESET_SYS_WHITELIST_COMMAND);
+        mOriginalSystemWhitelist = getSystemWhitelist();
+        if (mOriginalSystemWhitelist.size() < 1) {
+            LogUtil.CLog.w("No packages found in system whitelist");
+            Assume.assumeTrue(false);
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+        getDevice().executeShellCommand(RESET_SYS_WHITELIST_COMMAND);
+    }
+
+    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));
+    }
+
+    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/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..71aeff6 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api23/Android.mk
@@ -24,7 +24,11 @@
 
 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
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
index 1c50763..6e6227c 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/api25/Android.mk
@@ -24,7 +24,11 @@
 
 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
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
index 81a23d2..4e2b51c 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/latest/Android.mk
@@ -24,7 +24,11 @@
 
 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
 
@@ -33,6 +37,6 @@
 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..40dfbb3
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearApplicationDataTest.java
@@ -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.
+ */
+
+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 Semaphore mSemaphore = new Semaphore(0);
+    private static final long CLEAR_APPLICATION_DATA_TIMEOUT_S = 10;
+
+    public void testClearApplicationData() throws Exception {
+        mDevicePolicyManager.clearApplicationUserData(ADMIN_RECEIVER_COMPONENT, TEST_PKG,
+                AsyncTask.THREAD_POOL_EXECUTOR,
+                (String pkg, boolean succeeded) -> {
+                    assertEquals(TEST_PKG, pkg);
+                    assertTrue(succeeded);
+                    mSemaphore.release();
+                });
+
+        assertTrue("Clearing application data took too long",
+                mSemaphore.tryAcquire(CLEAR_APPLICATION_DATA_TIMEOUT_S, TimeUnit.SECONDS));
+    }
+}
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..d951fe1
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/MeteredDataRestrictionTest.java
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.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);
+        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..21503dd
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PrintingPolicyTest.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+
+/**
+ * Validates that Device Owner or Profile Owner can disable printing.
+ */
+public class PrintingPolicyTest extends BaseDeviceAdminTest {
+
+    public void testPrintingPolicy() throws Exception {
+        DevicePolicyManager.class
+                .getDeclaredMethod("setPrintingEnabled", ComponentName.class, boolean.class)
+                .invoke(mDevicePolicyManager, ADMIN_RECEIVER_COMPONENT, false);
+        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..835a0f2 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/Android.mk
@@ -22,20 +22,28 @@
 
 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
+    cts-security-test-support-library
 
 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/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/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..6ed1ca7 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,8 @@
  */
 package com.android.cts.deviceowner;
 
-import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.net.Uri;
-import android.os.Process;
-import android.os.UserHandle;
-import android.support.v4.content.LocalBroadcastManager;
 import android.test.AndroidTestCase;
 
 /**
@@ -36,77 +29,24 @@
  */
 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;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
 
-        mDevicePolicyManager = (DevicePolicyManager)
-                mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-        assertDeviceOwner(mDevicePolicyManager);
+        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);
     }
 }
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 a540c27..c91a65d 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.SynchronousQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
 
 /**
  * Test {@link DevicePolicyManager#createAndManageUser}.
@@ -50,6 +61,13 @@
     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;
@@ -122,140 +140,280 @@
         }
     }
 
-// 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) {
+            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);
+    }
+
+    public void testCreateAndManageUser_GetSecondaryUsers() 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(
+        mUserHandle = mDevicePolicyManager.createAndManageUser(
                 getWho(),
                 testUserName,
                 getWho(),
                 null,
-                makeEphemeralFlag);
+                /* flags */ 0);
+        Log.d(TAG, "User create: " + mUserHandle);
+
+        List<UserHandle> secondaryUsers = mDevicePolicyManager.getSecondaryUsers(getWho());
+        assertEquals(1, secondaryUsers.size());
+        assertEquals(mUserHandle, secondaryUsers.get(0));
     }
 
-    /**
-     * 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_SwitchUser() throws Exception {
+        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
+                getContext());
+
         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);
+        mUserHandle = mDevicePolicyManager.createAndManageUser(
+                getWho(),
+                testUserName,
+                getWho(),
+                null,
+                /* flags */ 0);
+        Log.d(TAG, "User create: " + mUserHandle);
 
+        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
+        localBroadcastManager.registerReceiver(broadcastReceiver,
+                new IntentFilter(BasicAdminReceiver.ACTION_USER_SWITCHED));
         try {
-            mDevicePolicyManager.createAndManageUser(
-                    getWho(),
-                    testUserName,
-                    getWho(),
-                    null,
-                    makeEphemeralFlag);
-        } catch (IllegalArgumentException e) {
-            // Success, the expected exception was thrown.
-            return;
+            assertTrue(mDevicePolicyManager.switchUser(getWho(), mUserHandle));
+            assertEquals(mUserHandle, broadcastReceiver.waitForBroadcastReceived());
+        } finally {
+            localBroadcastManager.unregisterReceiver(broadcastReceiver);
         }
-        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_StartInBackground() throws Exception {
+        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
+                getContext());
+
+        String testUserName = "TestUser_" + System.currentTimeMillis();
+
+        mUserHandle = mDevicePolicyManager.createAndManageUser(
+                getWho(),
+                testUserName,
+                getWho(),
+                null,
+                /* flags */ 0);
+        Log.d(TAG, "User create: " + mUserHandle);
+
+        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
+        localBroadcastManager.registerReceiver(broadcastReceiver,
+                new IntentFilter(BasicAdminReceiver.ACTION_USER_STARTED));
+
+        try {
+            // Start user in background and wait for onUserStarted
+            assertTrue(mDevicePolicyManager.startUserInBackground(getWho(), mUserHandle));
+            assertEquals(mUserHandle, broadcastReceiver.waitForBroadcastReceived());
+        } finally {
+            localBroadcastManager.unregisterReceiver(broadcastReceiver);
+        }
+    }
+
+    public void testCreateAndManageUser_StopUser() throws Exception {
+        LocalBroadcastManager localBroadcastManager = LocalBroadcastManager.getInstance(
+                getContext());
+
+        String testUserName = "TestUser_" + System.currentTimeMillis();
+
+        // Do not assign return value to mUserHandle, so it is not removed in tearDown.
+        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
+                getWho(),
+                testUserName,
+                getWho(),
+                null,
+                /* flags */ 0);
+        Log.d(TAG, "User create: " + userHandle);
+        assertTrue(mDevicePolicyManager.startUserInBackground(getWho(), userHandle));
+
+        LocalBroadcastReceiver broadcastReceiver = new LocalBroadcastReceiver();
+        localBroadcastManager.registerReceiver(broadcastReceiver,
+                new IntentFilter(BasicAdminReceiver.ACTION_USER_STOPPED));
+
+        try {
+            assertTrue(mDevicePolicyManager.stopUser(getWho(), userHandle));
+            assertEquals(userHandle, broadcastReceiver.waitForBroadcastReceived());
+        } finally {
+            localBroadcastManager.unregisterReceiver(broadcastReceiver);
+        }
+    }
+
+    @SuppressWarnings("unused")
+    private static void logoutUser(Context context, DevicePolicyManager devicePolicyManager,
+            ComponentName componentName) {
+        assertTrue("cannot logout user", 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);
+
+        // Do not assign return value to mUserHandle, so it is not removed in tearDown.
+        UserHandle userHandle = mDevicePolicyManager.createAndManageUser(
+                getWho(),
+                testUserName,
+                SecondaryUserAdminReceiver.getComponentName(getContext()),
+                bundle,
+                createAndManageUserFlags);
+        Log.d(TAG, "User create: " + userHandle);
+        assertTrue(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() {
@@ -283,7 +441,8 @@
         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));
         try {
             mUserHandle = mDevicePolicyManager.createAndManageUser(getWho(), "Test User", getWho(),
                     null, 0);
@@ -292,7 +451,8 @@
         } 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());
@@ -307,7 +467,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 +478,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..bd51d86 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,41 +15,54 @@
  */
 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_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.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.List;
+import java.util.Set;
+import javax.security.auth.x500.X500Principal;
 
 public class KeyManagementTest extends ActivityInstrumentationTestCase2<KeyManagementActivity> {
 
@@ -67,7 +80,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 +142,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 +234,248 @@
         }
     }
 
+    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);
+    }
+
+    public void testCanGenerateECKeyPairWithKeyAttestation() throws Exception {
+        final String alias = "com.android.test.attested-ec-1";
+        byte[] attestationChallenge = new byte[] {0x01, 0x02, 0x03};
+        try {
+            KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
+                    alias,
+                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                    .setDigests(KeyProperties.DIGEST_SHA256)
+                    .setAttestationChallenge(attestationChallenge)
+                    .build();
+            AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+                    getWho(), "EC", spec, 0);
+            assertNotNull(generated);
+            final KeyPair keyPair = generated.getKeyPair();
+            final String algorithmIdentifier = "SHA256withECDSA";
+            verifySignatureOverData(algorithmIdentifier, keyPair);
+            List<Certificate> attestation = generated.getAttestationRecord();
+            validateAttestationRecord(attestation, attestationChallenge);
+            validateSignatureChain(attestation, keyPair.getPublic());
+        } finally {
+            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+        }
+    }
+
+    public void testCanGenerateECKeyPairWithDeviceIdAttestation() throws Exception {
+        final String alias = "com.android.test.devid-attested-ec-1";
+        byte[] attestationChallenge = new byte[] {0x01, 0x02, 0x03};
+        try {
+            KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(
+                    alias,
+                    KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                    .setDigests(KeyProperties.DIGEST_SHA256)
+                    .setAttestationChallenge(attestationChallenge)
+                    .build();
+            AttestedKeyPair generated = mDevicePolicyManager.generateKeyPair(
+                    getWho(), "EC", spec, ID_TYPE_SERIAL);
+            if (generated == null) {
+                // Since supporting Device ID attestation is optional, do not fail the test if no
+                // attestation record was generated.
+                return;
+            }
+            final KeyPair keyPair = generated.getKeyPair();
+            verifySignatureOverData("SHA256withECDSA", keyPair);
+            List<Certificate> attestation = generated.getAttestationRecord();
+            validateAttestationRecord(attestation, attestationChallenge);
+            validateDeviceIdAttestationData(attestation.get(0), Build.getSerial(), null, null);
+            validateSignatureChain(attestation, keyPair.getPublic());
+        } finally {
+            assertTrue(mDevicePolicyManager.removeKeyPair(getWho(), alias));
+        }
+    }
+
+    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 +545,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..6ef2ba1 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,22 @@
  */
 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_RECENTS;
+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.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 +47,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.TimeUnit;
+
 @RunWith(AndroidJUnit4.class)
 public class LockTaskTest {
 
@@ -46,7 +56,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 +64,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 +120,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 +150,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 +183,34 @@
         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_RECENTS,
+                LOCK_TASK_FEATURE_GLOBAL_ACTIONS,
+                LOCK_TASK_FEATURE_KEYGUARD
+        };
+
+        int cumulative = LOCK_TASK_FEATURE_NONE;
+        for (int flag : flags) {
+            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 +258,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 +300,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 +318,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 +340,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 +413,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 +505,15 @@
     }
 
     /**
+     * Starts LockTaskUtilityActivity with {@link ActivityOptions#setLockTaskMode(boolean)}
+     */
+    private void startLockTaskWithOptions(String className) throws InterruptedException {
+        Intent intent = getLockTaskUtility(className);
+        Bundle options = ActivityOptions.makeBasic().setLockTaskMode(true).toBundle();
+        startAndWait(intent, options);
+    }
+
+    /**
      * Calls stopLockTask on the LockTaskUtilityActivity
      */
     private void stopLockTask(String className) throws InterruptedException {
@@ -435,9 +537,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 +565,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..bc4fe8d 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
@@ -17,13 +17,14 @@
 
 import android.app.admin.SecurityLog.SecurityEvent;
 import android.os.Parcel;
-import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
 
 import java.util.Arrays;
 import java.util.List;
-import java.util.concurrent.TimeUnit;
 
 public class SecurityLoggingTest extends BaseDeviceOwnerTest {
+    private static final String ARG_BATCH_NUMBER = "batchNumber";
+    private static final int BUFFER_ENTRIES_NOTIFICATION_LEVEL = 1024;
 
     /**
      * Test: retrieving security logs can only be done if there's one user on the device or all
@@ -56,14 +57,27 @@
      */
     public void testGetSecurityLogs() {
         List<SecurityEvent> events = mDevicePolicyManager.retrieveSecurityLogs(getWho());
+        String param = InstrumentationRegistry.getArguments().getString(ARG_BATCH_NUMBER);
+        int batchNumber = param == null ? 0 : Integer.parseInt(param);
+        verifySecurityLogs(batchNumber, events);
+    }
 
-        // There must be at least some events, e.g. PackageManager logs all process launches.
+    private static void verifySecurityLogs(int batchNumber, List<SecurityEvent> events) {
         assertTrue("Unable to get events", events != null && events.size() > 0);
-
+        assertTrue(
+                "First id in batch " + events.get(0).getId() + " is too small for the batch number "
+                        + batchNumber,
+                events.get(0).getId() >= (BUFFER_ENTRIES_NOTIFICATION_LEVEL * batchNumber));
         // 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);
 
+            // 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();
             event.writeToParcel(p, 0);
@@ -81,6 +95,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",
@@ -114,7 +130,7 @@
      * Test: retrieving security logs should be rate limited - subsequent attempts should return
      * null.
      */
-    public void testRetrievingSecurityLogsNotPossibleImmediatelyAfterPreviousSuccessfulRetrieval() {
+    public void testSecurityLoggingRetrievalRateLimited() {
         List<SecurityEvent> logs = mDevicePolicyManager.retrieveSecurityLogs(getWho());
         // if logs is null it means that that attempt was rate limited => test PASS
         if (logs != null) {
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/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/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..26f627e 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.wipeDataWithReason(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..1903ced 100644
--- a/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
+++ b/hostsidetests/devicepolicy/app/ProfileOwner/Android.mk
@@ -24,13 +24,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
+    ub-uiautomator
 
 LOCAL_SDK_VERSION := test_current
 
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..d99fd39
--- /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 conscrypt 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..6a62620
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerIncomingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferIncomingTest.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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 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;
+
+@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";
+
+    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));
+    }
+
+    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();
+    }
+
+    private 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..5e3f6a9
--- /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 conscrypt 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..cc9b201
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/DeviceAndProfileOwnerTransferOutgoingTest.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.cts.transferowner;
+
+import static junit.framework.Assert.assertNotNull;
+
+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.os.PersistableBundle;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.lang.reflect.InvocationTargetException;
+
+public abstract class DeviceAndProfileOwnerTransferOutgoingTest {
+    public static class BasicAdminReceiver extends DeviceAdminReceiver {
+        public BasicAdminReceiver() {}
+    }
+
+    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";
+    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");
+
+    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,
+                () -> {
+                    transferOwnership(
+                            mOutgoingComponentName, mOutgoingComponentName, b);
+                });
+    }
+
+    @Test
+    public void testTransferInvalidTarget() {
+        PersistableBundle b = new PersistableBundle();
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> {
+                    transferOwnership(mOutgoingComponentName, INVALID_TARGET_COMPONENT, b);
+                });
+    }
+
+    protected void transferOwnership(ComponentName outgoing, ComponentName incoming,
+            PersistableBundle parameters)
+            throws Throwable {
+        try {
+            mDevicePolicyManager.getClass().getMethod("transferOwnership",
+                    ComponentName.class, ComponentName.class, PersistableBundle.class)
+                    .invoke(mDevicePolicyManager, outgoing, incoming, parameters);
+        } catch (InvocationTargetException e) {
+            throw e.getTargetException();
+        }
+    }
+
+    @Test
+    public void testTransferOwnerChangedBroadcast() throws Throwable {
+        BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(mContext,
+                mOwnerChangedBroadcastAction);
+        try {
+            receiver.register();
+            PersistableBundle b = new PersistableBundle();
+            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();
+        transferOwnership(mOutgoingComponentName, INCOMING_COMPONENT_NAME, b);
+    }
+
+    @Test
+    public void testTransferNoMetadata() throws Throwable {
+        PersistableBundle b = new PersistableBundle();
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> {
+                    transferOwnership(mOutgoingComponentName,
+                            INCOMING_NO_METADATA_COMPONENT_NAME, b);
+                });
+    }
+}
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..3a1709f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/TransferOwnerOutgoingApp/src/com/android/cts/transferowner/TransferDeviceOwnerOutgoingTest.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.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();
+        transferOwnership(mOutgoingComponentName, INCOMING_COMPONENT_NAME, b);
+    }
+
+    @Test
+    public void testTransfer() throws Throwable {
+        PersistableBundle b = new PersistableBundle();
+        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);
+        });
+    }
+}
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..cf760c6
--- /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();
+        transferOwnership(mOutgoingComponentName, INCOMING_COMPONENT_NAME, b);
+    }
+
+    @Test
+    public void testTransfer() throws Throwable {
+        PersistableBundle b = new PersistableBundle();
+        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..b41eac3 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -19,18 +19,24 @@
 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.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.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;
@@ -193,6 +199,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 +229,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 +249,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 +274,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 +306,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);
         }
     }
@@ -398,6 +453,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 +868,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..7367e76
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerHostSideTransferTest.java
@@ -0,0 +1,105 @@
+package com.android.cts.devicepolicy;
+
+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);
+    }
+
+    /* 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..42dce85 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,26 @@
                 Collections.singletonMap(ARG_ALLOW_FAILURE, Boolean.toString(allowFailures)));
     }
 
+    public void testClearApplicationData() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(INTENT_RECEIVER_APK, mUserId);
+        runDeviceTestsAsUser(INTENT_RECEIVER_PKG, INTENT_RECEIVER_PKG + ".ClearApplicationDataTest",
+                "testWriteToSharedPreference", mUserId);
+        executeDeviceTestMethod(".ClearApplicationDataTest", "testClearApplicationData");
+        runDeviceTestsAsUser(INTENT_RECEIVER_PKG, INTENT_RECEIVER_PKG + ".ClearApplicationDataTest",
+                "testSharedPreferenceCleared", mUserId);
+    }
+
+    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 +982,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/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 7a07d7c..7f43ede 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -16,7 +16,12 @@
 
 package com.android.cts.devicepolicy;
 
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+
+import java.io.File;
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -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 BUFFER_SECURITY_ENTRIES_NOTIFICATION_LEVEL = 1024;
+
+    private static final String ARG_NETWORK_LOGGING_BATCH_COUNT = "batchCount";
+
+    /** 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;
+
+    /** CreateAndManageUser is available and an additional user can be created. */
+    private boolean mHasCreateAndManageUserFeature;
 
     @Override
     protected void setUp() throws Exception {
@@ -67,9 +84,11 @@
                 fail("Failed to set device owner");
             }
         }
-        mHasEphemeralUserFeature = mHasFeature && canCreateAdditionalUsers(1) && hasUserSplit();
-        mHasDisabledEphemeralUserFeature =
-                mHasFeature && canCreateAdditionalUsers(1) && !hasUserSplit();
+        mHasForceEphemeralUserFeature = mHasFeature && canCreateAdditionalUsers(1)
+                && hasUserSplit();
+        mHasDisabledForceEphemeralUserFeature = mHasFeature && canCreateAdditionalUsers(1)
+                && !hasUserSplit();
+        mHasCreateAndManageUserFeature = mHasFeature && canCreateAdditionalUsers(1);
     }
 
     @Override
@@ -125,7 +144,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 +155,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 +167,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 +190,7 @@
      * before all other users are removed.
      */
     public void testRemoveUsersOnSetForceEphemeralUsersWithUserSwitch() throws Exception {
-        if (!mHasEphemeralUserFeature) {
+        if (!mHasForceEphemeralUserFeature) {
             return;
         }
 
@@ -206,7 +225,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;
         }
 
@@ -218,14 +237,102 @@
     }
 
     /**
-     * Test creating an epehemeral user using the DevicePolicyManager's createAndManageUser method.
+     * Test creating an user using the DevicePolicyManager's createAndManageUser.
+     * {@link android.app.admin.DevicePolicyManager#getSecondaryUsers} is tested.
      */
-    public void testCreateAndManageEphemeralUser() throws Exception {
-        if (!mHasEphemeralUserFeature) {
+    public void testCreateAndManageUser_GetSecondaryUsers() throws Exception {
+        if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
         }
 
-        executeDeviceTestMethod(".CreateAndManageUserTest", "testCreateAndManageEphemeralUser");
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_GetSecondaryUsers");
+    }
+
+    /**
+     * 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#switchUser} is tested.
+     */
+    public void testCreateAndManageUser_SwitchUser() throws Exception {
+        if (!mHasFeature || !canCreateAdditionalUsers(1) || !canStartAdditionalUsers(1)) {
+            return;
+        }
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_SwitchUser");
+    }
+
+    /**
+     * 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 (!mHasFeature || !canCreateAdditionalUsers(1) || !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#stopUser} is tested.
+     */
+    public void testCreateAndManageUser_StopUser() throws Exception {
+        if (!mHasFeature || !canCreateAdditionalUsers(1) || !canStartAdditionalUsers(1)) {
+            return;
+        }
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_StopUser");
+        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#logoutUser} is tested.
+     */
+    public void testCreateAndManageUser_LogoutUser() throws Exception {
+        if (!mHasFeature || !canCreateAdditionalUsers(1) || !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 (!mHasFeature || !canCreateAdditionalUsers(1) || !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 (!mHasFeature || !canCreateAdditionalUsers(1) || !canStartAdditionalUsers(1)) {
+            return;
+        }
+
+        executeDeviceTestMethod(".CreateAndManageUserTest",
+                "testCreateAndManageUser_Ephemeral");
 
         List<Integer> newUsers = getUsersCreatedByTests();
         assertEquals(1, newUsers.size());
@@ -237,49 +344,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 (!mHasFeature || !canCreateAdditionalUsers(1) || !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)) {
             executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_AddRestrictionSet");
+                    "testCreateAndManageUser_AddRestrictionSet");
         }
     }
 
     public void testCreateAndManageUser_RemoveRestrictionSet() throws Exception {
         if (mHasFeature && canCreateAdditionalUsers(1)) {
             executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testCreateAndManageUser_RemoveRestrictionSet");
+                    "testCreateAndManageUser_RemoveRestrictionSet");
         }
     }
 
     public void testUserAddedOrRemovedBroadcasts() throws Exception {
         if (mHasFeature && canCreateAdditionalUsers(1)) {
             executeDeviceTestMethod(".CreateAndManageUserTest",
-                "testUserAddedOrRemovedBroadcasts");
+                    "testUserAddedOrRemovedBroadcasts");
         }
     }
 
@@ -307,22 +417,53 @@
         if (!mHasFeature) {
             return;
         }
-        executeDeviceTestMethod(".SecurityLoggingTest",
-                "testRetrievingSecurityLogsNotPossibleImmediatelyAfterPreviousSuccessfulRetrieval");
+        executeDeviceTestMethod(".SecurityLoggingTest", "testDisablingSecurityLogging");
+
+        // Generate more than enough events for a batch of security events.
+        int batchSize = BUFFER_SECURITY_ENTRIES_NOTIFICATION_LEVEL + 100;
         try {
             executeDeviceTestMethod(".SecurityLoggingTest", "testEnablingSecurityLogging");
+            // Reboot to ensure ro.device_owner is set to true in logd.
             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");
+
+            // First batch: retrieve and verify the events.
+            runBatch(0, batchSize);
+
+            // Verify event ids are consistent across a consecutive batch.
+            runBatch(1, batchSize);
+
+            // Reboot the device, so the security event ids are reset.
+            rebootAndWaitUntilReady();
+
+            // First batch after reboot: retrieve and verify the events.
+            runBatch(0 /* batch number */, batchSize);
+
+            // Immediately attempting to fetch events again should fail.
+            executeDeviceTestMethod(".SecurityLoggingTest",
+                    "testSecurityLoggingRetrievalRateLimited");
         } finally {
             // Always attempt to disable security logging to bring the device to initial state.
             executeDeviceTestMethod(".SecurityLoggingTest", "testDisablingSecurityLogging");
         }
     }
 
+    private void runBatch(int batchNumber, int batchSize) throws Exception {
+        // Trigger security events of type TAG_ADB_SHELL_CMD.
+        for (int i = 0; i < batchSize; i++) {
+            getDevice().executeShellCommand("adb shell echo just_testing_" + i);
+        }
+
+        // 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));
+
+        // Verify the contents of the batch.
+        executeDeviceTestMethod(".SecurityLoggingTest", "testGetSecurityLogs",
+                Collections.singletonMap(ARG_SECURITY_LOGGING_BATCH_NUMBER,
+                        Integer.toString(batchNumber)));
+    }
+
     public void testNetworkLoggingWithTwoUsers() throws Exception {
         if (!mHasFeature || !canCreateAdditionalUsers(1)) {
             return;
@@ -343,11 +484,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 +597,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 +654,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 +708,13 @@
         executeDeviceOwnerTest("BluetoothRestrictionTest");
     }
 
+    public void testSetTime() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        executeDeviceOwnerTest("SetTimeTest");
+    }
+
     public void testDeviceOwnerProvisioning() throws Exception {
         if (!mHasFeature) {
             return;
@@ -590,7 +750,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 +879,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 +895,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..1521282
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerHostSideTransferTest.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.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");
+            }
+        }
+    }
+}
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..f5a25d0
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerHostSideTransferTest.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 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);
+            }
+        }
+    }
+
+    private 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;
+    }
+}
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..54c0dfb 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BaseDumpsysTest.java
@@ -19,15 +19,15 @@
 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.TestResult;
+import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
 
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/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..2a2cdd9
--- /dev/null
+++ b/hostsidetests/gputools/apps/Android.mk
@@ -0,0 +1,69 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 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
+
+# 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
+
+# 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..69f6930
--- /dev/null
+++ b/hostsidetests/gputools/layers/Android.mk
@@ -0,0 +1,70 @@
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 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
+
+# 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..3ee3374
--- /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/button1"));
+        Assert.assertNotNull(obj);
+        obj.click();
+    }
+
+    private void clickUninstall() {
+        UiObject2 obj = getUiDevice().findObject(By.res("android:id/button2"));
+        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/Android.mk b/hostsidetests/incident/apps/errorsapp/Android.mk
index 191e9b7..1517807 100644
--- a/hostsidetests/incident/apps/errorsapp/Android.mk
+++ b/hostsidetests/incident/apps/errorsapp/Android.mk
@@ -26,7 +26,7 @@
 
 LOCAL_MULTILIB := both
 
-LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs cts-junit
 
 # Includes the jni code as a shared library
 LOCAL_JNI_SHARED_LIBRARIES := libcrash-jni libnativehelper
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/AlarmManagerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
new file mode 100644
index 0000000..59396dc
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/AlarmManagerIncidentTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.AlarmManagerServiceProto;
+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.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 AlarmManagerServiceProto dump =
+                getDump(AlarmManagerServiceProto.parser(), "dumpsys alarm --proto");
+
+        // 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 (ForceAppStandbyTrackerProto.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());
+        }
+
+        testAlarmProtoList(dump.getPendingUserBlockedBackgroundAlarmsList());
+
+        testAlarmProto(dump.getPendingIdleUntil());
+
+        testAlarmProtoList(dump.getPendingWhileIdleAlarmsList());
+
+        testAlarmProto(dump.getNextWakeFromIdle());
+
+        testAlarmProtoList(dump.getPastDueNonWakeupAlarmsList());
+
+        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());
+        }
+
+        long awimds = dump.getAllowWhileIdleMinDurationMs();
+        assertTrue(awimds == settings.getAllowWhileIdleShortDurationMs()
+                || awimds == settings.getAllowWhileIdleLongDurationMs());
+
+        for (AlarmManagerServiceProto.LastAllowWhileIdleDispatch l : dump.getLastAllowWhileIdleDispatchTimesList()) {
+            assertTrue(0 <= l.getUid());
+            assertTrue(0 < l.getTimeMs());
+        }
+
+        for (AlarmManagerServiceProto.TopAlarm ta : dump.getTopAlarmsList()) {
+            assertTrue(0 <= ta.getUid());
+            testFilterStatsProto(ta.getFilter());
+        }
+
+        for (AlarmManagerServiceProto.AlarmStat as : dump.getAlarmStatsList()) {
+            testBroadcastStatsProto(as.getBroadcast());
+            for (FilterStatsProto f : as.getFiltersList()) {
+                testFilterStatsProto(f);
+            }
+        }
+
+        for (IdleDispatchEntryProto id : dump.getAllowWhileIdleDispatchesList()) {
+            assertTrue(0 <= id.getUid());
+            assertTrue(0 <= id.getEntryCreationRealtime());
+            assertTrue(0 <= id.getArgRealtime());
+        }
+
+        for (WakeupEventProto we : dump.getRecentWakeupHistoryList()) {
+            assertTrue(0 <= we.getUid());
+            assertTrue(0 <= we.getWhen());
+        }
+    }
+
+    private void testAlarmProtoList(List<AlarmProto> alarms) throws Exception {
+        for (AlarmProto a : alarms) {
+            testAlarmProto(a);
+        }
+    }
+
+    private void testAlarmProto(AlarmProto alarm) throws Exception {
+        assertNotNull(alarm);
+
+        // 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 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 void testFilterStatsProto(FilterStatsProto filter) throws Exception {
+        assertNotNull(filter);
+
+        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..28000e0 100644
--- a/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryIncidentTest.java
@@ -16,6 +16,7 @@
 
 package com.android.server.cts;
 
+import android.os.BatteryManagerProto;
 import android.service.battery.BatteryServiceDumpProto;
 import com.android.tradefed.device.DeviceNotAvailableException;
 
@@ -37,10 +38,10 @@
             return;
         }
 
-        assertTrue(
-                dump.getPlugged()
-                        != BatteryServiceDumpProto.BatteryPlugged.BATTERY_PLUGGED_WIRELESS);
-        assertTrue(dump.getChargeCounter() > 0);
+        assertTrue(dump.getPlugged() != BatteryManagerProto.PlugType.PLUG_TYPE_WIRELESS);
+        assertTrue(dump.getMaxChargingCurrent() >= 0);
+        assertTrue(dump.getMaxChargingVoltage() >= 0);
+        assertTrue(dump.getChargeCounter() >= 0);
         assertTrue(
                 dump.getStatus() != BatteryServiceDumpProto.BatteryStatus.BATTERY_STATUS_INVALID);
         assertTrue(
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..fb971de
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/BatteryStatsIncidentTest.java
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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");
+        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);
+        }
+
+        testSystemProto(bs.getSystem());
+
+        batteryOffScreenOn();
+    }
+
+    private 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 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 void testSystemProto(SystemProto s) throws Exception {
+        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);
+        long batteryRealtimeMs = b.getBatteryRealtimeMs();
+        long batteryUptimeMs = b.getBatteryUptimeMs();
+        assertTrue(0 <= batteryUptimeMs);
+        assertTrue(batteryUptimeMs <= batteryRealtimeMs);
+        assertTrue(batteryRealtimeMs <= totalRealtimeMs);
+        assertTrue(batteryUptimeMs <= totalUptimeMs);
+        long screenOffRealtimeMs = b.getScreenOffRealtimeMs();
+        long screenOffUptimeMs = b.getScreenOffUptimeMs();
+        assertTrue(0 <= screenOffUptimeMs);
+        assertTrue(screenOffUptimeMs <= screenOffRealtimeMs);
+        assertTrue(screenOffRealtimeMs <= totalRealtimeMs);
+        assertTrue(screenOffUptimeMs <= totalUptimeMs);
+        long screenDozeDurationMs = b.getScreenDozeDurationMs();
+        assertTrue(0 <= screenDozeDurationMs);
+        assertTrue(screenDozeDurationMs <= screenOffRealtimeMs);
+        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 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 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 void testUidProto(UidProto u) 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()) {
+            assertNotNull(j.getName());
+            assertFalse(j.getName().isEmpty());
+            testTimerProto(j.getTotal());
+            testTimerProto(j.getBackground());
+        }
+
+        for (UidProto.JobCompletion jc : u.getJobCompletionList()) {
+            assertNotNull(jc.getName());
+            assertFalse(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()) {
+            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()) {
+            testTimerProto(w.getFull());
+            testTimerProto(w.getPartial());
+            testTimerProto(w.getBackgroundPartial());
+            testTimerProto(w.getWindow());
+        }
+
+        for (UidProto.WakeupAlarm wa : u.getWakeupAlarmList()) {
+            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..537a335 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);
@@ -710,64 +705,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..ed87f11 100644
--- a/hostsidetests/incident/src/com/android/server/cts/FingerprintIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/FingerprintIncidentTest.java
@@ -16,9 +16,9 @@
 
 package com.android.server.cts;
 
-import 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.log.LogUtil.CLog;
 
@@ -27,12 +27,7 @@
  * 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.");
@@ -42,24 +37,28 @@
         final FingerprintServiceDumpProto dump =
                 getDump(FingerprintServiceDumpProto.parser(), "dumpsys fingerprint --proto");
 
-        // One of them
-        assertEquals(1, dump.getUsersCount());
+        // There should be at least one user.
+        assertTrue(1 <= dump.getUsersCount());
 
-        final FingerprintUserStatsProto userStats = dump.getUsers(0);
-        assertEquals(0, userStats.getUserId());
-        assertEquals(0, userStats.getNumFingerprints());
+        for (int i = 0; i < dump.getUsersCount(); ++i) {
+            final FingerprintUserStatsProto userStats = dump.getUsers(i);
+            assertTrue(0 <= userStats.getUserId());
+            assertTrue(0 <= userStats.getNumFingerprints());
 
-        final FingerprintActionStatsProto normal = userStats.getNormal();
-        assertEquals(0, normal.getAccept());
-        assertEquals(0, normal.getReject());
-        assertEquals(0, normal.getAcquire());
-        assertEquals(0, normal.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 FingerprintActionStatsProto crypto = userStats.getCrypto();
-        assertEquals(0, crypto.getAccept());
-        assertEquals(0, crypto.getReject());
-        assertEquals(0, crypto.getAcquire());
-        assertEquals(0, crypto.getLockout());
+            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());
+        }
     }
 }
 
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..fa23d9a
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/JobSchedulerIncidentTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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");
+
+        testConstantsProto(dump.getSettings());
+
+        for (int u : dump.getStartedUsersList()) {
+            assertTrue(0 <= u);
+        }
+
+        for (JobSchedulerServiceDumpProto.RegisteredJob rj : dump.getRegisteredJobsList()) {
+            testJobStatusShortInfoProto(rj.getInfo());
+            testJobStatusDumpProto(rj.getDump());
+        }
+
+        for (StateControllerProto c : dump.getControllersList()) {
+            testStateControllerProto(c);
+        }
+
+        for (JobSchedulerServiceDumpProto.PriorityOverride po : dump.getPriorityOverridesList()) {
+            assertTrue(0 <= po.getUid());
+        }
+
+        for (int buu : dump.getBackingUpUidsList()) {
+            assertTrue(0 <= buu);
+        }
+
+        testJobPackageHistoryProto(dump.getHistory());
+
+        testJobPackageTrackerDumpProto(dump.getPackageTracker());
+
+        for (JobSchedulerServiceDumpProto.PendingJob pj : dump.getPendingJobsList()) {
+            testJobStatusShortInfoProto(pj.getInfo());
+            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());
+            assertTrue(0 <= ajRj.getRunningDurationMs());
+            assertTrue(0 <= ajRj.getTimeUntilTimeoutMs());
+            testJobStatusDumpProto(ajRj.getDump());
+            assertTrue(0 <= ajRj.getTimeSinceMadeActiveMs());
+            assertTrue(0 <= ajRj.getPendingDurationMs());
+        }
+
+        assertTrue(0 <= dump.getMaxActiveJobs());
+    }
+
+    private 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 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 void testJobPackageHistoryProto(JobPackageHistoryProto jph) 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()));
+        }
+    }
+
+    private void testJobPackageTrackerDumpProto(JobPackageTrackerDumpProto jptd) throws Exception {
+        assertNotNull(jptd);
+
+        for (DataSetProto ds : jptd.getHistoricalStatsList()) {
+            testDataSetProto(ds);
+        }
+        testDataSetProto(jptd.getCurrentStats());
+    }
+
+    private void testJobStatusShortInfoProto(JobStatusShortInfoProto jssi) throws Exception {
+        assertNotNull(jssi);
+
+        assertTrue(0 <= jssi.getCallingUid());
+    }
+
+    private 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 void testNetworkRequestProto(NetworkRequestProto nr) throws Exception {
+        assertNotNull(nr);
+
+        assertTrue(NetworkRequestProto.Type.getDescriptor().getValues()
+                .contains(nr.getType().getValueDescriptor()));
+        testNetworkCapabilitesProto(nr.getNetworkCapabilities());
+    }
+
+    private 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 void testStateControllerProto(StateControllerProto sc) throws Exception {
+        assertNotNull(sc);
+
+        StateControllerProto.AppIdleController aic = sc.getAppIdle();
+        for (StateControllerProto.AppIdleController.TrackedJob tj : aic.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo());
+            assertTrue(0 <= tj.getSourceUid());
+        }
+        StateControllerProto.BackgroundJobsController bjc = sc.getBackground();
+        for (StateControllerProto.BackgroundJobsController.TrackedJob tj : bjc.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo());
+            assertTrue(0 <= tj.getSourceUid());
+        }
+        StateControllerProto.BatteryController bc = sc.getBattery();
+        for (StateControllerProto.BatteryController.TrackedJob tj : bc.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo());
+            assertTrue(0 <= tj.getSourceUid());
+        }
+        StateControllerProto.ConnectivityController cc = sc.getConnectivity();
+        for (StateControllerProto.ConnectivityController.TrackedJob tj : cc.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo());
+            assertTrue(0 <= tj.getSourceUid());
+            testNetworkRequestProto(tj.getRequiredNetwork());
+        }
+        StateControllerProto.ContentObserverController coc = sc.getContentObserver();
+        for (StateControllerProto.ContentObserverController.TrackedJob tj : coc.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo());
+            assertTrue(0 <= tj.getSourceUid());
+        }
+        for (StateControllerProto.ContentObserverController.Observer o : coc.getObserversList()) {
+            assertTrue(0 <= o.getUserId());
+
+            for (StateControllerProto.ContentObserverController.Observer.TriggerContentData tcd : o.getTriggersList()) {
+                for (StateControllerProto.ContentObserverController.Observer.TriggerContentData.JobInstance ji : tcd.getJobsList()) {
+                    testJobStatusShortInfoProto(ji.getInfo());
+
+                    assertTrue(0 <= ji.getSourceUid());
+                    assertTrue(0 <= ji.getTriggerContentUpdateDelayMs());
+                    assertTrue(0 <= ji.getTriggerContentMaxDelayMs());
+                }
+            }
+        }
+        StateControllerProto.DeviceIdleJobsController dijc = sc.getDeviceIdle();
+        for (StateControllerProto.DeviceIdleJobsController.TrackedJob tj : dijc.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo());
+            assertTrue(0 <= tj.getSourceUid());
+        }
+        StateControllerProto.IdleController ic = sc.getIdle();
+        for (StateControllerProto.IdleController.TrackedJob tj : ic.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo());
+            assertTrue(0 <= tj.getSourceUid());
+        }
+        StateControllerProto.StorageController scr = sc.getStorage();
+        for (StateControllerProto.StorageController.TrackedJob tj : scr.getTrackedJobsList()) {
+            testJobStatusShortInfoProto(tj.getInfo());
+            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());
+            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..69fe2ac
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/MemInfoIncidentTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.MemInfoProto;
+import com.android.server.am.proto.MemInfoProto.AppData;
+import com.android.server.am.proto.MemInfoProto.MemItem;
+import com.android.server.am.proto.MemInfoProto.ProcessMemory;
+
+/** Test to check that ActivityManager properly outputs meminfo data. */
+public class MemInfoIncidentTest extends ProtoDumpTestCase {
+
+    public void testBatteryServiceDump() throws Exception {
+        final MemInfoProto dump =
+                getDump(MemInfoProto.parser(), "dumpsys meminfo -a --proto");
+
+        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(0 < dump.getTuningMb());
+        assertTrue(0 < dump.getTuningLargeMb());
+
+        assertTrue(0 <= dump.getOomKb());
+
+        assertTrue(0 < dump.getRestoreLimitKb());
+    }
+
+    private 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 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 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 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 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..51b8474
--- /dev/null
+++ b/hostsidetests/incident/src/com/android/server/cts/NotificationIncidentTest.java
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.NotificationRecordProto.State;
+import android.service.notification.RankingHelperProto;
+import android.service.notification.RankingHelperProto.RecordProto;
+import android.service.notification.ZenModeProto;
+import android.service.notification.ZenModeProto.ZenMode;
+
+/**
+ * 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 off, no suppressors
+    public void testZenMode() throws Exception {
+        final NotificationServiceDumpProto dump = getDump(NotificationServiceDumpProto.parser(),
+                "dumpsys notification --proto");
+        ZenModeProto zenProto = dump.getZen();
+
+        assertEquals(ZenMode.ZEN_MODE_OFF, zenProto.getZenMode());
+        assertEquals(0, zenProto.getEnabledActiveConditionsCount());
+
+        // b/64606626 Watches intentionally suppress notifications always
+        if (!getDevice().hasFeature(FEATURE_WATCH)) {
+            assertEquals(0, zenProto.getSuppressedEffects());
+            assertEquals(0, zenProto.getSuppressorsCount());
+        }
+
+        zenProto.getPolicy();
+    }
+}
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 f91c8a7..0000000
--- a/hostsidetests/incident/src/com/android/server/cts/NotificationTest.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 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 off, no suppressors
-    public void testZenMode() throws Exception {
-        final NotificationServiceDumpProto dump = getDump(NotificationServiceDumpProto.parser(),
-                "dumpsys notification --proto");
-        ZenModeProto zenProto = dump.getZen();
-
-        assertEquals(ZenMode.ZEN_MODE_OFF, zenProto.getZenMode());
-        assertEquals(0, zenProto.getEnabledActiveConditionsCount());
-
-        // b/64606626 Watches intentionally suppress notifications always
-        if (!getDevice().hasFeature(FEATURE_WATCH)) {
-            assertEquals(0, zenProto.getSuppressedEffects());
-            assertEquals(0, zenProto.getSuppressorsCount());
-        }
-
-        zenProto.getPolicy();
-    }
-}
diff --git a/hostsidetests/incident/src/com/android/server/cts/PowerIncidentTest.java b/hostsidetests/incident/src/com/android/server/cts/PowerIncidentTest.java
index 77c0163..a0cfd07 100644
--- a/hostsidetests/incident/src/com/android/server/cts/PowerIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/PowerIncidentTest.java
@@ -16,28 +16,37 @@
 
 package com.android.server.cts;
 
+import android.app.ProcessState;
+import android.content.IntentProto;
+import android.os.BatteryManagerProto;
 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");
+
+        assertTrue(dump.getBatteryLevel() >= 0);
+        assertTrue(dump.getBatteryLevel() <= 100);
 
         assertTrue(
-                PowerServiceDumpProto.Wakefulness.getDescriptor()
+                PowerManagerInternalProto.Wakefulness.getDescriptor()
                         .getValues()
                         .contains(dump.getWakefulness().getValueDescriptor()));
         assertTrue(
-                PowerServiceDumpProto.PlugType.getDescriptor()
+                BatteryManagerProto.PlugType.getDescriptor()
                         .getValues()
                         .contains(dump.getPlugType().getValueDescriptor()));
         assertTrue(
-                PowerServiceDumpProto.DockState.getDescriptor()
+                IntentProto.DockState.getDescriptor()
                         .getValues()
                         .contains(dump.getDockState().getValueDescriptor()));
 
@@ -47,22 +56,34 @@
         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(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(ProcessState.getDescriptor()
+                    .getValues()
+                    .contains(us.getProcessState().getValueDescriptor()));
+        }
 
         final LooperProto looper = dump.getLooper();
         assertNotNull(looper.getThreadName());
@@ -70,5 +91,26 @@
         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()) {
+            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..dbb7e07 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.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;
@@ -176,4 +180,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..d9ee469 100644
--- a/hostsidetests/incident/src/com/android/server/cts/SettingsIncidentTest.java
+++ b/hostsidetests/incident/src/com/android/server/cts/SettingsIncidentTest.java
@@ -58,7 +58,7 @@
     private 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());
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..1f694d2 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 {
@@ -135,15 +106,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/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/test/DeviceTestConstants.java b/hostsidetests/inputmethodservice/common/src/android/inputmethodservice/cts/common/test/DeviceTestConstants.java
index aa90e11..4502325 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,4 @@
            "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";
 }
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..643eb52 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,6 +51,11 @@
         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;
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..b1f16e5 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,19 +16,12 @@
 
 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_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.ImeCommandConstants.ACTION_IME_COMMAND;
 import static android.inputmethodservice.cts.common.ImeCommandConstants.COMMAND_SWITCH_INPUT_METHOD;
 import static android.inputmethodservice.cts.common.ImeCommandConstants.EXTRA_ARG_STRING1;
@@ -36,29 +29,24 @@
 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.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 {
@@ -72,8 +60,7 @@
 
         pollingCheck(() -> helper.queryAllEvents()
                         .collect(startingFrom(helper.isStartOfTest()))
-                        .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE)))
-                        .findAny().isPresent(),
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE))),
                 TIMEOUT, "CtsInputMethod1.onCreate is called");
 
         final long startActivityTime = SystemClock.uptimeMillis();
@@ -81,8 +68,7 @@
 
         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");
     }
 
@@ -94,8 +80,7 @@
 
         pollingCheck(() -> helper.queryAllEvents()
                         .collect(startingFrom(helper.isStartOfTest()))
-                        .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE)))
-                        .findAny().isPresent(),
+                        .anyMatch(isFrom(Ime1Constants.CLASS).and(isType(ON_CREATE))),
                 TIMEOUT, "CtsInputMethod1.onCreate is called");
 
         final long startActivityTime = SystemClock.uptimeMillis();
@@ -103,8 +88,7 @@
 
         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 +105,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,97 +116,6 @@
                 "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);
-
-        helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
-        helper.findUiObject(R.id.text_entry).click();
-
-        pollingCheck(() -> !helper.shell(ShellCommandUtils.getCurrentIme())
-                        .equals(Ime1Constants.IME_ID),
-                TIMEOUT,
-                "CtsInputMethod1 is uninstalled or disabled, and current IME becomes other IME");
-    }
-
-    @Test
-    public void testSearchView_giveFocusShowIme1() 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);
-
-        helper.launchActivity(DeviceTestConstants.PACKAGE, DeviceTestConstants.TEST_ACTIVITY_CLASS);
-        helper.findUiObject(R.id.text_entry).click();
-
-        // we should've only one onStartInput call.
-        pollingCheck(() -> helper.queryAllEvents()
-                        .collect(startingFrom(helper.isStartOfTest()))
-                        .filter(isFrom(Ime1Constants.CLASS).and(isType(ON_START_INPUT)))
-                        .findAny()
-                        .isPresent(),
-                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());
-
-        assertEquals("CtsInputMethod1.onStartInput is called exactly once",
-                startInputEvents.size(),
-                1);
-
-        // 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);
-    }
-
     /**
      * Build stream collector of {@link DeviceEvent} collecting sequence that elements have
      * specified types.
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/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..6ae3568 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,18 @@
 
 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_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 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 +42,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();
         if (DEBUG) {
             Log.d(mLogTag, "onCreate:");
         }
-        sendEvent(DeviceEvent.builder().setType(ON_CREATE));
+        sendEvent(ON_CREATE);
 
         super.onCreate();
 
@@ -85,10 +62,8 @@
                     + " 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,8 +74,7 @@
                     + " editorInfo=" + editorInfo
                     + " restarting=" + restarting);
         }
-
-        sendEvent(DeviceEvent.builder().setType(ON_START_INPUT_VIEW));
+        sendEvent(ON_START_INPUT_VIEW, editorInfo, restarting);
 
         super.onStartInputView(editorInfo, restarting);
     }
@@ -110,7 +84,7 @@
         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 +94,7 @@
         if (DEBUG) {
             Log.d(mLogTag, "onFinishInput:");
         }
-        sendEvent(DeviceEvent.builder().setType(ON_FINISH_INPUT));
+        sendEvent(ON_FINISH_INPUT);
 
         super.onFinishInput();
     }
@@ -130,23 +104,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 +142,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/hostside/AndroidTest.xml b/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
index bf7e9a7..0b9dba6 100644
--- a/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
+++ b/hostsidetests/inputmethodservice/hostside/AndroidTest.xml
@@ -16,6 +16,7 @@
 -->
 
 <configuration description="Config for CTS Input Method Service host test cases">
+    <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="framework" />
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command" value="input keyevent KEYCODE_WAKEUP" />
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..bdf0983 100644
--- a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
@@ -22,10 +22,6 @@
 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;
 
@@ -36,33 +32,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 +78,30 @@
 
     @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);
     }
 
     private void sendTestStartEvent(final TestInfo deviceTest) throws Exception {
@@ -179,8 +127,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/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/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/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..e7be900 100644
--- a/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java
+++ b/hostsidetests/media/bitstreams/src/android/media/cts/bitstreams/ReportProcessor.java
@@ -140,6 +140,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..dcb1e5a 100644
--- a/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java
+++ b/hostsidetests/media/src/android/media/cts/BaseMultiUserTest.java
@@ -19,13 +19,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.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.TestResult;
+import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
 
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..133a43b 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
@@ -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;
 
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..47ab9fa 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
@@ -87,7 +87,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 +108,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 +140,7 @@
             enableLocation();
         }
         mSupported = setUpActiveNetworkMeteringState();
+        setAppIdle(false);
 
         Log.i(TAG, "Apps status on " + getName() + ":\n"
                 + "\ttest app: uid=" + mMyUid + ", state=" + getProcessStateByUid(mMyUid) + "\n"
@@ -751,6 +754,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
@@ -1004,7 +1013,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..efc405f 100644
--- a/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java
+++ b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTestCase.java
@@ -20,12 +20,12 @@
 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.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;
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..3a27c78 100644
--- a/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
+++ b/hostsidetests/os/src/android/os/cts/StaticSharedLibsHostTests.java
@@ -19,11 +19,12 @@
 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.TestResult;
+import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
 
@@ -496,7 +497,7 @@
             StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
             for (Map.Entry<TestIdentifier, 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..726e64b
--- /dev/null
+++ b/hostsidetests/seccomp/app/Android.mk
@@ -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.
+
+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++ \
+          libnativehelper \
+          libcutils \
+          libc++ \
+          libpackagelistparser \
+
+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..a0604a1
--- /dev/null
+++ b/hostsidetests/seccomp/app/jni/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_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_SHARED_LIBRARIES := \
+		libnativehelper \
+		liblog \
+		libcutils \
+		libc++ \
+		libpackagelistparser \
+
+
+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..de82b44
--- /dev/null
+++ b/hostsidetests/seccomp/app/jni/android_seccomp_cts_app_SeccompDeviceTest.cpp
@@ -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.
+ */
+
+#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);
+        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 984df84..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" />
@@ -90,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-2017-0564/kernel-headers/linux/ion.h b/hostsidetests/security/securityPatch/CVE-2017-0564/kernel-headers/linux/ion.h
deleted file mode 100644
index 7b5b031..0000000
--- a/hostsidetests/security/securityPatch/CVE-2017-0564/kernel-headers/linux/ion.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/****************************************************************************
- ****************************************************************************
- ***
- ***   This header was automatically generated from a Linux kernel header
- ***   of the same name, to make information necessary for userspace to
- ***   call into the kernel available to libc.  It contains only constants,
- ***   structures, and macros generated from the original header, and thus,
- ***   contains no copyrightable information.
- ***
- ***   To edit the content of this header, modify the corresponding
- ***   source file (e.g. under external/kernel-headers/original/) then
- ***   run bionic/libc/kernel/tools/update_all.py
- ***
- ***   Any manual change here will be lost the next time this script will
- ***   be run. You've been warned!
- ***
- ****************************************************************************
- ****************************************************************************/
-#ifndef _UAPI_LINUX_ION_H
-#define _UAPI_LINUX_ION_H
-#include <linux/ioctl.h>
-#include <linux/types.h>
-typedef int ion_user_handle_t;
-enum ion_heap_type {
-  ION_HEAP_TYPE_SYSTEM,
-  ION_HEAP_TYPE_SYSTEM_CONTIG,
-  ION_HEAP_TYPE_CARVEOUT,
-  ION_HEAP_TYPE_CHUNK,
-  ION_HEAP_TYPE_DMA,
-  ION_HEAP_TYPE_CUSTOM,
-};
-#define ION_NUM_HEAP_IDS (sizeof(unsigned int) * 8)
-#define ION_FLAG_CACHED 1
-#define ION_FLAG_CACHED_NEEDS_SYNC 2
-struct ion_allocation_data {
-  size_t len;
-  size_t align;
-  unsigned int heap_id_mask;
-  unsigned int flags;
-  ion_user_handle_t handle;
-};
-struct ion_fd_data {
-  ion_user_handle_t handle;
-  int fd;
-};
-struct ion_handle_data {
-  ion_user_handle_t handle;
-};
-struct ion_custom_data {
-  unsigned int cmd;
-  unsigned long arg;
-};
-#define MAX_HEAP_NAME 32
-struct ion_heap_data {
-  char name[MAX_HEAP_NAME];
-  __u32 type;
-  __u32 heap_id;
-  __u32 reserved0;
-  __u32 reserved1;
-  __u32 reserved2;
-};
-struct ion_heap_query {
-  __u32 cnt;
-  __u32 reserved0;
-  __u64 heaps;
-  __u32 reserved1;
-  __u32 reserved2;
-};
-#define ION_IOC_MAGIC 'I'
-#define ION_IOC_ALLOC _IOWR(ION_IOC_MAGIC, 0, struct ion_allocation_data)
-#define ION_IOC_FREE _IOWR(ION_IOC_MAGIC, 1, struct ion_handle_data)
-#define ION_IOC_MAP _IOWR(ION_IOC_MAGIC, 2, struct ion_fd_data)
-#define ION_IOC_SHARE _IOWR(ION_IOC_MAGIC, 4, struct ion_fd_data)
-#define ION_IOC_IMPORT _IOWR(ION_IOC_MAGIC, 5, struct ion_fd_data)
-#define ION_IOC_SYNC _IOWR(ION_IOC_MAGIC, 7, struct ion_fd_data)
-#define ION_IOC_CUSTOM _IOWR(ION_IOC_MAGIC, 6, struct ion_custom_data)
-#define ION_IOC_HEAP_QUERY _IOWR(ION_IOC_MAGIC, 8, struct ion_heap_query)
-#endif
diff --git a/hostsidetests/security/securityPatch/CVE-2017-0564/original-kernel-headers/linux/ion.h b/hostsidetests/security/securityPatch/CVE-2017-0564/original-kernel-headers/linux/ion.h
deleted file mode 100644
index 14cd873..0000000
--- a/hostsidetests/security/securityPatch/CVE-2017-0564/original-kernel-headers/linux/ion.h
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * drivers/staging/android/uapi/ion.h
- *
- * Copyright (C) 2011 Google, Inc.
- *
- * This software is licensed under the terms of the GNU General Public
- * License version 2, as published by the Free Software Foundation, and
- * may be copied, distributed, and modified under those terms.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- *
- */
-
-#ifndef _UAPI_LINUX_ION_H
-#define _UAPI_LINUX_ION_H
-
-#include <linux/ioctl.h>
-#include <linux/types.h>
-
-typedef int ion_user_handle_t;
-
-/**
- * enum ion_heap_types - list of all possible types of heaps
- * @ION_HEAP_TYPE_SYSTEM:	 memory allocated via vmalloc
- * @ION_HEAP_TYPE_SYSTEM_CONTIG: memory allocated via kmalloc
- * @ION_HEAP_TYPE_CARVEOUT:	 memory allocated from a prereserved
- *				 carveout heap, allocations are physically
- *				 contiguous
- * @ION_HEAP_TYPE_DMA:		 memory allocated via DMA API
- * @ION_NUM_HEAPS:		 helper for iterating over heaps, a bit mask
- *				 is used to identify the heaps, so only 32
- *				 total heap types are supported
- */
-enum ion_heap_type {
-	ION_HEAP_TYPE_SYSTEM,
-	ION_HEAP_TYPE_SYSTEM_CONTIG,
-	ION_HEAP_TYPE_CARVEOUT,
-	ION_HEAP_TYPE_CHUNK,
-	ION_HEAP_TYPE_DMA,
-	ION_HEAP_TYPE_CUSTOM, /*
-			       * must be last so device specific heaps always
-			       * are at the end of this enum
-			       */
-};
-
-#define ION_NUM_HEAP_IDS		(sizeof(unsigned int) * 8)
-
-/**
- * allocation flags - the lower 16 bits are used by core ion, the upper 16
- * bits are reserved for use by the heaps themselves.
- */
-
-/*
- * mappings of this buffer should be cached, ion will do cache maintenance
- * when the buffer is mapped for dma
- */
-#define ION_FLAG_CACHED 1
-
-/*
- * mappings of this buffer will created at mmap time, if this is set
- * caches must be managed manually
- */
-#define ION_FLAG_CACHED_NEEDS_SYNC 2
-
-/**
- * DOC: Ion Userspace API
- *
- * create a client by opening /dev/ion
- * most operations handled via following ioctls
- *
- */
-
-/**
- * struct ion_allocation_data - metadata passed from userspace for allocations
- * @len:		size of the allocation
- * @align:		required alignment of the allocation
- * @heap_id_mask:	mask of heap ids to allocate from
- * @flags:		flags passed to heap
- * @handle:		pointer that will be populated with a cookie to use to
- *			refer to this allocation
- *
- * Provided by userspace as an argument to the ioctl
- */
-struct ion_allocation_data {
-	size_t len;
-	size_t align;
-	unsigned int heap_id_mask;
-	unsigned int flags;
-	ion_user_handle_t handle;
-};
-
-/**
- * struct ion_fd_data - metadata passed to/from userspace for a handle/fd pair
- * @handle:	a handle
- * @fd:		a file descriptor representing that handle
- *
- * For ION_IOC_SHARE or ION_IOC_MAP userspace populates the handle field with
- * the handle returned from ion alloc, and the kernel returns the file
- * descriptor to share or map in the fd field.  For ION_IOC_IMPORT, userspace
- * provides the file descriptor and the kernel returns the handle.
- */
-struct ion_fd_data {
-	ion_user_handle_t handle;
-	int fd;
-};
-
-/**
- * struct ion_handle_data - a handle passed to/from the kernel
- * @handle:	a handle
- */
-struct ion_handle_data {
-	ion_user_handle_t handle;
-};
-
-/**
- * struct ion_custom_data - metadata passed to/from userspace for a custom ioctl
- * @cmd:	the custom ioctl function to call
- * @arg:	additional data to pass to the custom ioctl, typically a user
- *		pointer to a predefined structure
- *
- * This works just like the regular cmd and arg fields of an ioctl.
- */
-struct ion_custom_data {
-	unsigned int cmd;
-	unsigned long arg;
-};
-
-#define MAX_HEAP_NAME			32
-
-/**
- * struct ion_heap_data - data about a heap
- * @name - first 32 characters of the heap name
- * @type - heap type
- * @heap_id - heap id for the heap
- */
-struct ion_heap_data {
-	char name[MAX_HEAP_NAME];
-	__u32 type;
-	__u32 heap_id;
-	__u32 reserved0;
-	__u32 reserved1;
-	__u32 reserved2;
-};
-
-/**
- * struct ion_heap_query - collection of data about all heaps
- * @cnt - total number of heaps to be copied
- * @heaps - buffer to copy heap data
- */
-struct ion_heap_query {
-	__u32 cnt; /* Total number of heaps to be copied */
-	__u32 reserved0; /* align to 64bits */
-	__u64 heaps; /* buffer to be populated */
-	__u32 reserved1;
-	__u32 reserved2;
-};
-
-#define ION_IOC_MAGIC		'I'
-
-/**
- * DOC: ION_IOC_ALLOC - allocate memory
- *
- * Takes an ion_allocation_data struct and returns it with the handle field
- * populated with the opaque handle for the allocation.
- */
-#define ION_IOC_ALLOC		_IOWR(ION_IOC_MAGIC, 0, \
-				      struct ion_allocation_data)
-
-/**
- * DOC: ION_IOC_FREE - free memory
- *
- * Takes an ion_handle_data struct and frees the handle.
- */
-#define ION_IOC_FREE		_IOWR(ION_IOC_MAGIC, 1, struct ion_handle_data)
-
-/**
- * DOC: ION_IOC_MAP - get a file descriptor to mmap
- *
- * Takes an ion_fd_data struct with the handle field populated with a valid
- * opaque handle.  Returns the struct with the fd field set to a file
- * descriptor open in the current address space.  This file descriptor
- * can then be used as an argument to mmap.
- */
-#define ION_IOC_MAP		_IOWR(ION_IOC_MAGIC, 2, struct ion_fd_data)
-
-/**
- * DOC: ION_IOC_SHARE - creates a file descriptor to use to share an allocation
- *
- * Takes an ion_fd_data struct with the handle field populated with a valid
- * opaque handle.  Returns the struct with the fd field set to a file
- * descriptor open in the current address space.  This file descriptor
- * can then be passed to another process.  The corresponding opaque handle can
- * be retrieved via ION_IOC_IMPORT.
- */
-#define ION_IOC_SHARE		_IOWR(ION_IOC_MAGIC, 4, struct ion_fd_data)
-
-/**
- * DOC: ION_IOC_IMPORT - imports a shared file descriptor
- *
- * Takes an ion_fd_data struct with the fd field populated with a valid file
- * descriptor obtained from ION_IOC_SHARE and returns the struct with the handle
- * filed set to the corresponding opaque handle.
- */
-#define ION_IOC_IMPORT		_IOWR(ION_IOC_MAGIC, 5, struct ion_fd_data)
-
-/**
- * DOC: ION_IOC_SYNC - syncs a shared file descriptors to memory
- *
- * Deprecated in favor of using the dma_buf api's correctly (syncing
- * will happen automatically when the buffer is mapped to a device).
- * If necessary should be used after touching a cached buffer from the cpu,
- * this will make the buffer in memory coherent.
- */
-#define ION_IOC_SYNC		_IOWR(ION_IOC_MAGIC, 7, struct ion_fd_data)
-
-/**
- * DOC: ION_IOC_CUSTOM - call architecture specific ion ioctl
- *
- * Takes the argument of the architecture specific ioctl to call and
- * passes appropriate userdata for that ioctl
- */
-#define ION_IOC_CUSTOM		_IOWR(ION_IOC_MAGIC, 6, struct ion_custom_data)
-
-/**
- * DOC: ION_IOC_HEAP_QUERY - information about available heaps
- *
- * Takes an ion_heap_query structure and populates information about
- * available Ion heaps.
- */
-#define ION_IOC_HEAP_QUERY     _IOWR(ION_IOC_MAGIC, 8, \
-					struct ion_heap_query)
-
-#endif /* _UAPI_LINUX_ION_H */
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_12.java b/hostsidetests/security/src/android/security/cts/Poc16_12.java
index a0258c0..8ae30d6 100644
--- a/hostsidetests/security/src/android/security/cts/Poc16_12.java
+++ b/hostsidetests/security/src/android/security/cts/Poc16_12.java
@@ -125,73 +125,6 @@
     }
 
     /**
-     *  b/32700935
-     */
-    @SecurityTest
-    public void testPocCVE_2016_8435() throws Exception {
-        enableAdbRoot(getDevice());
-        if(containsDriver(getDevice(), "/dev/dri/renderD129")) {
-            AdbUtils.runPoc("CVE-2016-8435", getDevice(), 60);
-        }
-    }
-
-    /**
-     *  b/31568617
-     */
-    @SecurityTest
-    public void testPocCVE_2016_9120() throws Exception {
-        enableAdbRoot(getDevice());
-        if(containsDriver(getDevice(), "/dev/ion")) {
-            AdbUtils.runPoc("CVE-2016-9120", getDevice(), 60);
-        }
-    }
-
-    //Highs
-    /**
-     *  b/31225246
-     */
-    @SecurityTest
-    public void testPocCVE_2016_8412() throws Exception {
-        enableAdbRoot(getDevice());
-        if(containsDriver(getDevice(), "/dev/v4l-subdev7")) {
-            AdbUtils.runPoc("CVE-2016-8412", getDevice(), 60);
-        }
-    }
-
-    /**
-     *  b/31243641
-     */
-    @SecurityTest
-    public void testPocCVE_2016_8444() throws Exception {
-        enableAdbRoot(getDevice());
-        if(containsDriver(getDevice(), "/dev/v4l-subdev17")) {
-            AdbUtils.runPoc("CVE-2016-8444", getDevice(), 60);
-        }
-    }
-
-    /**
-     *  b/31791148
-     */
-    @SecurityTest
-    public void testPocCVE_2016_8448() throws Exception {
-        enableAdbRoot(getDevice());
-        if(containsDriver(getDevice(), "/dev/graphics/fb0")) {
-            AdbUtils.runPoc("CVE-2016-8448", getDevice(), 60);
-        }
-    }
-
-    /**
-     *  b/31798848
-     */
-    @SecurityTest
-    public void testPocCVE_2016_8449() throws Exception {
-        enableAdbRoot(getDevice());
-        if(containsDriver(getDevice(), "/dev/tegra_avpchannel")) {
-            AdbUtils.runPoc("CVE-2016-8449", getDevice(), 60);
-        }
-    }
-
-    /**
      *  b/31668540
      */
     @SecurityTest
@@ -203,37 +136,6 @@
     }
 
     /**
-     *  b/32402548
-     */
-    @SecurityTest
-    public void testPocCVE_2017_0403() throws Exception {
-        enableAdbRoot(getDevice());
-        AdbUtils.runPoc("CVE-2017-0403", getDevice(), 60);
-    }
-
-    /**
-     *  b/32510733
-     */
-    @SecurityTest
-    public void testPocCVE_2017_0404() throws Exception {
-        enableAdbRoot(getDevice());
-        if(containsDriver(getDevice(), "/proc/asound/version")) {
-            AdbUtils.runPoc("CVE-2017-0404", getDevice(), 60);
-        }
-    }
-
-    /**
-     *  b/32178033
-     */
-    @SecurityTest
-    public void testPocCVE_2016_8451() throws Exception {
-        enableAdbRoot(getDevice());
-        String command =
-            "echo AAAAAAAAA > /sys/devices/f9924000.i2c/i2c-2/2-0070/power_control";
-        AdbUtils.runCommandLine(command, getDevice());
-    }
-
-    /**
      *  b/32659848
      */
     @SecurityTest
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 45b30c6..a75352c 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];
@@ -657,7 +657,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/security/src/android/security/cts/SecurityTestCase.java b/hostsidetests/security/src/android/security/cts/SecurityTestCase.java
index 277d591..85df4fe 100644
--- a/hostsidetests/security/src/android/security/cts/SecurityTestCase.java
+++ b/hostsidetests/security/src/android/security/cts/SecurityTestCase.java
@@ -16,12 +16,20 @@
 
 package android.security.cts;
 
+import com.android.tradefed.device.CollectingOutputReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.NativeDevice;
 import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.log.LogUtil.CLog;
 
+import android.platform.test.annotations.RootPermissionTest;
+
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Scanner;
 import java.util.regex.Pattern;
 
 public class SecurityTestCase extends DeviceTestCase {
@@ -54,18 +62,18 @@
     }
 
     /**
-     * Use {@link NativeDevice#enableAdbRoot()} internally.
-     *
-     * The test methods calling this function should run even if enableAdbRoot fails, which is why 
-     * the return value is ignored. However, we may want to act on that data point in the future.
+     * Takes a device and runs a root command.  There is a more robust version implemented by
+     * NativeDevice, but due to some other changes it isnt trivially acessible, but I can get
+     * that implementation fairly easy if we think it is a better idea.
      */
-    public boolean enableAdbRoot(ITestDevice mDevice) throws DeviceNotAvailableException {
-        if(mDevice.enableAdbRoot()) {
-            return true;
-        } else {
-            CLog.w("\"enable-root\" set to false! Root is required to check if device is vulnerable.");
-            return false;
+    public void enableAdbRoot(ITestDevice mDevice) throws DeviceNotAvailableException {
+        boolean isUserDebug =
+            "userdebug".equals(mDevice.executeShellCommand("getprop ro.build.type").trim());
+        if (!isUserDebug) {
+            //TODO(badash@): This would Noop once cl: ag/1594311 is in
+            return;
         }
+        mDevice.executeAdbCommand("root");
     }
 
     /**
@@ -92,7 +100,7 @@
         assertTrue("Phone has had a hard reset",
             (System.currentTimeMillis()/1000 - uptime - kernelStartTime < 2));
         //TODO(badash@): add ability to catch runtime restart
-        getDevice().disableAdbRoot();
+        getDevice().executeAdbCommand("unroot");
     }
 
     public void assertMatches(String pattern, String input) throws Exception {
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/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/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/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/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 025f9b8..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/ActivityManagerAppConfigurationTests.java
+++ /dev/null
@@ -1,598 +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 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);
-    }
-
-    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 a441e56..0000000
--- a/hostsidetests/services/activityandwindowmanager/activitymanager/src/android/server/cts/KeyguardLockedTests.java
+++ /dev/null
@@ -1,202 +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.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/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/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/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/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/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 bfed6d5..0000000
--- a/hostsidetests/services/activityandwindowmanager/windowmanager/src/android/server/cts/AlertWindowsTests.java
+++ /dev/null
@@ -1,168 +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 void assertAlertWindows(String packageName, boolean hasAlertWindowPermission,
-            boolean atLeastO) {
-        final WindowManagerState wMState = mAmWmState.getWmState();
-
-        final ArrayList<WindowManagerState.WindowState> alertWindows = new ArrayList();
-        wMState.getWindowsByPackageName(packageName, mAlertWindowTypes, alertWindows);
-
-        if (!hasAlertWindowPermission) {
-            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..c5d4f7f 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
@@ -18,13 +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.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.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;
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..9e5890a
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/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)
+
+LOCAL_PACKAGE_NAME := CtsStatsdAtomsApp
+
+# 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..db7c886
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
@@ -0,0 +1,83 @@
+<?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_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.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 -->
+
+
+    <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:launchMode="singleTop" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <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" />
+
+    </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/AndroidTest.xml b/hostsidetests/statsd/apps/statsdapp/AndroidTest.xml
new file mode 100644
index 0000000..402a73b
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/AndroidTest.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.
+-->
+<configuration description="Config for CTS Statsd 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="CtsStatsdAtomsApp.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="pm grant com.android.server.cts.device.statsd android.permission.DUMP" />
+        <option name="teardown-command" value="pm revoke com.android.server.cts.device.statsd android.permission.DUMP"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.server.cts.device.statsd" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
\ 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/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..3cc2816
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
@@ -0,0 +1,402 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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 testAudioState() {
+        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 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 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
+    /** 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) {
+                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/MetricsTests.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/MetricsTests.java
new file mode 100644
index 0000000..0bd1af7
--- /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__DISPLAY_STATE__STATE_OFF);
+        StatsLog.write(StatsLog.SCREEN_STATE_CHANGED,
+                StatsLog.SCREEN_STATE_CHANGED__DISPLAY_STATE__STATE_ON);
+    }
+
+    @Test
+    public void testEventCountWithCondition() {
+        StatsLog.write(StatsLog.DEVICE_TEMPERATURE_REPORTED, 25);
+        StatsLog.write(StatsLog.SCREEN_STATE_CHANGED,
+                StatsLog.SCREEN_STATE_CHANGED__DISPLAY_STATE__STATE_OFF);
+        StatsLog.write(StatsLog.DEVICE_TEMPERATURE_REPORTED, 26);
+    }
+}
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..a598475
--- /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");
+                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..f2c3cda
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundService.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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";
+
+    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/VideoPlayerActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/VideoPlayerActivity.java
new file mode 100644
index 0000000..b15ec35
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/VideoPlayerActivity.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 com.android.server.cts.device.statsd;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.VideoView;
+
+public class VideoPlayerActivity extends Activity {
+    /** Called when the activity is first created. */
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        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();
+    }
+}
+
+
+
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..8561614
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
@@ -0,0 +1,526 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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.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.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 {
+
+    private static final String UPDATE_CONFIG_CMD = "cmd stats config update";
+    private static final String DUMP_REPORT_CMD = "cmd stats dump-report";
+    private static final String REMOVE_CONFIG_CMD = "cmd stats config remove";
+    protected static final String CONFIG_UID = "1000";
+    /** ID of the config, which evaluates to -1572883457. */
+    protected 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 {
+        File configFile = File.createTempFile("statsdconfig", ".config");
+        configFile.deleteOnExit();
+        Files.write(config.toByteArray(), configFile);
+        String remotePath = "/data/" + configFile.getName();
+        getDevice().pushFile(configFile, remotePath);
+        getDevice().executeShellCommand(
+                String.join(" ", "cat", remotePath, "|", UPDATE_CONFIG_CMD, CONFIG_UID,
+                        String.valueOf(CONFIG_ID)));
+        getDevice().executeShellCommand("rm " + remotePath);
+    }
+
+    protected void removeConfig(long configId) throws Exception {
+        getDevice().executeShellCommand(
+                String.join(" ", REMOVE_CONFIG_CMD, CONFIG_UID, String.valueOf(configId)));
+    }
+
+    protected List<EventMetricData> getEventMetricDataList() throws Exception {
+        ConfigMetricsReportList reportList = getReportList();
+        assertTrue(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::getTimestampNanos));
+
+        LogUtil.CLog.d("Get EventMetricDataList as following:\n");
+        for (EventMetricData d : data) {
+            LogUtil.CLog.d("Atom at " + d.getTimestampNanos() + ":\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()) {
+            data.add(gaugeMetricData.getBucketInfo(0).getAtom());
+        }
+
+        LogUtil.CLog.d("Get GaugeMetricDataList as following:\n");
+        for (Atom d : data) {
+            LogUtil.CLog.d("Atom:\n" + d.toString());
+        }
+        return data;
+    }
+
+    protected ConfigMetricsReportList getReportList() throws Exception {
+        ConfigMetricsReportList reportList = getDump(ConfigMetricsReportList.parser(),
+                String.join(" ", DUMP_REPORT_CMD, CONFIG_UID, String.valueOf(CONFIG_ID), "--proto"));
+        return reportList;
+    }
+
+    /** 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.DISPLAY_STATE_FIELD_NUMBER)
+                                .setEqInt(ScreenStateChanged.State.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.DISPLAY_STATE_FIELD_NUMBER)
+                                        .setEqInt(ScreenStateChanged.State.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();
+    }
+
+    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");
+    }
+
+    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 getScreenTimeoutMs() throws Exception {
+        return Integer.parseInt(
+                getDevice().executeShellCommand("settings get system screen_off_timeout").trim());
+    }
+    protected void setScreenTimeoutMs(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();
+    }
+
+    protected void assertScreenOff() throws Exception {
+        final long deadLine = System.currentTimeMillis() + SCREEN_STATE_CHANGE_TIMEOUT;
+        boolean screenAwake = true;
+        do {
+            final String dumpsysPower = getDevice().executeShellCommand("dumpsys power").trim();
+            for (String line : dumpsysPower.split("\n")) {
+                if (line.contains("Display Power")) {
+                    screenAwake = line.trim().endsWith("ON");
+                    break;
+                }
+            }
+            Thread.sleep(SCREEN_STATE_POLLING_INTERVAL);
+        } while (screenAwake && System.currentTimeMillis() < deadLine);
+        assertFalse("Screen could not be turned off", screenAwake);
+    }
+
+    protected void assertScreenOn() throws Exception {
+        // this also checks that the keyguard is dismissed
+        final long deadLine = System.currentTimeMillis() + SCREEN_STATE_CHANGE_TIMEOUT;
+        boolean screenAwake;
+        do {
+            final String dumpsysWindowPolicy =
+                    getDevice().executeShellCommand("dumpsys window policy").trim();
+            boolean keyguardStateLines = false;
+            screenAwake = true;
+            for (String line : dumpsysWindowPolicy.split("\n")) {
+                if (line.contains("KeyguardServiceDelegate")) {
+                    keyguardStateLines = true;
+                } else if (keyguardStateLines && line.contains("showing=")) {
+                    screenAwake &= line.trim().endsWith("false");
+                } else if (keyguardStateLines && line.contains("screenState=")) {
+                    screenAwake &= line.trim().endsWith("SCREEN_STATE_ON");
+                }
+            }
+            Thread.sleep(SCREEN_STATE_POLLING_INTERVAL);
+        } while (!screenAwake && System.currentTimeMillis() < deadLine);
+        assertTrue("Screen could not be turned on", screenAwake);
+    }
+
+    /**
+     * 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.getTimestampNanos() - d0.getTimestampNanos()) / 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;
+    }
+
+}
\ No newline at end of file
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..2db4438
--- /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.TestIdentifier;
+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.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<TestIdentifier, 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..2241322
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/DeviceAtomTestCase.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.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 = "CtsStatsdAtomsApp.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);
+        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));
+    }
+}
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..ddb59d4
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/HostAtomTests.java
@@ -0,0 +1,643 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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.KernelWakelock;
+import com.android.os.AtomsProto.PluggedStateChanged;
+import com.android.os.AtomsProto.ScreenStateChanged;
+import com.android.os.AtomsProto.SubsystemSleepState;
+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 adb (hostside).
+ */
+public class HostAtomTests extends AtomTestCase {
+
+    private static final String TAG = "Statsd.HostAtomTests";
+
+    private static final boolean TESTS_ENABLED = false;
+    // For tests that require incidentd. Keep as true until TESTS_ENABLED is permanently enabled.
+    private static final boolean INCIDENTD_TESTS_ENABLED = true;
+
+    public void testScreenStateChangedAtom() throws Exception {
+        if (!TESTS_ENABLED) {return;}
+
+        // Setup, make sure the screen is off.
+        turnScreenOff();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        final int atomTag = Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER;
+
+        Set<Integer> screenOnStates = new HashSet<>(
+                Arrays.asList(ScreenStateChanged.State.STATE_ON_VALUE,
+                        ScreenStateChanged.State.STATE_ON_SUSPEND_VALUE,
+                        ScreenStateChanged.State.STATE_VR_VALUE));
+        Set<Integer> screenOffStates = new HashSet<>(
+                Arrays.asList(ScreenStateChanged.State.STATE_OFF_VALUE,
+                        ScreenStateChanged.State.STATE_DOZE_VALUE,
+                        ScreenStateChanged.State.STATE_DOZE_SUSPEND_VALUE,
+                        ScreenStateChanged.State.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().getDisplayState().getNumber());
+    }
+
+    public void testChargingStateChangedAtom() throws Exception {
+        if (!TESTS_ENABLED) {return;}
+
+        // 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(ChargingStateChanged.State.BATTERY_STATUS_UNKNOWN_VALUE));
+        Set<Integer> batteryChargingStates = new HashSet<>(
+                Arrays.asList(ChargingStateChanged.State.BATTERY_STATUS_CHARGING_VALUE));
+        Set<Integer> batteryDischargingStates = new HashSet<>(
+                Arrays.asList(ChargingStateChanged.State.BATTERY_STATUS_DISCHARGING_VALUE));
+        Set<Integer> batteryNotChargingStates = new HashSet<>(
+                Arrays.asList(ChargingStateChanged.State.BATTERY_STATUS_NOT_CHARGING_VALUE));
+        Set<Integer> batteryFullStates = new HashSet<>(
+                Arrays.asList(ChargingStateChanged.State.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().getChargingState().getNumber());
+    }
+
+    public void testPluggedStateChangedAtom() throws Exception {
+        if (!TESTS_ENABLED) {return;}
+
+        // 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(PluggedStateChanged.State.BATTERY_PLUGGED_NONE_VALUE));
+        Set<Integer> acStates = new HashSet<>(
+                Arrays.asList(PluggedStateChanged.State.BATTERY_PLUGGED_AC_VALUE));
+        Set<Integer> usbStates = new HashSet<>(
+                Arrays.asList(PluggedStateChanged.State.BATTERY_PLUGGED_USB_VALUE));
+        Set<Integer> wirelessStates = new HashSet<>(
+                Arrays.asList(PluggedStateChanged.State.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().getPluggedState().getNumber());
+    }
+
+    public void testBatteryLevelChangedAtom() throws Exception {
+        if (!TESTS_ENABLED) {return;}
+
+        // 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 {
+        if (!TESTS_ENABLED) {return;}
+
+        // Setup: record initial brightness state, set mode to manual and brightness to full.
+        int initialBrightness = getScreenBrightness();
+        boolean isInitialManual = isScreenBrightnessModeManual();
+        int initialTimeout = getScreenTimeoutMs();
+        setScreenTimeoutMs(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);
+        setScreenTimeoutMs(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 {
+        if (!TESTS_ENABLED) {return;}
+
+        // 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(DeviceIdleModeStateChanged.State.DEVICE_IDLE_MODE_OFF_VALUE));
+        Set<Integer> dozeLight = new HashSet<>(
+                Arrays.asList(DeviceIdleModeStateChanged.State.DEVICE_IDLE_MODE_LIGHT_VALUE));
+        Set<Integer> dozeDeep = new HashSet<>(
+                Arrays.asList(DeviceIdleModeStateChanged.State.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 {
+        if (!TESTS_ENABLED) {return;}
+
+        // 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());
+    }
+
+    // TODO: Anomaly detection will be moved to general statsd device-side tests.
+    // Tests that anomaly detection for count works.
+    // Also tests that anomaly detection works when spanning multiple buckets.
+    public void testCountAnomalyDetection() throws Exception {
+        if (!TESTS_ENABLED) return;
+        if (!INCIDENTD_TESTS_ENABLED) return;
+        // TODO: Don't use screen-state as the atom.
+        StatsdConfig config = getPulledAndAnomalyConfig()
+                .addCountMetric(CountMetric.newBuilder()
+                        .setId("METRIC".hashCode())
+                        .setWhat("SCREEN_TURNED_ON".hashCode())
+                        .setBucket(TimeUnit.CTS)
+                )
+                .addAlert(Alert.newBuilder()
+                        .setId("testCountAnomalyDetectionAlert".hashCode())
+                        .setMetricId("METRIC".hashCode())
+                        .setNumBuckets(4)
+                        .setRefractoryPeriodSecs(20)
+                        .setTriggerIfSumGt(2)
+                )
+                .addSubscription(Subscription.newBuilder()
+                        .setId("AlertSub".hashCode())
+                        .setRuleType(Subscription.RuleType.ALERT)
+                        .setRuleId("testCountAnomalyDetectionAlert".hashCode())
+                        .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(-1))
+                )
+                .build();
+        uploadConfig(config);
+
+        String markDeviceDate = getCurrentLogcatDate();
+        turnScreenOn(); // count -> 1 (not an anomaly, since not "greater than 2")
+        Thread.sleep(1000);
+        turnScreenOff();
+        Thread.sleep(3000);
+        assertFalse(didIncidentdFireSince(markDeviceDate));
+
+        turnScreenOn(); // count ->2 (not an anomaly, since not "greater than 2")
+        Thread.sleep(1000);
+        turnScreenOff();
+        Thread.sleep(1000);
+        assertFalse(didIncidentdFireSince(markDeviceDate));
+
+        turnScreenOn(); // count ->3 (anomaly, since "greater than 2"!)
+        Thread.sleep(1000);
+        assertTrue(didIncidentdFireSince(markDeviceDate));
+
+        turnScreenOff();
+    }
+
+    // Tests that anomaly detection for duration works.
+    // Also tests that refractory periods in anomaly detection work.
+    public void testDurationAnomalyDetection() throws Exception {
+        if (!TESTS_ENABLED) return;
+        if (!INCIDENTD_TESTS_ENABLED) return;
+        // TODO: Do NOT use screenState for this, since screens auto-turn-off after a variable time.
+        StatsdConfig config = getPulledAndAnomalyConfig()
+                .addDurationMetric(DurationMetric.newBuilder()
+                        .setId("METRIC".hashCode())
+                        .setWhat("SCREEN_IS_ON".hashCode())
+                        .setAggregationType(DurationMetric.AggregationType.SUM)
+                        .setBucket(TimeUnit.CTS)
+                )
+                .addAlert(Alert.newBuilder()
+                        .setId("testDurationAnomalyDetectionAlert".hashCode())
+                        .setMetricId("METRIC".hashCode())
+                        .setNumBuckets(12)
+                        .setRefractoryPeriodSecs(20)
+                        .setTriggerIfSumGt(15_000_000_000L) // 15 seconds in nanoseconds
+                )
+                .addSubscription(Subscription.newBuilder()
+                        .setId("AlertSub".hashCode())
+                        .setRuleType(Subscription.RuleType.ALERT)
+                        .setRuleId("testDurationAnomalyDetectionAlert".hashCode())
+                        .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(-1))
+                )
+                .build();
+        uploadConfig(config);
+
+        // Test that alarm doesn't fire early.
+        String markDeviceDate = getCurrentLogcatDate();
+        turnScreenOn();
+        Thread.sleep(6_000);
+        assertFalse(didIncidentdFireSince(markDeviceDate));
+
+        turnScreenOff();
+        Thread.sleep(1_000);
+        assertFalse(didIncidentdFireSince(markDeviceDate));
+
+        // Test that alarm does fire when it is supposed to.
+        turnScreenOn();
+        Thread.sleep(13_000);
+        assertTrue(didIncidentdFireSince(markDeviceDate));
+
+        // Now test that the refractory period is obeyed.
+        markDeviceDate = getCurrentLogcatDate();
+        turnScreenOff();
+        Thread.sleep(1_000);
+        turnScreenOn();
+        Thread.sleep(1_000);
+        assertFalse(didIncidentdFireSince(markDeviceDate));
+
+        // Test that detection works again after refractory period finishes.
+        turnScreenOff();
+        Thread.sleep(20_000);
+        turnScreenOn();
+        Thread.sleep(15_000);
+        assertTrue(didIncidentdFireSince(markDeviceDate));
+    }
+
+    // TODO: There is no value anomaly detection code yet! So this will fail.
+    // Tests that anomaly detection for value works.
+    public void testValueAnomalyDetection() throws Exception {
+        if (!TESTS_ENABLED) return;
+        if (!INCIDENTD_TESTS_ENABLED) return;
+        // TODO: Definitely don't use screen-state as the atom. This MUST be changed.
+        StatsdConfig config = getPulledAndAnomalyConfig()
+                .addValueMetric(ValueMetric.newBuilder()
+                        .setId("METRIC".hashCode())
+                        .setWhat("SCREEN_TURNED_ON".hashCode())
+                        .setValueField(FieldMatcher.newBuilder()
+                                .setField(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+                                .addChild(FieldMatcher.newBuilder()
+                                        .setField(ScreenStateChanged.DISPLAY_STATE_FIELD_NUMBER)))
+                        .setBucket(TimeUnit.CTS)
+                )
+                .addAlert(Alert.newBuilder()
+                        .setId("testValueAnomalyDetectionAlert".hashCode())
+                        .setMetricId("METRIC".hashCode())
+                        .setNumBuckets(4)
+                        .setRefractoryPeriodSecs(20)
+                        .setTriggerIfSumGt(ScreenStateChanged.State.STATE_OFF.getNumber())
+                )
+                .addSubscription(Subscription.newBuilder()
+                        .setId("AlertSub".hashCode())
+                        .setRuleType(Subscription.RuleType.ALERT)
+                        .setRuleId("testValueAnomalyDetectionAlert".hashCode())
+                        .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(-1))
+                )
+                .build();
+        uploadConfig(config);
+
+        turnScreenOff();
+        String markDeviceDate = getCurrentLogcatDate();
+        turnScreenOff(); // value = STATE_OFF = 1 (probably)
+        Thread.sleep(2000);
+        assertFalse(didIncidentdFireSince(markDeviceDate));
+        turnScreenOn(); // value = STATE_ON = 2 (probably)
+        Thread.sleep(2000);
+        assertTrue(didIncidentdFireSince(markDeviceDate));
+
+        turnScreenOff();
+    }
+
+    // Tests that anomaly detection for gauge works.
+    public void testGaugeAnomalyDetection() throws Exception {
+        if (!TESTS_ENABLED) return;
+        if (!INCIDENTD_TESTS_ENABLED) return;
+        // TODO: Definitely don't use screen-state as the atom. This MUST be changed.
+        StatsdConfig config = getPulledAndAnomalyConfig()
+                .addGaugeMetric(GaugeMetric.newBuilder()
+                        .setId("METRIC".hashCode())
+                        .setWhat("SCREEN_TURNED_ON".hashCode())
+                        .setGaugeFieldsFilter(
+                                FieldFilter.newBuilder()
+                                        .setFields(FieldMatcher.newBuilder()
+                                                .setField(Atom.SCREEN_STATE_CHANGED_FIELD_NUMBER)
+                                                .addChild(FieldMatcher.newBuilder()
+                                                        .setField(ScreenStateChanged.DISPLAY_STATE_FIELD_NUMBER))
+                                        ))
+                        .setBucket(TimeUnit.CTS)
+                )
+                .addAlert(Alert.newBuilder()
+                        .setId("testGaugeAnomalyDetectionAlert".hashCode())
+                        .setMetricId("METRIC".hashCode())
+                        .setNumBuckets(1)
+                        .setRefractoryPeriodSecs(20)
+                        .setTriggerIfSumGt(ScreenStateChanged.State.STATE_OFF.getNumber())
+                )
+                .addSubscription(Subscription.newBuilder()
+                        .setId("AlertSub".hashCode())
+                        .setRuleType(Subscription.RuleType.ALERT)
+                        .setRuleId("testGaugeAnomalyDetectionAlert".hashCode())
+                        .setIncidentdDetails(IncidentdDetails.newBuilder().addSection(-1))
+                )
+                .build();
+        uploadConfig(config);
+
+        turnScreenOff();
+        String markDeviceDate = getCurrentLogcatDate();
+        turnScreenOff(); // gauge = STATE_OFF = 1 (probably)
+        Thread.sleep(2000);
+        assertFalse(didIncidentdFireSince(markDeviceDate));
+        turnScreenOn(); // gauge = STATE_ON = 2 (probably)
+        Thread.sleep(2000);
+        assertTrue(didIncidentdFireSince(markDeviceDate));
+
+        turnScreenOff();
+    }
+
+    public void testKernelWakelock() throws Exception {
+        if (!TESTS_ENABLED) {return;}
+        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 {
+        if (!TESTS_ENABLED) {return;}
+        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().getTimeMs() > 0);
+    }
+
+    public void testSubsystemSleepState() throws Exception {
+        if (!TESTS_ENABLED) {return;}
+        StatsdConfig.Builder config = getPulledAndAnomalyConfig();
+        FieldMatcher.Builder dimension = FieldMatcher.newBuilder()
+                .setField(Atom.SUBSYSTEM_SLEEP_STATE_FIELD_NUMBER)
+                .addChild(FieldMatcher.newBuilder()
+                        .setField(SubsystemSleepState.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().getName().equals(""));
+            assertTrue(atom.getSubsystemSleepState().getCount() >= 0);
+            assertTrue(atom.getSubsystemSleepState().getTimeMs() >= 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..8743277
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/ProcStateAtomTests.java
@@ -0,0 +1,289 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ProcessState; // from atoms.proto's activitymanager.proto's enum.
+
+import com.android.os.AtomsProto.Atom;
+import com.android.os.StatsLog.EventMetricData;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+/**
+ * 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 boolean TESTS_ENABLED = false;
+
+    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(
+                    ProcessState.PROCESS_STATE_IMPORTANT_BACKGROUND_VALUE,
+                    ProcessState.PROCESS_STATE_TRANSIENT_BACKGROUND_VALUE,
+                    ProcessState.PROCESS_STATE_BACKUP_VALUE,
+                    ProcessState.PROCESS_STATE_SERVICE_VALUE,
+                    ProcessState.PROCESS_STATE_RECEIVER_VALUE,
+                    ProcessState.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(
+                    ProcessState.PROCESS_STATE_HOME_VALUE,
+                    ProcessState.PROCESS_STATE_LAST_ACTIVITY_VALUE,
+                    ProcessState.PROCESS_STATE_CACHED_ACTIVITY_VALUE,
+                    ProcessState.PROCESS_STATE_CACHED_ACTIVITY_CLIENT_VALUE,
+                    ProcessState.PROCESS_STATE_CACHED_RECENT_VALUE,
+                    ProcessState.PROCESS_STATE_CACHED_EMPTY_VALUE
+            ));
+
+    private static final Set<Integer> MISC_STATES = new HashSet<>(
+            Arrays.asList(
+                    ProcessState.PROCESS_STATE_PERSISTENT_VALUE, // TODO: untested
+                    ProcessState.PROCESS_STATE_PERSISTENT_UI_VALUE, // TODO: untested
+                    ProcessState.PROCESS_STATE_TOP_VALUE,
+                    ProcessState.PROCESS_STATE_BOUND_FOREGROUND_SERVICE_VALUE, // TODO: untested
+                    ProcessState.PROCESS_STATE_FOREGROUND_SERVICE_VALUE,
+                    ProcessState.PROCESS_STATE_IMPORTANT_FOREGROUND_VALUE,
+                    ProcessState.PROCESS_STATE_TOP_SLEEPING_VALUE,
+
+                    ProcessState.PROCESS_STATE_UNKNOWN_VALUE,
+                    ProcessState.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;
+
+    public void testForegroundService() throws Exception {
+        if (!TESTS_ENABLED) return;
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessState.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 {
+        if (!TESTS_ENABLED) return;
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessState.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();
+        assertScreenOn();
+        executeForegroundActivity(ACTION_SHOW_APPLICATION_OVERLAY);
+        final int waitTime = 2 * 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, 0, PROC_STATE_FUNCTION);
+
+        turnScreenOff();
+    }
+
+    public void testBackground() throws Exception {
+        if (!TESTS_ENABLED) return;
+        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 {
+        if (!TESTS_ENABLED) return;
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessState.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();
+        assertScreenOn();
+        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);
+
+        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 {
+        if (!TESTS_ENABLED) return;
+        Set<Integer> onStates = new HashSet<>(Arrays.asList(
+                ProcessState.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();
+        assertScreenOn();
+        executeForegroundActivity(ACTION_SLEEP_WHILE_TOP);
+        // ASAP, turn off the screen to make proc state -> top_sleeping.
+        turnScreenOff();
+        assertScreenOff();
+        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 {
+        if (!TESTS_ENABLED) return;
+        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);
+    }
+
+    /**
+     * 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..36e34f8
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -0,0 +1,454 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.FieldMatcher;
+import com.android.internal.os.StatsdConfigProto.StatsdConfig;
+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.GpsScanStateChanged;
+import com.android.os.AtomsProto.MediaCodecActivityChanged;
+import com.android.os.AtomsProto.ScheduledJobStateChanged;
+import com.android.os.AtomsProto.SyncStateChanged;
+import com.android.os.AtomsProto.WifiLockStateChanged;
+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";
+
+    private static final boolean TESTS_ENABLED = false;
+
+    // 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";
+
+    public void testBleScan() throws Exception {
+        if (!TESTS_ENABLED) return;
+        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 minTimeDiffMs = 1_500;
+        final int maxTimeDiffMs = 3_000;
+
+        List<EventMetricData> data = doDeviceMethodOnOff("testBleScanUnoptimized", atom, field,
+                stateOn, stateOff, minTimeDiffMs, maxTimeDiffMs, 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 (!TESTS_ENABLED) return;
+        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 minTimeDiffMs = 1_500;
+        final int maxTimeDiffMs = 3_000;
+
+        List<EventMetricData> data = doDeviceMethodOnOff("testBleScanUnoptimized", atom, field,
+                stateOn, stateOff, minTimeDiffMs, maxTimeDiffMs, 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 (!TESTS_ENABLED) return;
+        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 (!TESTS_ENABLED) return;
+        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 {
+        if (!TESTS_ENABLED) {return;}
+        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);
+        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.getCpuTimePerUid().getUid() == uid) {
+                found = true;
+                assertTrue(atom.getCpuTimePerUid().getUserTimeMs() > 0);
+                assertTrue(atom.getCpuTimePerUid().getSysTimeMs() > 0);
+            }
+        }
+        assertTrue("found uid " + uid, found);
+    }
+
+    public void testCpuTimePerUidFreq() throws Exception {
+        if (!TESTS_ENABLED) {return;}
+        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().getTimeMs() > 0);
+            }
+        }
+        assertTrue("found uid " + uid, found);
+    }
+
+    public void testFlashlightState() throws Exception {
+        if (!TESTS_ENABLED)
+            return;
+        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 testGpsScan() throws Exception {
+        if (!TESTS_ENABLED) return;
+        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 minTimeDiffMs = 500;
+        final int maxTimeDiffMs = 60_000;
+
+        List<EventMetricData> data = doDeviceMethodOnOff("testGpsScan", atom, key,
+                stateOn, stateOff, minTimeDiffMs, maxTimeDiffMs, 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 testScheduledJobState() throws Exception {
+        if (!TESTS_ENABLED)
+            return;
+
+        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 {
+        if (!TESTS_ENABLED) return;
+
+        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 testWakeupAlarm() throws Exception {
+        if (!TESTS_ENABLED) return;
+
+        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 (!TESTS_ENABLED) return;
+        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 testWifiScan() throws Exception {
+        if (!TESTS_ENABLED) return;
+        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 minTimeDiffMs = 500;
+        final int maxTimeDiffMs = 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, minTimeDiffMs, maxTimeDiffMs, 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 (!TESTS_ENABLED) return;
+        if (!hasFeature(FEATURE_AUDIO_OUTPUT, true)) return;
+
+        final int atomTag = Atom.AUDIO_STATE_CHANGED_FIELD_NUMBER;
+        final String name = "testAudioState";
+
+        Set<Integer> onState = new HashSet<>(
+                Arrays.asList(AudioStateChanged.State.ON_VALUE));
+        Set<Integer> offState = new HashSet<>(
+                Arrays.asList(AudioStateChanged.State.OFF_VALUE));
+
+        // Add state sets to the list in order.
+        List<Set<Integer>> stateSet = Arrays.asList(onState, offState);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", name);
+
+        Thread.sleep(WAIT_TIME_SHORT);
+        // Sorted list of events in order in which they occurred.
+        List<EventMetricData> data = getEventMetricDataList();
+
+        // Sorted list of events in order in which they occurred.
+        // Assert that the events happened in the expected order.
+        assertStatesOccurred(stateSet, data, 200,
+                atom -> atom.getAudioStateChanged().getState().getNumber());
+    }
+
+    public void testMediaCodecActivity() throws Exception {
+        if (!TESTS_ENABLED) return;
+        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);
+
+        createAndUploadConfig(atomTag, true);  // True: uses attribution.
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        runVideoPlayerApp();
+
+        // 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.getMediaCodecActivityChanged().getState().getNumber());
+    }
+
+    private void runVideoPlayerApp() throws Exception {
+        turnScreenOn();
+        getDevice().executeShellCommand(
+                "am start -n com.android.server.cts.device.statsd/.VideoPlayerActivity");
+
+        Thread.sleep(WAIT_TIME_LONG);
+        getDevice().executeShellCommand(
+                "am force-stop com.android.server.cts.device.statsd");
+
+        Thread.sleep(WAIT_TIME_SHORT);
+    }
+}
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..bb1d599
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsTestCase.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 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 {
+    @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();
+    }
+
+    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 temperatureMatcher =
+                MetricsUtils.getAtomMatcher(Atom.DEVICE_TEMPERATURE_REPORTED_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(temperatureMatcher)
+                .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..596ff8a
--- /dev/null
+++ b/hostsidetests/statsd/src/android/cts/statsd/metric/MetricsUtils.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.statsd.metric;
+
+import com.android.internal.os.StatsdConfigProto;
+
+public class MetricsUtils {
+    public static final long COUNT_METRIC_ID = 3333;
+
+    public static StatsdConfigProto.StatsdConfig.Builder getEmptyConfig() {
+        StatsdConfigProto.StatsdConfig.Builder builder =
+                StatsdConfigProto.StatsdConfig.newBuilder();
+        // only accept the log events from this cts to avoid noise.
+        builder.addAllowedLogSource("com.android.server.cts.device.statsd");
+        return builder;
+    }
+
+    public static StatsdConfigProto.AtomMatcher.Builder getAtomMatcher(int atomTag) {
+        StatsdConfigProto.AtomMatcher.Builder builder = StatsdConfigProto.AtomMatcher.newBuilder();
+        builder.setSimpleAtomMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                        .setAtomId(atomTag).build());
+        return builder;
+    }
+}
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..21f9b8f
--- /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..9b917be
--- /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..2c0bcfe
--- /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..19fde77
--- /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..e6362cd
--- /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..c12d694
--- /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..7e61c6f
--- /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..3a0d94e
--- /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..eae593d
--- /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..3903001
--- /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..c46f16b
--- /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..6c84f66
--- /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..93490d6
--- /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..f7aaad9
--- /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..8b1113e
--- /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..acf9972 100644
--- a/hostsidetests/ui/src/android/ui/cts/TaskSwitchingTest.java
+++ b/hostsidetests/ui/src/android/ui/cts/TaskSwitchingTest.java
@@ -21,10 +21,10 @@
 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.TestRunResult;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IAbiReceiver;
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..d93ea5c 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)
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..ceb8fb9
--- /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>
+        <activity android:name=".TestAlarmActivity"
+                  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/TestAlarmActivity.java b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmActivity.java
new file mode 100644
index 0000000..8f666dd8
--- /dev/null
+++ b/tests/AlarmManager/app/src/android/alarmmanager/alarmtestapp/cts/TestAlarmActivity.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Activity;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+/**
+ * This Activity is to be used as part of
+ * {@link android.alarmmanager.cts.BackgroundRestrictedAlarmsTest}
+ */
+public class TestAlarmActivity extends Activity {
+    private static final String TAG = TestAlarmActivity.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 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 onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final AlarmManager am = getSystemService(AlarmManager.class);
+        final Intent intent = getIntent();
+        final Intent receiverIntent = new Intent(this, TestAlarmReceiver.class);
+        receiverIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        final PendingIntent alarmClockSender =
+                PendingIntent.getBroadcast(this, 0, receiverIntent, 0);
+        final PendingIntent alarmSender = PendingIntent.getBroadcast(this, 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);
+                Log.d(TAG, "Setting alarm: type=" + type + ", triggerTime=" + triggerTime
+                        + ", interval=" + interval);
+                if (interval > 0) {
+                    am.setRepeating(type, triggerTime, interval, 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;
+        }
+        finish();
+    }
+}
\ 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/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
new file mode 100644
index 0000000..2b37bfd
--- /dev/null
+++ b/tests/AlarmManager/src/android/alarmmanager/cts/BackgroundRestrictedAlarmsTest.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.TestAlarmActivity;
+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;
+
+@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_ACTIVITY = TEST_APP_PACKAGE + ".TestAlarmActivity";
+    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 = 1_000;
+    private static final long MIN_REPEATING_INTERVAL = 10_000;
+
+    private Object mLock = new Object();
+    private Context mContext;
+    private ComponentName mAlarmActivity;
+    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());
+        mAlarmActivity = new ComponentName(TEST_APP_PACKAGE, TEST_APP_ACTIVITY);
+        mAlarmCount = 0;
+        updateAlarmManagerConstants();
+        setAppOpsMode(APP_OP_MODE_IGNORED);
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(TestAlarmReceiver.ACTION_REPORT_ALARM_EXPIRED);
+        mContext.registerReceiver(mAlarmStateReceiver, intentFilter);
+    }
+
+    private void scheduleAlarm(int type, long triggerMillis, long interval) {
+        final Intent setAlarmIntent = new Intent(TestAlarmActivity.ACTION_SET_ALARM);
+        setAlarmIntent.setComponent(mAlarmActivity);
+        setAlarmIntent.putExtra(TestAlarmActivity.EXTRA_TYPE, type);
+        setAlarmIntent.putExtra(TestAlarmActivity.EXTRA_TRIGGER_TIME, triggerMillis);
+        setAlarmIntent.putExtra(TestAlarmActivity.EXTRA_REPEAT_INTERVAL, interval);
+        setAlarmIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(setAlarmIntent);
+    }
+
+    private void scheduleAlarmClock(long triggerRTC) {
+        AlarmManager.AlarmClockInfo alarmInfo = new AlarmManager.AlarmClockInfo(triggerRTC, null);
+
+        final Intent setAlarmClockIntent = new Intent(TestAlarmActivity.ACTION_SET_ALARM_CLOCK);
+        setAlarmClockIntent.setComponent(mAlarmActivity);
+        setAlarmClockIntent.putExtra(TestAlarmActivity.EXTRA_ALARM_CLOCK_INFO, alarmInfo);
+        setAlarmClockIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(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);
+        makeTestPackageIdle();
+        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(TestAlarmActivity.ACTION_CANCEL_ALL_ALARMS);
+        cancelAlarmsIntent.setComponent(mAlarmActivity);
+        cancelAlarmsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(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 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 void makeTestPackageIdle() throws IOException {
+        StringBuilder commandBuilder = new StringBuilder("am make-uid-idle --user current ")
+                .append(TEST_APP_PACKAGE);
+        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..0ea7527 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,7 @@
 # Must match the package name in CtsTestCaseList.mk
 LOCAL_PACKAGE_NAME := CtsJobSchedulerTestCases
 
-LOCAL_SDK_VERSION := current
+#LOCAL_SDK_VERSION := current
 
 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..f7b7a1d
--- /dev/null
+++ b/tests/JobScheduler/JobTestApp/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)
+
+# 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
+
+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..0918a3b
--- /dev/null
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/DeviceIdleJobsTest.java
@@ -0,0 +1,281 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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));
+    }
+
+    @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 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..6b15240 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
 
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..5348373 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityEventTest.java
@@ -32,7 +32,7 @@
 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
@@ -206,6 +206,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 +263,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 +275,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/AccessibilityNodeInfoTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index 1dfbf94..eb3578b 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -25,7 +25,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;
@@ -37,7 +36,7 @@
 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 = 34;
 
     /**
      * The number of properties that are purposely not marshalled
@@ -221,6 +220,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 +233,7 @@
         info.setDrawingOrder(5);
         info.setAvailableExtraData(
                 Arrays.asList(AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY));
+        info.setPaneTitle("Pane title");
     }
 
     /**
@@ -284,6 +285,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 +307,8 @@
                 receivedInfo.getDrawingOrder());
         assertEquals("Extra data flags have incorrect value", expectedInfo.getAvailableExtraData(),
                 receivedInfo.getAvailableExtraData());
+        assertEquals("Pane title has incorrect value", expectedInfo.getPaneTitle(),
+                receivedInfo.getPaneTitle());
     }
 
     /**
@@ -322,6 +327,7 @@
         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 +341,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/AccessibilityRecordTest.java b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
index 7862cb4..5bf02c8 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/AccessibilityRecordTest.java
@@ -35,7 +35,7 @@
 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/ServiceControlUtils.java b/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
index 9783cb8..01275f3 100644
--- a/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
+++ b/tests/accessibility/src/android/view/accessibility/cts/ServiceControlUtils.java
@@ -181,6 +181,9 @@
         final Object waitLockForA11yOff = new Object();
         AccessibilityManager manager = (AccessibilityManager) instrumentation
                 .getContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
+        if (!manager.isEnabled()) {
+            return;
+        }
         manager.addAccessibilityStateChangeListener(
                 new AccessibilityManager.AccessibilityStateChangeListener() {
                     @Override
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 cf6b774..fe5e18a 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..cba3a15 100644
--- a/tests/accessibilityservice/res/layout/end_to_end_test.xml
+++ b/tests/accessibilityservice/res/layout/end_to_end_test.xml
@@ -29,16 +29,19 @@
     <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: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..0e8d78a 100644
--- a/tests/accessibilityservice/res/values/strings.xml
+++ b/tests/accessibilityservice/res/values/strings.xml
@@ -122,6 +122,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>
 
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/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..7052fc3 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -16,30 +16,41 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterForEventType;
+import static android.accessibilityservice.cts.utils.RunOnMainUtils.getOnMain;
+
+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.test.suitebuilder.annotation.MediumTest;
 import android.text.TextUtils;
 import android.util.Log;
 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 +62,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}.
      */
@@ -430,6 +449,187 @@
         }
     }
 
+    @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
+    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
+    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());
+    }
+
+    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>.
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..aad045f 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityFocusAndInputFocusSyncTest.java
@@ -17,8 +17,11 @@
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS;
 import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS;
 
+import android.accessibilityservice.cts.activities.AccessibilityFocusAndInputFocusSyncActivity;
+import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
 
@@ -26,6 +29,7 @@
 
 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
@@ -228,4 +232,29 @@
             }
         }
     }
+
+    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/AccessibilityGestureDispatchTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGestureDispatchTest.java
index df8c6f3..cc68b72 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,24 +32,26 @@
 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.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
 import android.view.WindowManager;
 import android.widget.TextView;
+
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.hamcrest.TypeSafeMatcher;
@@ -72,7 +85,6 @@
     final List<MotionEvent> mMotionEvents = new ArrayList<>();
     StubGestureAccessibilityService mService;
     MyTouchListener mMyTouchListener = new MyTouchListener();
-    MyGestureCallback mCallback;
     TextView mFullScreenTextView;
     int[] mViewLocation = new int[2];
     boolean mGotUpEvent;
@@ -107,7 +119,6 @@
         mService = StubGestureAccessibilityService.enableSelf(getInstrumentation());
 
         mMotionEvents.clear();
-        mCallback = new MyGestureCallback();
         mGotUpEvent = false;
     }
 
@@ -126,10 +137,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());
@@ -161,10 +170,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);
@@ -183,13 +190,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();
@@ -206,30 +212,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());
@@ -245,16 +252,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();
 
@@ -313,13 +318,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 {
@@ -331,16 +333,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
@@ -376,30 +378,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)));
@@ -415,25 +412,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());
@@ -444,23 +440,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);
 
@@ -479,20 +472,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 {
@@ -507,52 +500,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) {
@@ -597,53 +544,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];
@@ -675,19 +607,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> {
@@ -705,13 +628,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..fc71a79 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityGlobalActionsTest.java
@@ -134,6 +134,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..4593d06
--- /dev/null
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityLoggingTest.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the
+ * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.accessibilityservice.cts;
+
+import static android.app.AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.AppOpsUtils;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+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/AccessibilityPaneTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityPaneTest.java
new file mode 100644
index 0000000..b07e3ca
--- /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.utils.AccessibilityEventFilterUtils.AccessibilityEventTypeMatcher;
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.ContentChangesMatcher;
+import static android.accessibilityservice.cts.AccessibilityActivityTestCase.TIMEOUT_ASYNC_PROCESSING;
+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/AccessibilitySoftKeyboardModesTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
index 49c209f..f873849 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilitySoftKeyboardModesTest.java
@@ -16,7 +16,9 @@
 
 import android.accessibilityservice.AccessibilityServiceInfo;
 import android.accessibilityservice.AccessibilityService.SoftKeyboardController;
+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;
@@ -36,6 +38,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 +73,8 @@
     private InstrumentedAccessibilityService mService;
     private SoftKeyboardController mKeyboardController;
     private UiAutomation mUiAutomation;
+    private Activity mActivity;
+    private View mKeyboardTargetView;
 
     private Object mLock = new Object();
 
@@ -83,7 +88,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 +98,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 +107,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 {
@@ -228,20 +238,24 @@
      */
     private boolean tryShowSoftInput() throws Exception {
         final BlockingQueue<Integer> queue = new ArrayBlockingQueue<>(1);
+        final AtomicBoolean showSoftInputResult = new AtomicBoolean(false);
+        final Activity activity = getActivity();
+        final ResultReceiver resultReceiver =
+                new ResultReceiver(new Handler(activity.getMainLooper())) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        queue.add(resultCode);
+                    }
+                };
 
-        getInstrumentation().runOnMainSync(() -> {
-            Activity activity = getActivity();
-            ResultReceiver resultReceiver =
-                    new ResultReceiver(new Handler(activity.getMainLooper())) {
-                            @Override
-                            protected void onReceiveResult(int resultCode, Bundle resultData) {
-                                queue.add(resultCode);
-                            }
-                    };
-            View editText = activity.findViewById(R.id.edit_text);
-            activity.getSystemService(InputMethodManager.class)
-                    .showSoftInput(editText, InputMethodManager.SHOW_FORCED, resultReceiver);
-        });
+        final Instrumentation instrumentation = getInstrumentation();
+        instrumentation.runOnMainSync(() -> mKeyboardTargetView.requestFocus());
+        instrumentation.waitForIdleSync();
+        final InputMethodManager imm = mActivity.getSystemService(InputMethodManager.class);
+        instrumentation.runOnMainSync(() -> showSoftInputResult.set(imm.showSoftInput(
+                mKeyboardTargetView, InputMethodManager.SHOW_FORCED, resultReceiver)));
+
+        assertTrue("InputMethodManager refused to show a soft input", showSoftInputResult.get());
 
         Integer result;
         try {
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..38bb738 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextActionTest.java
@@ -14,10 +14,10 @@
 
 package android.accessibilityservice.cts;
 
+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;
@@ -52,7 +52,6 @@
 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.
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..646d9c3 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTextTraversalTest.java
@@ -14,6 +14,7 @@
 
 package android.accessibilityservice.cts;
 
+import android.accessibilityservice.cts.activities.AccessibilityTextTraversalActivity;
 import android.app.UiAutomation;
 import android.content.pm.PackageManager;
 import android.os.Bundle;
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..a76a027 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityViewTreeReportingTest.java
@@ -14,10 +14,22 @@
 
 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.activities.AccessibilityViewTreeReportingActivity;
+import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.Context;
-import android.test.suitebuilder.annotation.MediumTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
 import android.text.TextUtils;
 import android.view.View;
 import android.view.ViewGroup;
@@ -28,100 +40,121 @@
 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..8b1154e 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityVolumeTest.java
@@ -63,16 +63,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 31292c3..0859e8a 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,14 +36,15 @@
 
 import android.accessibilityservice.AccessibilityService;
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
 import android.app.UiAutomation;
+import android.app.UiAutomation.AccessibilityEventFilter;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
 import android.graphics.Rect;
 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;
@@ -59,23 +70,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 &&
@@ -91,13 +95,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
@@ -142,30 +141,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());
     }
 
@@ -214,30 +195,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.
@@ -269,30 +232,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.
@@ -301,19 +246,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();
         }
@@ -324,22 +258,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();
@@ -347,24 +293,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(
@@ -439,20 +368,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);
@@ -466,20 +385,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);
@@ -517,20 +426,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);
 
@@ -627,13 +524,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);
@@ -652,7 +544,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
@@ -673,8 +565,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;
             }
@@ -704,8 +594,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));
+                }
             }
         }
     }
@@ -713,7 +606,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();
@@ -722,118 +615,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 () {
@@ -853,26 +709,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..2977843 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowReportingTest.java
@@ -16,80 +16,253 @@
 
 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_PIP;
+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.os.Debug;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.WindowManager;
 import android.view.accessibility.AccessibilityEvent;
 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 +271,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 +287,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 +300,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/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..5cc46ea1
--- /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.CoreMatchers.is;
+import static org.hamcrest.Matchers.empty;
+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.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.test.InstrumentationRegistry;
+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..3b3c4d5 100644
--- a/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -26,6 +26,7 @@
 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.test.AndroidTestCase;
@@ -218,7 +219,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 +747,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 +901,43 @@
             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());
+        }
+    }
 }
diff --git a/tests/app/Android.mk b/tests/app/Android.mk
index 6bd42ef..7c77d44 100644
--- a/tests/app/Android.mk
+++ b/tests/app/Android.mk
@@ -21,7 +21,12 @@
 # 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 \
+    telephony-common \
+    voip-common \
+    org.apache.http.legacy \
+    android.test.base.stubs
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     compatibility-device-util \
@@ -29,12 +34,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..ad7e880
--- /dev/null
+++ b/tests/app/CantSaveState1/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)
+
+# 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
+
+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..05da3f6
--- /dev/null
+++ b/tests/app/CantSaveState2/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)
+
+# 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
+
+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 191f4cc..5e22eb2 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/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/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..2e6867d 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);
@@ -738,16 +832,17 @@
                 SIMPLE_PACKAGE_NAME + SIMPLE_ACTIVITY_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();
 
@@ -764,37 +859,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 +908,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..f1272f0 100644
--- a/tests/app/src/android/app/cts/ActivityManagerTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerTest.java
@@ -369,7 +369,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/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..453fd44 100755
--- a/tests/app/src/android/app/cts/DialogTest.java
+++ b/tests/app/src/android/app/cts/DialogTest.java
@@ -318,6 +318,38 @@
         assertNotNull(d.findViewById(R.id.password_edit));
     }
 
+    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
+        runTestOnUiThread(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
+        }
+    }
+
     public void testSetTitle() {
         final String expectedTitle = "Test Dialog Without theme";
         startDialogActivity(DialogStubActivity.TEST_DIALOG_WITHOUT_THEME);
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..eca5e24 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,66 @@
             }
             mNotificationManager.deleteNotificationChannel(nc.getId());
         }
+
+        List<NotificationChannelGroup> groups = mNotificationManager.getNotificationChannelGroups();
+        // Delete all groups.
+        for (NotificationChannelGroup ncg : groups) {
+            mNotificationManager.deleteNotificationChannelGroup(ncg.getId());
+        }
+    }
+
+    public void testPrePCannotDisallowAlarmsOrMediaTest() throws Exception {
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.O;
+
+        // 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();
+        assert((policy.priorityCategories & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS)
+                != 0);
+        assert((policy.priorityCategories &
+                NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) != 0);
+
+        // attempt to toggle off alarms and media
+        // toggle on alarms and media
+        mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
+
+        policy = mNotificationManager.getNotificationPolicy();
+        assert((policy.priorityCategories & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS)
+                != 0);
+        assert((policy.priorityCategories &
+                NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) != 0);
+    }
+
+    public void testPostPCanDisallowAlarmsOrMediaTest() throws Exception {
+        toggleNotificationPolicyAccess(mContext.getPackageName(),
+                InstrumentationRegistry.getInstrumentation(), true);
+        mContext.getApplicationInfo().targetSdkVersion = Build.VERSION_CODES.P;
+
+        // 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();
+        assert((policy.priorityCategories & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS)
+                != 0);
+        assert((policy.priorityCategories &
+                NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) != 0);
+
+        // attempt to toggle off alarms and media
+        // toggle on alarms and media
+        mNotificationManager.setNotificationPolicy(new NotificationManager.Policy(0, 0, 0));
+
+        policy = mNotificationManager.getNotificationPolicy();
+        assert((policy.priorityCategories & NotificationManager.Policy.PRIORITY_CATEGORY_ALARMS)
+                == 0);
+        assert((policy.priorityCategories &
+                NotificationManager.Policy.PRIORITY_CATEGORY_MEDIA_SYSTEM_OTHER) == 0);
     }
 
     public void testCreateChannelGroup() throws Exception {
@@ -88,18 +157,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 +245,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 +462,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 +865,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 +924,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/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/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..d01f5c0 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,11 +35,12 @@
             </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" />
@@ -66,6 +70,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"
@@ -83,6 +99,15 @@
                 <action android:name="android.service.autofill.AutofillService" />
             </intent-filter>
         </service>
+        <!--  BadAutofillService does not declare the proper permission -->
+        <service
+            android:name=".BadAutofillService"
+            android:label="BadAutofillService"
+            android:permission="android.permission.BIND_AUTOFILL" >
+            <intent-filter>
+                <action android:name="android.service.autofill.AutofillService" />
+            </intent-filter>
+        </service>
     </application>
 
     <instrumentation
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/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/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/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/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/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index 74e168b..0452179 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -19,39 +19,60 @@
 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 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,16 +83,28 @@
     public final RequiredFeatureRule mRequiredFeatureRule =
             new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL);
 
-    protected final Context mContext;
+    @Rule
+    public final SafeCleanerRule mSafeCleanerRule = new SafeCleanerRule()
+            .run(() -> sReplier.assertNoUnhandledFillRequests())
+            .run(() -> sReplier.assertNoUnhandledSaveRequests())
+            .add(() -> { return sReplier.getExceptions(); });
+
+    protected final Context mContext = sContext;
     protected final String mPackageName;
+    protected final UiBot mUiBot;
+
     /**
      * Stores the previous logging level so it's restored after the test.
      */
     private String mLoggingLevel;
 
     protected AutoFillServiceTestCase() {
-        mContext = InstrumentationRegistry.getTargetContext();
+        this(sDefaultUiBot);
+    }
+
+    protected AutoFillServiceTestCase(UiBot uiBot) {
         mPackageName = mContext.getPackageName();
+        mUiBot = uiBot;
     }
 
     @BeforeClass
@@ -85,24 +118,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 cleanupState() {
+        Helper.preTestCleanup();
         sReplier.reset();
     }
 
@@ -121,6 +139,15 @@
         }
     }
 
+    /**
+     * Cleans up activities that might have been left over.
+     */
+    @Before
+    @After
+    public void finishActivities() {
+        WelcomeActivity.finishIt(mUiBot);
+    }
+
     @After
     public void resetVerboseLogging() {
         try {
@@ -130,24 +157,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..9262808 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFinishSessionTest.java
@@ -36,10 +36,15 @@
 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 +67,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 +83,7 @@
 
         sReplier.getNextFillRequest();
 
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // remove first set of views
         mActivity.syncRunOnUiThread(() -> {
@@ -99,7 +103,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 +114,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 +202,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 +213,7 @@
 
         sReplier.getNextFillRequest();
 
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         mActivity.syncRunOnUiThread(() -> {
             mEditText1.setText("editText1-filled");
@@ -212,20 +229,20 @@
             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")
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..9c44f8f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillLoggingTestRule.java
@@ -16,13 +16,14 @@
 
 package android.autofillservice.cts;
 
-import static android.autofillservice.cts.Helper.runShellCommand;
+import static android.autofillservice.cts.common.ShellHelper.runShellCommand;
 
 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:
  *
@@ -52,8 +53,12 @@
                 try {
                     base.evaluate();
                 } catch (Throwable t) {
-                    final String dump = runShellCommand("dumpsys autofill");
-                    Log.e(mTag, "dump for " + description.getDisplayName() + ": \n" + dump, t);
+                    final String name = description.getDisplayName();
+                    final String autofillDump = runShellCommand("dumpsys autofill");
+                    Log.e(mTag, "autofill dump for " + name + ": \n" + autofillDump, t);
+                    final String activityDump =
+                            runShellCommand("dumpsys activity android.autofillservice.cts");
+                    Log.e(mTag, "activity dump for " + name + ": \n" + activityDump, t);
                     throw t;
                 } finally {
                     if (!levelBefore.equals("verbose")) {
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..e51f957 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,6 +63,7 @@
 
     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;
@@ -68,12 +73,19 @@
     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;
@@ -87,12 +99,20 @@
         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 +142,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);
@@ -137,7 +158,7 @@
                             : new SaveInfo.Builder(mSaveType,
                                     getAutofillIds(nodeResolver, mRequiredSavableIds));
 
-            saveInfo.setFlags(mFlags);
+            saveInfo.setFlags(mSaveInfoFlags);
 
             if (mValidator != null) {
                 saveInfo.setValidator(mValidator);
@@ -153,6 +174,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 +191,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 +222,22 @@
                 + ",datasets=" + mDatasets
                 + ", requiredSavableIds=" + Arrays.toString(mRequiredSavableIds)
                 + ", optionalSavableIds=" + Arrays.toString(mOptionalSavableIds)
-                + ", flags=" + mFlags
+                + ", 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,6 +249,7 @@
 
     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;
@@ -202,12 +260,19 @@
         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;
@@ -240,8 +305,13 @@
             return this;
         }
 
-        public Builder setFlags(int flags) {
-            mFlags = flags;
+        public Builder setSaveInfoFlags(int flags) {
+            mSaveInfoFlags = flags;
+            return this;
+        }
+
+        public Builder setFillResponseFlags(int flags) {
+            mFillResponseFlags = flags;
             return this;
         }
 
@@ -312,6 +382,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 +402,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;
+        }
     }
 
     /**
@@ -344,6 +466,7 @@
     static class CannedDataset {
         private final Map<String, AutofillValue> mFieldValues;
         private final Map<String, RemoteViews> mFieldPresentations;
+        private final Map<String, Pattern> mFieldFilters;
         private final RemoteViews mPresentation;
         private final IntentSender mAuthentication;
         private final String mId;
@@ -351,6 +474,7 @@
         private CannedDataset(Builder builder) {
             mFieldValues = builder.mFieldValues;
             mFieldPresentations = builder.mFieldPresentations;
+            mFieldFilters = builder.mFieldFilters;
             mPresentation = builder.mPresentation;
             mAuthentication = builder.mAuthentication;
             mId = builder.mId;
@@ -374,10 +498,19 @@
                     final AutofillId autofillid = node.getAutofillId();
                     final AutofillValue value = entry.getValue();
                     final RemoteViews presentation = mFieldPresentations.get(id);
+                    final 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, presentation);
+                        }
                     } else {
-                        builder.setValue(autofillid, value);
+                        if (filter == null) {
+                            builder.setValue(autofillid, value);
+                        } else {
+                            builder.setValue(autofillid, value, filter);
+                        }
                     }
                 }
             }
@@ -390,12 +523,15 @@
             return "CannedDataset " + mId + " : [hasPresentation=" + (mPresentation != null)
                     + ", fieldPresentations=" + (mFieldPresentations)
                     + ", hasAuthentication=" + (mAuthentication != null)
-                    + ", fieldValues=" + mFieldValues + "]";
+                    + ", fieldValues=" + mFieldValues
+                    + ", fieldFilters=" + mFieldFilters + "]";
         }
 
         static class Builder {
             private final Map<String, AutofillValue> mFieldValues = new HashMap<>();
             private final Map<String, RemoteViews> mFieldPresentations = new HashMap<>();
+            private final Map<String, Pattern> mFieldFilters = new HashMap<>();
+
             private RemoteViews mPresentation;
             private IntentSender mAuthentication;
             private String mId;
@@ -420,6 +556,17 @@
             }
 
             /**
+             * 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), filter);
+            }
+
+            /**
              * 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 +612,19 @@
             }
 
             /**
+             * 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, Pattern filter) {
+                setField(id, value);
+                mFieldFilters.put(id, 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 +638,20 @@
             }
 
             /**
+             * 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, 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/CharSequenceTransformationTest.java b/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java
index e68483b..9d0a7aa 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CharSequenceTransformationTest.java
@@ -221,6 +221,29 @@
         verify(template, never()).setCharSequence(eq(0), any(), any());
     }
 
+    @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();
+
+        ValueFinder finder = mock(ValueFinder.class);
+        RemoteViews template = mock(RemoteViews.class);
+
+        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")));
+    }
+
     static class CharSequenceMatcher implements ArgumentMatcher<CharSequence> {
         private final CharSequence mExpected;
 
@@ -232,5 +255,10 @@
         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/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/CustomDescriptionTest.java b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionTest.java
index b7acfc6..728fab2 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,253 @@
                     .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(".*"));
+            Validator invalidCondition = new RegexValidator(usernameId, Pattern.compile("D'OH"));
+            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 +374,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 +390,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 +406,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 +422,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 +445,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 +468,7 @@
         mActivity.tapLogin();
 
         final String expectedText = matchFirst ? "polo" : "POLO";
-        assertSaveUiWithCustomDescriptionIsShown(expectedText);
+        assertSaveUiIsShownWithTwoLines(expectedText);
     }
 
     @Test
@@ -269,18 +481,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 +514,59 @@
         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();
+        assertSaveUiChild(saveUi, "first", line1Visitor);
+        assertSaveUiChild(saveUi, "second", line2Visitor);
+        assertSaveUiChild(saveUi, "third", line3Visitor);
+        return saveUi;
+    }
+
+    /**
+     * Asserts the contents of save UI child element.
+     *
+     * @param saveUi save UI
+     * @param childId resource id of the child
+     * @param assertion if {@code null}, asserts the child does not exist; otherwise, asserts the
+     * child with it.
+     */
+    private void assertSaveUiChild(@NonNull UiObject2 saveUi, @NonNull String childId,
+            @Nullable Visitor<UiObject2> assertion) {
+        final UiObject2 child = saveUi.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/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 33f0943..6890d7e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/CustomDescriptionWithLinkTestCase.java
@@ -61,12 +61,13 @@
      */
     @Test
     public final void testTapLink_changeOrientationThenTapBack() throws Exception {
-        sUiBot.setScreenOrientation(UiBot.PORTRAIT);
+        mUiBot.setScreenOrientation(UiBot.PORTRAIT);
         try {
             saveUiRestoredAfterTappingLinkTest(
                     PostSaveLinkTappedAction.ROTATE_THEN_TAP_BACK_BUTTON);
         } finally {
-            sUiBot.setScreenOrientation(UiBot.PORTRAIT);
+            mUiBot.setScreenOrientation(UiBot.PORTRAIT);
+            cleanUpAfterScreenOrientationIsBackToPortrait();
         }
     }
 
@@ -83,6 +84,9 @@
     protected abstract void saveUiRestoredAfterTappingLinkTest(PostSaveLinkTappedAction type)
             throws Exception;
 
+    protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception {
+    }
+
     /**
      * Tests scenarios when user taps a link in the custom description, taps back to return to the
      * activity with the Save UI, and touch outside the Save UI to dismiss it.
@@ -192,6 +196,9 @@
         saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction.LAUNCH_NEW_ACTIVITY);
     }
 
+    protected abstract void saveUiCancelledAfterTappingLinkTest(PostSaveLinkTappedAction type)
+            throws Exception;
+
     @Test
     public final void testTapLink_launchTrampolineActivityThenTapBackAndStartNewSession()
             throws Exception {
@@ -201,6 +208,18 @@
     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,
@@ -213,41 +232,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/DatasetTest.java b/tests/autofillservice/src/android/autofillservice/cts/DatasetTest.java
new file mode 100644
index 0000000..741fdda
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/DatasetTest.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue,
+                (Pattern) null));
+    }
+
+    @Test
+    public void testBuilder_setFilteredValueWithPresentationNullFilter() {
+        final Dataset.Builder builder = new Dataset.Builder();
+        assertThrows(NullPointerException.class, () -> builder.setValue(mId, mValue, null,
+                mPresentation));
+    }
+
+    @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..55bc654 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();
@@ -79,7 +73,7 @@
         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();
@@ -88,7 +82,7 @@
         activity.setDate(2010, 11, 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/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..981655d
--- /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.assertNoDatasets();
+            // 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/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/FatActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
index b2dd1d4..ba6f7a1 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FatActivityTest.java
@@ -70,7 +70,7 @@
         // Trigger auto-fill.
         mFatActivity.onInput((v) -> v.requestFocus());
         final FillRequest fillRequest = sReplier.getNextFillRequest();
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // TODO: should only have 5 children, but there is an extra
         // TextView that's probably coming from the title. For now we're just ignoring it, but
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..bffce78
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/FieldsClassificationTest.java
@@ -0,0 +1,380 @@
+/*
+ * 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 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("myId", "FULLY");
+        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.assertNoDatasets();
+        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("1stId", "Iam1ST").add("2ndId", "Iam2ND").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.assertNoDatasets();
+        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("myId", "FULLY").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.assertNoDatasets();
+        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", 1.0F),
+                });
+    }
+
+    @Test
+    public void testHit_manyUserData_manyDetectableFields() throws Exception {
+        // Set service.
+        enableService();
+
+        // Set expectations.
+        mAfm.setUserData(new UserData.Builder("myId", "FULLY")
+                .add("totalMiss", "ZZZZZZZZZZ") // should not have matched any
+                .add("otherId", "EMPTY")
+                .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.assertNoDatasets();
+        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("myId", "ABCDEF").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.assertNoDatasets();
+        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("myId", "FULLY").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.assertNoDatasets();
+        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..3acce91
--- /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());
+        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.assertNoDatasets();
+
+        // 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.assertNoDatasets();
+
+        // 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..29af4a2 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/GridActivity.java
@@ -83,7 +83,7 @@
         getSystemService(AutofillManager.class).cancel();
     }
 
-    private EditText getCell(int row, int column) {
+    EditText getCell(int row, int column) {
         return mCells[row - 1][column - 1];
     }
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
index f1d02dd..41df23e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Helper.java
@@ -18,8 +18,14 @@
 
 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;
@@ -27,10 +33,16 @@
 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;
@@ -40,13 +52,14 @@
 import android.view.View;
 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 java.lang.reflect.Field;
 import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.function.Function;
 
 /**
@@ -54,7 +67,7 @@
  */
 final class Helper {
 
-    private static final String TAG = "AutoFillCtsHelper";
+    static final String TAG = "AutoFillCtsHelper";
 
     static final boolean VERBOSE = false;
 
@@ -64,54 +77,18 @@
     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;
-
-    /**
-     * 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";
@@ -134,74 +111,24 @@
         return id.equals(getHtmlName(node));
     };
 
+    private static final NodeFilter HTML_NAME_OR_RESOURCE_ID_FILTER = (node, id) -> {
+        return id.equals(getHtmlName(node)) || id.equals(node.getIdEntry());
+    };
+
     private static final NodeFilter 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 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);
-        }
-    }
-
-    /**
      * Dump the assist structure on logcat.
      */
     static void dumpStructure(String message, AssistStructure structure) {
@@ -236,32 +163,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) {
@@ -391,6 +293,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
@@ -428,7 +338,7 @@
     }
 
     /**
-     * Asserts a text-base node is sanitized.
+     * Asserts a text-based node is sanitized.
      */
     static void assertTextIsSanitized(ViewNode node) {
         final CharSequence text = node.getText();
@@ -436,9 +346,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) {
@@ -449,22 +365,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) {
@@ -474,7 +399,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) {
@@ -483,16 +408,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();
         }
     }
 
@@ -501,9 +438,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;
     }
@@ -513,9 +451,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;
     }
@@ -525,9 +464,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);
     }
 
@@ -594,7 +534,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);
@@ -701,7 +641,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);
     }
@@ -718,28 +658,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;
+        });
     }
 
     /**
@@ -782,58 +715,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) {
@@ -842,14 +760,6 @@
     }
 
     /**
-     * Destroys all sessions.
-     */
-    public static void destroyAllSessions() {
-        runShellCommand("cmd autofill destroy sessions");
-        assertNoDanglingSessions();
-    }
-
-    /**
      * Gets the instrumentation context.
      */
     public static Context getContext() {
@@ -911,7 +821,6 @@
         disableAutofillService(getContext(), SERVICE_NAME);
         InstrumentedAutoFillService.setIgnoreUnexpectedRequests(true);
 
-        destroyAllSessions();
         InstrumentedAutoFillService.resetStaticState();
         AuthenticationActivity.resetStaticState();
     }
@@ -952,17 +861,274 @@
     /**
      * 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 remoteId at (%s, %s): %s", i, j, match)
+                .that(match.getRemoteId()).isEqualTo(expectedResult.remoteIds[j]);
+            assertWithMessage("Wrong score at (%s, %s): %s", i, j, match)
+                .that(match.getScore()).isWithin(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;
+        }
     }
 
     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..2fbc09a 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,109 @@
  */
 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 =
             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;
+
     public InstrumentedAutoFillService() {
         sInstance.set(this);
     }
 
-    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();
+
+    }
+
     @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 +187,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 +224,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 +239,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 +257,8 @@
     }
 
     static void resetStaticState() {
-        sConnectionStates.clear();
+        sInstance.set(null);
+        sConnected = false;
     }
 
     /**
@@ -209,6 +283,11 @@
             this.flags = flags;
             structure = contexts.get(contexts.size() - 1).getStructure();
         }
+
+        @Override
+        public String toString() {
+            return "FillRequest:" + getActivityName(contexts);
+        }
     }
 
     /**
@@ -217,12 +296,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 +312,12 @@
             this.contexts = contexts;
             this.data = data;
             this.callback = callback;
+            this.datasetIds = datasetIds;
+        }
+
+        @Override
+        public String toString() {
+            return "SaveRequest:" + getActivityName(contexts);
         }
     }
 
@@ -246,13 +333,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 +356,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 +391,51 @@
         }
 
         /**
+         * 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 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 assertNumberUnhandledFillRequests(int expected) {
-            assertWithMessage("Invalid number of fill requests").that(mFillRequests.size())
-                    .isEqualTo(expected);
+        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 +450,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 +493,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 +504,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 +512,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,6 +547,10 @@
                         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);
                 }
@@ -427,10 +565,15 @@
             }
         }
 
-        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/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..eee8e5e 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() {
@@ -210,6 +249,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 898ca84..a5d9e3c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -20,29 +20,29 @@
 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.UNUSED_AUTOFILL_VALUE;
+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.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 +64,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;
@@ -84,14 +85,15 @@
 import android.view.autofill.AutofillValue;
 import android.widget.RemoteViews;
 
-import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Pattern;
 
 /**
  * This is the test case covering most scenarios - other test cases will cover characteristics
@@ -105,6 +107,10 @@
     public final AutofillActivityTestRule<LoginActivity> mActivityRule =
         new AutofillActivityTestRule<LoginActivity>(LoginActivity.class);
 
+    @Rule
+    public final AutofillActivityTestRule<CheckoutActivity> mCheckoutActivityRule =
+            new AutofillActivityTestRule<CheckoutActivity>(CheckoutActivity.class, false);
+
     private LoginActivity mActivity;
 
     @Before
@@ -112,9 +118,36 @@
         mActivity = mActivityRule.getActivity();
     }
 
-    @After
-    public void finishWelcomeActivity() {
-        WelcomeActivity.finishIt();
+    /**
+     * Request focus on username and expect Window event happens.
+     */
+    void requestFocusOnUsername() throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.onUsername(View::requestFocus),
+                Timeouts.UI_TIMEOUT.getMaxValue());
+    }
+
+    /**
+     * Request focus on username and expect no Window event happens.
+     */
+    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");
+    }
+
+    /**
+     * Request focus on password and expect Window event happens.
+     */
+    void requestFocusOnPassword() throws TimeoutException {
+        mUiBot.waitForWindowChange(() -> mActivity.onPassword(View::requestFocus),
+                Timeouts.UI_TIMEOUT.getMaxValue());
     }
 
     @Test
@@ -128,19 +161,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.assertNoDatasets();
 
         // Test connection lifecycle.
         waitUntilDisconnected();
@@ -148,18 +175,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.assertNoDatasets();
 
         // Try again, forcing it
         sReplier.addResponse(new CannedDataset.Builder()
@@ -167,15 +204,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();
@@ -183,6 +226,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();
 
@@ -190,41 +242,111 @@
         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.assertNoDatasets();
+        sReplier.assertNoUnhandledFillRequests();
         mActivity.onPassword(View::requestFocus);
-        sUiBot.assertNoDatasets();
-        sReplier.assertNumberUnhandledFillRequests(0);
+        mUiBot.assertNoDatasets();
+        sReplier.assertNoUnhandledFillRequests();
 
         // Try again, forcing it
-        saveOnlyTest(true);
+        saveOnlyTest(manually);
     }
 
     @Test
-    public void testAutoFillOneDataset() throws Exception {
+    public void testAutofillManuallyAlwaysCallServiceAgain() throws Exception {
         // Set service.
         enableService();
 
-        // Set expectations.
+        // 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();
@@ -264,18 +386,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();
@@ -314,23 +436,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.
@@ -338,6 +460,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
@@ -372,25 +531,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.assertNoDatasets();
     }
 
     @Test
@@ -407,19 +568,19 @@
         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.assertNoDatasets();
     }
 
     @Test
@@ -436,24 +597,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();
@@ -499,7 +663,7 @@
         sReplier.getNextFillRequest();
 
         // Auto-fill it.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Assert callback was called
         final View username = mActivity.getUsername();
@@ -517,6 +681,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"))
@@ -527,15 +692,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();
@@ -554,23 +727,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
@@ -594,7 +766,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();
@@ -608,7 +780,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);
 
@@ -638,7 +810,7 @@
             assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue();
 
             // Auto-fill it.
-            sUiBot.selectDataset("The Dude");
+            mUiBot.selectDataset("The Dude");
 
             // Check the results.
             mActivity.assertAutoFilled();
@@ -658,23 +830,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);
@@ -743,14 +912,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();
@@ -775,21 +944,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();
@@ -819,21 +988,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();
@@ -862,21 +1031,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();
@@ -905,20 +1074,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();
@@ -947,20 +1116,20 @@
         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();
@@ -991,33 +1160,33 @@
                 .build());
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
 
         // With no filter text all datasets should be shown
-        sUiBot.assertDatasets(AA, AB, B);
+        mUiBot.assertDatasets(AA, AB, B);
 
         // Only two datasets start with 'a'
-        runShellCommand("input keyevent KEYCODE_A");
-        sUiBot.assertDatasets(AA, AB);
+        mActivity.onUsername((v) -> v.setText("a"));
+        mUiBot.assertDatasets(AA, AB);
 
         // Only one dataset start with 'aa'
-        runShellCommand("input keyevent KEYCODE_A");
-        sUiBot.assertDatasets(AA);
+        mActivity.onUsername((v) -> v.setText("aa"));
+        mUiBot.assertDatasets(AA);
 
         // Only two datasets start with 'a'
-        runShellCommand("input keyevent KEYCODE_DEL");
-        sUiBot.assertDatasets(AA, AB);
+        mActivity.onUsername((v) -> v.setText("a"));
+        mUiBot.assertDatasets(AA, AB);
 
         // With no filter text all datasets should be shown
-        runShellCommand("input keyevent KEYCODE_DEL");
-        sUiBot.assertDatasets(AA, AB, B);
+        mActivity.onUsername((v) -> v.setText(""));
+        mUiBot.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();
+        final MyAutofillCallback callback = mActivity.registerCallback();
+        mActivity.onUsername((v) -> v.setText("aaa"));
+        callback.assertUiHiddenEvent(mActivity.getUsername());
+        mUiBot.assertNoDatasets();
     }
 
     @Test
@@ -1045,33 +1214,31 @@
                 .build());
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
 
         // With no filter text all datasets should be shown
-        sUiBot.assertDatasets(AA, AB, B);
+        mUiBot.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);
+        mActivity.onUsername((v) -> v.setText("a"));
+        mUiBot.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);
+        mActivity.onUsername((v) -> v.setText("aa"));
+        mUiBot.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);
+        mActivity.onUsername((v) -> v.setText("a"));
+        mUiBot.assertDatasets(AA, AB, B);
 
         // With no filter text all datasets should be shown
-        runShellCommand("input keyevent KEYCODE_DEL");
-        sUiBot.assertDatasets(AA, AB, B);
+        mActivity.onUsername((v) -> v.setText(""));
+        mUiBot.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);
+        mActivity.onUsername((v) -> v.setText("aaa"));
+        mUiBot.assertDatasets(B);
     }
 
     @Test
@@ -1099,20 +1266,75 @@
                 .build());
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         sReplier.getNextFillRequest();
 
         // With no filter text all datasets should be shown
-        sUiBot.assertDatasets(A, B, C);
+        mUiBot.assertDatasets(A, B, C);
 
         mActivity.onUsername((v) -> v.setText("a"));
-        sUiBot.assertDatasets(A);
+        mUiBot.assertDatasets(A);
 
         mActivity.onUsername((v) -> v.setText("b"));
-        sUiBot.assertDatasets(B);
+        mUiBot.assertDatasets(B);
 
         mActivity.onUsername((v) -> v.setText("c"));
-        sUiBot.assertDatasets(C);
+        mUiBot.assertDatasets(C);
+    }
+
+    @Test
+    public void filterTextUsingRegex() 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
@@ -1141,7 +1363,7 @@
         }
 
         // Sanity check.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Wait for onFill() before proceeding, otherwise the fields might be changed before
         // the session started
@@ -1157,10 +1379,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 {
@@ -1172,9 +1395,6 @@
             dumpStructure("saveOnlyTest() failed", saveRequest.structure);
             throw e;
         }
-
-        // Sanity check: once saved, the session should be finished.
-        assertNoDanglingSessions();
     }
 
     @Test
@@ -1193,12 +1413,12 @@
         startCheckoutActivityAsNewTask();
         try {
             // .. then the real activity being tested.
-            sUiBot.switchAppsUsingRecents();
-            sUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
+            mUiBot.switchAppsUsingRecents();
+            mUiBot.assertShownByRelativeId(ID_USERNAME_CONTAINER);
 
             saveGoesAway(DismissType.RECENTS_BUTTON);
         } finally {
-            CheckoutActivity.finishIt();
+            CheckoutActivity.finishIt(mUiBot);
         }
     }
 
@@ -1207,12 +1427,11 @@
         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 startCheckoutActivityAsNewTask() throws Exception {
+        final Intent intent = new Intent(mContext, CheckoutActivity.class)
+                .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS);
+        mCheckoutActivityRule.launchActivity(intent);
+        mUiBot.assertShownByRelativeId(CheckoutActivity.ID_ADDRESS);
     }
 
     private void saveGoesAway(DismissType dismissType) throws Exception {
@@ -1227,7 +1446,7 @@
         mActivity.onUsername(View::requestFocus);
 
         // Sanity check.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Wait for onFill() before proceeding, otherwise the fields might be changed before
         // the session started
@@ -1243,27 +1462,27 @@
         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();
+                mUiBot.assertShownByText(expectedMessage).click();
                 break;
             case RECENTS_BUTTON:
-                sUiBot.switchAppsUsingRecents();
-                sUiBot.assertShownByRelativeId(CheckoutActivity.ID_ADDRESS);
+                mUiBot.switchAppsUsingRecents();
+                mUiBot.assertShownByRelativeId(CheckoutActivity.ID_ADDRESS);
                 break;
             default:
                 throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
         }
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_PASSWORD);
     }
 
     @Test
@@ -1296,7 +1515,7 @@
         }
 
         // Sanity check.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Wait for onFill() before proceeding, otherwise the fields might be changed before
         // the session started
@@ -1312,10 +1531,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 {
@@ -1327,9 +1546,6 @@
             dumpStructure("saveOnlyTest() failed", saveRequest.structure);
             throw e;
         }
-
-        // Sanity check: once saved, the session should be finsihed.
-        assertNoDanglingSessions();
     }
 
     @Test
@@ -1350,7 +1566,7 @@
         // Wait for onFill() before changing value, otherwise the fields might be changed before
         // the session started
         sReplier.getNextFillRequest();
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Set credentials...
         mActivity.onPassword((v) -> v.setText("thou should pass")); // contains pass
@@ -1361,10 +1577,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 {
@@ -1376,9 +1592,6 @@
             dumpStructure("saveOnlyTest() failed", saveRequest.structure);
             throw e;
         }
-
-        // Sanity check: once saved, the session should be finsihed.
-        assertNoDanglingSessions();
     }
 
     @Test
@@ -1395,7 +1608,7 @@
         mActivity.onUsername(View::requestFocus);
 
         // Sanity check.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Wait for onFill() before proceeding, otherwise the fields might be changed before
         // the session started
@@ -1412,7 +1625,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();
 
@@ -1421,9 +1634,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
@@ -1460,7 +1670,7 @@
         mActivity.onUsername(View::requestFocus);
 
         // Sanity check.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Wait for onFill() before proceeding, otherwise the fields might be changed before
         // the session started
@@ -1485,13 +1695,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();
 
@@ -1503,9 +1712,6 @@
             final ViewNode password = findNodeByResourceId(saveRequest.structure, ID_PASSWORD);
             assertTextAndValue(password, "whatever");
         }
-
-        // Sanity check: once saved, the session should be finished.
-        assertNoDanglingSessions();
     }
 
     @Test
@@ -1553,7 +1759,7 @@
         mActivity.onUsername(View::requestFocus);
 
         // Sanity check.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Wait for onFill() before proceeding, otherwise the fields might be changed before
         // the session started.
@@ -1569,14 +1775,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.assertNoDatasets();
+
+        // 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();
@@ -1627,55 +1866,55 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Wait for onFill() before proceeding.
         sReplier.getNextFillRequest();
         final View username = mActivity.getUsername();
         callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth response");
+        mUiBot.assertDatasets("Tap to auth response");
 
         // Make sure UI is show on 2nd field as well
         final View password = mActivity.getPassword();
-        mActivity.onPassword(View::requestFocus);
+        requestFocusOnPassword();
         callback.assertUiHiddenEvent(username);
         callback.assertUiShownEvent(password);
-        sUiBot.assertDatasets("Tap to auth response");
+        mUiBot.assertDatasets("Tap to auth response");
 
         // Now tap on 1st field to show it again...
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         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");
+            mUiBot.selectDataset("Tap to auth response");
             callback.assertUiHiddenEvent(username);
             callback.assertUiShownEvent(username);
-            sUiBot.assertDatasets("Tap to auth response");
+            mUiBot.assertDatasets("Tap to auth response");
 
             // Make sure it's still shown on other fields...
-            mActivity.onPassword(View::requestFocus);
+            requestFocusOnPassword();
             callback.assertUiHiddenEvent(username);
             callback.assertUiShownEvent(password);
-            sUiBot.assertDatasets("Tap to auth response");
+            mUiBot.assertDatasets("Tap to auth response");
 
             // Tap on 1st field to show it again...
-            mActivity.onUsername(View::requestFocus);
+            requestFocusOnUsername();
             callback.assertUiHiddenEvent(password);
             callback.assertUiShownEvent(username);
         }
 
         // ...and select it this time
         AuthenticationActivity.setResultCode(RESULT_OK);
-        sUiBot.selectDataset("Tap to auth response");
+        mUiBot.selectDataset("Tap to auth response");
         callback.assertUiHiddenEvent(username);
         callback.assertUiShownEvent(username);
-        final UiObject2 picker = sUiBot.assertDatasets("Dataset");
-        sUiBot.selectDataset(picker, "Dataset");
+        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
+        mUiBot.selectDataset(picker, "Dataset");
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -1716,31 +1955,31 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Wait for onFill() before proceeding.
         sReplier.getNextFillRequest();
         final View username = mActivity.getUsername();
         callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth response");
+        mUiBot.assertDatasets("Tap to auth response");
 
         // Make sure UI is not show on 2nd field
-        mActivity.onPassword(View::requestFocus);
+        requestFocusOnPassword();
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
         // Now tap on 1st field to show it again...
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         callback.assertUiShownEvent(username);
 
         // ...and select it this time
-        sUiBot.selectDataset("Tap to auth response");
+        mUiBot.selectDataset("Tap to auth response");
         callback.assertUiHiddenEvent(username);
-        final UiObject2 picker = sUiBot.assertDatasets("Dataset");
+        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
 
         callback.assertUiShownEvent(username);
-        sUiBot.selectDataset(picker, "Dataset");
+        mUiBot.selectDataset(picker, "Dataset");
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -1774,13 +2013,13 @@
                 .build());
 
         // Trigger autofill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Wait for onFill() before proceeding.
         sReplier.getNextFillRequest();
         final View username = mActivity.getUsername();
         callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth response");
+        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.
@@ -1790,7 +2029,7 @@
         final CountDownLatch latch = new CountDownLatch(1);
         AuthenticationActivity.setResultCode(latch, RESULT_OK);
 
-        sUiBot.selectDataset("Tap to auth response");
+        mUiBot.selectDataset("Tap to auth response");
         callback.assertUiHiddenEvent(username);
 
         // Cancel session...
@@ -1799,7 +2038,7 @@
         // ...before finishing the Auth UI.
         latch.countDown();
 
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
     }
 
     @Test
@@ -1834,7 +2073,7 @@
                 .build());
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Wait for onFill() before proceeding.
         sReplier.getNextFillRequest();
@@ -1842,9 +2081,112 @@
         callback.assertUiShownEvent(username);
 
         // Select the authentication dialog.
-        sUiBot.selectDataset("Tap to auth response");
+        mUiBot.selectDataset("Tap to auth response");
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        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
@@ -1877,7 +2219,7 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Wait for onFill() before proceeding.
         sReplier.getNextFillRequest();
@@ -1885,27 +2227,27 @@
 
         // Make sure it's showing initially...
         callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth response");
+        mUiBot.assertDatasets("Tap to auth response");
 
         // ..then type something to hide it.
-        runShellCommand("input keyevent KEYCODE_A");
+        mActivity.onUsername((v) -> v.setText("a"));
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Now delete the char and assert it's shown again...
-        runShellCommand("input keyevent KEYCODE_DEL");
+        mActivity.onUsername((v) -> v.setText(""));
         callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth response");
+        mUiBot.assertDatasets("Tap to auth response");
 
         // ...and select it this time
         AuthenticationActivity.setResultCode(RESULT_OK);
-        sUiBot.selectDataset("Tap to auth response");
+        mUiBot.selectDataset("Tap to auth response");
         callback.assertUiHiddenEvent(username);
         callback.assertUiShownEvent(username);
-        final UiObject2 picker = sUiBot.assertDatasets("Dataset");
-        sUiBot.selectDataset(picker, "Dataset");
+        final UiObject2 picker = mUiBot.assertDatasets("Dataset");
+        mUiBot.selectDataset(picker, "Dataset");
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -1927,10 +2269,6 @@
     }
 
     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();
@@ -1940,14 +2278,13 @@
                 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)
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
                         .setPresentation(createPresentation("Tap to auth dataset"))
                         .setAuthentication(authentication)
                         .build())
@@ -1957,52 +2294,52 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Wait for onFill() before proceeding.
         sReplier.getNextFillRequest();
         final View username = mActivity.getUsername();
         callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth dataset");
+        mUiBot.assertDatasets("Tap to auth dataset");
 
         // Make sure UI is show on 2nd field as well
         final View password = mActivity.getPassword();
-        mActivity.onPassword(View::requestFocus);
+        requestFocusOnPassword();
         callback.assertUiHiddenEvent(username);
         callback.assertUiShownEvent(password);
-        sUiBot.assertDatasets("Tap to auth dataset");
+        mUiBot.assertDatasets("Tap to auth dataset");
 
         // Now tap on 1st field to show it again...
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
         callback.assertUiHiddenEvent(password);
         callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth dataset");
+        mUiBot.assertDatasets("Tap to auth dataset");
 
         if (cancelFirstAttempt) {
             // Trigger the auth dialog, but emulate cancel.
             AuthenticationActivity.setResultCode(RESULT_CANCELED);
-            sUiBot.selectDataset("Tap to auth dataset");
+            mUiBot.selectDataset("Tap to auth dataset");
             callback.assertUiHiddenEvent(username);
             callback.assertUiShownEvent(username);
-            sUiBot.assertDatasets("Tap to auth dataset");
+            mUiBot.assertDatasets("Tap to auth dataset");
 
             // Make sure it's still shown on other fields...
-            mActivity.onPassword(View::requestFocus);
+            requestFocusOnPassword();
             callback.assertUiHiddenEvent(username);
             callback.assertUiShownEvent(password);
-            sUiBot.assertDatasets("Tap to auth dataset");
+            mUiBot.assertDatasets("Tap to auth dataset");
 
             // Tap on 1st field to show it again...
-            mActivity.onUsername(View::requestFocus);
+            requestFocusOnUsername();
             callback.assertUiHiddenEvent(password);
             callback.assertUiShownEvent(username);
         }
 
         // ...and select it this time
         AuthenticationActivity.setResultCode(RESULT_OK);
-        sUiBot.selectDataset("Tap to auth dataset");
+        mUiBot.selectDataset("Tap to auth dataset");
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -2043,7 +2380,7 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Wait for onFill() before proceeding.
         sReplier.getNextFillRequest();
@@ -2051,14 +2388,14 @@
 
         // Authenticate
         callback.assertUiShownEvent(username);
-        sUiBot.selectDataset("Tap to auth dataset");
+        mUiBot.selectDataset("Tap to auth dataset");
         callback.assertUiHiddenEvent(username);
 
         // Select a dataset from the new response
         callback.assertUiShownEvent(username);
-        sUiBot.selectDataset("Dataset");
+        mUiBot.selectDataset("Dataset");
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -2080,7 +2417,6 @@
                 new CannedDataset.Builder()
                         .setField(ID_USERNAME, "dude")
                         .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("Dataset"))
                         .build());
 
         // Configure the service behavior
@@ -2097,7 +2433,7 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Wait for onFill() before proceeding.
         sReplier.getNextFillRequest();
@@ -2105,9 +2441,9 @@
 
         // Authenticate
         callback.assertUiShownEvent(username);
-        sUiBot.selectDataset("Tap to auth dataset");
+        mUiBot.selectDataset("Tap to auth dataset");
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -2115,10 +2451,6 @@
 
     @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();
@@ -2127,7 +2459,6 @@
         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);
@@ -2137,14 +2468,14 @@
         // Configure the service behavior
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, bogusValue)
-                        .setField(ID_PASSWORD, bogusValue)
+                        .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, bogusValue)
-                        .setField(ID_PASSWORD, bogusValue)
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
                         .setPresentation(createPresentation("Tap to auth dataset 2"))
                         .setAuthentication(authentication2)
                         .build())
@@ -2154,7 +2485,7 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Wait for onFill() before proceeding.
         sReplier.getNextFillRequest();
@@ -2162,11 +2493,11 @@
 
         // Authenticate
         callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth dataset 1", "Tap to auth dataset 2");
+        mUiBot.assertDatasets("Tap to auth dataset 1", "Tap to auth dataset 2");
 
-        sUiBot.selectDataset("Tap to auth dataset 1");
+        mUiBot.selectDataset("Tap to auth dataset 1");
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -2192,7 +2523,6 @@
                 new CannedDataset.Builder()
                         .setField(ID_USERNAME, "dude")
                         .setField(ID_PASSWORD, "sweet")
-                        .setPresentation(createPresentation("Dataset"))
                         .build());
 
         // Configure the service behavior
@@ -2218,7 +2548,7 @@
         }
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Wait for onFill() before proceeding.
         sReplier.getNextFillRequest();
@@ -2226,23 +2556,19 @@
 
         // Authenticate
         callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
+        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
 
         final String chosenOne = selectAuth ? "Tap to auth dataset" : "What, me auth?";
-        sUiBot.selectDataset(chosenOne);
+        mUiBot.selectDataset(chosenOne);
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        mUiBot.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?";
-
+    public void testDatasetAuthNoFiltering() throws Exception {
         // Set service.
         enableService();
         final MyAutofillCallback callback = mActivity.registerCallback();
@@ -2251,7 +2577,6 @@
         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);
@@ -2259,8 +2584,8 @@
         // Configure the service behavior
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, bogusValue)
-                        .setField(ID_PASSWORD, bogusValue)
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
                         .setPresentation(createPresentation("Tap to auth dataset"))
                         .setAuthentication(authentication)
                         .build())
@@ -2270,7 +2595,7 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Wait for onFill() before proceeding.
         sReplier.getNextFillRequest();
@@ -2278,22 +2603,177 @@
 
         // Make sure it's showing initially...
         callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth dataset");
+        mUiBot.assertDatasets("Tap to auth dataset");
 
         // ..then type something to hide it.
-        runShellCommand("input keyevent KEYCODE_A");
+        mActivity.onUsername((v) -> v.setText("a"));
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Now delete the char and assert it's shown again...
-        runShellCommand("input keyevent KEYCODE_DEL");
+        mActivity.onUsername((v) -> v.setText(""));
         callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth dataset");
+        mUiBot.assertDatasets("Tap to auth dataset");
 
         // ...and select it this time
-        sUiBot.selectDataset("Tap to auth dataset");
+        mUiBot.selectDataset("Tap to auth dataset");
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        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();
@@ -2310,10 +2790,6 @@
     }
 
     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();
@@ -2322,7 +2798,6 @@
         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);
@@ -2330,8 +2805,8 @@
         // Configure the service behavior
         sReplier.addResponse(new CannedFillResponse.Builder()
                 .addDataset(new CannedDataset.Builder()
-                        .setField(ID_USERNAME, bogusValue)
-                        .setField(ID_PASSWORD, bogusValue)
+                        .setField(ID_USERNAME, UNUSED_AUTOFILL_VALUE)
+                        .setField(ID_PASSWORD, UNUSED_AUTOFILL_VALUE)
                         .setPresentation(createPresentation("Tap to auth dataset"))
                         .setAuthentication(authentication)
                         .build())
@@ -2350,7 +2825,7 @@
         }
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Wait for onFill() before proceeding.
         sReplier.getNextFillRequest();
@@ -2358,37 +2833,110 @@
 
         // Make sure it's showing initially...
         callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
+        mUiBot.assertDatasets("Tap to auth dataset", "What, me auth?");
 
         // Filter the auth dataset.
-        runShellCommand("input keyevent KEYCODE_D");
-        sUiBot.assertDatasets("What, me auth?");
+        mActivity.onUsername((v) -> v.setText("d"));
+        mUiBot.assertDatasets("What, me auth?");
 
         // Filter all.
-        runShellCommand("input keyevent KEYCODE_W");
+        mActivity.onUsername((v) -> v.setText("dw"));
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Now delete the char and assert the non-auth is shown again.
-        runShellCommand("input keyevent KEYCODE_DEL");
+        mActivity.onUsername((v) -> v.setText("d"));
         callback.assertUiShownEvent(username);
-        sUiBot.assertDatasets("What, me auth?");
+        mUiBot.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?");
+        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?";
-        sUiBot.selectDataset(chosenOne);
+        mUiBot.selectDataset(chosenOne);
         callback.assertUiHiddenEvent(username);
-        sUiBot.assertNoDatasets();
+        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 testDisableSelf() throws Exception {
         enableService();
 
@@ -2440,13 +2988,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
@@ -2489,13 +3035,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
@@ -2531,7 +3075,7 @@
         // Trigger auto-fill.
         mActivity.onUsername(View::requestFocus);
 
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         final FillRequest fillRequest = sReplier.getNextFillRequest();
 
@@ -2576,13 +3120,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();
@@ -2626,11 +3170,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();
@@ -2657,14 +3201,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();
@@ -2687,7 +3231,7 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        mActivity.onUsername(View::requestFocus);
+        requestFocusOnUsername();
 
         // Assert request.
         final FillRequest fillRequest1 = sReplier.getNextFillRequest();
@@ -2696,7 +3240,7 @@
         assertTextIsSanitized(fillRequest1.structure, ID_PASSWORD);
 
         // Select it.
-        sUiBot.selectDataset("The Dude");
+        mUiBot.selectDataset("The Dude");
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -2719,12 +3263,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();
@@ -2751,12 +3295,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();
@@ -2779,12 +3323,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();
@@ -2811,7 +3355,7 @@
                 mActivity.onUsername(View::requestFocus);
 
                 // Sanity check.
-                sUiBot.assertNoDatasets();
+                mUiBot.assertNoDatasets();
 
                 // Wait for onFill() before proceeding, otherwise the fields might be changed before
                 // the session started
@@ -2829,7 +3373,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();
 
@@ -2842,7 +3386,6 @@
                 assertTextAndValue(passwordNode, password);
 
                 waitUntilDisconnected();
-                assertNoDanglingSessions();
             } catch (RetryableException e) {
                 throw new RetryableException(e, "on step %d", i);
             } catch (Throwable t) {
@@ -2868,25 +3411,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) {
@@ -2919,424 +3461,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
@@ -3353,6 +3490,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();
 
@@ -3383,15 +3533,54 @@
         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();
+    }
+
+    @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.assertNoDatasets();
     }
 
     @Test
@@ -3408,11 +3597,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();
@@ -3459,13 +3648,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.
     }
@@ -3477,7 +3666,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.
@@ -3491,4 +3680,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..ce92b6f
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/MultiWindowLoginActivityTest.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.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 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 {
+
+    private static final String TAG = "MultiWindowTest";
+
+    @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 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);
+    }
+
+    /**
+     * 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);
+    }
+
+    @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());
+        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());
+        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..a510639 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,8 +62,8 @@
     }
 
     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)
+        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 String actual = mEditText.getText().toString();
         assertWithMessage("Wrong auto-fill value on EditText %s", mName)
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/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..20287a7 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.assertNoDatasets();
 
         // 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.assertNoDatasets();
 
         // 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..1882ed8 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.assertNoDatasets();
     }
 
     /**
@@ -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..a0d4eab 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();
@@ -226,39 +236,39 @@
             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();
+                mUiBot.switchAppsUsingRecents();
                 // Make sure right activity is showing.
-                sUiBot.assertShownByRelativeId(ID_INPUT);
+                mUiBot.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 +304,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 +334,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/RetryRule.java b/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java
index be740b1..2de94f8 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/RetryRule.java
@@ -45,17 +45,24 @@
 
             @Override
             public void evaluate() throws Throwable {
+                final String name = description.getDisplayName();
                 Throwable caught = null;
                 for (int i = 1; i <= mMaxAttempts; i++) {
                     try {
                         base.evaluate();
                         return;
-                    } catch (RetryableException | StaleObjectException e) {
+                    } catch (RetryableException e) {
+                        final Timeout timeout = e.getTimeout();
+                        if (timeout != null) {
+                            timeout.increase();
+                        }
                         caught = e;
-                        Log.w(TAG,
-                                description.getDisplayName() + ": attempt " + i + " failed: " + e);
+                    } catch (StaleObjectException e) {
+                        caught = e;
                     }
+                    Log.w(TAG, name + ": attempt " + i + " failed: " + caught);
                 }
+                Log.e(TAG, 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..bceb11b
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/SafeCleanerRule.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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<>();
+
+    /**
+     * 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;
+    }
+
+    @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) {
+                    throw mThrowables.get(0);
+                }
+                throw new MultipleExceptions(mThrowables);
+            }
+        };
+    }
+
+    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;
+        }
+    }
+}
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..7fc021e
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/SafeCleanerRuleTest.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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!");
+
+    // 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 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 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_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 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();
+    }
+}
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 9fb16a2..be065f9 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,24 +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.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() {
@@ -54,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);
     }
@@ -63,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
@@ -89,7 +108,7 @@
 
         // Select dataset.
         final FillExpectation autofillExpecation = mActivity.expectAutoFill("id", "pass");
-        sUiBot.selectDataset("YO");
+        mUiBot.selectDataset("YO");
         autofillExpecation.assertAutoFilled();
 
         mActivity.syncRunOnUiThread(() -> {
@@ -97,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();
@@ -130,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
@@ -151,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();
@@ -167,11 +186,12 @@
 
     @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();
         }
     }
 
@@ -196,15 +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) {
-            sUiBot.setScreenOrientation(UiBot.LANDSCAPE);
-            saveUi = sUiBot.assertSaveShowing(SAVE_DATA_TYPE_GENERIC);
+            // After the device rotates, the input field get focus and generate a new session.
+            sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+            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();
@@ -212,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();
 
@@ -236,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
@@ -261,7 +312,6 @@
 
         // Cancel session.
         mActivity.getAutofillManager().cancel();
-        Helper.assertNoDanglingSessions();
 
         // Trigger save.
         mActivity.syncRunOnUiThread(() -> {
@@ -270,7 +320,7 @@
         });
 
         // Assert it's not showing.
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
     }
 
     @Test
@@ -304,7 +354,7 @@
 
         // Then launches the main activity.
         startActivity(true);
-        sUiBot.assertShownByRelativeId(ID_INPUT);
+        mUiBot.assertShownByRelativeId(ID_INPUT);
 
         // And finally test it..
         dismissSaveTest(DismissType.RECENTS_BUTTON);
@@ -329,31 +379,31 @@
             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();
+                mUiBot.assertShownByText(TEXT_LABEL).click();
                 break;
             case RECENTS_BUTTON:
-                sUiBot.switchAppsUsingRecents();
-                WelcomeActivity.assertShowingDefaultMessage(sUiBot);
+                mUiBot.switchAppsUsingRecents();
+                WelcomeActivity.assertShowingDefaultMessage(mUiBot);
                 break;
             default:
                 throw new IllegalArgumentException("invalid dismiss type: " + dismissType);
         }
-        sUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
+        mUiBot.assertSaveNotShowing(SAVE_DATA_TYPE_GENERIC);
     }
 
     @Test
@@ -372,25 +422,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();
     }
 
@@ -407,19 +457,18 @@
         // Trigger autofill.
         mActivity.syncRunOnUiThread(() -> mActivity.mInput.requestFocus());
         sReplier.getNextFillRequest();
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // 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.
@@ -434,26 +483,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.assertNoDatasets();
 
         // 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
@@ -485,34 +533,44 @@
         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) {
             case ROTATE_THEN_TAP_BACK_BUTTON:
-                sUiBot.setScreenOrientation(UiBot.LANDSCAPE);
+                // After the device rotates, the input field get focus and generate a new session.
+                sReplier.addResponse(CannedFillResponse.NO_RESPONSE);
+
+                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");
+
+    }
+
+    @Override
+    protected void cleanUpAfterScreenOrientationIsBackToPortrait() throws Exception {
+        sReplier.getNextFillRequest();
     }
 
     @Override
@@ -544,38 +602,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()
@@ -599,7 +653,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();
@@ -634,30 +688,88 @@
         // 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();
+                mUiBot.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
@@ -693,14 +805,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()
@@ -712,9 +826,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.assertNoDatasets();
+
+        // 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..fd175c2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/Timeouts.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.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", 1000, 2F, 2000);
+
+    /**
+     * 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", 1000, 2F, 10000);
+
+    /**
+     * Timeout for expected autofill requests.
+     */
+    static final Timeout FILL_TIMEOUT = new Timeout("FILL_TIMEOUT", 1000, 2F, 2000);
+
+    /**
+     * Timeout for expected save requests.
+     */
+    static final Timeout SAVE_TIMEOUT = new Timeout("SAVE_TIMEOUT", 2000, 3F, 5000);
+
+    /**
+     * Time to wait if a UI change is not expected
+     */
+    static final Timeout NOT_SHOWING_TIMEOUT = new Timeout("NOT_SHOWING_TIMEOUT", 100, 2F, 500);
+
+    /**
+     * Timeout for UI operations. Typically used by {@link UiBot}.
+     */
+    static final Timeout UI_TIMEOUT = new Timeout("UI_TIMEOUT", 1000, 2F, 2000);
+
+    /**
+     * 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", 2000, 2F, 4000);
+
+    /**
+     * Timeout (in milliseconds) for an activity to be brought out to top.
+     */
+    static final Timeout ACTIVITY_RESURRECTION =
+            new Timeout("ACTIVITY_RESURRECTION", 6000, 1.5F, 20000);
+
+    /**
+     * Timeout for changing the screen orientation.
+     */
+    static final Timeout UI_SCREEN_ORIENTATION_TIMEOUT =
+            new Timeout("UI_SCREEN_ORIENTATION_TIMEOUT", 5000, 2F, 10000);
+
+    /**
+     * Timeout for using Recents to swtich activities.
+     */
+    static final Timeout UI_RECENTS_SWITCH_TIMEOUT =
+            new Timeout("UI_RECENTS_SWITCH_TIMEOUT", 200, 2F, 1000);
+
+    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 5611499..6646665 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -16,9 +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_TIMEOUT_MS;
+import static android.autofillservice.cts.Timeouts.NOT_SHOWING_TIMEOUT;
+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_RECENTS_SWITCH_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;
@@ -33,32 +36,39 @@
 import android.app.UiAutomation;
 import android.content.Context;
 import android.content.res.Resources;
-import android.os.RemoteException;
 import android.os.SystemClock;
 import android.service.autofill.SaveInfo;
+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 +80,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,13 +102,19 @@
     /** 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 {
+    UiBot() {
+        this(UI_TIMEOUT);
+    }
+
+    UiBot(Timeout defaultTimeout) {
+        mDefaultTimeout = defaultTimeout;
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
         mDevice = UiDevice.getInstance(instrumentation);
         mContext = instrumentation.getContext();
         mPackageName = mContext.getPackageName();
@@ -100,16 +124,8 @@
     /**
      * Asserts the dataset chooser is not shown.
      */
-    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;
-        }
-        throw new RetryableException(
-                "Should not be showing datasets, but got " + getChildrenAsText(picker));
+    void assertNoDatasets() throws Exception {
+        assertNotShowing("datasets", DATASET_PICKER_SELECTOR, NOT_SHOWING_TIMEOUT);
     }
 
     /**
@@ -117,10 +133,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 +183,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 +205,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 +218,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 +232,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 +251,94 @@
     /**
      * 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 after {@code timeout} milliseconds.
+     */
+    private void assertNotShowing(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));
+    }
 
     /**
      * 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);
     }
 
@@ -293,11 +361,16 @@
     /**
      * Uses the Recents button to switch back to previous activity
      */
-    void switchAppsUsingRecents() throws RemoteException {
+    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();
     }
@@ -305,15 +378,8 @@
     /**
      * 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 {
+        assertNotShowing("save UI for type " + type, SAVE_UI_SELECTOR, NOT_SHOWING_TIMEOUT);
     }
 
     private String getSaveTypeString(int type) {
@@ -340,33 +406,33 @@
         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();
 
@@ -403,11 +469,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);
@@ -422,7 +492,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);
@@ -434,7 +504,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);
     }
@@ -464,14 +534,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();
@@ -483,6 +553,10 @@
         throw new RetryableException("no '%s' on '%s'", expectedText, menuNames);
     }
 
+    String getAutofillContextualMenuTitle() {
+        return getString(RESOURCE_STRING_AUTOFILL);
+    }
+
     /**
      * Gets a string from the Android resources.
      */
@@ -506,8 +580,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);
     }
 
     /**
@@ -515,24 +589,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);
     }
 
     /**
@@ -541,17 +621,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);
     }
 
     /**
@@ -560,28 +648,26 @@
      * @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);
@@ -611,27 +697,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;
+        });
     }
 
     /**
@@ -646,7 +717,22 @@
     /**
      * 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;
+        }
     }
 }
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..951f6e4
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/UserDataTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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_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 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");
+
+
+    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 mRemoteId = "id1";
+    private final String mRemoteId2 = "id2";
+    private final String mValue = mShortValue + "-1";
+    private final String mValue2 = mShortValue + "-2";
+
+    private UserData.Builder mBuilder;
+
+    @Before
+    public void setFixtures() {
+        mBuilder = new UserData.Builder(mRemoteId, mValue);
+    }
+
+    @Test
+    public void testBuilder_invalid() {
+        assertThrows(NullPointerException.class, () -> new UserData.Builder(mRemoteId, null));
+        assertThrows(IllegalArgumentException.class, () -> new UserData.Builder(mRemoteId, ""));
+        assertThrows(IllegalArgumentException.class,
+                () -> new UserData.Builder(mRemoteId, mShortValue));
+        assertThrows(IllegalArgumentException.class,
+                () -> new UserData.Builder(mRemoteId, mLongValue));
+        assertThrows(NullPointerException.class, () -> new UserData.Builder(null, mValue));
+        assertThrows(IllegalArgumentException.class, () -> new UserData.Builder("", mValue));
+    }
+
+    @Test
+    public void testAdd_invalid() {
+        assertThrows(NullPointerException.class, () -> mBuilder.add(mRemoteId, null));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mRemoteId, ""));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mRemoteId, mShortValue));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add(mRemoteId, mLongValue));
+        assertThrows(NullPointerException.class, () -> mBuilder.add(null, mValue));
+        assertThrows(IllegalArgumentException.class, () -> mBuilder.add("", mValue));
+    }
+
+    @Test
+    public void testAdd_duplicatedId() {
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mRemoteId, mValue2));
+    }
+
+    @Test
+    public void testAdd_duplicatedValue() {
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mRemoteId2, mValue));
+    }
+
+    @Test
+    public void testAdd_maximumReached() {
+        // Must start from 1 because first is added on builder
+        for (int i = 1; i < UserData.getMaxFieldClassificationIdsSize() - 1; i++) {
+            mBuilder.add("ID" + i, mShortValue.toUpperCase() + i);
+        }
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mRemoteId, mValue));
+    }
+
+    @Test
+    public void testBuild_valid() {
+        assertThat(mBuilder.build()).isNotNull();
+    }
+
+    @Test
+    public void testNoMoreInteractionsAfterBuild() {
+        testBuild_valid();
+
+        assertThrows(IllegalStateException.class, () -> mBuilder.add(mRemoteId2, mValue));
+        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/VirtualContainerActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
index 31eae24..695f7f0 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerActivityTest.java
@@ -42,13 +42,11 @@
 import android.support.test.uiautomator.UiObject2;
 import android.view.autofill.AutofillManager;
 
-import org.junit.After;
 import org.junit.Before;
 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.
@@ -66,11 +64,6 @@
         mActivity = mActivityRule.getActivity();
     }
 
-    @After
-    public void finishWelcomeActivity() {
-        WelcomeActivity.finishIt();
-    }
-
     @Test
     public void testAutofillSync() throws Exception {
         autofillTest(true);
@@ -82,6 +75,30 @@
     }
 
     /**
+     * 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 {
@@ -98,13 +115,13 @@
         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.
@@ -145,14 +162,10 @@
         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
@@ -176,18 +189,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();
@@ -217,14 +230,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
@@ -267,15 +276,11 @@
         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
@@ -293,43 +298,43 @@
         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 {
         // 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 {
         // Set service.
         enableService();
         final MyAutofillCallback callback = mActivity.registerCallback();
@@ -338,11 +343,11 @@
         sReplier.addResponse(response);
 
         // Trigger auto-fill.
-        mActivity.mUsername.changeFocus(true);
+        focusToUsernameExpectNoWindowEvent();
         sReplier.getNextFillRequest();
 
         // Auto-fill it.
-        sUiBot.assertNoDatasets();
+        mUiBot.assertNoDatasets();
 
         // Assert callback was called
         callback.assertUiUnavailableEvent(mActivity.mCustomView, mActivity.mUsername.text.id);
@@ -365,34 +370,28 @@
         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 testSaveDialogShownWhenAllVirtualViewsNotVisible() throws Throwable {
         // 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)
+                .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
@@ -409,7 +408,7 @@
         });
 
         // Make sure save is shown
-        sUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
+        mUiBot.assertSaveShowing(SAVE_DATA_TYPE_PASSWORD);
     }
 
     @Test
@@ -427,7 +426,7 @@
                 .build());
 
         // Trigger auto-fill.
-        mActivity.mUsername.changeFocus(true);
+        focusToUsername();
         assertDatasetShown(mActivity.mUsername, "The Dude");
 
         // Make sure package name was sanitized.
@@ -439,8 +438,8 @@
     /**
      * 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 {
+        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);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java b/tests/autofillservice/src/android/autofillservice/cts/VirtualContainerView.java
index 8eecc29..92fc7a7 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;
 
@@ -74,7 +74,7 @@
     private int mUnfocusedColor;
     private boolean mSync = true;
     private boolean mOverrideDispatchProvideAutofillStructure = false;
-    private ComponentName mFakedComponentName;
+    private ComponentName mFackedComponentName;
 
     public VirtualContainerView(Context context, AttributeSet attrs) {
         super(context, attrs);
@@ -195,15 +195,15 @@
         Log.d(TAG, "onProvideAutofillVirtualStructure(): flags = " + flags);
         super.onProvideAutofillVirtualStructure(structure, flags);
 
-        if (mFakedComponentName != null) {
-            Log.d(TAG, "Faking package name to " + mFakedComponentName);
+        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);
             }
         }
 
@@ -270,9 +270,10 @@
     }
 
     void fakePackageName(ComponentName name) {
-        mFakedComponentName = name;
+        mFackedComponentName = name;
     }
 
+
     void setOverrideDispatchProvideAutofillStructure(boolean flag) {
         mOverrideDispatchProvideAutofillStructure = flag;
     }
@@ -372,8 +373,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)
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..e981118 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.assertNoDatasets();
     }
 
     @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.assertNoDatasets();
 
         // 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..374710f
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsHelper.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.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}.
+ */
+// TODO: make it more generic (it's hardcoded to 'secure' provider on current user
+public final class SettingsHelper {
+
+    /**
+     * Uses a Shell command to set the given preference.
+     */
+    public static void set(@NonNull String key, @Nullable String value) {
+        if (value == null) {
+            delete(key);
+            return;
+        }
+        runShellCommand("settings put secure %s %s default", 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 key,
+            @Nullable String value) {
+        if (value == null) {
+            syncDelete(context, key);
+            return;
+        }
+
+        final OneTimeSettingsListener observer = new OneTimeSettingsListener(context, key);
+        set(key, value);
+        observer.assertCalled();
+
+        final String newValue = get(key);
+        assertWithMessage("invalid value for '%s' settings", key).that(newValue).isEqualTo(value);
+    }
+
+    /**
+     * Uses a Shell command to delete the given preference.
+     */
+    public static void delete(@NonNull String key) {
+        runShellCommand("settings delete secure %s", 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 key) {
+
+        final OneTimeSettingsListener observer = new OneTimeSettingsListener(context, key);
+        delete(key);
+        observer.assertCalled();
+
+        final String newValue = get(key);
+        assertWithMessage("invalid value for '%s' settings", key).that(newValue).isEqualTo("null");
+    }
+
+    /**
+     * Gets the value of a given preference using Shell command.
+     */
+    @NonNull
+    public static String get(@NonNull String key) {
+        return runShellCommand("settings get secure %s", 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..82b2ef5
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateChangerRule.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.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.
+     *
+     * @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) {
+        super(new SettingsStateManager(context, 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..2e8262c
--- /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, 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..057c3b0
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/SettingsStateManager.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;
+
+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 mKey;
+
+    /**
+     * Default constructor.
+     *
+     * @param context context used to retrieve the {@link Settings} provider.
+     * @param key prefence key.
+     */
+    public SettingsStateManager(@NonNull Context context, @NonNull String key) {
+        mContext = Preconditions.checkNotNull(context);
+        mKey = Preconditions.checkNotNull(key);
+    }
+
+    @Override
+    public void set(@Nullable String value) {
+        SettingsHelper.syncSet(mContext, mKey, value);
+    }
+
+    @Override
+    @Nullable
+    public String get() {
+        return SettingsHelper.get(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..3e15af2
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/common/ShellHelper.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.autofillservice.cts.common;
+
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+/**
+ * Provides Shell-based utilities such as running a command.
+ */
+public final class ShellHelper {
+
+    private static final String TAG = "ShellHelper";
+
+    /**
+     * Runs a Shell command, returning a trimmed response.
+     */
+    @NonNull
+    public static String runShellCommand(@NonNull String template, Object...args) {
+        final String command = String.format(template, args);
+        Log.d(TAG, "runShellCommand(): " + command);
+        try {
+            final String result = SystemUtil
+                    .runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+            return TextUtils.isEmpty(result) ? "" : result.trim();
+        } catch (Exception e) {
+            throw new RuntimeException("Command '" + command + "' failed: ", e);
+        }
+    }
+
+    private ShellHelper() {
+        throw new UnsupportedOperationException("contain static methods only");
+    }
+}
diff --git a/tests/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..cd8b669 100644
--- a/tests/backup/Android.mk
+++ b/tests/backup/Android.mk
@@ -21,7 +21,13 @@
 # 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 \
+    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
 
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..f3b4942 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -35,9 +35,13 @@
 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.Handler;
 import android.os.SystemClock;
@@ -76,7 +80,10 @@
     private static final int REPEATING_CAPTURE_EXPECTED_RESULT_COUNT = 5;
     private static final int MAX_NUM_IMAGES = 5;
     private static final int MIN_FPS_REQUIRED_FOR_STREAMING = 20;
+    private static final int FPS_REQUIRED_FOR_MOTION_TRACKING_BASE = 30;
+    private static final int FPS_REQUIRED_FOR_MOTION_TRACKING_HIGH = 60;
     private static final int DEFAULT_POST_RAW_SENSITIVITY_BOOST = 100;
+    private static final float MAX_MOTION_TRACKING_FOCUS_DISTANCE = 0.5f;
 
     private CameraCaptureSession mSession;
 
@@ -90,12 +97,14 @@
             CameraDevice.TEMPLATE_PREVIEW,
             CameraDevice.TEMPLATE_RECORD,
             CameraDevice.TEMPLATE_STILL_CAPTURE,
-            CameraDevice.TEMPLATE_VIDEO_SNAPSHOT
+            CameraDevice.TEMPLATE_VIDEO_SNAPSHOT,
+            CameraDevice.TEMPLATE_MOTION_TRACKING_PREVIEW,
+            CameraDevice.TEMPLATE_MOTION_TRACKING_BEST
     };
 
     private static int[] sInvalidTemplates = new int[] {
             CameraDevice.TEMPLATE_PREVIEW - 1,
-            CameraDevice.TEMPLATE_MANUAL + 1,
+            CameraDevice.TEMPLATE_MOTION_TRACKING_BEST + 1,
     };
 
     // Request templates that are unsupported by LEGACY mode.
@@ -104,6 +113,8 @@
         sLegacySkipTemplates.add(CameraDevice.TEMPLATE_VIDEO_SNAPSHOT);
         sLegacySkipTemplates.add(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
         sLegacySkipTemplates.add(CameraDevice.TEMPLATE_MANUAL);
+        sLegacySkipTemplates.add(CameraDevice.TEMPLATE_MOTION_TRACKING_PREVIEW);
+        sLegacySkipTemplates.add(CameraDevice.TEMPLATE_MOTION_TRACKING_BEST);
     }
 
     @Override
@@ -277,6 +288,26 @@
         }
     }
 
+    /**
+     * <p>
+     * Test camera capture request motion tracking templates
+     * </p>
+     *
+     * <p>
+     * The request template returned by the camera device must include a
+     * necessary set of metadata keys, and their values must be set correctly.
+     * The motion tracking templates are mostly like preview.
+     * </p>
+     */
+    public void testCameraDeviceMotionTrackinTemplates() throws Exception {
+        for (int i = 0; i < mCameraIds.length; i++) {
+            captureTemplateTestByCamera(mCameraIds[i],
+                    CameraDevice.TEMPLATE_MOTION_TRACKING_PREVIEW);
+            captureTemplateTestByCamera(mCameraIds[i],
+                    CameraDevice.TEMPLATE_MOTION_TRACKING_BEST);
+        }
+    }
+
     public void testCameraDeviceCreateCaptureBuilder() throws Exception {
         for (int i = 0; i < mCameraIds.length; i++) {
             try {
@@ -296,6 +327,12 @@
                             sTemplates[j] != CameraDevice.TEMPLATE_PREVIEW) {
                         continue;
                     }
+                    if (!mStaticInfo.isCapabilitySupported(
+                            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING) &&
+                        (sTemplates[j] == CameraDevice.TEMPLATE_MOTION_TRACKING_PREVIEW ||
+                            sTemplates[j] == CameraDevice.TEMPLATE_MOTION_TRACKING_BEST)) {
+                        continue;
+                    }
                     CaptureRequest.Builder capReq = mCamera.createCaptureRequest(sTemplates[j]);
                     assertNotNull("Failed to create capture request", capReq);
                     if (mStaticInfo.areKeysAvailable(CaptureRequest.SENSOR_EXPOSURE_TIME)) {
@@ -783,6 +820,213 @@
         }
     }
 
+    /**
+     * 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);
+            }
+        }
+    }
+
+    /**
+     * 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 +1113,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 +1162,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 +1182,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 +1448,14 @@
                                 sTemplates[j] != CameraDevice.TEMPLATE_PREVIEW) {
                             continue;
                         }
+                        // Skip MOTION_TRACKING templates for non-MOTION_TRACKING devices
+                        if (!mStaticInfo.isCapabilitySupported(CameraCharacteristics.
+                                    REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING) &&
+                                (sTemplates[j] == CameraDevice.TEMPLATE_MOTION_TRACKING_PREVIEW ||
+                                 sTemplates[j] == CameraDevice.TEMPLATE_MOTION_TRACKING_BEST)) {
+                            continue;
+                        }
+
                         captureSingleShot(mCameraIds[i], sTemplates[j], repeating, abort);
                     }
                 }
@@ -1219,9 +1477,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 +1573,13 @@
                     templates[i] != CameraDevice.TEMPLATE_PREVIEW) {
                 continue;
             }
+            if (!mStaticInfo.isCapabilitySupported(
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING) &&
+                    (templates[i] == CameraDevice.TEMPLATE_MOTION_TRACKING_PREVIEW ||
+                     templates[i] == CameraDevice.TEMPLATE_MOTION_TRACKING_BEST)) {
+                continue;
+            }
+
             CaptureRequest.Builder requestBuilder = mCamera.createCaptureRequest(templates[i]);
             assertNotNull("Failed to create capture request", requestBuilder);
             requestBuilder.addTarget(mReaderSurface);
@@ -1469,7 +1736,26 @@
         }
 
 
-        if (template != CameraDevice.TEMPLATE_MANUAL &&
+        if (template == CameraDevice.TEMPLATE_MOTION_TRACKING_PREVIEW) {
+            mCollector.expectEquals(
+                    "AE_TARGET_FPS_RANGE min FPS must be 30 for MOTION_TRACKING_PREVIEW",
+                    minFps, FPS_REQUIRED_FOR_MOTION_TRACKING_BASE);
+            mCollector.expectEquals(
+                    "AE_TARGET_FPS_RANGE max FPS must be 30 for MOTION_TRACKING_PREVIEW",
+                    maxFps, FPS_REQUIRED_FOR_MOTION_TRACKING_BASE);
+        } else if (template == CameraDevice.TEMPLATE_MOTION_TRACKING_BEST) {
+            if (minFps != FPS_REQUIRED_FOR_MOTION_TRACKING_BASE &&
+                minFps != FPS_REQUIRED_FOR_MOTION_TRACKING_HIGH) {
+                mCollector.addMessage("Min FPS for MOTION_TRACKING_BEST must be 30 or 60");
+            }
+            if (maxFps != FPS_REQUIRED_FOR_MOTION_TRACKING_BASE &&
+                maxFps != FPS_REQUIRED_FOR_MOTION_TRACKING_HIGH) {
+                mCollector.addMessage("Max FPS for MOTION_TRACKING_BEST must be 30 or 60");
+            }
+            if (minFps != maxFps) {
+                mCollector.addMessage("Min and Max FPS for MOTION_TRACKING_BEST must be the same");
+            }
+        } else if (template != CameraDevice.TEMPLATE_MANUAL &&
                 template != CameraDevice.TEMPLATE_STILL_CAPTURE) {
             if (maxFps < MIN_FPS_REQUIRED_FOR_STREAMING) {
                 mCollector.addMessage("Max fps should be at least "
@@ -1522,6 +1808,17 @@
             }
         } else if (template == CameraDevice.TEMPLATE_MANUAL) {
             targetAfMode = CaptureRequest.CONTROL_AF_MODE_OFF;
+        } else if (template == CameraDevice.TEMPLATE_MOTION_TRACKING_PREVIEW ||
+                template == CameraDevice.TEMPLATE_MOTION_TRACKING_BEST) {
+            targetAfMode = CaptureRequest.CONTROL_AF_MODE_OFF;
+            if (mStaticInfo.isCapabilitySupported(CameraCharacteristics.
+                    REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
+                mCollector.expectKeyValueInRange(request, CaptureRequest.LENS_FOCUS_DISTANCE,
+                    0.f, MAX_MOTION_TRACKING_FOCUS_DISTANCE);
+            } else {
+                mCollector.expectKeyValueEquals(request, CaptureRequest.LENS_FOCUS_DISTANCE,
+                    0.f);
+            }
         }
 
         mCollector.expectKeyValueEquals(request, CONTROL_AF_MODE, targetAfMode);
@@ -1581,12 +1878,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 +1898,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);
@@ -1696,9 +1989,19 @@
                     props.get(CameraCharacteristics.LENS_INFO_AVAILABLE_OPTICAL_STABILIZATION);
             if (availableOIS.length > 1) {
                 mCollector.expectKeyValueNotNull(request, LENS_OPTICAL_STABILIZATION_MODE);
+                if (template == CameraDevice.TEMPLATE_MOTION_TRACKING_PREVIEW ||
+                        template == CameraDevice.TEMPLATE_MOTION_TRACKING_BEST) {
+                    mCollector.expectKeyValueEquals(request, LENS_OPTICAL_STABILIZATION_MODE,
+                            LENS_OPTICAL_STABILIZATION_MODE_OFF);
+                }
             }
         }
 
+        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 +2040,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 +2068,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 +2096,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 +2145,17 @@
             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 ||
+                template == CameraDevice.TEMPLATE_MOTION_TRACKING_PREVIEW ||
+                template == CameraDevice.TEMPLATE_MOTION_TRACKING_BEST) {
+
+            // 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 +2168,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 +2190,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 +2270,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 +2302,39 @@
                     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;
+            case CameraDevice.TEMPLATE_MOTION_TRACKING_PREVIEW:
+            case CameraDevice.TEMPLATE_MOTION_TRACKING_BEST:
+                mCollector.expectKeyValueEquals(request, CONTROL_CAPTURE_INTENT,
+                        CameraCharacteristics.CONTROL_CAPTURE_INTENT_MOTION_TRACKING);
+                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.
@@ -1946,7 +2346,7 @@
 
             assertTrue("Camera template " + template + " is out of range!",
                     template >= CameraDevice.TEMPLATE_PREVIEW
-                            && template <= CameraDevice.TEMPLATE_MANUAL);
+                            && template <= CameraDevice.TEMPLATE_MOTION_TRACKING_BEST);
 
             mCollector.setCameraId(cameraId);
 
@@ -1965,6 +2365,11 @@
                         !mStaticInfo.isCapabilitySupported(CameraCharacteristics.
                                 REQUEST_AVAILABLE_CAPABILITIES_PRIVATE_REPROCESSING)) {
                     // OK.
+                } else if ((template == CameraDevice.TEMPLATE_MOTION_TRACKING_PREVIEW ||
+                        template == CameraDevice.TEMPLATE_MOTION_TRACKING_BEST) &&
+                        !mStaticInfo.isCapabilitySupported(CameraCharacteristics.
+                                REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING)) {
+                    // OK.
                 } else if (sLegacySkipTemplates.contains(template) &&
                         mStaticInfo.isHardwareLevelLegacy()) {
                     // OK
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..f84d520 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureResultTest.java
@@ -426,6 +426,10 @@
                                 result.get(CaptureResult.NOISE_REDUCTION_MODE));
                     } else if (key.equals(CaptureResult.REQUEST_PIPELINE_DEPTH)) {
 
+                    } else if (key.equals(CaptureResult.STATISTICS_OIS_DATA_MODE)) {
+                        mCollector.expectEquals(msg,
+                                requestBuilder.get(CaptureRequest.STATISTICS_OIS_DATA_MODE),
+                                result.get(CaptureResult.STATISTICS_OIS_DATA_MODE));
                     } else {
                         // Only do non-null check for the rest of keys.
                         mCollector.expectKeyValueNotNull(failMsg, result, key);
@@ -535,6 +539,17 @@
             waiverKeys.add(CaptureResult.CONTROL_ENABLE_ZSL);
         }
 
+        if (!mStaticInfo.isAfSceneChangeSupported()) {
+            waiverKeys.add(CaptureResult.CONTROL_AF_SCENE_CHANGE);
+        }
+
+        if (!mStaticInfo.isOisDataModeSupported()) {
+            waiverKeys.add(CaptureResult.STATISTICS_OIS_DATA_MODE);
+            waiverKeys.add(CaptureResult.STATISTICS_OIS_TIMESTAMPS);
+            waiverKeys.add(CaptureResult.STATISTICS_OIS_X_SHIFTS);
+            waiverKeys.add(CaptureResult.STATISTICS_OIS_Y_SHIFTS);
+        }
+
         if (mStaticInfo.isHardwareLevelAtLeastFull()) {
             return waiverKeys;
         }
@@ -770,6 +785,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 +829,10 @@
         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_TIMESTAMPS);
+        resultKeys.add(CaptureResult.STATISTICS_OIS_X_SHIFTS);
+        resultKeys.add(CaptureResult.STATISTICS_OIS_Y_SHIFTS);
         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..aa0c064 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 =
@@ -941,6 +923,183 @@
     }
 
     /**
+     * Check motion tracking capability metadata
+     */
+    public void testMotionTrackingCharacteristics() throws Exception {
+        int counter = 0;
+
+        for (CameraCharacteristics c : mCharacteristics) {
+            Log.i(TAG, "testMotionTrackingCharacteristics: Testing camera ID " + mIds[counter]);
+
+            int[] capabilities = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+            assertNotNull("android.request.availableCapabilities must never be null",
+                    capabilities);
+            boolean supportMotionTracking = arrayContains(capabilities,
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING);
+            StreamConfigurationMap configs =
+                    c.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+
+            int[] outputFormats = configs.getOutputFormats();
+
+            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);
+
+            Integer timestampSource = c.get(CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE);
+
+            if (supportMotionTracking) {
+
+                verifyLensCalibration(poseRotation, poseTranslation, poseReference, cameraIntrinsics, radialDistortion, precorrectionArray);
+
+                if (poseReference != null) {
+                    mCollector.expectTrue("POSE_REFERENCE must be GYROSCOPE", poseReference == CameraCharacteristics.LENS_POSE_REFERENCE_GYROSCOPE);
+                }
+
+                mCollector.expectTrue("TIMESTAMP_SOURCE must be REALTIME",
+                        timestampSource != null && timestampSource == CameraCharacteristics.SENSOR_INFO_TIMESTAMP_SOURCE_REALTIME);
+
+                // Verify MOTION_TRACKING guaranteed stream configuration sizes and frame duration
+
+                Size[] yuvSizes = configs.getOutputSizes(ImageFormat.YUV_420_888);
+                Size maxYuvSize = CameraTestUtils.getMaxSize(yuvSizes);
+                float maxAspect = maxYuvSize.getWidth() / (float) maxYuvSize.getHeight();
+
+                Size vgaSize = new Size(640, (int)(640 / maxAspect));
+                boolean gotVgaSize = false;
+                for (Size s : yuvSizes) {
+                    if (s.equals(vgaSize)) {
+                        gotVgaSize = true;
+                    }
+                }
+                mCollector.expectTrue("MOTION_TRACKING requires a full-FOV YUV output with 640 width, which is "  + vgaSize +
+                        " for this camera device. Not found in list of YUV_420_888 output sizes", gotVgaSize);
+
+                if (gotVgaSize) {
+                    long minVgaDuration = configs.getOutputMinFrameDuration(ImageFormat.YUV_420_888, vgaSize);
+                    mCollector.expectTrue("Full-FOV YUV output " + vgaSize + " has minimum frame duration > 1/30s",
+                            minVgaDuration <= 1/30. * 1e9);
+                }
+
+                Size[] jpegSizes = configs.getOutputSizes(ImageFormat.JPEG);
+                Size maxJpegSizeAt30fps = null;
+                for (Size j : jpegSizes) {
+                    if (configs.getOutputMinFrameDuration(ImageFormat.JPEG, j) <=
+                            FRAME_DURATION_30FPS_NSEC) {
+                        if (maxJpegSizeAt30fps == null) {
+                            maxJpegSizeAt30fps = j;
+                        } else if (maxJpegSizeAt30fps.getWidth() < j.getWidth() ||
+                                maxJpegSizeAt30fps.getWidth() == j.getWidth() &&
+                                maxJpegSizeAt30fps.getHeight() < j.getHeight()) {
+                            maxJpegSizeAt30fps = j;
+                        }
+                    }
+                }
+                mCollector.expectTrue(
+                    "No JPEG resolution found with minimum frame duration at 1/30s or below",
+                    maxJpegSizeAt30fps != null);
+
+                WindowManager windowManager = (WindowManager) mContext.getSystemService(
+                    Context.WINDOW_SERVICE);
+
+                Size maxPreviewSize = CameraTestUtils.getSupportedPreviewSizes(
+                    mIds[counter], mCameraManager,
+                    CameraTestUtils.getPreviewSizeBound(windowManager,
+                            CameraTestUtils.PREVIEW_SIZE_BOUND)).get(0);
+                long minPreviewDuration = configs.getOutputMinFrameDuration(
+                    ImageFormat.YUV_420_888, maxPreviewSize);
+                mCollector.expectTrue(
+                    "Preview output " + maxPreviewSize + " has minimum frame duration > 1/30s",
+                    minPreviewDuration <= 1/30. * 1e9);
+
+            }
+
+            counter++;
+        }
+    }
+
+    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
      */
     public void testStreamConfigurationMap() throws Exception {
@@ -1303,6 +1462,63 @@
             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);
+
+                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);
+                }
+            }
+            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..96f363c 100644
--- a/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -553,6 +553,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 +777,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,
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..b7bd5aa
--- /dev/null
+++ b/tests/camera/src/android/hardware/camera2/cts/LogicalCameraDeviceTest.java
@@ -0,0 +1,499 @@
+/*
+ * 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.helpers.StaticMetadata;
+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 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.1;
+
+    /**
+     * 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 yuv size to use.
+                Size yuvSize = findMaxPhysicalYuvSize(id);
+                if (yuvSize == null) {
+                    Log.i(TAG, "Camera " + id + ": No matching physical YUV streams, skipping");
+                    continue;
+                }
+
+                if (VERBOSE) {
+                    Log.v(TAG, "Camera " + id + ": Testing YUV size of " + yuvSize.getWidth() +
+                        " x " + yuvSize.getHeight());
+                }
+                List<String> physicalCameraIds =
+                        mStaticInfo.getCharacteristics().getPhysicalCameraIds();
+                assertTrue("Logical camera must contain at least 2 physical camera ids",
+                        physicalCameraIds.size() >= 2);
+
+                List<String> noPhysicalIds = new ArrayList<>();
+                double avgLogicalDurationsMs = measureYuvFrameDuration(id, noPhysicalIds, yuvSize);
+
+                List<String> onePhysicalIds = new ArrayList<>();
+                onePhysicalIds.add(physicalCameraIds.get(0));
+                double avg1PhysicalDurationsMs = measureYuvFrameDuration(id,
+                        onePhysicalIds, yuvSize);
+                mCollector.expectLessOrEqual("The average frame duration increase of a physical "
+                        + "stream is larger than threshold: "
+                        + String.format("increase = %.2f, threshold = %.2f",
+                          (avg1PhysicalDurationsMs - avgLogicalDurationsMs)/avgLogicalDurationsMs,
+                          FRAME_DURATION_THRESHOLD),
+                        avgLogicalDurationsMs*(1+FRAME_DURATION_THRESHOLD),
+                        avg1PhysicalDurationsMs);
+
+                double avgAllPhysicalDurationsMs = measureYuvFrameDuration(
+                        id, physicalCameraIds, yuvSize);
+
+                mCollector.expectLessOrEqual("The average frame duration increase of all physical "
+                        + "streams is larger than threshold: "
+                        + String.format("increase = %.2f, threshold = %.2f",
+                          (avgAllPhysicalDurationsMs - avgLogicalDurationsMs)/avgLogicalDurationsMs,
+                          FRAME_DURATION_THRESHOLD),
+                        avgLogicalDurationsMs*(1+FRAME_DURATION_THRESHOLD),
+                        avgAllPhysicalDurationsMs);
+
+            } 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 to use.
+                Size yuvSize= findMaxPhysicalYuvSize(id);
+                if (yuvSize == null) {
+                    Log.i(TAG, "Camera " + id + ": No matching physical YUV streams, skipping");
+                    continue;
+                }
+
+                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<String> physicalCameraIds =
+                        mStaticInfo.getCharacteristics().getPhysicalCameraIds();
+                assertTrue("Logical camera must contain at least 2 physical camera ids",
+                        physicalCameraIds.size() >= 2);
+                ArraySet<String> physicalIdSet = new ArraySet<String>(physicalCameraIds.size());
+                physicalIdSet.addAll(physicalCameraIds);
+
+                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()));
+
+                CaptureRequest.Builder requestBuilder =
+                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW, physicalIdSet);
+                requestBuilder.addTarget(config.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);
+                    readerListener.getImage(WAIT_FOR_RESULT_TIMEOUT_MS);
+                }
+
+                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());
+
+                // Figure out yuv size to use.
+                Size yuvSize= findMaxPhysicalYuvSize(id);
+                if (yuvSize == null) {
+                    Log.i(TAG, "Camera " + id + ": No matching physical YUV streams, skipping");
+                    continue;
+                }
+
+                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()) {
+                    List<String> physicalCameraIds =
+                        mStaticInfo.getCharacteristics().getPhysicalCameraIds();
+                    assertTrue("Logical camera must contain at least 2 physical camera ids",
+                            physicalCameraIds.size() >= 2);
+
+                    physicalIdSet.clear();
+                    physicalIdSet.addAll(physicalCameraIds);
+                    requestBuilder =
+                        mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW, physicalIdSet);
+                    requestBuilder.addTarget(config.getSurface());
+                    CaptureRequest request = requestBuilder.build();
+
+                    // Streaming requests with individual physical camera settings are not
+                    // supported.
+                    try {
+                        mSession.setRepeatingRequest(request, new SimpleCaptureCallback(),
+                                mHandler);
+                        fail("Streaming requests that include physical camera settings are " +
+                                "supported");
+                    } catch (IllegalArgumentException e) {
+                        //expected
+                    }
+
+                    try {
+                        ArrayList<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
+                        requestList.add(request);
+                        mSession.setRepeatingBurst(requestList, new SimpleCaptureCallback(),
+                                mHandler);
+                        fail("Streaming requests that include physical camera settings are " +
+                                "supported");
+                    } catch (IllegalArgumentException e) {
+                        //expected
+                    }
+                }
+
+                if (mSession != null) {
+                    mSession.close();
+                }
+            } finally {
+                closeDevice();
+            }
+        }
+    }
+
+    /**
+     * Find the maximum YUV stream size that's supported by physical cameras.
+     */
+    private Size findMaxPhysicalYuvSize(String cameraId) throws Exception {
+        List<String> physicalCameras =
+                mStaticInfo.getCharacteristics().getPhysicalCameraIds();
+        List<Size> yuvSizes = CameraTestUtils.getSortedSizesForFormat(
+                cameraId, mCameraManager, ImageFormat.YUV_420_888, /*bound*/null);
+        Size bestYuvSize = null;
+        for (Size yuvSize : yuvSizes) {
+            boolean physicalSizeSupported = true;
+            for (String physicalCameraId : physicalCameras) {
+                List<Size> yuvSizesForPhysicalCamera =
+                        CameraTestUtils.getSortedSizesForFormat(physicalCameraId,
+                        mCameraManager, ImageFormat.YUV_420_888, /*bound*/null);
+                if (!yuvSizesForPhysicalCamera.contains(yuvSize)) {
+                    physicalSizeSupported = false;
+                    break;
+                }
+            }
+            if (physicalSizeSupported) {
+                bestYuvSize = yuvSize;
+                break;
+            }
+        }
+        return bestYuvSize;
+    }
+
+    /**
+     * Measure the average frame duration of logical YUV stream.
+     */
+    private double measureYuvFrameDuration(String logicalCameraId,
+            List<String> physicalCameraIds, Size yuvSize) throws Exception {
+        List<OutputConfiguration> outputConfigs = new ArrayList<>();
+        List<ImageReader> imageReaders = new ArrayList<>();
+        if (physicalCameraIds.size() == 0) {
+            ImageReader yuvTarget = CameraTestUtils.makeImageReader(yuvSize,
+                    ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
+                    new ImageDropperListener(), mHandler);
+            imageReaders.add(yuvTarget);
+            OutputConfiguration config = new OutputConfiguration(yuvTarget.getSurface());
+            outputConfigs.add(new OutputConfiguration(yuvTarget.getSurface()));
+        } else {
+            for (String physicalCameraId : physicalCameraIds) {
+                ImageReader yuvTarget = CameraTestUtils.makeImageReader(yuvSize,
+                        ImageFormat.YUV_420_888, MAX_IMAGE_COUNT,
+                        new ImageDropperListener(), mHandler);
+                OutputConfiguration config = new OutputConfiguration(yuvTarget.getSurface());
+                config.setPhysicalCameraId(physicalCameraId);
+                outputConfigs.add(config);
+                imageReaders.add(yuvTarget);
+            }
+        }
+
+        // Stream YUV size and note down the FPS
+        CaptureRequest.Builder requestBuilder =
+                mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+        for (OutputConfiguration c : outputConfigs) {
+            requestBuilder.addTarget(c.getSurface());
+        }
+
+        mSessionListener = new BlockingSessionCallback();
+        mSession = configureCameraSessionWithConfig(mCamera, outputConfigs,
+                mSessionListener, mHandler);
+
+        SimpleCaptureCallback simpleResultListener =
+                new SimpleCaptureCallback();
+        StreamConfigurationMap config = mStaticInfo.getCharacteristics().get(
+                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        final long minFrameDuration = config.getOutputMinFrameDuration(
+                ImageFormat.YUV_420_888, yuvSize);
+        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);
+        }
+
+        long prevTimestamp = -1;
+        double[] frameDurationMs = new double[NUM_FRAMES_CHECKED-1];
+        for (int i = 0; i < NUM_FRAMES_CHECKED; i++) {
+            CaptureResult captureResult =
+                    simpleResultListener.getCaptureResult(
+                    CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS);
+            long timestamp = captureResult.get(
+                    CaptureResult.SENSOR_TIMESTAMP);
+            if (prevTimestamp != -1) {
+                frameDurationMs[i-1] = (double)(timestamp - prevTimestamp)/NS_PER_MS;
+            }
+            prevTimestamp = timestamp;
+        }
+        double avgDurationMs = Stat.getAverage(frameDurationMs);
+        if (VERBOSE) {
+            Log.v(TAG, "average Duration is " + avgDurationMs + " ms");
+        }
+
+        // Stop preview
+        if (mSession != null) {
+            mSession.close();
+        }
+
+        return avgDurationMs;
+    }
+}
diff --git a/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java b/tests/camera/src/android/hardware/camera2/cts/MultiViewTest.java
index 265729d..e9eac65 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,10 @@
  */
 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;
 
     public void testTextureViewPreview() throws Exception {
         for (String cameraId : mCameraIds) {
@@ -235,6 +243,473 @@
     }
 
     /*
+     * 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 {
+        if (OutputConfiguration.getMaxSharedSurfaceCount() < (IMG_READER_COUNT + 1)) {
+            return;
+        }
+
+        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);
+
+        //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);
+    }
+
+    /*
+     * 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;
+
+        if ((surfaceLimit <= 1) ||
+                (surfaceLimit < OutputConfiguration.getMaxSharedSurfaceCount())) {
+            return;
+        }
+
+        // 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();
+
+        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 >= OutputConfiguration.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 >= OutputConfiguration.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 {
@@ -517,7 +992,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 +1022,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 +1049,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 +1059,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..3045da0 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -30,10 +30,11 @@
 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.OutputConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.CamcorderProfile;
 import android.media.Image;
 import android.media.ImageReader;
@@ -48,7 +49,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 +149,7 @@
 
                 assertTrue("Camera does not contain outputted image resolution " + actualSize,
                         testSizes.contains(actualSize));
+                imageReader.close();
             } finally {
                 closeDevice(id);
             }
@@ -238,6 +242,11 @@
             {YUV,  PREVIEW,  JPEG, MAXIMUM,  RAW, MAXIMUM}
         };
 
+        final int[][] MOTION_TRACKING_COMBINATIONS = {
+            // YUV preview with tracking YUV output and high-resolution 30fps JPEG for still capture
+            {YUV, PREVIEW, YUV, VGA_FULL_FOV, JPEG, MAX_30FPS}
+        };
+
         final int[][] LEVEL_3_COMBINATIONS = {
             // In-app viewfinder analysis with dynamic selection of output format
             {PRIV, PREVIEW, PRIV, VGA, YUV, MAXIMUM, RAW, MAXIMUM},
@@ -306,6 +315,13 @@
                     }
                 }
 
+                if (mStaticInfo.isCapabilitySupported(
+                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MOTION_TRACKING)) {
+                    for (int[] config : MOTION_TRACKING_COMBINATIONS) {
+                        testOutputCombination(id, config, maxSizes);
+                    }
+                }
+
                 if (mStaticInfo.isHardwareLevelAtLeast(
                         CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3)) {
                     for (int[] config: LEVEL_3_COMBINATIONS) {
@@ -957,6 +973,134 @@
         }
     }
 
+    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);
+
+                        long[] oisTimestamps = result.get(CaptureResult.STATISTICS_OIS_TIMESTAMPS);
+                        float[] oisXShifts = result.get(CaptureResult.STATISTICS_OIS_X_SHIFTS);
+                        float[] oisYShifts = result.get(CaptureResult.STATISTICS_OIS_Y_SHIFTS);
+
+                        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 timestamps reported in OIS_DATA_MODE_OFF",
+                                    oisTimestamps == null || oisTimestamps.length == 0);
+                            mCollector.expectTrue("OIS x shifts reported in OIS_DATA_MODE_OFF",
+                                    oisXShifts == null || oisXShifts.length == 0);
+                            mCollector.expectTrue("OIS y shifts reported in OIS_DATA_MODE_OFF",
+                                    oisYShifts == null || oisYShifts.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 timestamps not reported in OIS_DATA_MODE_ON",
+                                    oisTimestamps != null && oisTimestamps.length != 0);
+                            mCollector.expectTrue(
+                                    "Number of x shifts doesn't match number of OIS timetamps.",
+                                    oisXShifts != null && oisXShifts.length == oisTimestamps.length);
+                            mCollector.expectTrue(
+                                    "Number of y shifts doesn't match number of OIS timetamps.",
+                                    oisYShifts != null && oisYShifts.length == oisTimestamps.length);
+                        } 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 +1315,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 +1351,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 +1500,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 +1681,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) || (yuvStreams == 2 && rawStreams == 1)) {
+                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*/);
 
         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 +1790,209 @@
         }
     }
 
-    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];
+            if (format != YUV && format != RAW) {
+                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, physicalCameraIds);
+
+            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*/);
+
+        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) {
 
         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;
+            StaticMetadata physicalStaticMetadata;
+
+            if (overridePhysicalCameraIds != null &&
+                    overridePhysicalCameraIds.size() > 1) {
+                numConfigs = overridePhysicalCameraIds.size();
+            }
+            for (int j = 0; j < numConfigs; j++) {
+                switch (format) {
+                    case PRIV: {
+                        Size targetSize = maxSizes.maxPrivSizes[sizeLimit];
+                        SurfaceTexture target = new SurfaceTexture(/*random int*/1);
+                        target.setDefaultBufferSize(targetSize.getWidth(), targetSize.getHeight());
+                        OutputConfiguration config = new OutputConfiguration(new Surface(target));
+                        if (numConfigs > 1) {
+                            physicalStaticMetadata =
+                                mAllStaticInfo.get(overridePhysicalCameraIds.get(j));
+                            Size[] privSizes = physicalStaticMetadata.getAvailableSizesForFormatChecked(PRIV,
+                                    StaticMetadata.StreamDirection.Output);
+                            if (!Arrays.asList(privSizes).contains(targetSize)) {
+                                continue;
+                            }
+                            config.setPhysicalCameraId(overridePhysicalCameraIds.get(j));
+                        }
+                        outputConfigs.add(config);
+                        privTargets.add(target);
+                        break;
+                    }
+                    case JPEG: {
+                        Size targetSize = maxSizes.maxJpegSizes[sizeLimit];
+                        ImageReader target = ImageReader.newInstance(
+                            targetSize.getWidth(), targetSize.getHeight(), JPEG, numBuffers);
+                        target.setOnImageAvailableListener(imageDropperListener, mHandler);
+                        OutputConfiguration config = new OutputConfiguration(target.getSurface());
+                        if (numConfigs > 1) {
+                            physicalStaticMetadata =
+                                    mAllStaticInfo.get(overridePhysicalCameraIds.get(j));
+                            Size[] jpegSizes = physicalStaticMetadata.getAvailableSizesForFormatChecked(JPEG,
+                                    StaticMetadata.StreamDirection.Output);
+                            if (!Arrays.asList(jpegSizes).contains(targetSize)) {
+                                continue;
+                            }
+                            config.setPhysicalCameraId(overridePhysicalCameraIds.get(j));
+                        }
+                        outputConfigs.add(config);
+                        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);
+                        OutputConfiguration config = new OutputConfiguration(target.getSurface());
+                        if (numConfigs > 1) {
+                            physicalStaticMetadata =
+                                    mAllStaticInfo.get(overridePhysicalCameraIds.get(j));
+                            Size[] yuvSizes = physicalStaticMetadata.getAvailableSizesForFormatChecked(YUV,
+                                    StaticMetadata.StreamDirection.Output);
+                            if (!Arrays.asList(yuvSizes).contains(targetSize)) {
+                                continue;
+                            }
+                            config.setPhysicalCameraId(overridePhysicalCameraIds.get(j));
+                        }
+                        outputConfigs.add(config);
+                        yuvTargets.add(target);
+                        break;
+                    }
+                    case RAW: {
+                        Size targetSize = maxSizes.maxRawSize;
+                        ImageReader target = ImageReader.newInstance(
+                            targetSize.getWidth(), targetSize.getHeight(), RAW, numBuffers);
+                        target.setOnImageAvailableListener(imageDropperListener, mHandler);
+                        OutputConfiguration config = new OutputConfiguration(target.getSurface());
+                        if (numConfigs > 1) {
+                            physicalStaticMetadata =
+                                    mAllStaticInfo.get(overridePhysicalCameraIds.get(j));
+                            Size[] rawSizes = physicalStaticMetadata.getAvailableSizesForFormatChecked(RAW,
+                                    StaticMetadata.StreamDirection.Output);
+                            if (!Arrays.asList(rawSizes).contains(targetSize)) {
+                                continue;
+                            }
+                            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 7e4fb0d..4c23389 100644
--- a/tests/core/runner/Android.mk
+++ b/tests/core/runner/Android.mk
@@ -14,21 +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_STATIC_JAVA_LIBRARIES := cts-test-runner
-
-include $(BUILD_CTSCORE_PACKAGE)
-
 #==========================================================
 # Build the core runner.
 #==========================================================
@@ -45,7 +30,7 @@
     vogarexpect \
     testng
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
 
 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..0a54354 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)
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/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..ee5ff8c
--- /dev/null
+++ b/tests/framework/base/activitymanager/Android.mk
@@ -0,0 +1,39 @@
+# 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_SRC_FILES := $(call all-java-files-under, src)
+
+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..ecff87c
--- /dev/null
+++ b/tests/framework/base/activitymanager/AndroidManifest.xml
@@ -0,0 +1,69 @@
+<?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.StartActivityTests$TestActivity" android:exported="true"/>
+
+    </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..7f487ef
--- /dev/null
+++ b/tests/framework/base/activitymanager/AndroidTest.xml
@@ -0,0 +1,38 @@
+<?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="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>
+    <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..2bed053
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/AndroidManifest.xml
@@ -0,0 +1,410 @@
+<?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: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..60d4b12
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/AssistantActivity.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.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.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";
+    // Display on which Assistant runs
+    public static final String EXTRA_ASSISTANT_DISPLAY_ID = "assistant_display_id";
+
+    @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);
+            if (getIntent().hasExtra(EXTRA_ASSISTANT_DISPLAY_ID)) {
+                ActivityOptions displayOptions = ActivityOptions.makeBasic();
+                displayOptions.setLaunchDisplayId(Integer.parseInt(getIntent()
+                        .getStringExtra(EXTRA_ASSISTANT_DISPLAY_ID)));
+                startActivity(i, displayOptions.toBundle());
+            } else {
+                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.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..9e53e23
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/BroadcastReceiverActivity.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.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.server.am.util.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/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..116bd22
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/LaunchAssistantActivityIntoAssistantStack.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.server.am;
+
+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/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..1e51cef
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/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;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.server.am.util.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/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..26992c1
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/SlowCreateActivity.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;
+
+public class SlowCreateActivity extends Activity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        try {
+            Thread.sleep(2000);
+        } catch(InterruptedException e) {}
+        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..3301b6a
--- /dev/null
+++ b/tests/framework/base/activitymanager/app/src/android/server/am/VirtualDisplayActivity.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 android.app.Activity;
+import android.content.Intent;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.os.Bundle;
+import android.server.am.util.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/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..b42a87c
--- /dev/null
+++ b/tests/framework/base/activitymanager/app27/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 := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-v4 \
+    cts-am-app-base \
+
+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/hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/Android.mk b/tests/framework/base/activitymanager/appDebuggable/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/appDebuggable/Android.mk
rename to tests/framework/base/activitymanager/appDebuggable/Android.mk
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/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/hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/Android.mk b/tests/framework/base/activitymanager/appDisplaySize/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/appDisplaySize/Android.mk
rename to tests/framework/base/activitymanager/appDisplaySize/Android.mk
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/SmallestWidthActivity.java b/tests/framework/base/activitymanager/appDisplaySize/src/android/server/am/displaysize/SmallestWidthActivity.java
new file mode 100644
index 0000000..1ac4c0f
--- /dev/null
+++ b/tests/framework/base/activitymanager/appDisplaySize/src/android/server/am/displaysize/SmallestWidthActivity.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.displaysize;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+
+public class SmallestWidthActivity extends Activity {
+
+    /**
+     * Extra key to launch another activity. The extra value is activity's component name.
+     */
+    private static final String EXTRA_LAUNCH_ANOTHER_ACTIVITY = "launch_another_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..45942def
--- /dev/null
+++ b/tests/framework/base/activitymanager/appPrereleaseSdk/Android.mk
@@ -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.
+
+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 := CtsDevicePrereleaseSdkApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/framework/base/activitymanager/appPrereleaseSdk/AndroidManifest.xml b/tests/framework/base/activitymanager/appPrereleaseSdk/AndroidManifest.xml
new file mode 100644
index 0000000..76cc2b6
--- /dev/null
+++ b/tests/framework/base/activitymanager/appPrereleaseSdk/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.
+-->
+
+<!-- Manually set the compileSdkVersion and codename to simulate being compiled against
+     pre-release O SDK. -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.server.am.prerelease"
+          android:compileSdkVersion="25"
+          android:compileSdkVersionCodename="O">
+
+    <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/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/hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/Android.mk b/tests/framework/base/activitymanager/appSecondUid/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/appSecondUid/Android.mk
rename to tests/framework/base/activitymanager/appSecondUid/Android.mk
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/LaunchBroadcastReceiver.java b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/LaunchBroadcastReceiver.java
new file mode 100644
index 0000000..196be3e
--- /dev/null
+++ b/tests/framework/base/activitymanager/appSecondUid/src/android/server/am/second/LaunchBroadcastReceiver.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.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/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/hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/Android.mk b/tests/framework/base/activitymanager/appThirdUid/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/appThirdUid/Android.mk
rename to tests/framework/base/activitymanager/appThirdUid/Android.mk
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/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..dba74d4
--- /dev/null
+++ b/tests/framework/base/activitymanager/app_base/Android.mk
@@ -0,0 +1,19 @@
+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) \
+
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_MODULE := cts-am-app-base
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
diff --git a/tests/framework/base/activitymanager/app_base/src/android/server/am/AbstractLifecycleLogActivity.java b/tests/framework/base/activitymanager/app_base/src/android/server/am/AbstractLifecycleLogActivity.java
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..2b8d3be
--- /dev/null
+++ b/tests/framework/base/activitymanager/app_base/src/android/server/am/LaunchingActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am;
+
+import android.os.Bundle;
+import android.server.am.util.ActivityLauncher;
+import android.app.Activity;
+import android.content.Intent;
+import android.server.am.util.ActivityLauncher;
+import android.util.Log;
+
+/**
+ * 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..a71ab34
--- /dev/null
+++ b/tests/framework/base/activitymanager/app_base/src/android/server/am/TestActivity.java
@@ -0,0 +1,88 @@
+/*
+ * 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.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.am.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/tests/framework/base/activitymanager/app_base/src/android/server/am/util/ActivityLauncher.java b/tests/framework/base/activitymanager/app_base/src/android/server/am/util/ActivityLauncher.java
new file mode 100644
index 0000000..4f60e0a
--- /dev/null
+++ b/tests/framework/base/activitymanager/app_base/src/android/server/am/util/ActivityLauncher.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.server.am.util;
+
+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.am.TestActivity;
+import android.text.TextUtils;
+import android.util.Log;
+
+/** Utility class which contains common code for launching activities. */
+public class ActivityLauncher {
+    private static final String TAG = ActivityLauncher.class.getSimpleName();
+
+    public static 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 targetComponent = extras.getString("target_component");
+        final String targetActivity = extras.getString("target_activity");
+        if (!TextUtils.isEmpty(targetComponent)) {
+            newIntent.setComponent(ComponentName.unflattenFromString(targetComponent));
+        } else 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);
+        }
+
+        final Context launchContext = extras.getBoolean("use_application_context") ?
+                context.getApplicationContext() : context;
+
+        try {
+            launchContext.startActivity(newIntent, options != null ? options.toBundle() : null);
+        } catch (SecurityException e) {
+            Log.e(TAG, "SecurityException launching activity");
+        } catch (Exception e) {
+            if (extras.getBoolean("suppress_exceptions")) {
+                Log.e(TAG, "Exception launching activity");
+            } else {
+                throw e;
+            }
+        }
+    }
+}
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..116c026
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityAndWindowManagerOverrideConfigTests.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.server.am.StateLogger.log;
+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 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 final String TEST_ACTIVITY_NAME = "LogConfigurationActivity";
+
+    private static class ConfigurationChangeObserver {
+        private final Pattern mConfigurationChangedPattern =
+            Pattern.compile("(.+)Configuration changed: (\\d+),(\\d+)");
+
+        private ConfigurationChangeObserver() {
+        }
+
+        private boolean findConfigurationChange(String activityName, String logSeparator)
+                throws 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;
+        }
+    }
+
+    @Test
+    public void testReceiveOverrideConfigFromRelayout() throws Exception {
+        assumeTrue("Device doesn't support freeform. Skipping test.", supportsFreeform());
+
+        launchActivity(TEST_ACTIVITY_NAME, WINDOWING_MODE_FREEFORM);
+
+        try (final RotationSession rotationSession = new RotationSession()) {
+            rotationSession.set(ROTATION_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();
+            rotationSession.set(ROTATION_180);
+            final boolean reportedSizeAfterRotation = c.findConfigurationChange(TEST_ACTIVITY_NAME,
+                    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..47fcc00
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerActivityVisibilityTests.java
@@ -0,0 +1,424 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.support.test.filters.FlakyTest;
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.After;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases: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";
+
+    @Presubmit
+    @Test
+    public void testTranslucentActivityOnTopOfPinnedStack() throws Exception {
+        assumeTrue(supportsPip());
+
+        executeShellCommand(getAmStartCmdOverHome(PIP_ON_PIP_ACTIVITY));
+        mAmWmState.waitForValidState(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().getStackIdByActivityName(PIP_ON_PIP_ACTIVITY);
+
+        assertNotEquals(stackId, INVALID_STACK_ID);
+        executeShellCommand(getMoveToPinnedStackCommand(stackId));
+
+        mAmWmState.computeState(new String[] {PIP_ON_PIP_ACTIVITY, TRANSLUCENT_ACTIVITY});
+        mAmWmState.assertFrontStack("Pinned stack must be the front stack.",
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        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.
+     */
+    @Test
+    public void testTranslucentActivityOnTopOfHome() throws Exception {
+        assumeTrue(hasHomeScreen());
+
+        launchHomeActivity();
+        launchActivity(TRANSLUCENT_ACTIVITY);
+
+        mAmWmState.computeState( new String[]{TRANSLUCENT_ACTIVITY});
+        mAmWmState.assertFrontStack("Fullscreen stack must be the front stack.",
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        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.
+     */
+    @Presubmit
+    @Test
+    public void testHomeVisibleOnActivityTaskPinned() throws Exception {
+        assumeTrue(supportsPip());
+
+        launchHomeActivity();
+        launchActivity(TEST_ACTIVITY_NAME);
+        launchHomeActivity();
+        launchActivity(TRANSLUCENT_ACTIVITY);
+        final int stackId = mAmWmState.getAmState().getStackIdByActivityName(TRANSLUCENT_ACTIVITY);
+
+        assertNotEquals(stackId, INVALID_STACK_ID);
+        executeShellCommand(getMoveToPinnedStackCommand(stackId));
+
+        mAmWmState.computeState(new String[]{TRANSLUCENT_ACTIVITY});
+
+        mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY, true);
+        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, false);
+        mAmWmState.assertHomeActivityVisible(true);
+    }
+
+    @Presubmit
+    @Test
+    public void testTranslucentActivityOverDockedStack() throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        launchActivitiesInSplitScreen(DOCKED_ACTIVITY_NAME, TEST_ACTIVITY_NAME);
+        launchActivity(TRANSLUCENT_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build(),
+                new WaitForValidActivityState.Builder(DOCKED_ACTIVITY_NAME).build(),
+                new WaitForValidActivityState.Builder(TRANSLUCENT_ACTIVITY_NAME).build());
+        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_NAME, true);
+        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
+        mAmWmState.assertVisibility(TRANSLUCENT_ACTIVITY_NAME, true);
+    }
+
+    @Presubmit
+    @Test
+    public void testTurnScreenOnActivity() throws Exception {
+        sleepDevice();
+        launchActivity(TURN_SCREEN_ON_ACTIVITY_NAME);
+        mAmWmState.computeState(new String[] { TURN_SCREEN_ON_ACTIVITY_NAME });
+        mAmWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY_NAME, 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().setTargetActivityName(BROADCAST_RECEIVER_ACTIVITY).execute();
+        mAmWmState.computeState(new String[] { BROADCAST_RECEIVER_ACTIVITY });
+        mAmWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
+        // Launch something to fullscreen stack to make it focused.
+        launchActivity(TEST_ACTIVITY_NAME, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+        mAmWmState.computeState(new String[] { TEST_ACTIVITY_NAME });
+        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
+        // Finish activity in non-focused (docked) stack.
+        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+
+        mAmWmState.waitForActivityState(LAUNCHING_ACTIVITY, STATE_PAUSED);
+        mAmWmState.waitForAllExitingWindows();
+
+        mAmWmState.computeState(new String[] { 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_NAME, "finish_point", finishPoint);
+        mAmWmState.waitForValidState(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.waitForValidState(BROADCAST_RECEIVER_ACTIVITY);
+        mAmWmState.waitForActivityState(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);
+        //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().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(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().setTargetActivityName(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(NOHISTORY_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 {
+        wakeUpAndRemoveLock();
+        sleepDevice();
+        final String logSeparator = clearLogcat();
+        launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME);
+        mAmWmState.computeState(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);
+    }
+
+    @Test
+    public void testTurnScreenOnAttrWithLockScreen() throws Exception {
+        assumeTrue(isHandheld());
+
+        try (final LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+            sleepDevice();
+            final String logSeparator = clearLogcat();
+            launchActivity(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME);
+            mAmWmState.computeState(new String[]{TURN_SCREEN_ON_ATTR_ACTIVITY_NAME});
+            assertFalse(isDisplayOn());
+            assertSingleLaunchAndStop(TURN_SCREEN_ON_ATTR_ACTIVITY_NAME, logSeparator);
+        }
+    }
+
+    @Test
+    public void testTurnScreenOnShowOnLockAttr() throws Exception {
+        sleepDevice();
+        mAmWmState.waitForAllStoppedActivities();
+        final String logSeparator = clearLogcat();
+        launchActivity(TURN_SCREEN_ON_SHOW_ON_LOCK_ACTIVITY_NAME);
+        mAmWmState.computeState(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);
+    }
+
+    @Test
+    public void testTurnScreenOnAttrRemove() throws Exception {
+        sleepDevice();
+        mAmWmState.waitForAllStoppedActivities();
+        String logSeparator = clearLogcat();
+        launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME);
+        mAmWmState.computeState(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();
+        logSeparator = clearLogcat();
+        launchActivity(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME);
+        assertFalse(isDisplayOn());
+        assertSingleStartAndStop(TURN_SCREEN_ON_ATTR_REMOVE_ATTR_ACTIVITY_NAME, logSeparator);
+    }
+
+    @Test
+    @Presubmit
+    @FlakyTest(bugId = 71868306)
+    public void testTurnScreenOnSingleTask() throws Exception {
+        sleepDevice();
+        String logSeparator = clearLogcat();
+        launchActivity(TURN_SCREEN_ON_SINGLE_TASK_ACTIVITY_NAME);
+        mAmWmState.computeState(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(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);
+    }
+
+    @Test
+    public void testTurnScreenOnActivity_withRelayout() throws Exception {
+        sleepDevice();
+        launchActivity(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY);
+        mAmWmState.computeState(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) {
+        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..ea2e2b3
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmProfileTests.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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 ComponentName DEBUGGABLE_APP_ACTIVITY = ComponentName.createRelative(
+            "android.server.am.debuggable", ".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 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",
+                    activityName.flattenToShortString(), 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..6c37afb
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAmStartOptionsTests.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+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 {
+
+    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";
+
+    @Test
+    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("am start -n " + getActivityComponentName(TEST_ACTIVITY_NAME) + " -D");
+
+            mAmWmState.waitForDebuggerWindowVisible(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;
+        }
+    }
+
+    @Test
+    public void testDashW_Direct() throws Exception {
+        testDashW(SINGLE_TASK_ACTIVITY_NAME, SINGLE_TASK_ACTIVITY_NAME);
+    }
+
+    @Test
+    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("am start -n " + getActivityComponentName(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) {
+        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.am")) {
+                    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/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..4732942
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAppConfigurationTests.java
@@ -0,0 +1,645 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS 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.StateLogger.log;
+import static android.server.am.StateLogger.logE;
+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.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases: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_NAME =
+            "android.server.am.translucentapp.TranslucentLandscapeActivity";
+    private static final ComponentName TRANSLUCENT_LANDSCAPE_ACTIVITY = new ComponentName(
+            "android.server.am.translucentapp", TRANSLUCENT_ACTIVITY_NAME);
+    private static final ComponentName SDK26_TRANSLUCENT_LANDSCAPE_ACTIVITY = new ComponentName(
+            "android.server.am.translucentapp26", TRANSLUCENT_ACTIVITY_NAME);
+
+    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.
+     */
+    @Test
+    public void testConfigurationUpdatesWhenResizedFromFullscreen() throws Exception {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        String logSeparator = clearLogcat();
+        launchActivity(RESIZEABLE_ACTIVITY_NAME, WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+        final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+                logSeparator);
+
+        logSeparator = clearLogcat();
+        setActivityTaskWindowingMode(RESIZEABLE_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+                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());
+
+        String logSeparator = clearLogcat();
+        launchActivity(RESIZEABLE_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final ReportedSizes dockedSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+                logSeparator);
+
+        logSeparator = clearLogcat();
+        setActivityTaskWindowingMode(RESIZEABLE_ACTIVITY_NAME, WINDOWING_MODE_FULLSCREEN);
+        final ReportedSizes fullscreenSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+                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 String logSeparator = clearLogcat();
+            launchActivity(RESIZEABLE_ACTIVITY_NAME,
+                    WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+            final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+                    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 String logSeparator = clearLogcat();
+            // Launch our own activity to side in case Recents (or other activity to side) doesn't
+            // support rotation.
+            launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+            // Launch target activity in docked stack.
+            getLaunchActivityBuilder().setTargetActivityName(RESIZEABLE_ACTIVITY_NAME).execute();
+            final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+                    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 String logSeparator = clearLogcat();
+            launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, RESIZEABLE_ACTIVITY_NAME);
+            final ReportedSizes initialSizes = getActivityDisplaySize(RESIZEABLE_ACTIVITY_NAME,
+                    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 String logSeparator = clearLogcat();
+            final int actualStackId = mAmWmState.getAmState().getTaskByActivityName(
+                    RESIZEABLE_ACTIVITY_NAME).mStackId;
+            final int displayId = mAmWmState.getAmState().getStackById(actualStackId).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_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.
+     */
+    @Test
+    public void testSameConfigurationFullSplitFullRelaunch() throws Exception {
+        moveActivityFullSplitFull(TEST_ACTIVITY_NAME);
+    }
+
+    /**
+     * Same as {@link #testSameConfigurationFullSplitFullRelaunch} but without relaunch.
+     */
+    @Presubmit
+    @Test
+    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 {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        // Launch to fullscreen stack and record size.
+        String 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_NAME);
+    }
+
+    /**
+     * Same as {@link #testSameConfigurationSplitFullSplitRelaunch} but without relaunch.
+     */
+    @Test
+    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
+    @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(DIALOG_WHEN_LARGE_ACTIVITY,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+    }
+
+    /**
+     * Test that device handles consequent requested orientations and displays the activities.
+     */
+    @Presubmit
+    @Test
+    @FlakyTest(bugId = 71875755)
+    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();
+    }
+
+    @Test
+    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();
+
+        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.
+    // @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_ACTIVITY_NAME);
+
+        assertFalse("target SDK > 26 non-fullscreen activity should not reach onResume",
+                mAmWmState.getAmState().containsActivity(
+                        TRANSLUCENT_LANDSCAPE_ACTIVITY.flattenToShortString()));
+    }
+
+    @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_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(1);
+
+        // Finish activity
+        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+
+        // Verify that activity brought to front is in originally requested orientation.
+        mAmWmState.computeState(
+            new WaitForValidActivityState.Builder(LANDSCAPE_ACTIVITY_NAME).build());
+        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_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(1);
+
+        // Finish activity
+        executeShellCommand(MOVE_TASK_TO_BACK_BROADCAST);
+
+        // Verify that activity brought to front is in originally requested orientation.
+        mAmWmState.waitForValidState(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.
+     */
+    @Presubmit
+    @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_ACTIVITY_NAME);
+        }
+    }
+
+    /**
+     * 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_ACTIVITY_NAME);
+        }
+    }
+
+    /**
+     * Rotate the device and launch specified activity in split-screen, checking if orientation
+     * didn't change.
+     */
+    private void requestOrientationInSplitScreen(RotationSession rotationSession, int orientation,
+            String 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().setTargetActivityName(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivityName(activity).setMultipleTask(true));
+
+        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(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 {
+        assumeTrue("Skipping test: no multi-window support", supportsSplitScreenMultiWindow());
+
+        // Launch to docked stack and record size.
+        String logSeparator = clearLogcat();
+        launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        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)
+            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(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState.Builder(activityName).build());
+        final ReportedSizes details = getLastReportedSizesForActivity(activityName, logSeparator);
+        assertNotNull(details);
+        return details;
+    }
+
+    private Rect getDisplayRect(String activityName)
+            throws Exception {
+        final String windowName = getWindowName(activityName);
+
+        mAmWmState.computeState(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.
+     */
+    @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..65c3c99
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerAssistantStackTests.java
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+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 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_ASSISTANT_DISPLAY_ID = "assistant_display_id";
+    private static final String EXTRA_FINISH_SELF = "finish_self";
+    private static final String EXTRA_IS_TRANSLUCENT = "is_translucent";
+
+    private static final String TEST_ACTIVITY_ACTION_FINISH_SELF =
+            "android.server.am.TestActivity.finish_self";
+
+    private static int mAssistantDisplayId = DEFAULT_DISPLAY_ID;
+
+    public void setUp() throws Exception {
+        super.setUp();
+        try (final AssistantSession assistantSession = new AssistantSession()) {
+            assistantSession.set(getActivityComponentName(VOICE_INTERACTION_SERVICE));
+            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK);
+            mAmWmState.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.set(getActivityComponentName(VOICE_INTERACTION_SERVICE));
+
+            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
+            mAmWmState.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");
+        mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.assertContainsStack("Must contain pinned stack.",
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+
+        // Dock a task
+        launchActivitiesInSplitScreen(DOCKED_ACTIVITY, 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.set(getActivityComponentName(VOICE_INTERACTION_SERVICE));
+
+            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_FROM_SESSION);
+            mAmWmState.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(DOCKED_ACTIVITY, 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.set(getActivityComponentName(VOICE_INTERACTION_SERVICE));
+
+            launchActivityOnDisplay(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK, mAssistantDisplayId,
+                    EXTRA_LAUNCH_NEW_TASK, 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
+        mAmWmState.waitForValidState(TEST_ACTIVITY, expectedWindowingMode, ACTIVITY_TYPE_STANDARD);
+        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.set(getActivityComponentName(VOICE_INTERACTION_SERVICE));
+
+            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+                    EXTRA_FINISH_SELF, "true");
+        }
+        mAmWmState.waitForValidState(TEST_ACTIVITY,
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        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.set(getActivityComponentName(VOICE_INTERACTION_SERVICE));
+
+            launchActivity(LAUNCH_ASSISTANT_ACTIVITY_INTO_STACK,
+                    EXTRA_ENTER_PIP, "true");
+        }
+        mAmWmState.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.set(getActivityComponentName(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, String.valueOf(true));
+            mAmWmState.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, String.valueOf(true));
+            mAmWmState.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, String.valueOf(true), EXTRA_LAUNCH_NEW_TASK,
+                    TEST_ACTIVITY);
+            mAmWmState.waitForValidState(TEST_ACTIVITY,
+                    WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+            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(DOCKED_ACTIVITY, 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, String.valueOf(true));
+                mAmWmState.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.set(getActivityComponentName(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);
+            assertEquals(1, mAmWmState.getAmState().getStackByActivityType(
+                    ACTIVITY_TYPE_ASSISTANT).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.
+            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);
+            assertEquals(1, mAmWmState.getAmState().getStackByActivityType(
+                    ACTIVITY_TYPE_ASSISTANT).getTasks().size());
+            assertEquals(taskId,
+                    mAmWmState.getAmState().getTaskByActivityName(ASSISTANT_ACTIVITY).mTaskId);
+
+        }
+    }
+
+    @Test
+    public void testPinnedStackWithAssistant() throws Exception {
+        assumeTrue(supportsPip());
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        try (final AssistantSession assistantSession = new AssistantSession()) {
+            assistantSession.set(getActivityComponentName(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));
+            mAmWmState.waitForValidStateWithActivityType(
+                    TRANSLUCENT_ASSISTANT_ACTIVITY, ACTIVITY_TYPE_ASSISTANT);
+            assertAssistantStackExists();
+            mAmWmState.assertVisibility(TRANSLUCENT_ASSISTANT_ACTIVITY, true);
+            mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+            mAmWmState.assertVisibility(TEST_ACTIVITY, true);
+
+        }
+    }
+
+    /**
+     * 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);
+        }
+    }
+}
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..18bf397
--- /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.StateLogger.log;
+import static android.server.am.StateLogger.logE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+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 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;
+
+    @Test
+    public void testRotation90Relaunch() throws Exception{
+        // Should relaunch on every rotation and receive no onConfigurationChanged()
+        testRotation(TEST_ACTIVITY_NAME, 1, 1, 0);
+    }
+
+    @Test
+    public void testRotation90NoRelaunch() throws Exception {
+        // Should receive onConfigurationChanged() on every rotation and no relaunch
+        testRotation(NO_RELAUNCH_ACTIVITY_NAME, 1, 0, 1);
+    }
+
+    @Test
+    public void testRotation180Relaunch() throws Exception {
+        // Should receive nothing
+        testRotation(TEST_ACTIVITY_NAME, 2, 0, 0);
+    }
+
+    @Test
+    public void testRotation180NoRelaunch() throws Exception {
+        // Should receive nothing
+        testRotation(NO_RELAUNCH_ACTIVITY_NAME, 2, 0, 0);
+    }
+
+    @Presubmit
+    @FlakyTest(bugId = 71877849)
+    @Test
+    public void testChangeFontScaleRelaunch() throws Exception {
+        // Should relaunch and receive no onConfigurationChanged()
+        testChangeFontScale(FONT_SCALE_ACTIVITY_NAME, true /* relaunch */);
+    }
+
+    @Presubmit
+    @FlakyTest(bugId = 71877849)
+    @Test
+    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(waitForActivitiesVisible);
+
+        final int initialRotation = 4 - rotationStep;
+        try (final RotationSession rotationSession = new RotationSession()) {
+            rotationSession.set(initialRotation);
+            mAmWmState.computeState(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) {
+                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 String logSeparator = clearLogcat();
+                rotationSession.set(rotation);
+                mAmWmState.computeState(waitForActivitiesVisible);
+                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(
+            String activityName, boolean relaunch) throws Exception {
+        try (final FontScaleSession fontScaleSession = new FontScaleSession()) {
+            fontScaleSession.set(1.0f);
+            String logSeparator = clearLogcat();
+            launchActivity(activityName);
+            final String[] waitForActivitiesVisible = new String[]{activityName};
+            mAmWmState.computeState(waitForActivitiesVisible);
+
+            final int densityDpi = getActivityDensityDpi(activityName, logSeparator);
+
+            for (float fontScale = 0.85f; fontScale <= 1.3f; fontScale += 0.15f) {
+                logSeparator = clearLogcat();
+                fontScaleSession.set(fontScale);
+                mAmWmState.computeState(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.
+     */
+    @Test
+    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((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/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..4837963
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayKeyguardTests.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 static org.junit.Assume.assumeTrue;
+import android.server.am.ActivityManagerState.ActivityDisplay;
+
+import org.junit.After;
+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 {
+    private static final String DISMISS_KEYGUARD_ACTIVITY = "DismissKeyguardActivity";
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        assumeTrue(supportsMultiDisplay());
+        assumeTrue(isHandheld());
+
+        setLockDisabled(false);
+    }
+
+    /**
+     * 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 VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            gotoKeyguard();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            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..c3c5d33
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayLockedKeyguardTests.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 android.server.am;
+
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.ActivityManagerState.STATE_STOPPED;
+
+import static org.junit.Assume.assumeTrue;
+
+import android.server.am.ActivityManagerState.ActivityDisplay;
+
+import org.junit.After;
+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 {
+
+    private static final String TEST_ACTIVITY_NAME = "TestActivity";
+    private static final String VIRTUAL_DISPLAY_ACTIVITY = "VirtualDisplayActivity";
+    private static final String DISMISS_KEYGUARD_ACTIVITY = "DismissKeyguardActivity";
+    private static final String SHOW_WHEN_LOCKED_ACTIVITY = "ShowWhenLockedActivity";
+
+    @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 LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.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_NAME, newDisplay.mId);
+            mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
+
+            // Lock the device.
+            gotoKeyguard();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.waitForActivityState(TEST_ACTIVITY_NAME, STATE_STOPPED);
+            mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, false /* visible */);
+
+            // Unlock and check if visibility is back.
+            lockCredentialSession.unlockDeviceWithCredential();
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.waitForActivityState(TEST_ACTIVITY_NAME, STATE_RESUMED);
+            mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, 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 LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            gotoKeyguard();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivityOnDisplay(DISMISS_KEYGUARD_ACTIVITY, newDisplay.mId);
+            lockCredentialSession.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 LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            gotoKeyguard();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.computeState(new WaitForValidActivityState(SHOW_WHEN_LOCKED_ACTIVITY));
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+            launchActivityOnDisplay(DISMISS_KEYGUARD_ACTIVITY, newDisplay.mId);
+            lockCredentialSession.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..09d793f
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java
@@ -0,0 +1,429 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.StateLogger.log;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+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
+ */
+public class ActivityManagerDisplayTestBase extends ActivityManagerTestBase {
+    private static final String WM_SIZE = "wm size";
+    private static final String WM_DENSITY = "wm density";
+
+    static final int CUSTOM_DENSITY_DPI = 222;
+
+    private static final String VIRTUAL_DISPLAY_ACTIVITY = "VirtualDisplayActivity";
+    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 String 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(String 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)
+                        .setTargetActivityName(VIRTUAL_DISPLAY_ACTIVITY)
+                        .execute();
+            } else {
+                launchActivity(VIRTUAL_DISPLAY_ACTIVITY);
+            }
+            mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+                    new WaitForValidActivityState(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_activity ")
+                        .append(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);
+        }
+
+        /**
+         * 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..d82c7c5
--- /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 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.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityManagerDisplayTests
+ */
+public class ActivityManagerDisplayTests extends ActivityManagerDisplayTestBase {
+    private static final String TEST_ACTIVITY_NAME = "TestActivity";
+
+    /**
+     * 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_NAME, newDisplay.mId);
+            mAmWmState.computeState(new WaitForValidActivityState(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);
+        }
+    }
+
+    @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()) {
+            // 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.
+            sleepDevice();
+
+            wakeUpAndUnlockDevice();
+            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..22d0934
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerFreeformStackTests.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 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 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";
+
+    @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().getTaskByActivityName(TEST_ACTIVITY).getBounds());
+    }
+
+    @Test
+    public void testNonResizeableActivityHasFullDisplayBounds() throws Exception {
+        launchActivity(NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_FREEFORM);
+
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(NON_RESIZEABLE_ACTIVITY).build());
+
+        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());
+    }
+
+    @Test
+    public void testActivityLifeCycleOnResizeFreeformTask() throws Exception {
+        launchActivity(TEST_ACTIVITY, WINDOWING_MODE_FREEFORM);
+        launchActivity(NO_RELAUNCH_ACTIVITY, WINDOWING_MODE_FREEFORM);
+
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY).build(),
+                new WaitForValidActivityState.Builder(NO_RELAUNCH_ACTIVITY).build());
+
+        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 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(new WaitForValidActivityState.Builder(TEST_ACTIVITY).build(),
+                new WaitForValidActivityState.Builder(NO_RELAUNCH_ACTIVITY).build());
+
+        assertActivityLifecycle(TEST_ACTIVITY, true /* relaunched */, logSeparator);
+        assertActivityLifecycle(NO_RELAUNCH_ACTIVITY, false /* relaunched */, logSeparator);
+    }
+}
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..cb9be66
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerManifestLayoutTests.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.junit.Assume.assumeTrue;
+
+import android.graphics.Rect;
+import android.server.am.WindowManagerState.Display;
+import android.server.am.WindowManagerState.WindowState;
+
+import junit.framework.Assert;
+
+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 {
+        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 (windowingMode == WINDOWING_MODE_FREEFORM) {
+            launchActivity(activityName, WINDOWING_MODE_FREEFORM);
+            resizeActivityTask(activityName, 0, 0, 1, 1);
+        } else { // stackId == DOCKED_STACK_ID
+            launchActivityInSplitScreenWithRecents(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 Rect 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 {
+        assumeTrue("Skipping test: no freeform support", supportsFreeform());
+
+        final String activityName = (vGravity == GRAVITY_VER_TOP ? "Top" : "Bottom")
+                + (hGravity == GRAVITY_HOR_LEFT ? "Left" : "Right") + "LayoutActivity";
+
+        // 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(String activityName, boolean checkFocus)
+            throws Exception {
+        final String windowName = getWindowName(activityName);
+
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(activityName).build());
+
+        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,
+            Rect containingFrame, Rect 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.top, containingFrame.top);
+        } else if (vGravity == GRAVITY_VER_BOTTOM) {
+            Assert.assertEquals("Should be on the bottom",
+                    parentFrame.bottom, containingFrame.bottom);
+        }
+
+        if (hGravity == GRAVITY_HOR_LEFT) {
+            Assert.assertEquals("Should be on the left", parentFrame.left, containingFrame.left);
+        } else if (hGravity == GRAVITY_HOR_RIGHT){
+            Assert.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..d73509d
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
@@ -0,0 +1,1800 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ActivityManagerDisplayTestBase.ReportedDisplayMetrics
+        .getDisplayMetrics;
+import static android.server.am.ActivityManagerState.STATE_RESUMED;
+import static android.server.am.ActivityManagerState.STATE_STOPPED;
+import static android.server.am.StateLogger.logE;
+
+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 {
+    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 SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME = "ShowWhenLockedAttrActivity";
+    private static final String SECOND_PACKAGE = "android.server.am.second";
+    private static final String THIRD_PACKAGE = "android.server.am.third";
+    private static final ComponentName SECOND_ACTIVITY = ComponentName.createRelative(
+            SECOND_PACKAGE, ".SecondActivity");
+    private static final ComponentName SECOND_NO_EMBEDDING_ACTIVITY = ComponentName.createRelative(
+            SECOND_PACKAGE, ".SecondActivityNoEmbedding");
+    private static final ComponentName LAUNCH_BROADCAST_RECEIVER = ComponentName.createRelative(
+            SECOND_PACKAGE, ".LaunchBroadcastReceiver");
+    /** See AndroidManifest.xml of appSecondUid. */
+    private static final String LAUNCH_BROADCAST_ACTION =
+            SECOND_PACKAGE + ".LAUNCH_BROADCAST_ACTION";
+    private static final ComponentName THIRD_ACTIVITY = ComponentName.createRelative(
+            THIRD_PACKAGE, ".ThirdActivity");
+
+    @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 String logSeparator = clearLogcat();
+            launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+            mAmWmState.computeState(new WaitForValidActivityState(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.mId);
+            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.
+     */
+    @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_NAME, newDisplay.mId);
+            mAmWmState.computeState(new WaitForValidActivityState(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.
+     */
+    @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_NAME, newDisplay.mId);
+            mAmWmState.computeState(new WaitForValidActivityState(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.",
+                    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_NAME);
+            // Launch a resizeable activity on new secondary display to create a new stack there.
+            launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, 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_NAME, externalFrontStackId);
+            mAmWmState.computeState(new WaitForValidActivityState(NON_RESIZEABLE_ACTIVITY_NAME));
+
+            mAmWmState.assertFocusedActivity(
+                    "Activity launched on secondary display must be focused",
+                    RESIZEABLE_ACTIVITY_NAME);
+
+            // 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",
+                    getActivityComponentName(NON_RESIZEABLE_ACTIVITY_NAME),
+                    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",
+                    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(new WaitForValidActivityState(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.mId);
+            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).
+     */
+    @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",
+                    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.mId == 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.
+     */
+    @Test
+    public void testLaunchWithoutPermissionOnVirtualDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // Create new virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession.createDisplay();
+
+            final String logSeparator = clearLogcat();
+
+            // Try to launch an activity and check it security exception was triggered.
+            final String broadcastTarget = "-a " + LAUNCH_BROADCAST_ACTION
+                    + " -p " + LAUNCH_BROADCAST_RECEIVER.getPackageName();
+            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.mId
+                    + includeStoppedPackagesFlag);
+
+            assertSecurityException("LaunchBroadcastReceiver", logSeparator);
+
+            mAmWmState.computeState(new WaitForValidActivityState(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.
+     */
+    @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.
+            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.mId);
+
+            mAmWmState.waitForValidState(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.mId,
+                    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
+    @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_NAME, newDisplay.mId);
+            mAmWmState.computeState(new WaitForValidActivityState(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(new WaitForValidActivityState(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",
+                    getActivityComponentName(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().setTargetActivityName(TEST_ACTIVITY_NAME).execute();
+            mAmWmState.computeState(new WaitForValidActivityState(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.mId);
+            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.
+     */
+    @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().setTargetActivityName(TEST_ACTIVITY_NAME).execute();
+            mAmWmState.computeState(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.mId);
+            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.
+     */
+    @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_NAME);
+
+            // 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",
+                    SECOND_ACTIVITY.flattenToShortString(), frontStack.mResumedActivity);
+
+            // Launch other activity with different uid and check if it has launched successfully.
+            getLaunchActivityBuilder()
+                    .setUseBroadcastReceiver(LAUNCH_BROADCAST_RECEIVER, LAUNCH_BROADCAST_ACTION)
+                    .setDisplayId(newDisplay.mId)
+                    .setTargetActivity(THIRD_ACTIVITY)
+                    .execute();
+            mAmWmState.waitForValidState(new WaitForValidActivityState(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",
+                    THIRD_ACTIVITY.flattenToShortString(), 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 String 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().setTargetActivityName(TEST_ACTIVITY_NAME)
+                    .setDisplayId(newDisplay.mId).execute();
+
+            // Check that activity is launched on external display.
+            mAmWmState.computeState(new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+            mAmWmState.assertFocusedActivity(
+                    "Activity launched on secondary display must be focused",
+                    TEST_ACTIVITY_NAME);
+            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",
+                    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
+    @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_NAME, newDisplay.mId);
+            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(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.
+     */
+    @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_NAME, newDisplay.mId);
+            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.mId, focusedStack.mDisplayId);
+
+            // Move activity from secondary display to primary.
+            moveActivityToStack(TEST_ACTIVITY_NAME, defaultDisplayStackId);
+            mAmWmState.waitForFocusedStack(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", 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(RESIZEABLE_ACTIVITY_NAME, 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 {
+        String 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(TEST_ACTIVITY_NAME, newDisplay.mId);
+            mAmWmState.assertFocusedActivity("Focus must be on secondary display",
+                    TEST_ACTIVITY_NAME);
+            final int frontStackId = mAmWmState.getAmState().getFrontStackId(newDisplay.mId);
+            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+
+            // Destroy virtual display.
+            logSeparator = clearLogcat();
+        }
+
+        assertActivityLifecycle(TEST_ACTIVITY_NAME, false /* relaunched */, logSeparator);
+        mAmWmState.waitForValidState(TEST_ACTIVITY_NAME, windowingMode, ACTIVITY_TYPE_STANDARD);
+        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",
+                windowingMode, ACTIVITY_TYPE_STANDARD);
+        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.
+     */
+    @Test
+    public void testStackFocusSwitchOnStackEmptied() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+            // 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.
+            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(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(new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
+            mAmWmState.assertFocusedActivity("Focus must be switched back to primary display",
+                    VIRTUAL_DISPLAY_ACTIVITY);
+
+            launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+
+            mAmWmState.computeState(new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+            mAmWmState.assertFocusedActivity(
+                    "Activity launched on secondary display must be focused",
+                    TEST_ACTIVITY_NAME);
+
+            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(new WaitForValidActivityState(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_NAME, newDisplay.mId);
+            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.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 " + SECOND_ACTIVITY.flattenToShortString()
+                            + " --display " + newDisplay.mId;
+            executeShellCommand(startCmd);
+
+            mAmWmState.waitForValidState(new WaitForValidActivityState(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 " + SECOND_ACTIVITY.flattenToShortString()
+                    + " --display " + newDisplay.mId;
+            executeShellCommand(startCmd);
+
+            mAmWmState.waitForValidState(new WaitForValidActivityState(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.
+            final String targetActivity =
+                    " --es target_activity " + THIRD_ACTIVITY.getShortClassName()
+                    + " --es package_name " + THIRD_ACTIVITY.getPackageName()
+                    + " --ei display_id " + newDisplay.mId;
+            final String includeStoppedPackagesFlag = " -f 0x00000020";
+            executeShellCommand("am broadcast -a " + LAUNCH_BROADCAST_ACTION
+                    + " -p " + LAUNCH_BROADCAST_RECEIVER.getPackageName()
+                    + targetActivity + includeStoppedPackagesFlag);
+
+            mAmWmState.waitForValidState(new WaitForValidActivityState(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(new WaitForValidActivityState(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",
+                    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().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 " + SECOND_ACTIVITY.flattenToShortString()
+                    + " --display " + newDisplay.mId;
+            executeShellCommand(startCmd);
+
+            mAmWmState.waitForValidState(new WaitForValidActivityState(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.
+            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.mId);
+
+            mAmWmState.waitForValidState(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.
+     */
+    @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_NAME, newDisplay.mId);
+            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.mId,
+                    focusedStack.mDisplayId);
+
+            final String logSeparator = clearLogcat();
+
+            // Launch other activity with different uid and check security exception is triggered.
+            final String includeStoppedPackagesFlag = " -f 0x00000020";
+            executeShellCommand("am broadcast -a " + LAUNCH_BROADCAST_ACTION
+                    + " -p " + LAUNCH_BROADCAST_RECEIVER.getPackageName()
+                    + " --ei display_id " + newDisplay.mId
+                    + includeStoppedPackagesFlag);
+
+            assertSecurityException("LaunchBroadcastReceiver", logSeparator);
+
+            mAmWmState.waitForValidState(false /* compareTaskAndStackBounds */, componentName,
+                    new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+            mAmWmState.assertFocusedActivity("Focus must be on first activity", 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.
+     */
+    @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 {
+        String 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_NAME, newDisplay.mId);
+            mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true /* visible */);
+            mAmWmState.assertFocusedActivity("Launched activity must be focused",
+                    TEST_ACTIVITY_NAME);
+            launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mId);
+            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.
+            logSeparator = clearLogcat();
+        }
+
+        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(
+                (state) -> !state.containsActivity(activityName1)
+                        && !state.containsActivity(activityName2),
+                "Waiting for activity to be removed");
+        mAmWmState.waitForWithWmState(
+                (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
+    @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 String initialLogSeparator = clearLogcat();
+            launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mId);
+            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(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(false /* compareTaskAndStackBounds */,
+                    new WaitForValidActivityState(RESIZEABLE_ACTIVITY_NAME),
+                    new WaitForValidActivityState(VIRTUAL_DISPLAY_ACTIVITY));
+            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.
+     */
+    @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_NAME, newDisplay.mId);
+            mAmWmState.assertVisibility(RESIZEABLE_ACTIVITY_NAME, 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 " + getActivityComponentName(
+                    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",
+                    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 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 " + getActivityComponentName(
+                    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",
+                    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 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.
+            String logSeparator = clearLogcat();
+            launchActivityOnDisplay(RESIZEABLE_ACTIVITY_NAME, newDisplay.mId);
+            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);
+
+            try (final RotationSession rotationSession = new RotationSession()) {
+                // Rotate primary display and check that activity on secondary display is not
+                // affected.
+
+                rotateAndCheckSameSizes(rotationSession, RESIZEABLE_ACTIVITY_NAME);
+
+                // 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_NAME, newDisplay.mId);
+                mAmWmState.waitForActivityState(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(RotationSession rotationSession, String activityName)
+            throws Exception {
+        for (int rotation = 3; rotation >= 0; --rotation) {
+            final String 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(new WaitForValidActivityState(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",
+                    getActivityComponentName(LAUNCHING_ACTIVITY), firstFrontStack.mResumedActivity);
+            mAmWmState.assertFocusedStack("Focus must be on secondary display", frontStackId);
+
+            executeShellCommand("am start -n " + getActivityComponentName(ALT_LAUNCHING_ACTIVITY));
+            mAmWmState.waitForValidState(false /* compareTaskAndStackBounds */, componentName,
+                    new WaitForValidActivityState(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",
+                    getActivityComponentName(ALT_LAUNCHING_ACTIVITY),
+                    defaultDisplayFrontStack.mResumedActivity);
+            mAmWmState.assertFocusedStack("Focus must be on primary display",
+                    defaultDisplayFrontStackId);
+
+            executeShellCommand("am start -n " + getActivityComponentName(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",
+                    getActivityComponentName(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",
+                    getActivityComponentName(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 "
+                    + getActivityComponentName(ALT_LAUNCHING_ACTIVITY)
+                    + " -f 0x10000000" // FLAG_ACTIVITY_NEW_TASK
+                    + " --display " + newDisplay.mId);
+            mAmWmState.computeState(new WaitForValidActivityState(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",
+                    getActivityComponentName(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(new WaitForValidActivityState(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",
+                    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(false /* compareTaskAndStackBounds */, componentName,
+                    new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+
+            // 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);
+            assertEquals("Focus must be on primary display", DEFAULT_DISPLAY_ID,
+                    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(false /* compareTaskAndStackBounds */, componentName,
+                    new WaitForValidActivityState(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",
+                    getActivityComponentName(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_NAME)
+                    .createDisplay();
+
+            // Check that activity is launched and placed correctly.
+            mAmWmState.waitForActivityState(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.mId);
+            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.
+     */
+    @Test
+    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");
+
+        try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession();
+             final PrimaryDisplayStateSession displayStateSession =
+                     new PrimaryDisplayStateSession()) {
+            final ActivityDisplay newDisplay =
+                    externalDisplaySession.createVirtualDisplay(true /* showContentWhenLocked */);
+
+            launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+
+            // Check that the activity is launched onto the external display
+            waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, 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_NAME,
+                    "Activity launched on primary display must be stopped after turning off");
+            waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, 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_NAME);
+        waitAndAssertActivityResumed(RESIZEABLE_ACTIVITY_NAME, 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_NAME,
+                    "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_NAME, newDisplay.mId);
+
+            // Check that the test activity is resumed on the external display
+            waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, 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_NAME, newDisplay.mId);
+
+            // Check that the test activity is resumed on the external display
+            waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, 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_NAME,
+                    "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_NAME, 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_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());
+
+        try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession()) {
+            final ActivityDisplay newDisplay =
+                    externalDisplaySession.createVirtualDisplay(true /* showContentWhenLocked */);
+
+            launchActivityOnDisplay(TEST_ACTIVITY_NAME, newDisplay.mId);
+
+            // Check that the test activity is resumed on the external display
+            waitAndAssertActivityResumed(TEST_ACTIVITY_NAME, newDisplay.mId,
+                    "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");
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.waitForValidState(new WaitForValidActivityState(TEST_ACTIVITY_NAME));
+            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_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(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 {
+        mAmWmState.waitForActivityState(activityName, STATE_STOPPED);
+
+        assertTrue(message, mAmWmState.getAmState().hasActivityState(activityName,
+                STATE_STOPPED));
+    }
+
+    /**
+     * Tests that showWhenLocked works on a secondary display.
+     */
+    public void testSecondaryDisplayShowWhenLocked() throws Exception {
+        try (final ExternalDisplaySession externalDisplaySession = new ExternalDisplaySession();
+             final LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+
+            launchActivity(TEST_ACTIVITY_NAME);
+
+            final ActivityDisplay newDisplay =
+                    externalDisplaySession.createVirtualDisplay(false /* showContentWhenLocked */);
+            launchActivityOnDisplay(SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME, newDisplay.mId);
+
+            gotoKeyguard();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+
+            mAmWmState.waitForActivityState(TEST_ACTIVITY_NAME, STATE_STOPPED);
+            mAmWmState.waitForActivityState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME, STATE_RESUMED);
+
+            mAmWmState.computeState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME);
+            assertTrue("Expected resumed activity on secondary display", mAmWmState.getAmState()
+                    .hasActivityState(SHOW_WHEN_LOCKED_ATTR_ACTIVITY_NAME, STATE_RESUMED));
+        }
+    }
+
+    /** 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";
+    }
+
+    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) {
+                new RuntimeException("No external display created");
+            }
+            mExternalDisplayHelper.turnDisplayOff();
+        }
+
+        void turnDisplayOn() {
+            if (mExternalDisplayHelper == null) {
+                new RuntimeException("No external display created");
+            }
+            mExternalDisplayHelper.turnDisplayOn();
+        }
+
+        @Override
+        public void close() throws Exception {
+            if (mExternalDisplayHelper != null) {
+                mExternalDisplayHelper.releaseDisplay();
+                mExternalDisplayHelper = null;
+            }
+        }
+    }
+
+    private static 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) {
+            // Either KeyEvent.KEYCODE_WAKEUP or KeyEvent.KEYCODE_SLEEP
+            int keycode = wantOn ? 224 : 223;
+            executeShellCommand("input keyevent " + keycode);
+            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..5601f79
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerPinnedStackTests.java
@@ -0,0 +1,1554 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.view.KeyEvent.KEYCODE_WINDOW;
+
+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 static org.junit.Assume.assumeTrue;
+
+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 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 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.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 TEST_ACTIVITY_ACTION_FINISH =
+            "android.server.am.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;
+
+    @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(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        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.Builder(PIP_ACTIVITY).build());
+        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(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        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(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+
+        // 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(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        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(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+
+        // 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, getActivityComponentName(NON_RESIZEABLE_ACTIVITY));
+        mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState.Builder(NON_RESIZEABLE_ACTIVITY).build());
+        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(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        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();
+        assertTrue(pinnedStack.getTasks().size() == 1);
+        assertTrue(pinnedStack.getTasks().get(0).mRealActivity.equals(getActivityComponentName(
+                ALWAYS_FOCUSABLE_PIP_ACTIVITY)));
+    }
+
+    @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) -> {
+            return amState.getFrontStackWindowingMode(DEFAULT_DISPLAY_ID)
+                    == WINDOWING_MODE_FULLSCREEN;
+        }, "Waiting for PIP to exit to fullscreen");
+        mAmWmState.waitForWithAmState((amState) -> {
+            return 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) -> {
+            return amState.getFrontStackWindowingMode(DEFAULT_DISPLAY_ID)
+                    == WINDOWING_MODE_FULLSCREEN;
+        }, "Waiting for PIP to exit to fullscreen");
+        mAmWmState.waitForWithAmState((amState) -> {
+            return 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());
+
+        // 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);
+    }
+
+    @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(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        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(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        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);
+        String logSeparator = clearLogcat();
+        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+        mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        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(PIP_ACTIVITY, WINDOWING_MODE_PINNED,
+                    ACTIVITY_TYPE_STANDARD);
+            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(
+                    PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+
+            // 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);
+        }
+    }
+
+    @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
+        String logSeparator = clearLogcat();
+        removeStacksInWindowingModes(WINDOWING_MODE_PINNED);
+        mAmWmState.waitForValidState(
+                PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+
+        // 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");
+            }
+        }
+    }
+
+    @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(
+                PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        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(
+                PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        assertPinnedStackDoesNotExist();
+        assertTrue(mAmWmState.getWmState().getLastOrientation() == ORIENTATION_LANDSCAPE);
+    }
+
+    @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(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        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) -> {
+            return 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
+        String logSeparator = clearLogcat();
+        executeShellCommand("am stack resize-animated 4 20 20 500 500");
+        executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH);
+        mAmWmState.waitFor((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);
+    }
+
+    @Test
+    public void testPinnedStackWithDockedStack() throws Exception {
+        assumeTrue(supportsPip());
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        launchActivity(PIP_ACTIVITY, EXTRA_ENTER_PIP, "true");
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivityName(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivityName(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().setTargetActivityName(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivityName(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
+        pressAppSwitchButton();
+        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().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);
+    }
+
+    @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, getActivityComponentName(TEST_ACTIVITY),
+                EXTRA_FINISH_SELF_ON_RESUME, "true");
+        mAmWmState.waitForValidState(
+                TEST_ACTIVITY, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        launchActivity(PIP_ACTIVITY_WITH_SAME_AFFINITY);
+        mAmWmState.waitForValidState(
+                PIP_ACTIVITY_WITH_SAME_AFFINITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        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);
+    }
+
+    @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, getActivityComponentName(PIP_ACTIVITY),
+                EXTRA_FINISH_SELF_ON_RESUME, "true");
+        mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        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(
+                PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+        assertPinnedStackDoesNotExist();
+        assertTrue(activityTaskId == mAmWmState.getAmState().getTaskByActivityName(
+                PIP_ACTIVITY).mTaskId);
+    }
+
+    /** Test that reported display size corresponds to fullscreen after exiting PiP. */
+    @FlakyTest
+    @Presubmit
+    @Test
+    public void testDisplayMetricsPinUnpin() throws Exception {
+        assumeTrue(supportsPip());
+
+        String logSeparator = clearLogcat();
+        launchActivity(TEST_ACTIVITY);
+        final int defaultWindowingMode = mAmWmState.getAmState()
+                .getTaskByActivityName(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(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.waitForAppTransitionIdle();
+        final ReportedSizes pinnedSizes = getLastReportedSizesForActivity(PIP_ACTIVITY,
+                logSeparator);
+        final Rect 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();
+        launchActivity(PIP_ACTIVITY, defaultWindowingMode);
+        final ReportedSizes finalSizes = getLastReportedSizesForActivity(PIP_ACTIVITY,
+                logSeparator);
+        final Rect 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());
+    }
+
+    @Presubmit
+    @Test
+    public void testEnterPictureInPictureSavePosition() throws Exception {
+        if (!supportsPip()) return;
+
+        // 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(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD);
+        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+        mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        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(PIP_ACTIVITY, WINDOWING_MODE_FULLSCREEN,
+                ACTIVITY_TYPE_STANDARD);
+        launchActivity(TEST_ACTIVITY);
+        executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH);
+        mAmWmState.waitForActivityState(PIP_ACTIVITY, STATE_RESUMED);
+        executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+        mAmWmState.waitForValidState(PIP_ACTIVITY, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        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 {
+        if (!supportsPip()) return;
+
+        // 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) -> {
+            return 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(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 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(String 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(String activity) throws Exception {
+        final WindowManagerState.WindowState windowState = getWindowState(activity);
+        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(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((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...");
+    }
+
+    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 activity}'s window.
+     */
+    private WindowManagerState.WindowState getWindowState(String activity) throws Exception {
+        String windowName = getWindowName(activity);
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(activity).build());
+        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(String activityName, int taskId)
+            throws Exception {
+        executeShellCommand(getAmStartCmd(activityName) + " --task " + taskId + " --task-overlay");
+
+        mAmWmState.waitForValidState(activityName, WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+    }
+
+    /**
+     * 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 {
+        mDevice.pressKeyCode(KEYCODE_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 startActivity,
+            String topActivityName, boolean moveTopToPinnedStack, boolean isFocusable)
+            throws Exception {
+        executeShellCommand(startActivityCmd);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(startActivity).build());
+
+        if (moveTopToPinnedStack) {
+            final int stackId = mAmWmState.getAmState().getStackIdByActivityName(topActivityName);
+
+            assertNotEquals(stackId, INVALID_STACK_ID);
+            executeShellCommand(getMoveToPinnedStackCommand(stackId));
+        }
+
+        mAmWmState.waitForValidState(topActivityName,
+                WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.computeState();
+
+        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..faa72a9
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerReplaceWindowTests.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.StateLogger.log;
+
+import static org.junit.Assume.assumeTrue;
+
+import junit.framework.Assert;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases: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();
+
+    @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 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);
+        }
+
+        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.
+        Thread.sleep(5000);
+
+        log("==========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(new WaitForValidActivityState.Builder(activityName).build());
+
+        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/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..36e6f04
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerSplitScreenTests.java
@@ -0,0 +1,661 @@
+/*
+ * 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.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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+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 String TEST_ACTIVITY_NAME = "TestActivity";
+    private static final String NON_RESIZEABLE_ACTIVITY_NAME = "NonResizeableActivity";
+    private static final String DOCKED_ACTIVITY_NAME = "DockedActivity";
+    private static final String 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 String TEST_ACTIVITY_ACTION_FINISH =
+        "android.server.am.TestActivity.finish_self";
+
+    private static final int TASK_SIZE = 600;
+    private static final int STACK_SIZE = 300;
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        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 = 71918731)
+    public void testStackList() throws Exception {
+        launchActivity(TEST_ACTIVITY_NAME);
+        mAmWmState.computeState(new String[] {TEST_ACTIVITY_NAME});
+        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_NAME);
+        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_NAME);
+
+        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(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+        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(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+        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 String logSeparator = clearLogcat();
+        removeStacksInWindowingModes(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        final ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
+                TEST_ACTIVITY_NAME, logSeparator);
+        assertEquals(1, lifecycleCounts.mMultiWindowModeChangedCount);
+    }
+
+    @Test
+    @Presubmit
+    public void testNoUserLeaveHintOnMultiWindowModeChanged() throws Exception {
+        launchActivity(TEST_ACTIVITY_NAME, WINDOWING_MODE_FULLSCREEN);
+
+        // Move to docked stack.
+        String logSeparator = clearLogcat();
+        setActivityTaskWindowingMode(TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
+                TEST_ACTIVITY_NAME, 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_NAME, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+
+        // Move activity back to fullscreen stack.
+        logSeparator = clearLogcat();
+        setActivityTaskWindowingMode(TEST_ACTIVITY_NAME, WINDOWING_MODE_FULLSCREEN);
+        lifecycleCounts = waitForOnMultiWindowModeChanged(TEST_ACTIVITY_NAME, logSeparator);
+        assertEquals("mMultiWindowModeChangedCount",
+                1, lifecycleCounts.mMultiWindowModeChangedCount);
+        assertEquals("mUserLeaveHintCount", 0, lifecycleCounts.mUserLeaveHintCount);
+    }
+
+    @Test
+    @Presubmit
+    public void testLaunchToSideAndBringToFront() throws Exception {
+        launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+
+        int taskNumberInitial = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        mAmWmState.assertFocusedActivity("Launched to side activity must be in front.",
+                TEST_ACTIVITY_NAME);
+
+        // Launch another activity to side to cover first one.
+        launchActivity(
+                NO_RELAUNCH_ACTIVITY_NAME, 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_NAME);
+
+        // Launch activity that was first launched to side. It should be brought to front.
+        getLaunchActivityBuilder()
+                .setTargetActivityName(TEST_ACTIVITY_NAME)
+                .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_NAME);
+    }
+
+    @Test
+    @Presubmit
+    public void testLaunchToSideMultiple() throws Exception {
+        launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+
+        int taskNumberInitial = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        assertNotNull("Launched to side activity must be in fullscreen stack.",
+                mAmWmState.getAmState().getTaskByActivityName(
+                        TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+
+        // Try to launch to side same activity again.
+        getLaunchActivityBuilder().setToSide(true).execute();
+        final String[] waitForActivitiesVisible =
+                new String[] {TEST_ACTIVITY_NAME, LAUNCHING_ACTIVITY};
+        mAmWmState.computeState(waitForActivitiesVisible);
+        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_NAME);
+        assertNotNull("Launched to side activity must remain in fullscreen stack.",
+                mAmWmState.getAmState().getTaskByActivityName(
+                        TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+    }
+
+    @Test
+    @Presubmit
+    @FlakyTest(bugId = 71918731)
+    public void testLaunchToSideSingleInstance() throws Exception {
+        launchTargetToSide(SINGLE_INSTANCE_ACTIVITY_NAME, false);
+    }
+
+    @Test
+    public void testLaunchToSideSingleTask() throws Exception {
+        launchTargetToSide(SINGLE_TASK_ACTIVITY_NAME, false);
+    }
+
+    @FlakyTest
+    @Presubmit
+    @Test
+    public void testLaunchToSideMultipleWithDifferentIntent() throws Exception {
+        launchTargetToSide(TEST_ACTIVITY_NAME, true);
+    }
+
+    private void launchTargetToSide(String targetActivityName, boolean taskCountMustIncrement)
+            throws Exception {
+        final LaunchActivityBuilder targetActivityLauncher = getLaunchActivityBuilder()
+                .setTargetActivityName(targetActivityName)
+                .setToSide(true)
+                .setRandomData(true)
+                .setMultipleTask(false);
+
+        // TODO(b/70618153): A workaround to allow activities to launch in split-screen leads to
+        // the target being launched directly. Options such as LaunchActivityBuilder#setRandomData
+        // are not respected.
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivityName(LAUNCHING_ACTIVITY),
+                targetActivityLauncher);
+
+        final WaitForValidActivityState[] waitForActivitiesVisible =
+                new WaitForValidActivityState[] {
+                    new WaitForValidActivityState.Builder(targetActivityName).build(),
+                    new WaitForValidActivityState.Builder(LAUNCHING_ACTIVITY).build()
+                };
+
+        mAmWmState.computeState(waitForActivitiesVisible);
+        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().getTaskByActivityName(
+                        targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+
+        // Try to launch to side same activity again with different data.
+        targetActivityLauncher.execute();
+        mAmWmState.computeState(waitForActivitiesVisible);
+        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().getTaskByActivityName(
+                        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(waitForActivitiesVisible);
+        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().getTaskByActivityName(
+                        targetActivityName, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+    }
+
+    @Presubmit
+    @Test
+    public void testLaunchToSideMultipleWithFlag() throws Exception {
+        launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+        int taskNumberInitial = mAmWmState.getAmState().getStandardTaskCountByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        assertNotNull("Launched to side activity must be in fullscreen stack.",
+                mAmWmState.getAmState().getTaskByActivityName(
+                        TEST_ACTIVITY_NAME, 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();
+        final String[] waitForActivitiesVisible =
+                new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
+        mAmWmState.computeState(waitForActivitiesVisible);
+        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_NAME);
+        assertNotNull("Launched to side activity must remain in fullscreen stack.",
+                mAmWmState.getAmState().getTaskByActivityName(
+                        TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_SECONDARY));
+    }
+
+    @Test
+    public void testRotationWhenDocked() throws Exception {
+        launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+        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.
+        String[] waitForActivitiesVisible =
+            new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
+        try (final RotationSession rotationSession = new RotationSession()) {
+            for (int i = 0; i < 4; i++) {
+                rotationSession.set(i);
+                mAmWmState.computeState(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.
+            rotationSession.set(ROTATION_90);
+            mAmWmState.computeState(waitForActivitiesVisible);
+            rotationSession.set(ROTATION_270);
+            mAmWmState.computeState(waitForActivitiesVisible);
+            rotationSession.set(ROTATION_0);
+            mAmWmState.computeState(waitForActivitiesVisible);
+            rotationSession.set(ROTATION_180);
+            mAmWmState.computeState(waitForActivitiesVisible);
+            rotationSession.set(ROTATION_0);
+            mAmWmState.computeState(waitForActivitiesVisible);
+        }
+    }
+
+    @Test
+    @Presubmit
+    public void testRotationWhenDockedWhileLocked() throws Exception {
+        launchActivitiesInSplitScreen(LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME);
+        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);
+
+        String[] waitForActivitiesVisible =
+                new String[] {LAUNCHING_ACTIVITY, TEST_ACTIVITY_NAME};
+        try (final RotationSession rotationSession = new RotationSession()) {
+            for (int i = 0; i < 4; i++) {
+                sleepDevice();
+                rotationSession.set(i);
+                wakeUpAndUnlockDevice();
+                mAmWmState.computeState(waitForActivitiesVisible);
+            }
+        }
+    }
+
+    @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_NAME);
+                if (!mAmWmState.isScreenPortrait() && isTablet()) {
+                    // Test minimize to the right only on tablets in landscape
+                    removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+                    launchActivityInDockStackAndMinimize(TEST_ACTIVITY_NAME,
+                            SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT);
+                }
+                removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+            }
+        }
+    }
+
+    @Test
+    @Presubmit
+    public void testRotationWhileDockMinimized() throws Exception {
+        launchActivityInDockStackAndMinimize(TEST_ACTIVITY_NAME);
+
+        // 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};
+        try (final RotationSession rotationSession = new RotationSession()) {
+            for (int i = 0; i < 4; i++) {
+                rotationSession.set(i);
+                mAmWmState.computeState(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.
+            rotationSession.set(ROTATION_90);
+            mAmWmState.computeState(waitForActivitiesVisible);
+            rotationSession.set(ROTATION_270);
+            mAmWmState.computeState(waitForActivitiesVisible);
+            rotationSession.set(ROTATION_0);
+            mAmWmState.computeState(waitForActivitiesVisible);
+            rotationSession.set(ROTATION_180);
+            mAmWmState.computeState(waitForActivitiesVisible);
+            rotationSession.set(ROTATION_0);
+            mAmWmState.computeState(waitForActivitiesVisible);
+        }
+    }
+
+    @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_NAME);
+
+                // Unminimize the docked stack
+                pressAppSwitchButton();
+                waitForDockNotMinimized();
+                assertDockNotMinimized();
+
+                // Dismiss the dock stack
+                launchActivity(TEST_ACTIVITY_NAME,
+                        WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+                setActivityTaskWindowingMode(DOCKED_ACTIVITY_NAME,
+                        WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+                mAmWmState.computeState(new String[]{DOCKED_ACTIVITY_NAME});
+
+                // Go home and check the app transition
+                assertNotSame(TRANSIT_WALLPAPER_OPEN, mAmWmState.getWmState().getLastTransition());
+                pressHomeButton();
+                mAmWmState.computeState();
+                assertEquals(TRANSIT_WALLPAPER_OPEN, mAmWmState.getWmState().getLastTransition());
+            }
+        }
+    }
+
+    @Test
+    @Presubmit
+    public void testFinishDockActivityWhileMinimized() throws Exception {
+        launchActivityInDockStackAndMinimize(TEST_ACTIVITY_NAME);
+
+        executeShellCommand("am broadcast -a " + TEST_ACTIVITY_ACTION_FINISH);
+        waitForDockNotMinimized();
+        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, false);
+        assertDockNotMinimized();
+    }
+
+    @Test
+    @Presubmit
+    public void testDockedStackToMinimizeWhenUnlocked() throws Exception {
+        launchActivityInSplitScreenWithRecents(TEST_ACTIVITY_NAME);
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+        sleepDevice();
+        wakeUpAndUnlockDevice();
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+        assertDockMinimized();
+    }
+
+    @Test
+    public void testMinimizedStateWhenUnlockedAndUnMinimized() throws Exception {
+        launchActivityInDockStackAndMinimize(TEST_ACTIVITY_NAME);
+
+        sleepDevice();
+        wakeUpAndUnlockDevice();
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+
+        // Unminimized back to splitscreen
+        pressAppSwitchButton();
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+    }
+
+    @Test
+    @Presubmit
+    public void testResizeDockedStack() throws Exception {
+        launchActivitiesInSplitScreen(DOCKED_ACTIVITY_NAME, TEST_ACTIVITY_NAME);
+        resizeDockedStack(STACK_SIZE, STACK_SIZE, TASK_SIZE, TASK_SIZE);
+        mAmWmState.computeState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build(),
+                new WaitForValidActivityState.Builder(DOCKED_ACTIVITY_NAME).build());
+        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_NAME);
+        mAmWmState.assertVisibility(DOCKED_ACTIVITY_NAME, true);
+        mAmWmState.assertVisibility(TEST_ACTIVITY_NAME, true);
+    }
+
+    @Test
+    public void testActivityLifeCycleOnResizeDockedStack() throws Exception {
+        final WaitForValidActivityState[] waitTestActivityName =
+                new WaitForValidActivityState[] {new WaitForValidActivityState.Builder(
+                        TEST_ACTIVITY_NAME).build()};
+        launchActivity(TEST_ACTIVITY_NAME);
+        mAmWmState.computeState(waitTestActivityName);
+        final Rect fullScreenBounds = mAmWmState.getWmState().getStandardStackByWindowingMode(
+                WINDOWING_MODE_FULLSCREEN).getBounds();
+
+        setActivityTaskWindowingMode(TEST_ACTIVITY_NAME, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        mAmWmState.computeState(waitTestActivityName);
+        launchActivity(NO_RELAUNCH_ACTIVITY_NAME,
+                WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY);
+
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build(),
+                new WaitForValidActivityState.Builder(NO_RELAUNCH_ACTIVITY_NAME).build());
+        final Rect initialDockBounds = mAmWmState.getWmState().getStandardStackByWindowingMode(
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) .getBounds();
+
+        final String logSeparator = clearLogcat();
+
+        Rect newBounds = computeNewDockBounds(fullScreenBounds, initialDockBounds, true);
+        resizeDockedStack(newBounds.width(), newBounds.height(), newBounds.width(), newBounds.height());
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build(),
+                new WaitForValidActivityState.Builder(NO_RELAUNCH_ACTIVITY_NAME).build());
+
+        // 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(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build(),
+                new WaitForValidActivityState.Builder(NO_RELAUNCH_ACTIVITY_NAME).build());
+        assertActivityLifecycle(TEST_ACTIVITY_NAME, true /* relaunched */, logSeparator);
+        assertActivityLifecycle(NO_RELAUNCH_ACTIVITY_NAME, 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_NAME);
+
+        final int homeStackIndex = mAmWmState.getStackIndexByActivityType(ACTIVITY_TYPE_HOME);
+        final int recentsStackIndex = mAmWmState.getStackIndexByActivityType(ACTIVITY_TYPE_RECENTS);
+        assertTrue("Recents stack should be on top of home stack",
+                recentsStackIndex < homeStackIndex);
+    }
+
+    @Test
+    @Presubmit
+    public void testStackListOrderOnSplitScreenDismissed() throws Exception {
+        launchActivitiesInSplitScreen(DOCKED_ACTIVITY_NAME, TEST_ACTIVITY_NAME);
+
+        setActivityTaskWindowingMode(DOCKED_ACTIVITY_NAME, WINDOWING_MODE_FULLSCREEN);
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(
+                DOCKED_ACTIVITY_NAME).setWindowingMode(WINDOWING_MODE_FULLSCREEN).build());
+
+        final int homeStackIndex = mAmWmState.getStackIndexByActivityType(ACTIVITY_TYPE_HOME);
+        final int prevSplitScreenPrimaryIndex =
+                mAmWmState.getAmState().getStackIndexByActivityName(DOCKED_ACTIVITY_NAME);
+        final int prevSplitScreenSecondaryIndex =
+                mAmWmState.getAmState().getStackIndexByActivityName(TEST_ACTIVITY_NAME);
+
+        final int expectedHomeStackIndex =
+                (prevSplitScreenPrimaryIndex > prevSplitScreenSecondaryIndex
+                        ? prevSplitScreenPrimaryIndex : prevSplitScreenSecondaryIndex) - 1;
+        assertTrue("Home stack needs to be directly behind the top stack",
+                expectedHomeStackIndex == homeStackIndex);
+    }
+
+    private void launchActivityInDockStackAndMinimize(String activityName) throws Exception {
+        launchActivityInDockStackAndMinimize(activityName, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
+    }
+
+    private void launchActivityInDockStackAndMinimize(String 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(new WaitForValidActivityState.Builder(TEST_ACTIVITY_NAME).build());
+        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..fe05359
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerTransitionSelectionTests.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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.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 {
+
+    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
+    @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_NAME);
+        if (bottomWallpaper) {
+            bottomStartCmd += " --ez USE_WALLPAPER true";
+        }
+        if (testSlowStop) {
+            bottomStartCmd += " --ei STOP_DELAY 3000";
+        }
+        executeShellCommand(bottomStartCmd);
+
+        mAmWmState.computeState(new WaitForValidActivityState(BOTTOM_ACTIVITY_NAME));
+
+        final String topActivityName = topTranslucent ?
+                TRANSLUCENT_TOP_ACTIVITY_NAME : TOP_ACTIVITY_NAME;
+        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(new WaitForValidActivityState(topActivityName));
+        } else {
+            mAmWmState.computeState(new WaitForValidActivityState(BOTTOM_ACTIVITY_NAME));
+        }
+
+        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..3c0a584
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerVrDisplayTests.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 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 String RESIZEABLE_ACTIVITY_NAME = "ResizeableActivity";
+    private static final String VR_TEST_ACTIVITY_NAME = "VrTestActivity";
+    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_NAME);
+            mAmWmState.computeState(new WaitForValidActivityState(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(new WaitForValidActivityState(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 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_NAME);
+            mAmWmState.computeState(new WaitForValidActivityState(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(new WaitForValidActivityState(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 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_NAME);
+            mAmWmState.computeState(new WaitForValidActivityState(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(new WaitForValidActivityState(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 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_NAME);
+        mAmWmState.computeState(new WaitForValidActivityState(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 primary display.
+        mAmWmState.assertFocusedActivity("Launched activity must be focused",
+                RESIZEABLE_ACTIVITY_NAME);
+        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",
+                getActivityComponentName(RESIZEABLE_ACTIVITY_NAME), 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..fd8ed2f
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/AnimationBackgroundTests.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.am;
+
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import org.junit.Test;
+
+import static android.server.am.ActivityAndWindowManagersState.DEFAULT_DISPLAY_ID;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:AnimationBackgroundTests
+ */
+public class AnimationBackgroundTests extends ActivityManagerTestBase {
+
+    @Test
+    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(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_ID);
+        getLaunchActivityBuilder().setTargetActivityName("AnimationTestActivity").execute();
+        mAmWmState.computeState(new WaitForValidActivityState.Builder("AnimationTestActivity").build());
+        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/DisplaySizeTest.java b/tests/framework/base/activitymanager/src/android/server/am/DisplaySizeTest.java
new file mode 100644
index 0000000..478c0b6
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/DisplaySizeTest.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+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 {
+    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 ComponentName TEST_ACTIVITY = ComponentName.createRelative(
+            "android.server.am", ".TestActivity");
+    private static final ComponentName SMALLEST_WIDTH_ACTIVITY = ComponentName.createRelative(
+            "android.server.am.displaysize", ".SmallestWidthActivity");
+    /** @see android.server.am.displaysize.SmallestWidthActivity#EXTRA_LAUNCH_ANOTHER_ACTIVITY */
+    private static final String EXTRA_LAUNCH_ANOTHER_ACTIVITY = "launch_another_activity";
+
+    /** @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();
+        resetDensity();
+
+        // 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).
+        executeShellCommand(getAmStartCmd(TEST_ACTIVITY));
+        assertActivityDisplayed(TEST_ACTIVITY);
+
+        setUnsupportedDensity();
+
+        // Launch target app.
+        executeShellCommand(getAmStartCmd(SMALLEST_WIDTH_ACTIVITY));
+        assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
+        assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
+    }
+
+    @Test
+    public void testCompatibilityDialogWhenFocused() throws Exception {
+        executeShellCommand(getAmStartCmd(SMALLEST_WIDTH_ACTIVITY));
+        assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
+
+        setUnsupportedDensity();
+
+        assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
+    }
+
+    @Test
+    public void testCompatibilityDialogAfterReturn() throws Exception {
+        // Launch target app.
+        executeShellCommand(getAmStartCmd(SMALLEST_WIDTH_ACTIVITY));
+        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, TEST_ACTIVITY.flattenToShortString());
+        executeShellCommand(startActivityOnTop);
+        assertActivityDisplayed(TEST_ACTIVITY);
+
+        setUnsupportedDensity();
+
+        pressBackButton();
+
+        assertActivityDisplayed(SMALLEST_WIDTH_ACTIVITY);
+        assertWindowDisplayed(UNSUPPORTED_DISPLAY_SIZE_DIALOG_NAME);
+    }
+
+    private 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);
+    }
+
+    private int getStableDensity() {
+        final String densityProp;
+        if (Build.getSerial().startsWith("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");
+    }
+
+    private void assertActivityDisplayed(final ComponentName activityName) throws Exception {
+        assertWindowDisplayed(activityName.flattenToString());
+    }
+
+    private void assertWindowDisplayed(final String windowName) throws Exception {
+        mAmWmState.waitForValidState(WaitForValidActivityState.forWindow(windowName));
+        assertTrue(windowName + "is visible", mAmWmState.getWmState().isWindowVisible(windowName));
+    }
+}
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..ce22ae7
--- /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 org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases: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.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 LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+            gotoKeyguard();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            lockCredentialSession.unlockDeviceWithCredential();
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.assertKeyguardGone();
+        }
+    }
+
+    @Test
+    public void testDismissKeyguard() throws Exception {
+        try (final LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+            gotoKeyguard();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivity("DismissKeyguardActivity");
+            lockCredentialSession.enterAndConfirmLockCredential();
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.assertKeyguardGone();
+            mAmWmState.assertVisibility("DismissKeyguardActivity", true);
+        }
+    }
+
+    @Test
+    public void testDismissKeyguard_whileOccluded() throws Exception {
+        try (final LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+            gotoKeyguard();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.computeState(new WaitForValidActivityState(SHOW_WHEN_LOCKED_ACTIVITY));
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+            launchActivity("DismissKeyguardActivity");
+            lockCredentialSession.enterAndConfirmLockCredential();
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.assertKeyguardGone();
+            mAmWmState.assertVisibility("DismissKeyguardActivity", true);
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, false);
+        }
+    }
+
+    @Test
+    public void testDismissKeyguard_fromShowWhenLocked_notAllowed() throws Exception {
+        try (final LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+            gotoKeyguard();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            launchActivity(SHOW_WHEN_LOCKED_ACTIVITY);
+            mAmWmState.computeState(new WaitForValidActivityState(SHOW_WHEN_LOCKED_ACTIVITY));
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+            executeShellCommand("am broadcast -a trigger_broadcast --ez dismissKeyguard true");
+            lockCredentialSession.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 LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+            final String logSeparator = clearLogcat();
+            gotoKeyguard();
+            mAmWmState.computeState();
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            launchActivity("DismissKeyguardMethodActivity");
+            lockCredentialSession.enterAndConfirmLockCredential();
+            mAmWmState.waitForKeyguardGone();
+            mAmWmState.computeState(new WaitForValidActivityState("DismissKeyguardMethodActivity"));
+            mAmWmState.assertVisibility("DismissKeyguardMethodActivity", true);
+            assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            assertOnDismissSucceededInLogcat(logSeparator);
+        }
+    }
+
+    @Test
+    public void testDismissKeyguardActivity_method_cancelled() throws Exception {
+        try (final LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+            final String logSeparator = clearLogcat();
+            gotoKeyguard();
+            mAmWmState.computeState();
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            launchActivity("DismissKeyguardMethodActivity");
+            pressBackButton();
+            assertOnDismissCancelledInLogcat(logSeparator);
+            mAmWmState.computeState();
+            mAmWmState.assertVisibility("DismissKeyguardMethodActivity", false);
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            lockCredentialSession.unlockDeviceWithCredential();
+        }
+    }
+
+    @Test
+    public void testEnterPipOverKeyguard() throws Exception {
+        assumeTrue(supportsPip());
+
+        try (final LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+            // Go to the keyguard
+            gotoKeyguard();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            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();
+            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
+            lockCredentialSession.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 LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+            launchActivity(PIP_ACTIVITY);
+            executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+            mAmWmState.computeState(new WaitForValidActivityState(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(new WaitForValidActivityState(SHOW_WHEN_LOCKED_ACTIVITY));
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+
+            gotoKeyguard();
+            mAmWmState.waitForKeyguardShowingAndOccluded();
+            mAmWmState.assertKeyguardShowingAndOccluded();
+            mAmWmState.assertVisibility(SHOW_WHEN_LOCKED_ACTIVITY, true);
+            mAmWmState.assertVisibility(PIP_ACTIVITY, false);
+        }
+    }
+
+    @Test
+    public void testShowWhenLockedPipActivity() throws Exception {
+        assumeTrue(supportsPip());
+
+        try (final LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+            launchActivity(PIP_ACTIVITY, EXTRA_SHOW_OVER_KEYGUARD, "true");
+            executeShellCommand("am broadcast -a " + PIP_ACTIVITY_ACTION_ENTER_PIP);
+            mAmWmState.computeState(new WaitForValidActivityState(PIP_ACTIVITY));
+            mAmWmState.assertContainsStack("Must contain pinned stack.", WINDOWING_MODE_PINNED,
+                    ACTIVITY_TYPE_STANDARD);
+            mAmWmState.assertVisibility(PIP_ACTIVITY, true);
+
+            gotoKeyguard();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            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..9a63396
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTestBase.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.server.am.StateLogger.log;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class KeyguardTestBase extends ActivityManagerTestBase {
+
+    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/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..830615d
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTests.java
@@ -0,0 +1,361 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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.After;
+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());
+
+        // Set screen lock (swipe)
+        setLockDisabled(false);
+    }
+
+    @Test
+    public void testKeyguardHidesActivity() throws Exception {
+        launchActivity("TestActivity");
+        mAmWmState.computeState(new WaitForValidActivityState.Builder( "TestActivity").build());
+        mAmWmState.assertVisibility("TestActivity", true);
+        gotoKeyguard();
+        mAmWmState.computeState();
+        mAmWmState.assertKeyguardShowingAndNotOccluded();
+        mAmWmState.assertVisibility("TestActivity", false);
+        unlockDevice();
+    }
+
+    @Test
+    public void testShowWhenLockedActivity() throws Exception {
+        launchActivity("ShowWhenLockedActivity");
+        mAmWmState.computeState(new WaitForValidActivityState.Builder( "ShowWhenLockedActivity").build());
+        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+        gotoKeyguard();
+        mAmWmState.computeState();
+        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+        mAmWmState.assertKeyguardShowingAndOccluded();
+        pressHomeButton();
+        unlockDevice();
+    }
+
+    /**
+     * Tests whether dialogs from SHOW_WHEN_LOCKED activities are also visible if Keyguard is
+     * showing.
+     */
+    @Test
+    public void testShowWhenLockedActivity_withDialog() throws Exception {
+        launchActivity("ShowWhenLockedWithDialogActivity");
+        mAmWmState.computeState(new WaitForValidActivityState.Builder("ShowWhenLockedWithDialogActivity").build());
+        mAmWmState.assertVisibility("ShowWhenLockedWithDialogActivity", true);
+        gotoKeyguard();
+        mAmWmState.computeState();
+        mAmWmState.assertVisibility("ShowWhenLockedWithDialogActivity", true);
+        assertTrue(mAmWmState.getWmState().allWindowsVisible(
+                getWindowName("ShowWhenLockedWithDialogActivity")));
+        mAmWmState.assertKeyguardShowingAndOccluded();
+        pressHomeButton();
+        unlockDevice();
+    }
+
+    /**
+     * Tests whether multiple SHOW_WHEN_LOCKED activities are shown if the topmost is translucent.
+     */
+    @Test
+    public void testMultipleShowWhenLockedActivities() throws Exception {
+        launchActivity("ShowWhenLockedActivity");
+        launchActivity("ShowWhenLockedTranslucentActivity");
+        mAmWmState.computeState(new WaitForValidActivityState.Builder("ShowWhenLockedActivity").build(),
+                new WaitForValidActivityState.Builder("ShowWhenLockedTranslucentActivity").build());
+        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+        mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
+        gotoKeyguard();
+        mAmWmState.computeState();
+        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+        mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
+        mAmWmState.assertKeyguardShowingAndOccluded();
+        pressHomeButton();
+        unlockDevice();
+    }
+
+    /**
+     * If we have a translucent SHOW_WHEN_LOCKED_ACTIVITY, the wallpaper should also be showing.
+     */
+    @Test
+    public void testTranslucentShowWhenLockedActivity() throws Exception {
+        launchActivity("ShowWhenLockedTranslucentActivity");
+        mAmWmState.computeState(new WaitForValidActivityState.Builder("ShowWhenLockedTranslucentActivity").build());
+        mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
+        gotoKeyguard();
+        mAmWmState.computeState();
+        mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
+        assertWallpaperShowing();
+        mAmWmState.assertKeyguardShowingAndOccluded();
+        pressHomeButton();
+        unlockDevice();
+    }
+
+    /**
+     * If we have a translucent SHOW_WHEN_LOCKED activity, the activity behind should not be shown.
+     */
+    @Test
+    public void testTranslucentDoesntRevealBehind() throws Exception {
+        launchActivity("TestActivity");
+        launchActivity("ShowWhenLockedTranslucentActivity");
+        mAmWmState.computeState(new WaitForValidActivityState.Builder("TestActivity").build(),
+                new WaitForValidActivityState.Builder("ShowWhenLockedTranslucentActivity").build());
+        mAmWmState.assertVisibility("TestActivity", true);
+        mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
+        gotoKeyguard();
+        mAmWmState.computeState();
+        mAmWmState.assertVisibility("ShowWhenLockedTranslucentActivity", true);
+        mAmWmState.assertVisibility("TestActivity", false);
+        mAmWmState.assertKeyguardShowingAndOccluded();
+        pressHomeButton();
+        unlockDevice();
+    }
+
+    @Test
+    public void testDialogShowWhenLockedActivity() throws Exception {
+        launchActivity("ShowWhenLockedDialogActivity");
+        mAmWmState.computeState(new WaitForValidActivityState.Builder( "ShowWhenLockedDialogActivity").build());
+        mAmWmState.assertVisibility("ShowWhenLockedDialogActivity", true);
+        gotoKeyguard();
+        mAmWmState.computeState();
+        mAmWmState.assertVisibility("ShowWhenLockedDialogActivity", true);
+        assertWallpaperShowing();
+        mAmWmState.assertKeyguardShowingAndOccluded();
+        pressHomeButton();
+        unlockDevice();
+    }
+
+    /**
+     * Test that showWhenLocked activity is fullscreen when shown over keyguard
+     */
+    @Test
+    @Presubmit
+    public void testShowWhenLockedActivityWhileSplit() throws Exception {
+        assumeTrue(supportsSplitScreenMultiWindow());
+
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivityName(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivityName("ShowWhenLockedActivity")
+                        .setRandomData(true)
+                        .setMultipleTask(false)
+        );
+        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+        gotoKeyguard();
+        mAmWmState.computeState(
+                new WaitForValidActivityState.Builder("ShowWhenLockedActivity").build());
+        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+        mAmWmState.assertKeyguardShowingAndOccluded();
+        mAmWmState.assertDoesNotContainStack("Activity must be full screen.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+        pressHomeButton();
+        unlockDevice();
+    }
+
+    /**
+     * Tests whether a FLAG_DISMISS_KEYGUARD activity occludes Keyguard.
+     */
+    @Test
+    public void testDismissKeyguardActivity() throws Exception {
+        gotoKeyguard();
+        mAmWmState.computeState();
+        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+        launchActivity("DismissKeyguardActivity");
+        mAmWmState.waitForKeyguardShowingAndOccluded();
+        mAmWmState.computeState(new WaitForValidActivityState.Builder( "DismissKeyguardActivity").build());
+        mAmWmState.assertVisibility("DismissKeyguardActivity", true);
+        mAmWmState.assertKeyguardShowingAndOccluded();
+    }
+
+    @Test
+    public void testDismissKeyguardActivity_method() throws Exception {
+        final String logSeparator = clearLogcat();
+        gotoKeyguard();
+        mAmWmState.computeState();
+        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+        launchActivity("DismissKeyguardMethodActivity");
+        mAmWmState.waitForKeyguardGone();
+        mAmWmState.computeState(new WaitForValidActivityState.Builder( "DismissKeyguardMethodActivity").build());
+        mAmWmState.assertVisibility("DismissKeyguardMethodActivity", true);
+        assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+        assertOnDismissSucceededInLogcat(logSeparator);
+    }
+
+    @Test
+    public void testDismissKeyguardActivity_method_notTop() throws Exception {
+        final String logSeparator = clearLogcat();
+        gotoKeyguard();
+        mAmWmState.computeState();
+        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+        launchActivity("BroadcastReceiverActivity");
+        launchActivity("TestActivity");
+        executeShellCommand("am broadcast -a trigger_broadcast --ez dismissKeyguardMethod true");
+        assertOnDismissErrorInLogcat(logSeparator);
+    }
+
+    @Test
+    public void testDismissKeyguardActivity_method_turnScreenOn() throws Exception {
+        final String logSeparator = clearLogcat();
+        sleepDevice();
+        mAmWmState.computeState();
+        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+        launchActivity("TurnScreenOnDismissKeyguardActivity");
+        mAmWmState.waitForKeyguardGone();
+        mAmWmState.computeState(new WaitForValidActivityState.Builder( "TurnScreenOnDismissKeyguardActivity").build());
+        mAmWmState.assertVisibility("TurnScreenOnDismissKeyguardActivity", true);
+        assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+        assertOnDismissSucceededInLogcat(logSeparator);
+        assertTrue(isDisplayOn());
+    }
+
+    @Test
+    public void testDismissKeyguard_fromShowWhenLocked_notAllowed() throws Exception {
+        gotoKeyguard();
+        mAmWmState.waitForKeyguardShowingAndNotOccluded();
+        mAmWmState.assertKeyguardShowingAndNotOccluded();
+        launchActivity("ShowWhenLockedActivity");
+        mAmWmState.computeState(new WaitForValidActivityState.Builder( "ShowWhenLockedActivity" ).build());
+        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+        mAmWmState.assertKeyguardShowingAndOccluded();
+        executeShellCommand("am broadcast -a trigger_broadcast --ez dismissKeyguard true");
+        mAmWmState.assertKeyguardShowingAndOccluded();
+        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+    }
+
+    @Test
+    public void testKeyguardLock() throws Exception {
+        gotoKeyguard();
+        mAmWmState.waitForKeyguardShowingAndNotOccluded();
+        mAmWmState.assertKeyguardShowingAndNotOccluded();
+        launchActivity("KeyguardLockActivity");
+        mAmWmState.computeState(new WaitForValidActivityState.Builder( "KeyguardLockActivity" ).build());
+        mAmWmState.assertVisibility("KeyguardLockActivity", true);
+        executeShellCommand(FINISH_ACTIVITY_BROADCAST);
+        mAmWmState.waitForKeyguardShowingAndNotOccluded();
+        mAmWmState.assertKeyguardShowingAndNotOccluded();
+    }
+
+    @Test
+    public void testUnoccludeRotationChange() throws Exception {
+        gotoKeyguard();
+        mAmWmState.waitForKeyguardShowingAndNotOccluded();
+        mAmWmState.assertKeyguardShowingAndNotOccluded();
+        executeShellCommand(getAmStartCmd("ShowWhenLockedActivity"));
+        mAmWmState.computeState(new WaitForValidActivityState("ShowWhenLockedActivity"));
+        mAmWmState.assertVisibility("ShowWhenLockedActivity", true);
+        try (final RotationSession rotationSession = new RotationSession()) {
+            rotationSession.set(ROTATION_90);
+            pressHomeButton();
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.waitForDisplayUnfrozen();
+            mAmWmState.assertSanity();
+            mAmWmState.assertHomeActivityVisible(false);
+            mAmWmState.assertKeyguardShowingAndNotOccluded();
+            mAmWmState.assertVisibility("ShowWhenLockedActivity", false);
+        }
+    }
+
+    private void assertWallpaperShowing() {
+        WindowState wallpaper =
+                mAmWmState.getWmState().findFirstWindowWithType(TYPE_WALLPAPER);
+        assertNotNull(wallpaper);
+        assertTrue(wallpaper.isShown());
+    }
+
+    @Test
+    public void testDismissKeyguardAttrActivity_method_turnScreenOn() throws Exception {
+        final String activityName = "TurnScreenOnAttrDismissKeyguardActivity";
+        sleepDevice();
+
+        final String logSeparator = clearLogcat();
+        mAmWmState.computeState();
+        assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+        launchActivity(activityName);
+        mAmWmState.waitForKeyguardGone();
+        mAmWmState.assertVisibility(activityName, true);
+        assertFalse(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+        assertOnDismissSucceededInLogcat(logSeparator);
+        assertTrue(isDisplayOn());
+    }
+
+    @Test
+    public void testDismissKeyguardAttrActivity_method_turnScreenOn_withSecureKeyguard() throws Exception {
+        final String activityName = "TurnScreenOnAttrDismissKeyguardActivity";
+
+        try (final LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+            sleepDevice();
+
+            mAmWmState.computeState();
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            launchActivity(activityName);
+            mAmWmState.waitForKeyguardShowingAndNotOccluded();
+            mAmWmState.assertVisibility(activityName, false);
+            assertTrue(mAmWmState.getAmState().getKeyguardControllerState().keyguardShowing);
+            assertTrue(isDisplayOn());
+        }
+    }
+
+    @Test
+    public void testScreenOffWhileOccludedStopsActivity() throws Exception {
+        final String logSeparator = clearLogcat();
+        gotoKeyguard();
+        mAmWmState.waitForKeyguardShowingAndNotOccluded();
+        mAmWmState.assertKeyguardShowingAndNotOccluded();
+        launchActivity("ShowWhenLockedAttrActivity");
+        mAmWmState.computeState(new WaitForValidActivityState.Builder( "ShowWhenLockedAttrActivity" ).build());
+        mAmWmState.assertVisibility("ShowWhenLockedAttrActivity", true);
+        mAmWmState.assertKeyguardShowingAndOccluded();
+        sleepDevice();
+        assertSingleLaunchAndStop("ShowWhenLockedAttrActivity", logSeparator);
+    }
+
+    @Test
+    public void testScreenOffCausesSingleStop() throws Exception {
+        final String logSeparator = clearLogcat();
+        launchActivity("TestActivity");
+        mAmWmState.assertVisibility("TestActivity", true);
+        sleepDevice();
+        assertSingleLaunchAndStop("TestActivity", 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..cb0d5b5e
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/KeyguardTransitionTests.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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());
+
+        // Set screen lock (swipe)
+        setLockDisabled(false);
+    }
+
+    @Test
+    public void testUnlock() throws Exception {
+        launchActivity("TestActivity");
+        gotoKeyguard();
+        unlockDevice();
+        mAmWmState.computeState(new WaitForValidActivityState("TestActivity"));
+        assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY,
+                mAmWmState.getWmState().getLastTransition());
+    }
+    @Test
+    public void testUnlockWallpaper() throws Exception {
+        launchActivity("WallpaperActivity");
+        gotoKeyguard();
+        unlockDevice();
+        mAmWmState.computeState(new WaitForValidActivityState("WallpaperActivity"));
+        assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_GOING_AWAY_ON_WALLPAPER,
+                mAmWmState.getWmState().getLastTransition());
+    }
+    @Test
+    public void testOcclude() throws Exception {
+        gotoKeyguard();
+        launchActivity("ShowWhenLockedActivity");
+        mAmWmState.computeState(new WaitForValidActivityState("ShowWhenLockedActivity"));
+        assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
+                mAmWmState.getWmState().getLastTransition());
+    }
+    @Test
+    public void testUnocclude() throws Exception {
+        gotoKeyguard();
+        launchActivity("ShowWhenLockedActivity");
+        launchActivity("TestActivity");
+        mAmWmState.waitForKeyguardShowingAndNotOccluded();
+        mAmWmState.computeState();
+        assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_UNOCCLUDE,
+                mAmWmState.getWmState().getLastTransition());
+    }
+    @Test
+    public void testNewActivityDuringOccluded() throws Exception {
+        launchActivity("ShowWhenLockedActivity");
+        gotoKeyguard();
+        launchActivity("ShowWhenLockedWithDialogActivity");
+        mAmWmState.computeState(new WaitForValidActivityState.Builder("ShowWhenLockedWithDialogActivity").build());
+        assertEquals("Picked wrong transition", TRANSIT_ACTIVITY_OPEN,
+                mAmWmState.getWmState().getLastTransition());
+    }
+    @Test
+    public void testOccludeManifestAttr() throws Exception {
+         String activityName = "ShowWhenLockedAttrActivity";
+
+         gotoKeyguard();
+         final String logSeparator = clearLogcat();
+         launchActivity(activityName);
+         mAmWmState.computeState(new WaitForValidActivityState.Builder(activityName).build());
+         assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
+                 mAmWmState.getWmState().getLastTransition());
+         assertSingleLaunch(activityName, logSeparator);
+    }
+    @Test
+    public void testOccludeAttrRemove() throws Exception {
+        String activityName = "ShowWhenLockedAttrRemoveAttrActivity";
+
+        gotoKeyguard();
+        String logSeparator = clearLogcat();
+        launchActivity(activityName);
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(activityName).build());
+        assertEquals("Picked wrong transition", TRANSIT_KEYGUARD_OCCLUDE,
+                mAmWmState.getWmState().getLastTransition());
+        assertSingleLaunch(activityName, logSeparator);
+
+        gotoKeyguard();
+        logSeparator = clearLogcat();
+        launchActivity(activityName);
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(activityName).build());
+        assertSingleStartAndStop(activityName, logSeparator);
+    }
+    @Test
+    public void testNewActivityDuringOccludedWithAttr() throws Exception {
+        String activityName1 = "ShowWhenLockedAttrActivity";
+        String activityName2 = "ShowWhenLockedAttrWithDialogActivity";
+
+        launchActivity(activityName1);
+        gotoKeyguard();
+        launchActivity(activityName2);
+        mAmWmState.computeState(new WaitForValidActivityState.Builder(activityName2).build());
+        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..6e98083
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/PrereleaseSdkTest.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 android.server.am;
+
+import static org.junit.Assert.assertTrue;
+
+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 {
+    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;
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+
+        // Ensure app process is stopped.
+        forceStopPackage("android.server.am.prerelease");
+        forceStopPackage("android.server.am");
+    }
+
+    @Test
+    public void testCompatibilityDialog() throws Exception {
+        // Launch target app.
+        startActivity("android.server.am.prerelease", "MainActivity");
+        verifyWindowDisplayed("MainActivity", ACTIVITY_TIMEOUT_MILLIS);
+        verifyWindowDisplayed("UnsupportedCompileSdkDialog", WINDOW_TIMEOUT_MILLIS);
+
+        // Go back to dismiss the warning dialog.
+        executeShellCommand("input keyevent 4");
+
+        // 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.
+        executeShellCommand("input keyevent 4");
+    }
+
+    private void forceStopPackage(String packageName) {
+        final String forceStopCmd = String.format(AM_FORCE_STOP, packageName);
+        executeShellCommand(forceStopCmd);
+    }
+
+    private void startActivity(String packageName, String activityName){
+        executeShellCommand(getStartCommand(packageName, activityName));
+    }
+
+    private String getStartCommand(String packageName, String activityName) {
+        return String.format(AM_START_COMMAND, packageName, packageName, activityName);
+    }
+
+    private void verifyWindowDisplayed(String windowName, long timeoutMillis) {
+        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 = executeShellCommand("dumpsys window");
+            success = output.contains(windowName);
+        }
+
+        assertTrue(windowName + " was not displayed", success);
+    }
+}
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..f4edf0f
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/SplashscreenTests.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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("SplashscreenActivity");
+        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..1f68aa3
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/StartActivityTests.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.server.am;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.filters.FlakyTest;
+import android.util.Log;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertFalse;
+
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:StartActivityTests
+ */
+@Presubmit
+@FlakyTest
+public class StartActivityTests extends ActivityManagerTestBase {
+    private static final String SDK_27_PACKAGE = "android.server.am.app27";
+    private static final String SDK_CURRENT_PACKAGE = "android.server.am";
+
+    private static final ComponentName SDK_27_LAUNCHING_ACTIVITY = ComponentName.createRelative(
+            SDK_27_PACKAGE, "android.server.am.LaunchingActivity");
+
+    private static final ComponentName TEST_ACTIVITY = ComponentName.createRelative(
+            SDK_CURRENT_PACKAGE, ".TestActivity");
+    private static final ComponentName TEST_ACTIVITY_2 = ComponentName.createRelative(
+            "android.server.cts.am",
+            "android.server.am.StartActivityTests$TestActivity");
+
+    /**
+     * 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.
+        getLaunchActivityBuilder()
+                .setTargetActivity(TEST_ACTIVITY_2)
+                .execute();
+
+        mAmWmState.computeState(new WaitForValidActivityState(TEST_ACTIVITY_2));
+
+        // Verify Activity was not started.
+        assertFalse(mAmWmState.getAmState().containsActivity(TEST_ACTIVITY.flattenToShortString()));
+        mAmWmState.assertResumedActivity(
+                "Activity launched from activity context should be present", TEST_ACTIVITY_2);
+    }
+
+    /**
+     * 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(new WaitForValidActivityState(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(new WaitForValidActivityState(TEST_ACTIVITY));
+        mAmWmState.assertResumedActivity("Test Activity should be resumed without older sdk",
+                TEST_ACTIVITY);
+    }
+
+    public static class TestActivity 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..a5673a6
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
@@ -0,0 +1,83 @@
+package android.server.am.lifecycle;
+
+import static android.server.am.StateLogger.log;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.server.am.ActivityManagerTestBase;
+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.support.test.runner.lifecycle.Stage;
+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 */);
+
+    private final ActivityLifecycleMonitor mLifecycleMonitor = ActivityLifecycleMonitorRegistry
+            .getInstance();
+    private 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, Stage>... activityStates) {
+        log("Start waitAndAssertActivityStates");
+        mLifecycleTracker.waitAndAssertActivityStates(activityStates);
+    }
+
+    LifecycleLog getLifecycleLog() {
+        return mLifecycleLog;
+    }
+
+    static Pair<Activity, Stage> state(Activity activity, Stage stage) {
+        return new Pair<>(activity, stage);
+    }
+
+    // Test activity
+    public static class FirstActivity extends Activity {
+    }
+
+    // Test activity
+    public static class SecondActivity extends Activity {
+    }
+}
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..1e39db6
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleKeyguardTests.java
@@ -0,0 +1,42 @@
+package android.server.am.lifecycle;
+
+import static android.support.test.runner.lifecycle.Stage.STOPPED;
+
+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.After;
+import org.junit.Before;
+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 {
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        gotoKeyguard();
+    }
+
+    @Test
+    public void testSingleLaunch() throws Exception {
+        try (final LockCredentialSession lockCredentialSession = new LockCredentialSession()) {
+            lockCredentialSession.setLockCredential();
+
+            final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
+            waitAndAssertActivityStates(state(activity, STOPPED));
+
+            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..4ec99fa
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTests.java
@@ -0,0 +1,71 @@
+package android.server.am.lifecycle;
+
+import static android.support.test.runner.lifecycle.Stage.DESTROYED;
+import static android.support.test.runner.lifecycle.Stage.RESUMED;
+import static android.support.test.runner.lifecycle.Stage.STOPPED;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.FlakyTest;
+import android.support.test.filters.MediumTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Build/Install/Run:
+ *     atest CtsActivityManagerDeviceTestCases:ActivityLifecycleTests
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+@Presubmit
+public class ActivityLifecycleTests extends ActivityLifecycleClientTestBase {
+
+    @Test
+    public void testSingleLaunch() throws Exception {
+        final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(activity, RESUMED));
+
+        LifecycleVerifier.assertLaunchSequence(FirstActivity.class, getLifecycleLog());
+    }
+
+    @Test
+    public void testLaunchOnTop() throws Exception {
+        final Activity firstActivity = mFirstActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, RESUMED));
+
+        getLifecycleLog().clear();
+        final Activity secondActivity = mSecondActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(firstActivity, STOPPED),
+                state(secondActivity, RESUMED));
+
+        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, DESTROYED));
+
+        LifecycleVerifier.assertLaunchAndDestroySequence(FirstActivity.class, getLifecycleLog());
+    }
+
+    @Test
+    public void testRelaunch() throws Exception {
+        final Activity activity = mFirstActivityTestRule.launchActivity(new Intent());
+        waitAndAssertActivityStates(state(activity, RESUMED));
+
+        getLifecycleLog().clear();
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(activity::recreate);
+        waitAndAssertActivityStates(state(activity, RESUMED));
+
+        LifecycleVerifier.assertRelaunchSequence(FirstActivity.class, getLifecycleLog());
+    }
+}
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..dab6316
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
@@ -0,0 +1,50 @@
+package android.server.am.lifecycle;
+
+import static android.server.am.StateLogger.log;
+
+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 {
+
+    private List<Pair<String, Stage>> 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, stage));
+    }
+
+    /** Get logs for all recorded transitions. */
+    List<Pair<String, Stage>> getLog() {
+        return mLog;
+    }
+
+    /** Get transition logs for the specified activity. */
+    List<Stage> getActivityLog(Class<? extends Activity> activityClass) {
+        final String activityName = activityClass.getCanonicalName();
+        log("Looking up log for activity: " + activityName);
+        final List<Stage> activityLog = new ArrayList<>();
+        for (Pair<String, Stage> transition : mLog) {
+            if (transition.first.equals(activityName)) {
+                activityLog.add(transition.second);
+            }
+        }
+        return activityLog;
+    }
+}
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..df2cff7
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java
@@ -0,0 +1,76 @@
+package android.server.am.lifecycle;
+
+import static org.junit.Assert.fail;
+
+import android.annotation.SuppressLint;
+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;
+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, Stage>[] activityStates) {
+        final boolean waitResult = waitForConditionWithTimeout(
+                () -> pendingStates(activityStates).isEmpty(), 5 * 1000);
+
+        if (!waitResult) {
+            fail("Expected lifecycle states not achieved: " + pendingStates(activityStates));
+        }
+    }
+
+    @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, Stage>> pendingStates(Pair<Activity, Stage>[] activityStates) {
+        final List<Pair<Activity, Stage>> notReachedActivityStates = new ArrayList<>();
+
+        for (Pair<Activity, Stage> activityState : activityStates) {
+            final Activity activity = activityState.first;
+            final List<Stage> transitionList = mLifecycleLog.getActivityLog(activity.getClass());
+            if (transitionList.isEmpty()
+                    || transitionList.get(transitionList.size() - 1) != activityState.second) {
+                // The activity either hasn't got any state transitions yet or the current state is
+                // not the one we expect.
+                notReachedActivityStates.add(activityState);
+            }
+        }
+        return notReachedActivityStates;
+    }
+
+    /** 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..bbb3ad8
--- /dev/null
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleVerifier.java
@@ -0,0 +1,94 @@
+package android.server.am.lifecycle;
+
+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.Stage;
+import android.util.Pair;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static android.server.am.StateLogger.log;
+import static org.junit.Assert.assertEquals;
+
+/** Util class that verifies correct activity state transition sequences. */
+class LifecycleVerifier {
+
+    static void assertLaunchSequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog) {
+        final List<Stage> observedTransitions = lifecycleLog.getActivityLog(activityClass);
+        log("Observed sequence: " + observedTransitions);
+        final String errorMessage = errorDuringTransition(activityClass, "launch");
+
+        final List<Stage> expectedTransitions =
+                Arrays.asList(PRE_ON_CREATE, CREATED, STARTED, RESUMED);
+        assertEquals(errorMessage, expectedTransitions, observedTransitions);
+    }
+
+    static void assertLaunchSequence(Class<? extends Activity> launchingActivity,
+            Class<? extends Activity> existingActivity, LifecycleLog lifecycleLog) {
+        final List<Pair<String, Stage>> observedTransitions = lifecycleLog.getLog();
+        log("Observed sequence: " + observedTransitions);
+        final String errorMessage = errorDuringTransition(launchingActivity, "launch");
+
+        final List<Pair<String, Stage>> expectedTransitions = Arrays.asList(
+                transition(existingActivity, PAUSED),
+                transition(launchingActivity, PRE_ON_CREATE),
+                transition(launchingActivity, CREATED),
+                transition(launchingActivity, STARTED),
+                transition(launchingActivity, RESUMED),
+                transition(existingActivity, STOPPED));
+        assertEquals(errorMessage, expectedTransitions, observedTransitions);
+    }
+
+    static void assertLaunchAndStopSequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog) {
+        final List<Stage> observedTransitions = lifecycleLog.getActivityLog(activityClass);
+        log("Observed sequence: " + observedTransitions);
+        final String errorMessage = errorDuringTransition(activityClass, "launch and stop");
+
+        final List<Stage> expectedTransitions =
+                Arrays.asList(PRE_ON_CREATE, CREATED, STARTED, RESUMED, PAUSED, STOPPED);
+        assertEquals(errorMessage, expectedTransitions, observedTransitions);
+    }
+
+    static void assertLaunchAndDestroySequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog) {
+        final List<Stage> observedTransitions = lifecycleLog.getActivityLog(activityClass);
+        log("Observed sequence: " + observedTransitions);
+        final String errorMessage = errorDuringTransition(activityClass, "launch and destroy");
+
+        final List<Stage> expectedTransitions =
+                Arrays.asList(PRE_ON_CREATE, CREATED, STARTED, RESUMED, PAUSED, STOPPED, DESTROYED);
+        assertEquals(errorMessage, expectedTransitions, observedTransitions);
+    }
+
+    static void assertRelaunchSequence(Class<? extends Activity> activityClass,
+            LifecycleLog lifecycleLog) {
+        final List<Stage> observedTransitions = lifecycleLog.getActivityLog(activityClass);
+        log("Observed sequence: " + observedTransitions);
+        final String errorMessage = errorDuringTransition(activityClass, "relaunch");
+
+        final List<Stage> expectedTransitions =
+                Arrays.asList(PAUSED, STOPPED, DESTROYED, PRE_ON_CREATE, CREATED, STARTED, RESUMED);
+        assertEquals(errorMessage, expectedTransitions, observedTransitions);
+    }
+
+    private static Pair<String, Stage> transition(
+            Class<? extends Activity> activityClass, Stage 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/hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/Android.mk b/tests/framework/base/activitymanager/translucentapp/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/translucentapp/Android.mk
rename to tests/framework/base/activitymanager/translucentapp/Android.mk
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/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/hostsidetests/services/activityandwindowmanager/activitymanager/translucentappsdk26/Android.mk b/tests/framework/base/activitymanager/translucentappsdk26/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/activitymanager/translucentappsdk26/Android.mk
rename to tests/framework/base/activitymanager/translucentappsdk26/Android.mk
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/util/Android.mk b/tests/framework/base/activitymanager/util/Android.mk
new file mode 100644
index 0000000..6fa0331
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/Android.mk
@@ -0,0 +1,36 @@
+# 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)
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    platformprotosnano \
+    compatibility-device-util \
+    android-support-test
+
+LOCAL_JAVA_LIBRARIES := core-oj core-libart
+
+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..7367875
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityAndWindowManagersState.java
@@ -0,0 +1,1009 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.ActivityManagerTestBase.componentName;
+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.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.ComponentName;
+import android.graphics.Rect;
+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.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();
+
+    @Deprecated
+    public void computeState(String... waitForActivitiesVisible)
+            throws Exception {
+        WaitForValidActivityState[] states = waitForActivitiesVisible != null ?
+                new WaitForValidActivityState[waitForActivitiesVisible.length] : null;
+        if (states != null) {
+            for (int i = 0; i < waitForActivitiesVisible.length; i++) {
+                states[i] =
+                        new WaitForValidActivityState.Builder(waitForActivitiesVisible[i]).build();
+            }
+        }
+        computeState(states);
+    }
+
+    @Deprecated
+    public void computeState() throws Exception {
+        computeState(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 waitForActivitiesVisible array of activity names to wait for.
+     */
+    public void computeState(WaitForValidActivityState... waitForActivitiesVisible)
+            throws Exception {
+        computeState(true, 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) throws Exception {
+        waitForValidState(compareTaskAndStackBounds, waitForActivitiesVisible);
+        assertSanity();
+        assertValidBounds(compareTaskAndStackBounds);
+    }
+
+    /**
+     * 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(String[] waitForActivityRecords) {
+        int retriesLeft = 5;
+        do {
+            mAmState.computeState();
+            mWmState.computeState();
+            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 waitForActivityVisible name of activity to wait for.
+     */
+    @Deprecated
+    void waitForValidState(String waitForActivityVisible)
+            throws Exception {
+        waitForValidState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState(waitForActivityVisible));
+    }
+
+    /** Wait for the activity to appear and for valid state in AM and WM. */
+    void waitForValidState(WaitForValidActivityState... waitForActivityVisible) throws Exception {
+        waitForValidState(false /* compareTaskAndStackBounds */, waitForActivityVisible);
+    }
+
+    /**
+     * Wait for the activity to appear in proper stack and for valid state in AM and WM.
+     *
+     * @param waitForActivityVisible name of activity to wait for.
+     * @param stackId                id of the stack where provided activity should be found.
+     */
+    @Deprecated
+    void waitForValidState(String waitForActivityVisible, int stackId)
+            throws Exception {
+        waitForValidState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState.Builder(waitForActivityVisible)
+                        .setStackId(stackId)
+                        .build());
+    }
+
+    void waitForValidStateWithActivityType(String waitForActivityVisible, int activityType)
+            throws Exception {
+        waitForValidState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState.Builder(waitForActivityVisible)
+                        .setActivityType(activityType)
+                        .build());
+    }
+
+    void waitForValidState(final ComponentName activityName, final int windowingMode,
+            final int activityType) throws Exception {
+        waitForValidState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState.Builder(activityName)
+                        .setActivityType(activityType)
+                        .setWindowingMode(windowingMode)
+                        .build());
+    }
+
+    @Deprecated
+    void waitForValidState(String waitForActivityVisible, int windowingMode, int activityType)
+            throws Exception {
+        waitForValidState(false /* compareTaskAndStackBounds */,
+                new WaitForValidActivityState.Builder(waitForActivityVisible)
+                        .setActivityType(activityType)
+                        .setWindowingMode(windowingMode)
+                        .build());
+    }
+
+    /**
+     * 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 state to wait for.
+     */
+    private void waitForValidState(boolean compareTaskAndStackBounds,
+            WaitForValidActivityState... waitForActivitiesVisible) throws Exception {
+        waitForValidState(compareTaskAndStackBounds, componentName, waitForActivitiesVisible);
+    }
+
+    /**
+     * 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 packageName               name of the package of activities that we're waiting for.
+     * @param waitForActivitiesVisible  array of activity states to wait for.
+     */
+    void waitForValidState(boolean compareTaskAndStackBounds, String packageName,
+            WaitForValidActivityState... waitForActivitiesVisible) 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();
+            mWmState.computeState();
+            if (shouldWaitForValidStacks(compareTaskAndStackBounds)
+                    || shouldWaitForActivities(packageName, waitForActivitiesVisible)
+                    || 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);
+    }
+
+    /**
+     * Ensures all exiting windows have been removed.
+     */
+    void waitForAllExitingWindows() {
+        int retriesLeft = 5;
+        do {
+            mWmState.computeState();
+            if (mWmState.containsExitingWindow()) {
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    log(e.toString());
+                    // Well I guess we are not waiting...
+                }
+            } else {
+                break;
+            }
+        } while (retriesLeft-- > 0);
+
+        assertFalse(mWmState.containsExitingWindow());
+    }
+
+    void waitForAllStoppedActivities() throws Exception {
+        int retriesLeft = 5;
+        do {
+            mAmState.computeState();
+            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() throws Exception {
+        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();
+            homeActivity = mAmState.getHomeActivityName();
+        }
+        assertNotNull("homeActivity should not be null", homeActivity);
+        waitForValidState(new WaitForValidActivityState(homeActivity));
+    }
+
+    /**
+     * @return true if recents activity is visible. Devices without recents will return false
+     */
+    boolean waitForRecentsActivityVisible() throws Exception {
+        waitForWithAmState(ActivityManagerState::isRecentsActivityVisible,
+                "***Waiting for recents activity to be visible...");
+        return mAmState.isRecentsActivityVisible();
+    }
+
+    void waitForKeyguardShowingAndNotOccluded() throws Exception {
+        waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
+                        && !state.getKeyguardControllerState().keyguardOccluded,
+                "***Waiting for Keyguard showing...");
+    }
+
+    void waitForKeyguardShowingAndOccluded() throws Exception {
+        waitForWithAmState(state -> state.getKeyguardControllerState().keyguardShowing
+                        && state.getKeyguardControllerState().keyguardOccluded,
+                "***Waiting for Keyguard showing and occluded...");
+    }
+
+    void waitForKeyguardGone() throws Exception {
+        waitForWithAmState(state -> !state.getKeyguardControllerState().keyguardShowing,
+                "***Waiting for Keyguard gone...");
+    }
+
+    void waitForRotation(int rotation) throws Exception {
+        waitForWithWmState(state -> state.getRotation() == rotation,
+                "***Waiting for Rotation: " + rotation);
+    }
+
+    void waitForDisplayUnfrozen() throws Exception {
+        waitForWithWmState(state -> !state.isDisplayFrozen(),
+                "***Waiting for Display unfrozen");
+    }
+
+    void waitForActivityState(String activityName, String activityState)
+            throws Exception {
+        waitForWithAmState(state -> state.hasActivityState(activityName, activityState),
+                "***Waiting for Activity State: " + activityState);
+    }
+
+    @Deprecated
+    void waitForFocusedStack(int stackId) throws Exception {
+        waitForWithAmState(state -> state.getFocusedStackId() == stackId,
+                "***Waiting for focused stack...");
+    }
+
+    void waitForFocusedStack(int windowingMode, int activityType) throws Exception {
+        waitForWithAmState(state ->
+                        (activityType == ACTIVITY_TYPE_UNDEFINED
+                                || state.getFocusedStackActivityType() == activityType)
+                        && (windowingMode == WINDOWING_MODE_UNDEFINED
+                                || state.getFocusedStackWindowingMode() == windowingMode),
+                "***Waiting for focused stack...");
+    }
+
+    void waitForAppTransitionIdle() throws Exception {
+        waitForWithWmState(
+                state -> WindowManagerState.APP_STATE_IDLE.equals(state.getAppTransitionState()),
+                "***Waiting for app transition idle...");
+    }
+
+    void waitForWithAmState(Predicate<ActivityManagerState> waitCondition,
+            String message) throws Exception {
+        waitFor((amState, wmState) -> waitCondition.test(amState), message);
+    }
+
+    void waitForWithWmState(Predicate<WindowManagerState> waitCondition,
+            String message) throws Exception {
+        waitFor((amState, wmState) -> waitCondition.test(wmState), message);
+    }
+
+    void waitFor(
+            BiPredicate<ActivityManagerState, WindowManagerState> waitCondition, String message)
+            throws Exception {
+        waitFor(message, () -> {
+            try {
+                mAmState.computeState();
+                mWmState.computeState();
+            } 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 packageName,
+            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 String activityComponentName;
+            final String windowName;
+            if (state.componentName != null) {
+                activityComponentName = state.componentName;
+                windowName = state.windowName;
+            } else {
+                final String activityName = state.activityName;
+                activityComponentName = (activityName != null)
+                        ? ActivityManagerTestBase.getActivityComponentName(packageName, activityName)
+                        : null;
+                // Check if window is visible - it should be represented as one of the window
+                // states.
+                windowName = (state.windowName != null) ? state.windowName
+                        : ActivityManagerTestBase.getWindowName(packageName, activityName);
+            }
+            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) {
+                log("Activity window not visible: " + windowName);
+                allActivityWindowsVisible = false;
+            } else if (activityComponentName != null
+                    && !mAmState.isActivityVisible(activityComponentName)) {
+                log("Activity not visible: " + activityComponentName);
+                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) {
+                    log("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) {
+            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<WindowState> matchingWindowStates = new ArrayList<>();
+        mWmState.getMatchingVisibleWindowState("android.server.am", 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 windowingMode, int activityType) throws Exception {
+        assertTrue(msg, mAmState.containsStack(windowingMode, activityType));
+        assertTrue(msg, mWmState.containsStack(windowingMode, activityType));
+    }
+
+    @Deprecated
+    void assertDoesNotContainStack(String msg, int stackId) throws Exception {
+        assertFalse(msg, mAmState.containsStack(stackId));
+        assertFalse(msg, mWmState.containsStack(stackId));
+    }
+
+    void assertDoesNotContainStack(String msg, int windowingMode, int activityType)
+            throws Exception {
+        assertFalse(msg, mAmState.containsStack(windowingMode, activityType));
+        assertFalse(msg, mWmState.containsStack(windowingMode, activityType));
+    }
+
+    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 assertFrontStack(String msg, int windowingMode, int activityType)
+            throws Exception {
+        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) throws Exception {
+        assertEquals(msg, activityType, mAmState.getFrontStackActivityType(DEFAULT_DISPLAY_ID));
+        assertEquals(msg, activityType, mWmState.getFrontStackActivityType(DEFAULT_DISPLAY_ID));
+    }
+
+    @Deprecated
+    void assertFocusedStack(String msg, int stackId) throws Exception {
+        assertEquals(msg, stackId, mAmState.getFocusedStackId());
+    }
+
+    void assertFocusedStack(String msg, int windowingMode, int activityType)
+            throws Exception {
+        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 = activityName.flattenToShortString();
+        assertEquals(msg, activityComponentName, mAmState.getFocusedActivity());
+        assertEquals(msg, activityComponentName, mWmState.getFocusedApp());
+    }
+
+    @Deprecated
+    void assertFocusedActivity(final String msg, final String activityName) {
+        final String activityComponentName =
+                ActivityManagerTestBase.getActivityComponentName(activityName);
+        assertEquals(msg, activityComponentName, mAmState.getFocusedActivity());
+        assertEquals(msg, activityComponentName, mWmState.getFocusedApp());
+    }
+
+    void assertNotFocusedActivity(String msg, String activityName) throws Exception {
+        final String componentName = ActivityManagerTestBase.getActivityComponentName(activityName);
+        if (mAmState.getFocusedActivity().equals(componentName)) {
+            assertNotEquals(msg, mAmState.getFocusedActivity(), componentName);
+        }
+        if (mWmState.getFocusedApp().equals(componentName)) {
+            assertNotEquals(msg, mWmState.getFocusedApp(), componentName);
+        }
+    }
+
+    void assertResumedActivity(final String msg, final ComponentName activityName) {
+        assertEquals(msg, activityName.flattenToShortString(), mAmState.getResumedActivity());
+    }
+
+    @Deprecated
+    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)) {
+            assertNotEquals(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)) {
+            assertNotEquals(msg, mWmState.getFocusedWindow(), windowName);
+        }
+    }
+
+    void assertFrontWindow(String msg, String windowName) {
+        assertEquals(msg, windowName, mWmState.getFrontWindow());
+    }
+
+    @Deprecated
+    public void assertVisibility(String activityName, boolean visible) {
+        final String activityComponentName =
+                ActivityManagerTestBase.getActivityComponentName(activityName);
+        final String windowName =
+                ActivityManagerTestBase.getWindowName(activityName);
+        assertVisibility(activityComponentName, windowName, visible);
+    }
+
+    public void assertVisibility(final ComponentName activityName, final boolean visible) {
+        final String activityComponentName = activityName.flattenToShortString();
+        final String windowName = activityName.flattenToString();
+        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) {
+        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) throws Exception {
+        computeState();
+        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(getAmState().getKeyguardControllerState().keyguardShowing);
+        assertTrue(getAmState().getKeyguardControllerState().keyguardOccluded);
+    }
+
+    public void assertKeyguardShowingAndNotOccluded() {
+        assertTrue(getAmState().getKeyguardControllerState().keyguardShowing);
+        assertFalse(getAmState().getKeyguardControllerState().keyguardOccluded);
+    }
+
+    public void assertKeyguardGone() {
+        assertFalse(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, String 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.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() == 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());
+                                assertTrue("Task height must be greater than stack height "
+                                                + "taskId=" + taskId + ", stackId=" + stackId,
+                                        aStackBounds.height() < wTaskBounds.height());
+                                assertEquals("Task and stack x position must be equal taskId="
+                                                + taskId + ", stackId=" + stackId,
+                                        wTaskBounds.left, wStackBounds.left);
+                            } else {
+                                assertTrue("Task width must be greater than stack width taskId="
+                                                + taskId + ", stackId=" + stackId,
+                                        aStackBounds.width() < 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());
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    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/ActivityManagerState.java b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerState.java
new file mode 100644
index 0000000..7be53c7
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerState.java
@@ -0,0 +1,668 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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.support.test.InstrumentationRegistry;
+
+import com.android.server.am.proto.nano.ActivityDisplayProto;
+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;
+
+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 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.
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException e) {
+                    log(e.toString());
+                    // Well I guess we are not waiting...
+                }
+            }
+
+            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 = ActivityStackSupervisorProto.parseFrom(sysDump);
+        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;
+        }
+    }
+
+
+    private void reset() {
+        mDisplays.clear();
+        mStacks.clear();
+        mFocusedStackId = -1;
+        mResumedActivityRecord = null;
+        mResumedActivities.clear();
+        mKeyguardControllerState = null;
+    }
+
+    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 getStackIndexByActivityName(String activityName) {
+        final String fullName = ActivityManagerTestBase.getActivityComponentName(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(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 containsActivityInWindowingMode(String activityName, int windowingMode) {
+        final String fullName = ActivityManagerTestBase.getActivityComponentName(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(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 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 getStackIdByActivityName(String activityName) {
+        final ActivityTask task = getTaskByActivityName(activityName);
+
+        if (task == null) {
+            return INVALID_STACK_ID;
+        }
+
+        return task.mStackId;
+    }
+
+    ActivityTask getTaskByActivityName(String activityName) {
+        return getTaskByActivityName(activityName, WINDOWING_MODE_UNDEFINED);
+    }
+
+    ActivityTask getTaskByActivityName(String activityName, int windowingMode) {
+        String fullName = ActivityManagerTestBase.getActivityComponentName(activityName);
+        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..4332a0b
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
@@ -0,0 +1,1559 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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.StateLogger.log;
+import static android.server.am.StateLogger.logE;
+import static android.view.KeyEvent.KEYCODE_APP_SWITCH;
+
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.os.ParcelFileDescriptor;
+import android.provider.Settings;
+import android.server.am.settings.SettingsSession;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.uiautomator.UiDevice;
+import android.view.Display;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.junit.After;
+import org.junit.Before;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.UUID;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+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";
+
+    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_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 String DEFAULT_COMPONENT_NAME = "android.server.am";
+
+    private static final int UI_MODE_TYPE_MASK = 0x0f;
+    private static final int UI_MODE_TYPE_VR_HEADSET = 0x07;
+
+    private static Boolean sHasHomeScreen = null;
+
+    // TODO: Remove this when all activity name are specified by {@link ComponentName}.
+    static String componentName = DEFAULT_COMPONENT_NAME;
+
+    protected static final int INVALID_DEVICE_ROTATION = -1;
+
+    protected Context mContext;
+    protected ActivityManager mAm;
+    protected UiDevice mDevice;
+
+    private boolean mLockDisabled;
+
+    @Deprecated
+    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.
+     */
+    // 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(activityName.flattenToShortString(), keyValuePairs);
+    }
+
+    @Deprecated
+    protected static String getAmStartCmd(final String activityName,
+            final String... keyValuePairs) {
+        return getAmStartCmdInternal(getActivityComponentName(activityName), keyValuePairs);
+    }
+
+    private static String getAmStartCmdInternal(final String activityName,
+            final String... keyValuePairs) {
+        final StringBuilder cmd = new StringBuilder("am start -n ").append(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) {
+            final String key = keyValuePairs[i];
+            final String value = keyValuePairs[i + 1];
+            cmd.append(String.format(" --es %s %s", key, value));
+        }
+        return cmd.toString();
+    }
+
+    protected static String getAmStartCmd(final String activityName, final int displayId) {
+        return "am start -n " + getActivityComponentName(activityName) + " -f 0x18000000"
+                + " --display " + displayId;
+    }
+
+    protected static String getAmStartCmd(final String activityName, final int displayId,
+                                          final String... keyValuePairs) {
+        String base = "am start -n " + getActivityComponentName(activityName) + " -f 0x18000000"
+                + " --display " + displayId;
+        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 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 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;
+    }
+
+    // TODO: Remove this when all activity name are specified by {@link ComponentName}.
+    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;
+    }
+
+    // TODO: Remove this when all activity name are specified by {@link ComponentName}.
+    // 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);
+    }
+
+    protected 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 + "." : "");
+    }
+
+    // TODO: Remove this when all activity name are specified by {@link ComponentName}.
+    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 SurfaceTraceReceiver mSurfaceTraceReceiver;
+    private Thread mSurfaceTraceThread;
+
+    protected void installSurfaceObserver(SurfaceTraceReceiver.SurfaceObserver observer) {
+        mSurfaceTraceReceiver = new SurfaceTraceReceiver(observer);
+        mSurfaceTraceThread = new Thread() {
+            @Override
+            public void run() {
+                try {
+                    registerSurfaceTraceReceiver("wm surface-trace", mSurfaceTraceReceiver);
+                } catch (IOException e) {
+                    logE("Error running wm surface-trace: " + e.toString());
+                }
+            }
+        };
+        mSurfaceTraceThread.start();
+    }
+
+    protected void removeSurfaceObserver() {
+        mSurfaceTraceThread.interrupt();
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mAm = mContext.getSystemService(ActivityManager.class);
+        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        setDefaultComponentName();
+        executeShellCommand("pm grant " + mContext.getPackageName()
+                + " android.permission.MANAGE_ACTIVITY_STACKS");
+        executeShellCommand("pm grant " + mContext.getPackageName()
+                + " android.permission.ACTIVITY_EMBEDDING");
+
+        wakeUpAndUnlockDevice();
+        pressHomeButton();
+        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+        mLockDisabled = isLockDisabled();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        setLockDisabled(mLockDisabled);
+        executeShellCommand(AM_FORCE_STOP_TEST_PACKAGE);
+        executeShellCommand(AM_FORCE_STOP_SECOND_TEST_PACKAGE);
+        executeShellCommand(AM_FORCE_STOP_THIRD_TEST_PACKAGE);
+        removeStacksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
+        wakeUpAndUnlockDevice();
+        pressHomeButton();
+    }
+
+    protected void removeStacksWithActivityTypes(int... activityTypes) {
+        mAm.removeStacksWithActivityTypes(activityTypes);
+    }
+
+    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 static void registerSurfaceTraceReceiver(String command, SurfaceTraceReceiver outputReceiver)
+            throws IOException {
+        log("Shell command: " + command);
+        ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .executeShellCommand(command);
+        byte[] buf = new byte[512];
+        int bytesRead;
+        FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+        while ((bytesRead = fis.read(buf)) != -1) {
+            outputReceiver.addOutput(buf, 0, bytesRead);
+        }
+        fis.close();
+    }
+
+    protected Bitmap takeScreenshot() throws Exception {
+        return InstrumentationRegistry.getInstrumentation().getUiAutomation().takeScreenshot();
+    }
+
+    @Deprecated
+    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 ComponentName activityName, final String... keyValuePairs)
+            throws Exception {
+        executeShellCommand(getAmStartCmd(activityName, keyValuePairs));
+        mAmWmState.waitForValidState(new WaitForValidActivityState(activityName));
+    }
+
+    @Deprecated
+    protected void launchActivity(final String targetActivityName, final String... keyValuePairs)
+            throws Exception {
+        executeShellCommand(getAmStartCmd(targetActivityName, keyValuePairs));
+        mAmWmState.waitForValidState(targetActivityName);
+    }
+
+    protected void launchActivityNoWait(final ComponentName targetActivityName,
+            final String... keyValuePairs) throws Exception {
+        executeShellCommand(getAmStartCmd(targetActivityName, keyValuePairs));
+    }
+
+    @Deprecated
+    protected void launchActivityNoWait(final String targetActivityName,
+            final String... keyValuePairs) throws Exception {
+        executeShellCommand(getAmStartCmd(targetActivityName, keyValuePairs));
+    }
+
+    @Deprecated
+    protected void launchActivityInNewTask(final String targetActivityName) throws Exception {
+        executeShellCommand(getAmStartCmdInNewTask(targetActivityName));
+        mAmWmState.waitForValidState(targetActivityName);
+    }
+
+    /**
+     * Starts an activity in a new stack.
+     * @return the stack id of the newly created stack.
+     */
+    @Deprecated
+    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();
+        }
+    }
+
+    private static void waitForIdle() {
+        InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+    }
+
+    /** Returns the set of stack ids. */
+    private HashSet<Integer> getStackIds() throws Exception {
+        mAmWmState.computeState();
+        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()
+            throws Exception {
+        executeShellCommand(AM_START_HOME_ACTIVITY_COMMAND);
+        mAmWmState.waitForHomeActivityVisible();
+    }
+
+    protected void launchActivity(String activityName, int windowingMode,
+            final String... keyValuePairs) throws Exception {
+        executeShellCommand(getAmStartCmd(activityName, keyValuePairs)
+                + " --windowingMode " + windowingMode);
+        mAmWmState.waitForValidState(new WaitForValidActivityState.Builder(activityName)
+                .setWindowingMode(windowingMode)
+                .build());
+    }
+
+    protected void launchActivityOnDisplay(String targetActivityName, int displayId,
+            String... keyValuePairs) throws Exception {
+        executeShellCommand(getAmStartCmd(targetActivityName, displayId, keyValuePairs));
+
+        mAmWmState.waitForValidState(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(String activityName) throws Exception {
+        launchActivityInSplitScreenWithRecents(activityName, SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
+    }
+
+    protected void launchActivityInSplitScreenWithRecents(String activityName, int createMode)
+            throws Exception {
+        launchActivity(activityName);
+        final int taskId = mAmWmState.getAmState().getTaskByActivityName(activityName).mTaskId;
+        mAm.setTaskWindowingModeSplitScreenPrimary(taskId, createMode, true /* onTop */,
+                false /* animate */, null /* initialBounds */, true /* showRecents */);
+
+        mAmWmState.waitForValidState(activityName,
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+        mAmWmState.waitForRecentsActivityVisible();
+    }
+
+    /** @see #launchActivitiesInSplitScreen(LaunchActivityBuilder, LaunchActivityBuilder) */
+    protected void launchActivitiesInSplitScreen(String primaryActivity, String secondaryActivity)
+            throws Exception {
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivityName(primaryActivity),
+                getLaunchActivityBuilder().setTargetActivityName(secondaryActivity));
+    }
+
+    /**
+     * 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) throws Exception {
+        // Launch split-screen primary.
+        String tmpLaunchingActivityName = primaryActivity.mLaunchingActivityName;
+        primaryActivity
+                // TODO(b/70618153): Work around issues with the activity launch builder where
+                // launching activity doesn't work. We don't really need launching activity in this
+                // case and should probably change activity launcher to work without a launching
+                // activity.
+                .setLaunchingActivityName(primaryActivity.mTargetActivityName)
+                .setWaitForLaunched(true)
+                .execute();
+        primaryActivity.setLaunchingActivityName(tmpLaunchingActivityName);
+
+        final int taskId = mAmWmState.getAmState().getTaskByActivityName(
+                primaryActivity.mTargetActivityName).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
+        tmpLaunchingActivityName = secondaryActivity.mLaunchingActivityName;
+        secondaryActivity
+                // TODO(b/70618153): Work around issues with the activity launch builder where
+                // launching activity doesn't work. We don't really need launching activity in this
+                // case and should probably change activity launcher to work without a launching
+                // activity.
+                .setLaunchingActivityName(secondaryActivity.mTargetActivityName)
+                .setWaitForLaunched(true)
+                .setToSide(true)
+                .execute();
+        secondaryActivity.setLaunchingActivityName(tmpLaunchingActivityName);
+    }
+
+    protected void setActivityTaskWindowingMode(final ComponentName activityName,
+            final int windowingMode) throws Exception {
+        final int taskId = getActivityTaskId(activityName);
+        mAm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */);
+        mAmWmState.waitForValidState(activityName, windowingMode, ACTIVITY_TYPE_STANDARD);
+    }
+
+    @Deprecated
+    protected void setActivityTaskWindowingMode(String activityName, int windowingMode)
+            throws Exception {
+        final int taskId = getActivityTaskId(activityName);
+        mAm.setTaskWindowingMode(taskId, windowingMode, true /* toTop */);
+        mAmWmState.waitForValidState(activityName, windowingMode, ACTIVITY_TYPE_STANDARD);
+    }
+
+    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(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) {
+        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 pressHomeButton() {
+        mDevice.pressHome();
+    }
+
+    protected void pressBackButton() {
+        mDevice.pressBack();
+    }
+
+    protected void pressAppSwitchButton() throws Exception {
+        mDevice.pressKeyCode(KEYCODE_APP_SWITCH);
+        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) {
+        return getWindowTaskId(activityName.flattenToString());
+    }
+
+    @Deprecated
+    protected int getActivityTaskId(final String activityName) {
+        return getWindowTaskId(getWindowName(activityName));
+    }
+
+    @Deprecated
+    private int getWindowTaskId(final String windowName) {
+        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() {
+        String output = executeShellCommand("dumpsys power");
+        for (String line : output.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() {
+        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() {
+        wakeUpDevice();
+        unlockDevice();
+    }
+
+    protected void wakeUpAndRemoveLock() {
+        wakeUpDevice();
+        setLockDisabled(true);
+    }
+
+    protected void wakeUpDevice() {
+        runCommandAndPrintOutput("input keyevent WAKEUP");
+    }
+
+    protected void unlockDevice() {
+        mDevice.pressMenu();
+    }
+
+    protected void gotoKeyguard() throws Exception {
+        sleepDevice();
+        wakeUpDevice();
+        mAmWmState.waitForKeyguardShowingAndNotOccluded();
+    }
+
+    protected class LockCredentialSession implements AutoCloseable {
+        private boolean mLockCredentialSet;
+
+        public LockCredentialSession() {
+            mLockCredentialSet = false;
+        }
+
+        public void setLockCredential() {
+            mLockCredentialSet = true;
+            runCommandAndPrintOutput("locksettings set-pin " + LOCK_CREDENTIAL);
+        }
+
+        public void unlockDeviceWithCredential() throws Exception {
+            mDevice.pressMenu();
+            enterAndConfirmLockCredential();
+        }
+
+        public void enterAndConfirmLockCredential() throws Exception {
+            mDevice.waitForIdle(3000);
+
+            runCommandAndPrintOutput("input text " + LOCK_CREDENTIAL);
+            mDevice.pressEnter();
+        }
+
+        private void removeLockCredential() {
+            runCommandAndPrintOutput("locksettings clear --old " + LOCK_CREDENTIAL);
+            mLockCredentialSet = false;
+        }
+
+        @Override
+        public void close() throws Exception {
+            if (mLockCredentialSet) {
+                removeLockCredential();
+                // Dismiss active keyguard after credential is cleared, so keyguard doesn't ask for
+                // the stale credential.
+                pressBackButton();
+                sleepDevice();
+                wakeUpAndUnlockDevice();
+            }
+        }
+    }
+
+    /**
+     * 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();
+        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) {
+        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;
+
+        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;
+    }
+
+    /**
+     * 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() {
+        executeShellCommand("logcat -c");
+        final String uniqueString = UUID.randomUUID().toString();
+        executeShellCommand("log -t " + LOG_SEPARATOR + " " + uniqueString);
+        return uniqueString;
+    }
+
+    void assertActivityLifecycle(String activityName, boolean relaunched,
+            String logSeparator) {
+        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) {
+        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) {
+        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) {
+        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) {
+        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) {
+        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 static String[] getDeviceLogsForComponent(String componentName, String logSeparator) {
+        return getDeviceLogsForComponents(new String[]{componentName}, logSeparator);
+    }
+
+    protected static String[] getDeviceLogsForComponents(final String[] componentNames,
+            String logSeparator) {
+        String filters = LOG_SEPARATOR + ":I ";
+        for (String component : componentNames) {
+            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)) {
+                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) {
+        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) {
+        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) {
+        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) {
+        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) {
+
+        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;
+    }
+
+    // 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;
+        }
+    }
+
+    ReportedSizes getLastReportedSizesForActivity(String activityName, String logSeparator) {
+        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) {
+        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;
+    }
+
+    /** Waits for at least one onMultiWindowModeChanged event. */
+    ActivityLifecycleCounts waitForOnMultiWindowModeChanged(
+            String activityName, String logSeparator) {
+        int retriesLeft = 5;
+        ActivityLifecycleCounts result;
+        do {
+            result = new ActivityLifecycleCounts(activityName, logSeparator);
+            if (result.mMultiWindowModeChangedCount < 1) {
+                log("***waitForOnMultiWindowModeChanged...");
+                try {
+                    Thread.sleep(1000);
+                } catch (InterruptedException e) {
+                    log(e.toString());
+                    // Well I guess we are not waiting...
+                }
+            } else {
+                break;
+            }
+        } while (retriesLeft-- > 0);
+        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(String activityName, String logSeparator) {
+            int lineIndex = 0;
+            waitForIdle();
+            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 = 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;
+                }
+            }
+        }
+    }
+
+    protected void stopTestPackage(final ComponentName activityName) throws Exception {
+        executeShellCommand("am force-stop " + activityName.getPackageName());
+    }
+
+    protected LaunchActivityBuilder getLaunchActivityBuilder() {
+        return new LaunchActivityBuilder(mAmWmState);
+    }
+
+    protected static class LaunchActivityBuilder {
+        private final ActivityAndWindowManagersState mAmWmState;
+
+        // The component to be launched
+        private ComponentName mComponent;
+
+        // The activity to be launched
+        private String mTargetActivityName = "TestActivity";
+        private String mTargetPackage = componentName;
+        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 String mLaunchingActivityName = LAUNCHING_ACTIVITY;
+        private ComponentName mLaunchingActivity;
+        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 String mBroadcastReceiverComponent;
+        private String mBroadcastReceiverAction;
+
+        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 LaunchActivityBuilder setTargetActivity(ComponentName activity) {
+            mComponent = activity;
+
+            mTargetActivityName = activity.getShortClassName();
+            mTargetPackage = activity.getPackageName();
+            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 setLaunchingActivity(ComponentName component) {
+            mLaunchingActivity = component;
+            return this;
+        }
+
+        public LaunchActivityBuilder setWaitForLaunched(boolean shouldWait) {
+            mWaitForLaunched = shouldWait;
+            return this;
+        }
+
+        /** Use broadcast receiver instead of launching activity. */
+        public LaunchActivityBuilder setUseBroadcastReceiver(final ComponentName broadcastReceiver,
+                final String broadcastAction) {
+            mBroadcastReceiverComponent = broadcastReceiver.flattenToShortString();
+            mBroadcastReceiverAction = broadcastAction;
+            return this;
+        }
+
+        /** Use {@link #setUseBroadcastReceiver(ComponentName, String)} instead. */
+        @Deprecated
+        public LaunchActivityBuilder setUseBroadcastReceiver(String componentName,
+                String broadcastAction) {
+            mBroadcastReceiverComponent = componentName;
+            mBroadcastReceiverAction = broadcastAction;
+            return this;
+        }
+
+        public LaunchActivityBuilder setSuppressExceptions(boolean suppress) {
+            mSuppressExceptions = suppress;
+            return this;
+        }
+
+        public void execute() throws Exception {
+            StringBuilder commandBuilder = new StringBuilder();
+            if (mBroadcastReceiverComponent != null && mBroadcastReceiverAction != null) {
+                // Use broadcast receiver to launch the target.
+                commandBuilder.append("am broadcast -a ").append(mBroadcastReceiverAction);
+                commandBuilder.append(" -p ").append(mBroadcastReceiverComponent);
+            } else {
+                // Use launching activity to launch the target.
+                if (mLaunchingActivity != null) {
+                    commandBuilder.append(getAmStartCmd(mLaunchingActivity));
+                } else {
+                    commandBuilder.append(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);
+            }
+
+            if (mUseApplicationContext) {
+                commandBuilder.append(" --ez use_application_context true");
+            }
+
+            if (mComponent != null) {
+                commandBuilder.append(" --es target_component ")
+                        .append(mComponent.flattenToString());
+            }
+
+            if (mSuppressExceptions) {
+                commandBuilder.append(" --ez suppress_exceptions true");
+            }
+            executeShellCommand(commandBuilder.toString());
+
+            if (mWaitForLaunched) {
+                mAmWmState.waitForValidState(false /* compareTaskAndStackBounds */, mTargetPackage,
+                        new WaitForValidActivityState.Builder(mTargetActivityName).build());
+            }
+        }
+    }
+}
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..e3ad3be
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/StateLogger.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.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);
+    }
+}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/SurfaceTraceReceiver.java b/tests/framework/base/activitymanager/util/src/android/server/am/SurfaceTraceReceiver.java
new file mode 100644
index 0000000..4b3953c
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/SurfaceTraceReceiver.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 org.junit.Assert.fail;
+
+import android.graphics.RectF;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+
+// 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 {
+
+    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 RectF mTmpRect = new RectF();
+    private byte[] mUnprocessedBytes = new byte[16384];
+    private byte[] mFullData = new byte[32768];
+    private int mUnprocessedBytesLength;
+
+    public 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, RectF crop) {}
+        default void setFinalCrop(String windowName, RectF 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.set(mTmpFloats[0], mTmpFloats[1], mTmpFloats[2],
+                    mTmpFloats[3]);
+            mObserver.setCrop(mCurrentWindowName, mTmpRect);
+            nextCmd(d);
+        }
+    }
+
+    void parseFinalCrop(DataInputStream d) throws IOException {
+        mTmpFloats[mArgPosition] = d.readInt();
+        mArgPosition++;
+        if (mArgPosition == 4) {
+            mTmpRect.set(mTmpFloats[0], mTmpFloats[1], mTmpFloats[2],
+                    mTmpFloats[3]);
+            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 appropriate chunks,
+    // We look for a sigil (0xfcfcfcfc) and only process data when it ends in as sigil.
+    // 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:
+                fail("Unexpected surface command: " + cmd);
+                break;
+        }
+    }
+}
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..5d5366a0
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/WaitForValidActivityState.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package 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_UNDEFINED;
+
+import android.content.ComponentName;
+import android.support.annotation.Nullable;
+
+public class WaitForValidActivityState {
+    @Nullable
+    public final String componentName;
+    @Nullable
+    public final String windowName;
+    /** Use {@link #componentName} and  {@link #windowName}. */
+    @Deprecated
+    @Nullable
+    public final String activityName;
+    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.componentName = activityName.flattenToShortString();
+        this.windowName = activityName.flattenToString();
+        this.activityName = getSimpleClassName(activityName);
+        this.stackId = INVALID_STACK_ID;
+        this.windowingMode = WINDOWING_MODE_UNDEFINED;
+        this.activityType = ACTIVITY_TYPE_UNDEFINED;
+    }
+
+    /** Use {@link #WaitForValidActivityState(ComponentName)}. */
+    @Deprecated
+    public WaitForValidActivityState(String activityName) {
+        this.componentName = null;
+        this.windowName = null;
+        this.activityName = activityName;
+        this.stackId = INVALID_STACK_ID;
+        this.windowingMode = WINDOWING_MODE_UNDEFINED;
+        this.activityType = ACTIVITY_TYPE_UNDEFINED;
+    }
+
+    private WaitForValidActivityState(final Builder builder) {
+        this.componentName = builder.mComponentName;
+        this.windowName = builder.mWindowName;
+        this.activityName = builder.mActivityName;
+        this.stackId = builder.mStackId;
+        this.windowingMode = builder.mWindowingMode;
+        this.activityType = builder.mActivityType;
+    }
+
+    /**
+     * @return the class name of <code>componentName</code>, either fully qualified class name or in
+     *         a shortened form (WITHOUT a leading '.') if it is a suffix of the package.
+     * @see ComponentName#getShortClassName()
+     */
+    private static String getSimpleClassName(final ComponentName componentName) {
+        final String packageName = componentName.getPackageName();
+        final String className = componentName.getClassName();
+        if (className.startsWith(packageName)) {
+            final int packageNameLen = packageName.length();
+            if (className.length() > packageNameLen && className.charAt(packageNameLen) == '.') {
+                return className.substring(packageNameLen + 1);
+            }
+        }
+        return className;
+    }
+
+    public static class Builder {
+        @Nullable
+        private String mComponentName = null;
+        @Nullable
+        private String mWindowName = null;
+        @Nullable
+        private String mActivityName = 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) {
+            mComponentName = activityName.flattenToShortString();
+            mWindowName = activityName.flattenToString();
+            mActivityName = getSimpleClassName(activityName);
+        }
+
+        /** Use {@link #Builder(ComponentName)}. */
+        @Deprecated
+        public Builder(String activityName) {
+            mActivityName = 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..1eb855e
--- /dev/null
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/WindowManagerState.java
@@ -0,0 +1,923 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.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.WindowManagerServiceProto;
+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...
+                try {
+                    Thread.sleep(500);
+                } catch (InterruptedException e) {
+                    log(e.toString());
+                    // Well I guess we are not waiting...
+                }
+            }
+
+            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();
+        WindowManagerServiceProto state = WindowManagerServiceProto.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;
+    }
+
+    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;
+
+        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);
+            }
+            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;
+        }
+
+        @Override
+        public String toString() {
+            return "Display #" + mDisplayId + ": 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;
+        }
+
+        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..712c9a3
--- /dev/null
+++ b/tests/framework/base/windowmanager/Android.mk
@@ -0,0 +1,42 @@
+#
+# 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)
+
+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..4bcd576
--- /dev/null
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?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>
+    </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..ff1bc97
--- /dev/null
+++ b/tests/framework/base/windowmanager/AndroidTest.xml
@@ -0,0 +1,36 @@
+<?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="CtsDeviceWindowFramesTestApp.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/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/Android.mk b/tests/framework/base/windowmanager/alertwindowapp/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowapp/Android.mk
rename to tests/framework/base/windowmanager/alertwindowapp/Android.mk
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/hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/Android.mk b/tests/framework/base/windowmanager/alertwindowappsdk25/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/alertwindowappsdk25/Android.mk
rename to tests/framework/base/windowmanager/alertwindowappsdk25/Android.mk
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/alertwindowservice/Android.mk b/tests/framework/base/windowmanager/alertwindowservice/Android.mk
new file mode 100644
index 0000000..c012a65
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowservice/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 := \
+    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..d531bbd
--- /dev/null
+++ b/tests/framework/base/windowmanager/alertwindowservice/src/android/server/wm/alertwindowservice/AlertWindowService.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.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 PACKAGE_NAME = "android.server.wm.alertwindowservice";
+    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/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/hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/Android.mk b/tests/framework/base/windowmanager/frametestapp/Android.mk
similarity index 100%
rename from hostsidetests/services/activityandwindowmanager/windowmanager/frametestapp/Android.mk
rename to tests/framework/base/windowmanager/frametestapp/Android.mk
diff --git a/tests/framework/base/windowmanager/frametestapp/AndroidManifest.xml b/tests/framework/base/windowmanager/frametestapp/AndroidManifest.xml
new file mode 100755
index 0000000..90dd8f0
--- /dev/null
+++ b/tests/framework/base/windowmanager/frametestapp/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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"
+    package="android.server.wm.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/tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/DialogTestActivity.java b/tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/DialogTestActivity.java
new file mode 100644
index 0000000..c0d8ffd
--- /dev/null
+++ b/tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/DialogTestActivity.java
@@ -0,0 +1,205 @@
+/*
+ * 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.wm.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 {
+
+    private static final String DIALOG_WINDOW_NAME = "TestDialog";
+
+    /**
+     * Extra key for test case name.
+     * @see android.server.wm.ParentChildTestBase#EXTRA_TEST_CASE
+     */
+    private static final String EXTRA_TEST_CASE = "test-case";
+
+    private 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) {
+        final String testCase = intent.getStringExtra(EXTRA_TEST_CASE);
+        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(DIALOG_WINDOW_NAME);
+        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/tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/MovingChildTestActivity.java b/tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/MovingChildTestActivity.java
new file mode 100644
index 0000000..78f8fa0
--- /dev/null
+++ b/tests/framework/base/windowmanager/frametestapp/src/android/server/wm/frametestapp/MovingChildTestActivity.java
@@ -0,0 +1,83 @@
+/*
+ * 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.wm.frametestapp;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.ViewGroup.LayoutParams;
+import android.view.Window;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.Space;
+
+// 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 {
+
+    private static final String POPUP_WINDOW_NAME = "ChildWindow";
+
+    private Space mView;
+    private int mX = 0;
+    private 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(POPUP_WINDOW_NAME);
+
+                ((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/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..7e4bdbc
--- /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 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: mmma -j32 cts/tests/framework/base
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsWindowManagerDeviceTestCases android.server.wm.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 static final String SDK25_PACKAGE_NAME = "android.server.wm.alertwindowappsdk25";
+
+    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_PACKAGE_NAME, 0)
+                .getSystemService(ActivityManager.class);
+
+        final Intent intent = new Intent()
+                .setClassName(AlertWindowService.PACKAGE_NAME, AlertWindowService.class.getName())
+                .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..418abc8
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AlertWindowsTests.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 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: mmma -j32 cts/tests/framework/base
+ * Run: cts/tests/framework/base/activitymanager/util/run-test CtsWindowManagerDeviceTestCases android.server.wm.AlertWindowsTests
+ */
+@Presubmit
+public class AlertWindowsTests extends ActivityManagerTestBase {
+
+    private static final ComponentName TEST_ACTIVITY = ComponentName.createRelative(
+            "android.server.wm.alertwindowapp", ".AlertWindowTestActivity");
+    private static final ComponentName SDK25_TEST_ACTIVITY = ComponentName.createRelative(
+            "android.server.wm.alertwindowappsdk25", ".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);
+
+    @Before
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        resetPermissionState(TEST_ACTIVITY);
+        resetPermissionState(SDK25_TEST_ACTIVITY);
+    }
+
+    @After
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        resetPermissionState(TEST_ACTIVITY);
+        resetPermissionState(SDK25_TEST_ACTIVITY);
+        stopTestPackage(TEST_ACTIVITY);
+        stopTestPackage(SDK25_TEST_ACTIVITY);
+    }
+
+    @Test
+    public void testAlertWindowAllowed() throws Exception {
+        runAlertWindowTest(TEST_ACTIVITY, true /* hasAlertWindowPermission */,
+                true /* atLeastO */);
+    }
+
+    @Test
+    public void testAlertWindowDisallowed() throws Exception {
+        runAlertWindowTest(TEST_ACTIVITY, false /* hasAlertWindowPermission */,
+                true /* atLeastO */);
+    }
+
+    @Test
+    public void testAlertWindowAllowedSdk25() throws Exception {
+        runAlertWindowTest(SDK25_TEST_ACTIVITY, true /* hasAlertWindowPermission */,
+                false /* atLeastO */);
+    }
+
+    @Test
+    public void testAlertWindowDisallowedSdk25() throws Exception {
+        runAlertWindowTest(SDK25_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 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) {
+            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/ChildMovementTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ChildMovementTests.java
new file mode 100644
index 0000000..41f620d
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ChildMovementTests.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.StateLogger.logE;
+
+import static org.junit.Assert.assertTrue;
+
+import android.content.ComponentName;
+import android.server.am.SurfaceTraceReceiver;
+import android.server.am.WaitForValidActivityState;
+import android.server.am.WindowManagerState.WindowState;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class ChildMovementTests extends ParentChildTestBase {
+
+    private static final ComponentName MOVING_CHILD_TEST_ACTIVITY = ComponentName
+            .unflattenFromString("android.server.wm.frametestapp/.MovingChildTestActivity");
+
+    /** @see android.server.wm.frametestapp.MovingChildTestActivity#POPUP_WINDOW_NAME */
+    private static final String POPUP_WINDOW_NAME = "ChildWindow";
+
+    private List<WindowState> mWindowList = new ArrayList<>();
+
+    @Override
+    ComponentName activityName() {
+        return MOVING_CHILD_TEST_ACTIVITY;
+    }
+
+    private WindowState getSingleWindow(final String windowName) {
+        try {
+            mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, mWindowList);
+            return mWindowList.get(0);
+        } catch (Exception e) {
+            logE("Couldn't find window: " + windowName);
+            return null;
+        }
+    }
+
+    @Override
+    void doSingleTest(ParentChildTest t) throws Exception {
+        final WaitForValidActivityState waitForVisible =
+                WaitForValidActivityState.forWindow(POPUP_WINDOW_NAME);
+
+        mAmWmState.computeState(waitForVisible);
+        WindowState popup = getSingleWindow(POPUP_WINDOW_NAME);
+        WindowState parent = getSingleWindow(activityName().flattenToString());
+
+        t.doTest(parent, popup);
+    }
+
+    private final Object monitor = new Object();
+    private boolean testPassed = false;
+    private String popupName = null;
+    private String mainName = null;
+
+    private final 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!
+     */
+    @Test
+    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/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..89ff760
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CrossAppDragAndDropTests.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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 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.support.test.filters.FlakyTest;
+import android.support.test.uiautomator.UiDevice;
+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
+@FlakyTest(bugId = 65739235)
+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 UiDevice mDevice;
+
+    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);
+        mDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+        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 {
+        mDevice.drag(from.x, from.y, to.x, to.y, 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 {
+            mDevice.wakeUp();
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+        // Unlock the screen.
+        mDevice.pressMenu();
+    }
+
+    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/DialogFrameTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
new file mode 100644
index 0000000..a5e5b4b
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS 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.StateLogger.logE;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+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 org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class DialogFrameTests extends ParentChildTestBase {
+
+    private static final ComponentName DIALOG_TEST_ACTIVITY = ComponentName
+            .unflattenFromString("android.server.wm.frametestapp/.DialogTestActivity");
+
+    /** @see android.server.wm.frametestapp.DialogTestActivity#DIALOG_WINDOW_NAME */
+    private static final String DIALOG_WINDOW_NAME = "TestDialog";
+
+    private List<WindowState> mWindowList = new ArrayList<>();
+
+    @Override
+    ComponentName activityName() {
+        return DIALOG_TEST_ACTIVITY;
+    }
+
+    private WindowState getSingleWindow(final String windowName) {
+        try {
+            mAmWmState.getWmState().getMatchingVisibleWindowState(windowName, mWindowList);
+            return mWindowList.get(0);
+        } catch (Exception e) {
+            logE("Couldn't find window: " + windowName);
+            return null;
+        }
+    }
+
+    @Override
+    void doSingleTest(ParentChildTest t) throws Exception {
+        final WaitForValidActivityState waitForVisible =
+                WaitForValidActivityState.forWindow(DIALOG_WINDOW_NAME);
+
+        mAmWmState.computeState(waitForVisible);
+        WindowState dialog = getSingleWindow(DIALOG_WINDOW_NAME);
+        WindowState parent = getSingleWindow(activityName().flattenToString());
+
+        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("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.
+    @Test
+    public void testMatchParentDialogLayoutInOverscan() throws Exception {
+        doParentChildTest("MatchParentLayoutInOverscan",
+                (WindowState parent, WindowState 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("ExplicitSize",
+                (WindowState parent, WindowState 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("ExplicitSizeTopLeftGravity",
+                (WindowState parent, WindowState 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("ExplicitSizeBottomRightGravity",
+                (WindowState parent, WindowState 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: 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.
+    // @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("OversizedDimensionsNoLimits",
+    // (WindowState parent, WindowState 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("ExplicitPositionMatchParent",
+                (WindowState parent, WindowState 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("ExplicitPositionMatchParentNoLimits",
+                (WindowState parent, WindowState 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("MatchParent",
+                (WindowState parent, WindowState dialog) -> {
+                    assertEquals(dialog.getName(), mAmWmState.getWmState().getFocusedWindow());
+                });
+    }
+
+    @Test
+    public void testNoFocusDialog() throws Exception {
+        doFullscreenTest("NoFocus",
+                (WindowState parent, WindowState dialog) -> {
+                    assertEquals(parent.getName(), mAmWmState.getWmState().getFocusedWindow());
+                });
+    }
+
+    @Test
+    public void testMarginsArePercentagesOfContentFrame() throws Exception {
+        float horizontalMargin = .25f;
+        float verticalMargin = .35f;
+        doParentChildTest("WithMargins",
+                (WindowState parent, WindowState 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("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(wmState.getZOrder(dialog) > 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..08f25c2
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ParentChildTestBase.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.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.server.am.StateLogger.log;
+
+import android.content.ComponentName;
+import android.server.am.ActivityManagerTestBase;
+import android.server.am.WindowManagerState.WindowState;
+
+abstract class ParentChildTestBase extends ActivityManagerTestBase {
+
+    /** Extra key for test case name. */
+    private static final String EXTRA_TEST_CASE = "test-case";
+
+    interface ParentChildTest {
+        void doTest(WindowState parent, WindowState child);
+    }
+
+    private void startTestCase(String testCase) throws Exception {
+        executeShellCommand(getAmStartCmd(activityName(), EXTRA_TEST_CASE, testCase));
+    }
+
+    private void startTestCaseDocked(String testCase) throws Exception {
+        startTestCase(testCase);
+        setActivityTaskWindowingMode(activityName(), WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+    }
+
+    abstract ComponentName activityName();
+
+    abstract void doSingleTest(ParentChildTest t) throws Exception;
+
+    void doFullscreenTest(String testCase, ParentChildTest t) throws Exception {
+        log("Running test fullscreen");
+        startTestCase(testCase);
+        doSingleTest(t);
+        stopTestPackage(activityName());
+    }
+
+    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);
+        stopTestPackage(activityName());
+    }
+
+    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 0c1ed5f..e0c7a62 100644
--- a/tests/inputmethod/Android.mk
+++ b/tests/inputmethod/Android.mk
@@ -26,17 +26,24 @@
 
 LOCAL_MULTILIB := both
 
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_JAVA_LIBRARIES := android.test.runner.stubs
 
 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_SDK_VERSION := test_current
+
 include $(BUILD_CTS_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
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..2cb0099 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="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" />
+        <!--
+            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..48d4e05
--- /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;
+    }
+}
\ No newline at end of file
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..a0dc613
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEvent.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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;
+
+/**
+ * An immutable object that stores event happened in the {@link MockIme}.
+ */
+public final class ImeEvent {
+
+    ImeEvent(@NonNull String eventName, int nestLevel, @NonNull String threadName, int threadId,
+            boolean isMainThread, long enterTimestamp, long exitTimestamp, long enterWallTime,
+            long exitWallTime, @NonNull ImeState enterState, @NonNull ImeState exitState,
+            @NonNull Bundle arguments) {
+        mEventName = eventName;
+        mNestLevel = nestLevel;
+        mThreadName = threadName;
+        mThreadId = threadId;
+        mIsMainThread = isMainThread;
+        mEnterTimestamp = enterTimestamp;
+        mExitTimestamp = exitTimestamp;
+        mEnterWallTime = enterWallTime;
+        mExitWallTime = exitWallTime;
+        mEnterState = enterState;
+        mExitState = exitState;
+        mArguments = arguments;
+    }
+
+    @NonNull
+    final 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.toBundle());
+        bundle.putBundle("mArguments", mArguments);
+        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");
+        return new ImeEvent(eventName, nestLevel, threadName,
+                threadId, isMainThread, enterTimestamp, exitTimestamp, enterWallTime, exitWallTime,
+                enterState, exitState, arguments);
+    }
+
+    /**
+     * 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.
+     */
+    @NonNull
+    public ImeState getExitState() {
+        return mExitState;
+    }
+
+    /**
+     * @return {@link Bundle} that stores parameters passed to the corresponding event handler.
+     */
+    @NonNull
+    public Bundle getArguments() {
+        return mArguments;
+    }
+
+    @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;
+    @NonNull
+    private final ImeState mExitState;
+    @NonNull
+    private final Bundle mArguments;
+}
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..7d021df
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStream.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.cts.mockime;
+
+import android.os.Bundle;
+import android.support.annotation.IntRange;
+import android.support.annotation.NonNull;
+import android.view.inputmethod.EditorInfo;
+
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+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 {
+
+    @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;
+        }
+    }
+
+    /**
+     * @return Debug info as a {@link String}.
+     */
+    public String dump() {
+        final ImeEventArray latest = mEventSupplier.get();
+        final StringBuilder sb = new StringBuilder();
+        final SimpleDateFormat dataFormat =
+                new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
+        sb.append("ImeEventStream:\n");
+        sb.append("  latest: array[").append(latest.mArray.length).append("] + {\n");
+        for (int i = 0; i < latest.mLength; ++i) {
+            final ImeEvent event = latest.mArray[i];
+            if (i == mCurrentPosition) {
+                sb.append("  ======== CurrentPosition ========  \n");
+            }
+            sb.append("   ").append(i).append(" :");
+            if (event != null) {
+                for (int j = 0; j < event.getNestLevel(); ++j) {
+                    sb.append(' ');
+                }
+                sb.append('{');
+                sb.append(dataFormat.format(new Date(event.getEnterWallTime())));
+                sb.append(" event=").append(event.getEventName());
+                sb.append(": args=");
+                dumpBundle(sb, event.getArguments());
+                sb.append("},\n");
+            } else {
+                sb.append("{null},\n");
+            }
+        }
+        if (mCurrentPosition >= latest.mLength) {
+            sb.append("  ======== CurrentPosition ========  \n");
+        }
+        sb.append("}\n");
+        return sb.toString();
+    }
+
+    private static final 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('}');
+    }
+
+    final static class ImeEventArray {
+        @NonNull
+        public final ImeEvent[] mArray;
+        public final int mLength;
+        public 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..5c1ef24
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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() {}
+
+    /**
+     * 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 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 {
+        try {
+            Optional<ImeEvent> result;
+            while (true) {
+                if (timeout < 0) {
+                    throw new TimeoutException(
+                            "event not found within the timeout: " + stream.dump());
+                }
+                result = stream.seekToFirst(condition);
+                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}.
+     *
+     * @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, timeout);
+    }
+
+    /**
+     * Assert that an event that matches the given {@code condition} will no be found in the stream
+     * within the given {@code timeout}.
+     *
+     * <p>Fails with {@link junit.framework.Assert#fail} if such an event is  found within the given
+     * {@code timeout}.</p>
+     *
+     * <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 timeout timeout in millisecond
+     */
+    public static void notExpectEvent(@NonNull ImeEventStream stream,
+            @NonNull Predicate<ImeEvent> condition, long timeout) {
+        try {
+            while (true) {
+                if (timeout < 0) {
+                    return;
+                }
+                if (stream.findFirst(condition).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;
+        }, 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 -> "onInputViewLayoutChanged".equals(event.getEventName());
+        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..9c729b3
--- /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 final static String NEW_LAYOUT_KEY = "newLayout";
+    private final static String OLD_LAYOUT_KEY = "oldLayout";
+    private final static String VIEW_ORIGIN_ON_SCREEN_KEY = "viewOriginOnScreen";
+    private final static String DISPLAY_SIZE_KEY = "displaySize";
+    private final static String SYSTEM_WINDOW_INSET_KEY = "systemWindowInset";
+    private final static 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..7b79c26
--- /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..0b7fd04
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeState.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.cts.mockime;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+
+/**
+ * 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
+    final Bundle toBundle() {
+        final Bundle bundle = new Bundle();
+        bundle.putBoolean("mHasInputBinding", mHasInputBinding);
+        bundle.putBoolean("mHasDummyInputConnection", mHasDummyInputConnection);
+        return bundle;
+    }
+
+    @NonNull
+    static ImeState fromBundle(@NonNull Bundle bundle) {
+        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..79c35f1
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -0,0 +1,652 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.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;
+
+        public 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;
+                    }
+                }
+            }
+        });
+    }
+
+    @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() {
+        getTracer().onCreate(() -> {
+            super.onCreate();
+            mSettings = readSettings();
+            if (mSettings == null) {
+                throw new IllegalStateException("Settings file is not found. "
+                        + "Make sure MockImeSession.create() is used to launch Mock IME.");
+            }
+
+            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()));
+
+            mImeEventActionName.set(mSettings.getEventCallbackActionName());
+            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;
+
+        public 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());
+    }
+
+    @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;
+
+        public 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;
+            ++mNestLevel;
+            T result;
+            try {
+                result = supplier.get();
+            } finally {
+                --mNestLevel;
+            }
+            final long exitTimestamp = SystemClock.elapsedRealtimeNanos();
+            final long exitWallTime = System.currentTimeMillis();
+            final ImeState exitState = mIme.getState();
+            sendEventInternal(new ImeEvent(eventName, nestLevel, mThreadName,
+                    mThreadId, mIsMainThread, enterTimestamp, exitTimestamp, enterWallTime,
+                    exitWallTime, enterState, exitState, arguments));
+            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 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..4fe9f5a
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.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 final static class EventStore {
+        private final static int INITIAL_ARRAY_SIZE = 32;
+
+        @NonNull
+        public final ImeEvent[] mArray;
+        public int mLength;
+
+        public EventStore() {
+            mArray = new ImeEvent[INITIAL_ARRAY_SIZE];
+            mLength = 0;
+        }
+
+        public 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;
+
+        public 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;
+    }
+}
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..d3efc72 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/BaseInputConnectionTest.java
@@ -493,8 +493,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..9b03ed7 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,6 +57,9 @@
         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());
     }
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..add9226
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
@@ -0,0 +1,452 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 final static String TEST_MARKER = "android.view.inputmethod.cts.FocusHandlingTest";
+
+    public EditText launchTestActivity() {
+        final AtomicReference<EditText> editTextRef = new AtomicReference<>();
+        TestActivity.startSync((TestActivity activity) -> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+
+            final EditText editText = new EditText(activity);
+            editText.setPrivateImeOptions(TEST_MARKER);
+            editText.setHint("editText");
+            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((TestActivity 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((TestActivity 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((TestActivity activity) -> {
+                final LinearLayout layout = new LinearLayout(activity);
+                layout.setOrientation(LinearLayout.VERTICAL);
+
+                final EditText editText = new EditText(activity);
+                editText.setText("Editable");
+                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((TestActivity 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(new Runnable() {
+                @Override
+                public void run() {
+                    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((TestActivity 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/InputMethodInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
index 2b5d7ed..b538d41 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodInfoTest.java
@@ -250,7 +250,7 @@
                     ApplicationInfo.FLAG_SYSTEM) {
                 continue;
             }
-            if (serviceInfo.encryptionAware) {
+            if (serviceInfo.directBootAware) {
                 hasEncryptionAwareInputMethod = true;
                 break;
             }
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..cccc279
--- /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 final static 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..014ef90
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/OnScreenPositionTest.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.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 final static String TEST_MARKER = "android.view.inputmethod.cts.OnScreenPositionTest";
+
+    public EditText launchTestActivity() {
+        final AtomicReference<EditText> editTextRef = new AtomicReference<>();
+        TestActivity.startSync((TestActivity activity) -> {
+            final LinearLayout layout = new LinearLayout(activity);
+            layout.setOrientation(LinearLayout.VERTICAL);
+
+            final EditText editText = new EditText(activity);
+            editText.setPrivateImeOptions(TEST_MARKER);
+            editText.setHint("editText");
+            editTextRef.set(editText);
+
+            layout.addView(editText);
+            return layout;
+        });
+        return editTextRef.get();
+    }
+
+    /**
+     * Regression test for Bug 33308065.
+     */
+    @Test
+    public void testImeIsNotBehindNavBar() throws Exception {
+        final int EXPECTED_KEYBOARD_HEIGHT = 100;
+
+        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..1512684
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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((TestActivity 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();
+
+            // Wait until the MockIme gets bound to the TestActivity.
+            expectBindInput(stream, Process.myPid(), TIMEOUT);
+
+            // Emulate tap event
+            CtsTouchUtils.emulateTapOnViewCenter(
+                    InstrumentationRegistry.getInstrumentation(), searchView);
+
+            // Wait until "showSoftInput" gets called with a real InputConnection
+            expectEvent(stream, event ->
+                    "showSoftInput".equals(event.getEventName())
+                            && !event.getExitState().hasDummyInputConnection(), 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..e68a2c7
--- /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..aef3bf3
--- /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/LightNavigationBarVerifier.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/LightNavigationBarVerifier.java
new file mode 100644
index 0000000..401caa3
--- /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 float LIGHT_NAVBAR_SUPPORTED_THRESHOLD = 20.0f;
+
+    /** This value actually does not have strong rationale. */
+    private static 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;
+
+        public 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..6cdb000
--- /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;
+
+        public 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;
+
+        public 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..3c71968
--- /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((TestActivity 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..e9c15cc
--- /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..85c3e66
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestActivity.java
@@ -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
+ */
+
+package android.view.inputmethod.cts.util;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.test.InstrumentationRegistry;
+import android.view.View;
+
+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;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (mInitializer == null) {
+            mInitializer = sInitializer.get();
+        }
+        setContentView(mInitializer.apply(this));
+    }
+
+    /**
+     * 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..b5f9c9a
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.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
+
+    /**
+     * 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
+     * @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 {
+        final AtomicBoolean result = new AtomicBoolean();
+
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        while (!result.get()) {
+            if (timeout < 0) {
+                throw new TimeoutException();
+            }
+            instrumentation.runOnMainSync(() -> {
+                if (condition.getAsBoolean()) {
+                    result.set(true);
+                }
+            });
+            try {
+                Thread.sleep(TIME_SLICE);
+            } catch (InterruptedException e) {
+                throw new IllegalStateException(e);
+            }
+            timeout -= TIME_SLICE;
+        }
+    }
+}
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..657420f
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusStealer.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 final static class MyResultReceiver extends ResultReceiver {
+        final BlockingQueue<Integer> mQueue = new ArrayBlockingQueue<>(1);
+
+        public 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
+     * @return
+     * @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..ecca4c2
--- /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 16954f2..d619003 100644
--- a/tests/jank/Android.mk
+++ b/tests/jank/Android.mk
@@ -32,7 +32,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 72d26b1..66b0f94 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/leanbackjank/Android.mk b/tests/leanbackjank/Android.mk
index ec114f3..7f1177a 100644
--- a/tests/leanbackjank/Android.mk
+++ b/tests/leanbackjank/Android.mk
@@ -32,11 +32,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 3fc685d..04c3d3f 100644
--- a/tests/leanbackjank/app/Android.mk
+++ b/tests/leanbackjank/app/Android.mk
@@ -27,25 +27,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..58340c5 100644
--- a/tests/mocking/AndroidTest.xml
+++ b/tests/mocking/AndroidTest.xml
@@ -15,6 +15,7 @@
   ~ limitations under the License.
   -->
 <configuration description="Config for Mockito mocking 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/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..d65d6f5 100644
--- a/tests/mocking/inline/AndroidTest.xml
+++ b/tests/mocking/inline/AndroidTest.xml
@@ -15,6 +15,7 @@
   ~ limitations under the License.
   -->
 <configuration description="Config for Mockito inline mocking 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/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/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/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..e237461 100644
--- a/tests/pdf/AndroidTest.xml
+++ b/tests/pdf/AndroidTest.xml
@@ -14,6 +14,7 @@
      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" />
 
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..5602e7e 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)
 
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-test-base-27-api/AndroidManifest.xml b/tests/signature/api-check/android-test-base-27-api/AndroidManifest.xml
index c65bfdf..684872f 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,7 +16,7 @@
  -->
 
 <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"/>
 
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/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/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-current-api/Android.mk b/tests/signature/api-check/apache-http-legacy-current-api/Android.mk
index df69004..cf391bd 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,7 @@
 LOCAL_PACKAGE_NAME := CtsApacheHttpLegacyCurrentApiSignatureTestCases
 
 LOCAL_SIGNATURE_API_FILES := \
-    apache-http-legacy-current.api \
+    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/AndroidTest.xml b/tests/signature/api-check/apache-http-legacy-current-api/AndroidTest.xml
index a5e69a9..9002d39 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,17 @@
      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="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" />
@@ -29,7 +33,9 @@
     <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="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/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/src/android/signature/cts/api/AbstractApiTest.java b/tests/signature/api-check/src/android/signature/cts/api/AbstractApiTest.java
new file mode 100644
index 0000000..8261013
--- /dev/null
+++ b/tests/signature/api-check/src/android/signature/cts/api/AbstractApiTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.PrintWriter;
+import java.io.StringWriter;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.stream.Stream;
+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<JDiffClassDescription> parseApiFilesAsStream(
+            ApiDocumentParser apiDocumentParser, String[] apiFiles)
+            throws XmlPullParserException, IOException {
+        return Stream.of(apiFiles).flatMap(apiFile -> {
+            File file = new File(API_FILE_DIRECTORY + "/" + apiFile);
+            try {
+                return apiDocumentParser.parseAsStream(new FileInputStream(file))
+                        .filter(AbstractApiTest::isAccessibleClass);
+            } catch (IOException|XmlPullParserException e) {
+                throw new RuntimeException(e);
+            }
+        });
+    }
+}
diff --git a/tests/signature/api-check/src/android/signature/cts/api/AnnotationTest.java b/tests/signature/api-check/src/android/signature/cts/api/AnnotationTest.java
new file mode 100644
index 0000000..0803507
--- /dev/null
+++ b/tests/signature/api-check/src/android/signature/cts/api/AnnotationTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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.AnnotationChecker;
+import android.signature.cts.ApiDocumentParser;
+
+/**
+ * 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() {
+        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/src/android/signature/cts/api/BootClassPathClassesProvider.java b/tests/signature/api-check/src/android/signature/cts/api/BootClassPathClassesProvider.java
new file mode 100644
index 0000000..02bd72e
--- /dev/null
+++ b/tests/signature/api-check/src/android/signature/cts/api/BootClassPathClassesProvider.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.signature.cts.api;
+
+import android.signature.cts.ClassProvider;
+import dalvik.system.DexFile;
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.stream.Stream;
+
+@SuppressWarnings("deprecation")
+public class BootClassPathClassesProvider extends ClassProvider {
+    @Override
+    public Stream<Class<?>> getAllClasses() {
+        Stream.Builder<Class<?>> builder = Stream.builder();
+        for (String file : getBootJarPaths()) {
+            try {
+                DexFile dexFile = new DexFile(file);
+                Enumeration<String> entries = dexFile.entries();
+                while (entries.hasMoreElements()) {
+                    String className = entries.nextElement();
+                    Class<?> clazz = getClass(className);
+                    if (clazz != null) {
+                        builder.add(clazz);
+                    }
+                }
+            } catch (IOException e) {
+                throw new RuntimeException("Failed to parse dex in " + file, e);
+            } catch (ClassNotFoundException e) {
+                throw new RuntimeException("Error while loading class in " + file, e);
+            }
+        }
+        return builder.build();
+    }
+
+    private String[] getBootJarPaths() {
+        return System.getProperty("java.boot.class.path").split(":");
+    }
+}
\ No newline at end of file
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
index 3473cfd..fadf748 100644
--- a/tests/signature/api-check/src/android/signature/cts/api/SignatureTest.java
+++ b/tests/signature/api-check/src/android/signature/cts/api/SignatureTest.java
@@ -17,103 +17,48 @@
 package android.signature.cts.api;
 
 import android.os.Bundle;
-import android.signature.cts.ApiDocumentParser;
 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 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 java.util.function.Predicate;
+import java.util.stream.Collectors;
 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 {
+public class SignatureTest extends AbstractApiTest {
 
     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[] baseApiFiles;
     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();
-
+    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");
     }
 
-    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 {
+        runWithTestResultObserver(mResultObserver -> {
             Set<JDiffClassDescription> unexpectedClasses = loadUnexpectedClasses();
             for (JDiffClassDescription classDescription : unexpectedClasses) {
-                Class<?> unexpectedClass = findUnexpectedClass(classDescription);
+                Class<?> unexpectedClass = findUnexpectedClass(classDescription, classProvider);
                 if (unexpectedClass != null) {
                     mResultObserver.notifyFailure(
                             FailureType.UNEXPECTED_CLASS,
@@ -122,46 +67,31 @@
                 }
             }
 
-            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;
-                    }
+            ApiComplianceChecker complianceChecker =
+                    new ApiComplianceChecker(mResultObserver, classProvider);
 
-                    // Ignore unexpected classes that are in the API definition.
-                    if (!unexpectedClasses.contains(classDescription)) {
-                        complianceChecker.checkSignatureCompliance(classDescription);
-                    }
-                }
-            });
+            // Load classes from any API files that form the base which the expected APIs extend.
+            loadBaseClasses(complianceChecker);
 
-            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());
-        }
+            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 Class<?> findUnexpectedClass(JDiffClassDescription classDescription) {
+    private static <T> Predicate<T> not(Predicate<T> predicate) {
+        return predicate.negate();
+    }
+
+    private Class<?> findUnexpectedClass(JDiffClassDescription classDescription,
+            ClassProvider classProvider) {
         try {
-            return ReflectionHelper.findMatchingClass(classDescription);
+            return ReflectionHelper.findMatchingClass(classDescription, classProvider);
         } catch (ClassNotFoundException e) {
             return null;
         }
@@ -170,19 +100,21 @@
     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;
+        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/android/signature/cts/api/TestResultObserver.java b/tests/signature/api-check/src/android/signature/cts/api/TestResultObserver.java
new file mode 100644
index 0000000..a3ea203
--- /dev/null
+++ b/tests/signature/api-check/src/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/system-annotation/Android.mk b/tests/signature/api-check/system-annotation/Android.mk
new file mode 100644
index 0000000..4e67d69
--- /dev/null
+++ b/tests/signature/api-check/system-annotation/Android.mk
@@ -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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsSystemApiAnnotationTestCases
+
+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..55318ed
--- /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/>
+
+    <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-current-api/Android.mk b/tests/signature/api-check/system-current-api/Android.mk
index 1c10d2c..feda408 100644
--- a/tests/signature/api-check/system-current-api/Android.mk
+++ b/tests/signature/api-check/system-current-api/Android.mk
@@ -19,7 +19,9 @@
 LOCAL_PACKAGE_NAME := CtsSystemCurrentApiSignatureTestCases
 
 LOCAL_SIGNATURE_API_FILES := \
+    current.api \
     system-current.api \
+    system-removed.api \
     android-test-mock-current.api \
     android-test-runner-current.api \
 
diff --git a/tests/signature/api-check/system-current-api/AndroidTest.xml b/tests/signature/api-check/system-current-api/AndroidTest.xml
index 6bf641a..6f846e4 100644
--- a/tests/signature/api-check/system-current-api/AndroidTest.xml
+++ b/tests/signature/api-check/system-current-api/AndroidTest.xml
@@ -14,15 +14,22 @@
      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="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="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.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">
@@ -35,7 +42,9 @@
     <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="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-current.api,system-removed.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>
diff --git a/tests/signature/api/Android.mk b/tests/signature/api/Android.mk
index f84ca36..abeae54 100644
--- a/tests/signature/api/Android.mk
+++ b/tests/signature/api/Android.mk
@@ -75,11 +75,28 @@
 
 include $(LOCAL_PATH)/build_xml_api_file.mk
 
-# current apache-http-legacy api, in XML format.
-# ==============================================
+# 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 := 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
+LOCAL_MODULE_CLASS := ETC
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_ETC)
 
-include $(LOCAL_PATH)/build_xml_api_file.mk
+# Tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+
+LOCAL_MODULE := cts-apache-http-legacy-minus-current-api
+LOCAL_MODULE_STEM := apache-http-legacy-minus-current.api
+
+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
index f5468bb..ba87b9a 100644
--- a/tests/signature/api/build_xml_api_file.mk
+++ b/tests/signature/api/build_xml_api_file.mk
@@ -27,7 +27,7 @@
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_ETC)
 
 # 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_SYSTEM)/base_rules.mk
 $(LOCAL_BUILT_MODULE) : ${LOCAL_SRC_FILES} | $(APICHECK)
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..cefbec7 100755
--- a/tests/signature/runSignatureTests.sh
+++ b/tests/signature/runSignatureTests.sh
@@ -17,6 +17,8 @@
 CtsAndroidTestRunnerCurrentApiSignatureTestCases
 CtsAndroidTestBase27ApiSignatureTestCases
 CtsApacheHttpLegacyCurrentApiSignatureTestCases
+
+CtsSystemApiAnnotationTestCases
 "
 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/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/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..547203e 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, 10);
+                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..dff8835
--- /dev/null
+++ b/tests/tests/alarmclock/src/android/alarmclock/cts/DismissTimerTest.java
@@ -0,0 +1,11 @@
+package android.alarmclock.cts;
+
+import android.alarmclock.common.Utils;
+
+public class DismissTimerTest extends AlarmClockTestBase {
+
+    public void testAll() throws Exception {
+        assertEquals(Utils.COMPLETION_RESULT, runTest(Utils.TestcaseType.SET_TIMER_FOR_DISMISSAL));
+        assertEquals(Utils.COMPLETION_RESULT, runTest(Utils.TestcaseType.DISMISS_TIMER));
+    }
+}
diff --git a/tests/tests/animation/Android.mk b/tests/tests/animation/Android.mk
index c625cbe..4bb9628 100644
--- a/tests/tests/animation/Android.mk
+++ b/tests/tests/animation/Android.mk
@@ -30,14 +30,16 @@
     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)
 
 # 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/app.usage/Android.mk b/tests/tests/app.usage/Android.mk
index dbef83c..fa5c60e 100644
--- a/tests/tests/app.usage/Android.mk
+++ b/tests/tests/app.usage/Android.mk
@@ -28,9 +28,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/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index ddc59b3..ab48481 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
@@ -178,6 +178,27 @@
         }
     }
 
+    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.mBucket == UsageStatsManager.STANDBY_BUCKET_RARE;
+            }
+        }
+
+        assertTrue(found);
+    }
+
     /**
      * 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
diff --git a/tests/tests/app/Android.mk b/tests/tests/app/Android.mk
index bd679a4..d087058 100644
--- a/tests/tests/app/Android.mk
+++ b/tests/tests/app/Android.mk
@@ -24,13 +24,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 5df6046..da7194b 100644
--- a/tests/tests/appwidget/Android.mk
+++ b/tests/tests/appwidget/Android.mk
@@ -28,8 +28,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..39026e4
--- /dev/null
+++ b/tests/tests/batterysaving/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 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" />
+        <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>
+
+    <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..2a54463
--- /dev/null
+++ b/tests/tests/batterysaving/apps/app_target_api_25/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)
+
+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 \
+    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..4d080e7
--- /dev/null
+++ b/tests/tests/batterysaving/apps/app_target_api_current/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)
+
+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 \
+    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..f0cf7e7a
--- /dev/null
+++ b/tests/tests/batterysaving/apps/app_target_api_current/src/android/os/cts/batterysaving/app/CommReceiver.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package 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.content.Context;
+import android.content.Intent;
+import android.os.cts.batterysaving.common.BaseCommunicationReceiver;
+import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload;
+import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload.TestServiceResponse;
+import android.util.Log;
+
+public class CommReceiver extends BaseCommunicationReceiver {
+    private static final String TAG = "CommReceiver";
+
+    @Override
+    protected void handleRequest(
+            Context context, Payload request, Payload.Builder responseBuilder) {
+        if (request.hasTestServiceRequest()) {
+            handleBatterySaverBgServiceRequest(context, request, responseBuilder);
+        }
+        return;
+    }
+
+    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);
+        }
+
+        responseBuilder.setTestServiceResponse(rb);
+    }
+}
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..9df0b41
--- /dev/null
+++ b/tests/tests/batterysaving/common/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+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 \
+    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..92cf9a4
--- /dev/null
+++ b/tests/tests/batterysaving/common/proto/battery_saver_cts_common.proto
@@ -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.
+ */
+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;
+    }
+
+    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 TestServiceResponse test_service_response = 2;
+}
diff --git a/tests/tests/batterysaving/common/src/android/os/cts/batterysaving/common/BaseCommunicationReceiver.java b/tests/tests/batterysaving/common/src/android/os/cts/batterysaving/common/BaseCommunicationReceiver.java
new file mode 100644
index 0000000..3674dec
--- /dev/null
+++ b/tests/tests/batterysaving/common/src/android/os/cts/batterysaving/common/BaseCommunicationReceiver.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.os.cts.batterysaving.common;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.cts.batterysaving.common.BatterySavingCtsCommon.Payload;
+import android.util.Log;
+
+import com.google.protobuf.InvalidProtocolBufferException;
+
+/**
+ * Base class for "CommReceiver"s that live in the app side and responds to request from the
+ * test process.
+ */
+public abstract class BaseCommunicationReceiver extends BroadcastReceiver {
+    private static final String TAG = "BaseCommunicationReceiver";
+
+    @Override
+    public final void onReceive(Context context, Intent intent) {
+        assertEquals(CommUtils.ACTION_REQUEST, intent.getAction());
+
+        // Parse the request.
+        final Payload request;
+        try {
+            request = Payload.parseFrom(
+                    intent.getByteArrayExtra(CommUtils.EXTRA_PAYLOAD));
+        } catch (InvalidProtocolBufferException e) {
+            throw new RuntimeException("Received invalid request", e);
+        }
+        Log.i(TAG, "Request received: " + request.toString());
+
+        // Handle it and generate a response.
+        final Payload.Builder responseBuilder = Payload.newBuilder();
+        handleRequest(context, request, responseBuilder);
+
+        final Payload response = responseBuilder.build();
+        Log.i(TAG, "Response generated: " + response.toString());
+
+        // Send back.
+        final Bundle extras = new Bundle();
+        extras.putByteArray(CommUtils.EXTRA_PAYLOAD, response.toByteArray());
+        setResultExtras(extras);
+    }
+
+    protected abstract void handleRequest(Context context,
+            Payload request, Payload.Builder responseBuilder);
+}
diff --git a/tests/tests/batterysaving/common/src/android/os/cts/batterysaving/common/CallbackAsserter.java b/tests/tests/batterysaving/common/src/android/os/cts/batterysaving/common/CallbackAsserter.java
new file mode 100644
index 0000000..32b26512
--- /dev/null
+++ b/tests/tests/batterysaving/common/src/android/os/cts/batterysaving/common/CallbackAsserter.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 android.os.cts.batterysaving.common;
+
+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;
+
+public class CallbackAsserter {
+    private static final String TAG = "CallbackAsserter";
+
+    final CountDownLatch mLatch = new CountDownLatch(1);
+
+    CallbackAsserter() {
+    }
+
+    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() {
+    }
+
+    public static CallbackAsserter forBroadcast(IntentFilter filter) {
+        return forBroadcast(filter, null);
+    }
+
+    public static CallbackAsserter forBroadcast(IntentFilter filter, Predicate<Intent> checker) {
+        return new BroadcastAsserter(filter, checker);
+    }
+
+    public static CallbackAsserter forContentUri(Uri watchUri) {
+        return forContentUri(watchUri, null);
+    }
+
+    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/tests/tests/batterysaving/common/src/android/os/cts/batterysaving/common/CommUtils.java b/tests/tests/batterysaving/common/src/android/os/cts/batterysaving/common/CommUtils.java
new file mode 100644
index 0000000..b7e86d5
--- /dev/null
+++ b/tests/tests/batterysaving/common/src/android/os/cts/batterysaving/common/CommUtils.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.os.cts.batterysaving.common;
+
+import static android.os.cts.batterysaving.common.Values.getCommReceiver;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.BroadcastReceiver;
+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;
+
+public class CommUtils {
+    private static final String TAG = "CommUtils";
+
+    static final String ACTION_REQUEST = "ACTION_REQUEST";
+    static final String EXTRA_PAYLOAD = "EXTRA_PAYLOAD";
+
+    static Handler sMainHandler = new Handler(Looper.getMainLooper());
+
+    /**
+     * Sends a request to the "CommReceiver" in a given package, and return the response.
+     */
+    public static BatterySavingCtsCommon.Payload sendRequest(
+            String targetPackage, BatterySavingCtsCommon.Payload request)
+            throws Exception {
+
+        // Create a request intent.
+        Log.i(TAG, "Request generated: " + request.toString());
+
+        final Intent requestIntent = new Intent(ACTION_REQUEST)
+                .setComponent(getCommReceiver(targetPackage))
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)
+                .putExtra(EXTRA_PAYLOAD, request.toByteArray());
+
+        // 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.
+        assertTrue("Didn't receive broadcast result.",
+                latch.await(60, TimeUnit.SECONDS));
+
+        assertNotNull("Didn't receive result extras", responseBundle.get());
+
+        final byte[] resultPayload = responseBundle.get().getByteArray(EXTRA_PAYLOAD);
+        assertNotNull("Didn't receive result payload", resultPayload);
+
+        Log.i(TAG, "Response received: " + resultPayload.toString());
+
+        return BatterySavingCtsCommon.Payload.parseFrom(resultPayload);
+    }
+}
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/BatterySaverBgServiceTest.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverBgServiceTest.java
new file mode 100644
index 0000000..e6932aa
--- /dev/null
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverBgServiceTest.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 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.os.cts.batterysaving.common.CommUtils;
+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 = CommUtils.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 = CommUtils.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 = CommUtils.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..1be4c72
--- /dev/null
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.content.pm.PackageManager.FEATURE_LOCATION;
+import static android.content.pm.PackageManager.FEATURE_LOCATION_GPS;
+import static android.provider.Settings.Secure.LOCATION_MODE_OFF;
+import static android.provider.Settings.Secure.LOCATION_PROVIDERS_ALLOWED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.content.IntentFilter;
+import android.location.LocationManager;
+import android.os.PowerManager;
+import android.os.cts.batterysaving.common.CallbackAsserter;
+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 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";
+
+    /**
+     * Test for the {@link PowerManager#LOCATION_MODE_ALL_DISABLED_WHEN_SCREEN_OFF} mode.
+     */
+    @Test
+    public void testLocationAllDisabled() throws Exception {
+        if (!hasFeatures(FEATURE_LOCATION, FEATURE_LOCATION_GPS)) {
+            Log.i(TAG, "Device doesn't support GPS");
+            return;
+        }
+        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());
+
+        // 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());
+
+        // Turn screen off.
+        runWithExpectingLocationCallback(() -> {
+            turnOnScreen(false);
+            waitUntil("Kill switch still off", () -> getLocationGlobalKillSwitch() == 1);
+            assertEquals(LOCATION_MODE_OFF, getLocationMode());
+        });
+
+        // On again.
+        runWithExpectingLocationCallback(() -> {
+            turnOnScreen(true);
+            waitUntil("Kill switch still off", () -> getLocationGlobalKillSwitch() == 0);
+            assertNotEquals(LOCATION_MODE_OFF, getLocationMode());
+        });
+
+        // Off again.
+        runWithExpectingLocationCallback(() -> {
+            turnOnScreen(false);
+            waitUntil("Kill switch still off", () -> getLocationGlobalKillSwitch() == 1);
+            assertEquals(LOCATION_MODE_OFF, getLocationMode());
+        });
+
+        // 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());
+        });
+    }
+
+    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..664a582
--- /dev/null
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.os.cts.batterysaving;
+
+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..6680e51
--- /dev/null
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySavingTestBase.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 junit.framework.Assert.fail;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.BatteryManager;
+import android.os.ParcelFileDescriptor;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.provider.Settings.Global;
+import android.support.test.InstrumentationRegistry;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Before;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.function.Predicate;
+
+public class BatterySavingTestBase {
+    private static final String TAG = "BatterySavingTestBase";
+
+    public static final boolean DEBUG = true;
+
+    public static final int DEFAULT_TIMEOUT_SECONDS = 30;
+
+    @Before
+    public final void resetDumpsysBatteryBeforeTest() throws Exception {
+        turnOnScreen(true);
+    }
+
+    @After
+    public final void resetDumpsysBatteryAfterTest() throws Exception {
+        runDumpsysBatteryReset();
+        turnOnScreen(true);
+    }
+
+    @FunctionalInterface
+    public interface BooleanSupplierWithThrow {
+        boolean getAsBoolean() throws Exception;
+    }
+
+    @FunctionalInterface
+    public interface RunnableWithThrow {
+        void run() throws Exception;
+    }
+
+    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);
+        }
+    }
+
+    /** Print an error log and fail. */
+    public void failWithLog(String message) {
+        Log.e(getLogTag(), message);
+        fail(message);
+    }
+
+    private static String readAll(ParcelFileDescriptor pfd) {
+        try {
+            try {
+                final StringBuilder ret = new StringBuilder(1024);
+
+                try (BufferedReader r = new BufferedReader(
+                        new FileReader(pfd.getFileDescriptor()))) {
+                    String line;
+                    while ((line = r.readLine()) != null) {
+                        ret.append(line);
+                        ret.append("\n");
+                    }
+                    r.readLine();
+                }
+                return ret.toString();
+            } finally {
+                pfd.close();
+            }
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Run a command and returns the result. IF resultAsserter is not null, apply it on the output
+     * and fail it it returns false.
+     */
+    public String runCommand(String command, Predicate<String> resultAsserter) {
+        debug("Running command: " + command);
+
+        final String result;
+        try {
+            result = readAll(InstrumentationRegistry.getInstrumentation()
+                    .getUiAutomation().executeShellCommand(command));
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+        debug("Command output (" + result.length() + " chars):\n" + result);
+
+        if (resultAsserter != null && !resultAsserter.test(result)) {
+            failWithLog("Command '" + command + "' failed, output was:\n" + result);
+        }
+        return result;
+    }
+
+    /** Run a command and return the result. */
+    public String runCommand(String command) {
+        return runCommand(command, null);
+    }
+
+    /** Run a command and return the result. */
+    public String runCommandWithNoOutput(String command) {
+        return runCommand(command, (output) -> output.length() == 0);
+    }
+
+    /** Run "cmd settings put" */
+    private void putSetting(String scope, String key, String value) {
+        // Hmm, technically we should escape a value, but if I do like '1', it won't work. ??
+        runCommandWithNoOutput("settings put " + scope + " " + key + " " + value);
+    }
+
+    /** Run "cmd settings put global" */
+    public void putGlobalSetting(String key, String value) {
+        putSetting("global", key, value);
+    }
+
+    /** Run "cmd settings put secure" */
+    public void putSecureSetting(String key, String value) {
+        putSetting("secure", key, value);
+    }
+
+    /** Make the target device think it's off charger. */
+    public void runDumpsysBatteryUnplug() throws Exception {
+        runCommandWithNoOutput("dumpsys battery unplug");
+        waitUntil("Device still charging", () -> !getBatteryManager().isCharging());
+
+        Log.d(TAG, "Battery UNPLUGGED");
+    }
+
+    /** Reset {@link #runDumpsysBatteryUnplug}.  */
+    public void runDumpsysBatteryReset() throws Exception {
+        runCommandWithNoOutput("dumpsys battery reset");
+
+        Log.d(TAG, "Battery RESET");
+    }
+
+    /** Run "adb shell am make-uid-idle PACKAGE" */
+    public void runMakeUidIdle(String packageName) {
+        runCommandWithNoOutput("am make-uid-idle " + packageName);
+    }
+
+    /** Run "adb shell am kill PACKAGE" */
+    public void runKill(String packageName) {
+        runCommandWithNoOutput("am kill " + packageName);
+    }
+
+    /**
+     * Wait until {@code predicate} is satisfied, or fail, with the default timeout.
+     */
+    public 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 void waitUntil(String message, int timeoutSecond, BooleanSupplierWithThrow predicate)
+            throws Exception {
+        if (timeoutSecond <= 0) {
+            timeoutSecond = DEFAULT_TIMEOUT_SECONDS;
+        }
+        final long timeout = SystemClock.uptimeMillis() + timeoutSecond * 1000;
+        while (SystemClock.uptimeMillis() < timeout) {
+            if (predicate.getAsBoolean()) {
+                return; // okay
+            }
+            Thread.sleep(1000);
+        }
+        failWithLog("Timeout: " + message);
+    }
+
+    public void waitUntilAlarmForceAppStandby(boolean expected) throws Exception {
+        waitUntil("Force all apps standby still " + !expected + " (alarm)", () ->
+                runCommand("dumpsys alarm").contains("Force all apps standby: " + expected));
+    }
+
+    public void waitUntilJobForceAppStandby(boolean expected) throws Exception {
+        waitUntil("Force all apps standby still " + !expected + " (job)", () ->
+                runCommand("dumpsys jobscheduler").contains("Force all apps standby: " + expected));
+    }
+
+    public void waitUntilForceBackgroundCheck(boolean expected) throws Exception {
+        waitUntil("Force background check still " + !expected + " (job)", () ->
+                runCommand("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 boolean hasFeatures(String... features) {
+        for (String feature : features) {
+            if (!getPackageManager().hasSystemFeature(feature)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Enable / disable battery saver. Note {@link #runDumpsysBatteryUnplug} must have been
+     * executed before enabling BS.
+     */
+    public 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"));
+    }
+
+    public void turnOnScreen(boolean on) throws Exception {
+        if (on) {
+            runCommandWithNoOutput("input keyevent KEYCODE_WAKEUP");
+            waitUntil("Device still not interactive", () -> getPowerManager().isInteractive());
+
+        } else {
+            runCommandWithNoOutput("input keyevent KEYCODE_SLEEP");
+            waitUntil("Device still interactive", () -> !getPowerManager().isInteractive());
+        }
+    }
+}
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/carrierapi/Android.mk b/tests/tests/carrierapi/Android.mk
index 954ae14..8a64870 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)
 
@@ -35,6 +34,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..89c3916 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)
 
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 3b64fa1..3e8e783 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
 
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index b191a11..eb6fecb 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 -->
@@ -288,6 +289,16 @@
                 android:process=":providerProcess">
         </provider>
 
+        <activity android:name="com.android.cts.content.StubActivity"/>
+
+        <service android:name="com.android.cts.content.SyncService">
+            <intent-filter>
+                <action android:name="android.content.SyncAdapter"/>
+            </intent-filter>
+            <meta-data android:name="android.content.SyncAdapter"
+                android:resource="@xml/accountaccesssyncadapter" />
+        </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..09523ba
--- /dev/null
+++ b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/Android.mk
@@ -0,0 +1,42 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    android-support-test \
+    ctstestrunner \
+    ub-uiautomator \
+    compatibility-device-util \
+    accountaccesslib
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsSyncAccountAccessOtherCertTestCases
+
+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/hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
similarity index 100%
rename from hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
rename to tests/tests/content/CtsSyncAccountAccessOtherCertTests/AndroidManifest.xml
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..3c791fd
--- /dev/null
+++ b/tests/tests/content/CtsSyncAccountAccessOtherCertTests/src/com/android/cts/content/CtsSyncAccountAccessOtherCertTestCases.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.junit.Assume.assumeFalse;
+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.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+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.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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.regex.Pattern;
+
+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 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");
+    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 {
+        assumeTrue(hasDataConnection());
+        assumeTrue(hasNotificationSupport());
+
+        // 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());
+
+        Intent intent = new Intent(getContext(), StubActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        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 {
+            AbstractThreadedSyncAdapter adapter = SyncAdapter.setNewDelegate();
+
+            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);
+
+            verify(adapter, timeout(SYNC_TIMEOUT_MILLIS).times(0)).onPerformSync(any(), any(),
+                    any(), any(), any());
+
+            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());
+        } 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());
+    }
+}
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/hostsidetests/content/test-apps/SyncAccountAccessStubs/AndroidManifest.xml b/tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
similarity index 100%
rename from hostsidetests/content/test-apps/SyncAccountAccessStubs/AndroidManifest.xml
rename to tests/tests/content/SyncAccountAccessStubs/AndroidManifest.xml
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/hostsidetests/content/test-apps/SyncAccountAccessStubs/src/com/android/cts/stub/StubAuthenticator.java b/tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubAuthenticator.java
similarity index 100%
rename from hostsidetests/content/test-apps/SyncAccountAccessStubs/src/com/android/cts/stub/StubAuthenticator.java
rename to tests/tests/content/SyncAccountAccessStubs/src/com/android/cts/stub/StubAuthenticator.java
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/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..559dc90
--- /dev/null
+++ b/tests/tests/content/lib/accountaccess/Android.mk
@@ -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.
+
+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
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file
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..2608b97
--- /dev/null
+++ b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/FlakyTestRule.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
+ */
+
+package com.android.cts.content;
+
+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/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..19fb8ee
--- /dev/null
+++ b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/SyncAdapter.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 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 static final Object sLock = new Object();
+    private static AbstractThreadedSyncAdapter sDelegate;
+
+    public static AbstractThreadedSyncAdapter setNewDelegate() {
+        AbstractThreadedSyncAdapter delegate = mock(AbstractThreadedSyncAdapter.class);
+
+        synchronized (sLock) {
+            sDelegate = delegate;
+        }
+
+        return delegate;
+    }
+
+    public SyncAdapter(Context context, boolean autoInitialize) {
+        super(context, autoInitialize);
+    }
+
+    private AbstractThreadedSyncAdapter getCopyOfDelegate() {
+        synchronized (sLock) {
+            return sDelegate;
+        }
+    }
+
+    @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);
+        }
+    }
+}
diff --git a/hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncService.java b/tests/tests/content/lib/accountaccess/src/com.android.cts.content/SyncService.java
similarity index 100%
rename from hostsidetests/content/test-apps/CtsSyncAccountAccessSameCertTests/src/com/android/cts/content/SyncService.java
rename to tests/tests/content/lib/accountaccess/src/com.android.cts.content/SyncService.java
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/accountaccesssyncadapter.xml
similarity index 100%
copy from hostsidetests/content/test-apps/CtsSyncAccountAccessOtherCertTests/res/xml/syncadapter.xml
copy to tests/tests/content/res/xml/accountaccesssyncadapter.xml
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..db27352
--- /dev/null
+++ b/tests/tests/content/src/android/content/cts/AccountAccessSameCertTest.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.cts;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+
+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.accounts.Account;
+import android.accounts.AccountManager;
+import android.app.Activity;
+import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SyncRequest;
+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 com.android.compatibility.common.util.SystemUtil;
+import com.android.cts.content.FlakyTestRule;
+import com.android.cts.content.StubActivity;
+import com.android.cts.content.SyncAdapter;
+
+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;
+
+/**
+ * Tests whether a sync adapter can access accounts.
+ */
+@RunWith(AndroidJUnit4.class)
+public class AccountAccessSameCertTest {
+    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 {
+        assumeTrue(hasDataConnection());
+        assumeTrue(hasNotificationSupport());
+
+        Intent intent = new Intent(getContext(), StubActivity.class);
+        intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
+        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 {
+            AbstractThreadedSyncAdapter adapter = SyncAdapter.setNewDelegate();
+
+            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);
+
+            verify(adapter, timeout(SYNC_TIMEOUT_MILLIS)).onPerformSync(any(), any(), any(), any(),
+                    any());
+        } 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/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index dbebbc0..1ba9753 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -181,6 +181,17 @@
     }
 
     /**
+     * Test ACTION_SHOW_ASSISTED_DIALING_SETTINGS, it will display the assisted dialing preferences.
+     */
+    public void testShowAssistedDialingSettings() {
+        PackageManager packageManager = mContext.getPackageManager();
+        if (packageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Intent intent = new Intent(TelecomManager.ACTION_SHOW_ASSISTED_DIALING_SETTINGS);
+            assertCanBeHandled(intent);
+        }
+    }
+
+    /**
      * Test ACTION_SHOW_CALL_SETTINGS, it will display the call preferences.
      */
     public void testShowCallSettings() {
@@ -353,4 +364,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/ContextTest.java b/tests/tests/content/src/android/content/cts/ContextTest.java
index 0d9c56d..4d608ab 100644
--- a/tests/tests/content/src/android/content/cts/ContextTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextTest.java
@@ -25,6 +25,8 @@
 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;
@@ -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());
diff --git a/tests/tests/content/src/android/content/cts/ContextWrapperTest.java b/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
index 5f48260..871107c 100644
--- a/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextWrapperTest.java
@@ -347,11 +347,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 +360,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));
 
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/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/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/ResourcesTest.java b/tests/tests/content/src/android/content/res/cts/ResourcesTest.java
index 7f5d6ce..6c02845 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;
@@ -324,6 +325,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 +798,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..b0dc266 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;
@@ -123,6 +141,7 @@
         assertEquals(TEST_STRING, cursorWindow.getString(TEST_NUMBER, 0));
     }
 
+    @Test
     public void testDataStructureOperations() {
         CursorWindow cursorWindow = new CursorWindow(true);
 
@@ -175,6 +194,7 @@
         }
     }
 
+    @Test
     public void testAccessDataValues() {
         final long NUMBER_LONG_INTEGER = (long) 0xaabbccddffL;
         final long NUMBER_INTEGER = (int) NUMBER_LONG_INTEGER;
@@ -214,8 +234,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 +246,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 +259,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 +279,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 +299,7 @@
         assertTrue(cursorWindow.isBlob(0, 4));
     }
 
+    @Test
     public void testCopyStringToBuffer() {
         int DEFAULT_ARRAY_LENGTH = 64;
         String baseString = "0123456789";
@@ -314,6 +335,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 +365,7 @@
         }
     }
 
+    @Test
     public void testClearAndOnAllReferencesReleased() {
         MockCursorWindow cursorWindow = new MockCursorWindow(true);
 
@@ -369,11 +392,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/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..56a815b 100644
--- a/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
+++ b/tests/tests/database/src/android/database/sqlite/cts/SQLiteDatabaseTest.java
@@ -33,6 +33,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 +1391,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 +1595,64 @@
         } catch (IllegalArgumentException expected) {
         }
     }
+
+    public void testDefaultJournalModeNotWAL() {
+        String defaultJournalMode = SQLiteGlobal.getDefaultJournalMode();
+        assertFalse("Default journal mode should not be WAL",
+                DatabaseUtils.stringForQuery(mDatabase, "PRAGMA journal_mode", null)
+                        .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);
+    }
+
 }
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..20c9a0d 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,73 @@
                 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();
+    }
+
+    /**
+     * 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 +298,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..a62d0ee 100644
--- a/tests/tests/display/Android.mk
+++ b/tests/tests/display/Android.mk
@@ -27,7 +27,9 @@
 
 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
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/dpi/Android.mk b/tests/tests/dpi/Android.mk
index 8bb7d64..d6380c5 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
 
+LOCAK_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 383c9c3..11e4b21 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/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/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/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/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/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/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/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..70c3202 100644
--- a/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/CanvasTest.java
@@ -1428,6 +1428,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)
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..3c94940
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/ImageDecoderTest.java
@@ -0,0 +1,1577 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.BitmapDrawable;
+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 com.android.compatibility.common.util.BitmapUtils;
+
+import java.io.ByteArrayOutputStream;
+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.lang.ArrayIndexOutOfBoundsException;
+import java.lang.NullPointerException;
+import java.lang.RuntimeException;
+import java.nio.ByteBuffer;
+import java.util.function.IntFunction;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@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 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 testPreferRamOverQualityPlusHardware() {
+        class Listener implements ImageDecoder.OnHeaderDecodedListener {
+            int allocator;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                decoder.setPreferRamOverQuality(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 testPreferRamOverQuality() {
+        class Listener implements ImageDecoder.OnHeaderDecodedListener {
+            boolean doPostProcess;
+            boolean preferRamOverQuality;
+            @Override
+            public void onHeaderDecoded(ImageDecoder decoder, ImageDecoder.ImageInfo info,
+                                        ImageDecoder.Source src) {
+                if (preferRamOverQuality) {
+                    decoder.setPreferRamOverQuality(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
+        // setPreferRamOverQuality.
+        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(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!");
+    }
+
+    @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);
+
+            SourceCreator[] creators = new SourceCreator[] {
+                // Internally, this gives the buffer a position, but not an offset.
+                unused -> ImageDecoder.createSource(ByteBuffer.wrap(array, offset, length)),
+                unused -> {
+                    // Same, but make it readOnly to ensure that we test the
+                    // ByteBufferSource rather than the ByteArraySource.
+                    ByteBuffer buf = ByteBuffer.wrap(array, offset, length);
+                    return ImageDecoder.createSource(buf.asReadOnlyBuffer());
+                },
+                unused -> {
+                    // slice() to give the buffer an offset.
+                    ByteBuffer buf = ByteBuffer.wrap(array, 0, array.length - extra);
+                    buf.position(offset);
+                    return ImageDecoder.createSource(buf.slice());
+                },
+                unused -> {
+                    // 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 ImageDecoder.createSource(buf.slice().asReadOnlyBuffer());
+                },
+                unused -> {
+                    // Use both a position and an offset.
+                    ByteBuffer buf = ByteBuffer.wrap(array, myOffset,
+                            array.length - extra - myOffset);
+                    buf = buf.slice();
+                    buf.position(myPosition);
+                    return ImageDecoder.createSource(buf);
+                },
+                unused -> {
+                    // Same, as readOnly.
+                    ByteBuffer buf = ByteBuffer.wrap(array, myOffset,
+                            array.length - extra - myOffset);
+                    buf = buf.slice();
+                    buf.position(myPosition);
+                    return ImageDecoder.createSource(buf.asReadOnlyBuffer());
+                },
+                unused -> {
+                    // Direct ByteBuffer with a position.
+                    ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
+                    buf.put(array);
+                    buf.position(offset);
+                    return ImageDecoder.createSource(buf);
+                },
+                unused -> {
+                    // Sliced direct ByteBuffer, for an offset.
+                    ByteBuffer buf = ByteBuffer.allocateDirect(array.length);
+                    buf.put(array);
+                    buf.position(offset);
+                    return ImageDecoder.createSource(buf.slice());
+                },
+                unused -> {
+                    // 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 ImageDecoder.createSource(buf);
+                },
+            };
+            for (SourceCreator f : creators) {
+                ImageDecoder.Source src = f.apply(0);
+                try {
+                    Drawable drawable = ImageDecoder.decodeDrawable(src, (decoder, info, s) -> {
+                        decoder.setOnPartialImageListener((error, source) -> false);
+                    });
+                    assertNotNull(drawable);
+                } catch (IOException e) {
+                    fail("Failed with exception " + e);
+                }
+            }
+        }
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java b/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
index 04d5564..32cdd38 100644
--- a/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/TypefaceTest.java
@@ -52,6 +52,19 @@
     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;
+
+    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);
+        final Paint paint = new Paint();
+        paint.setTypeface(typeface);
+        GLYPH_3EM_WIDTH = paint.measureText("a");
+        GLYPH_1EM_WIDTH = paint.measureText("b");
+    }
+
     // 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" };
@@ -531,4 +544,68 @@
 
         assertTrue(Math.abs(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);
+
+        final Paint paint = new Paint();
+        // By default, the font which weight is 400 is selected.
+        paint.setTypeface(family);
+        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0f);
+        assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0f);
+
+        // Draw with the font which weight is 100.
+        paint.setTypeface(Typeface.create(family, 100 /* weight */, false /* italic */));
+        assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0f);
+
+        // Draw with the font which weight is 700.
+        paint.setTypeface(Typeface.create(family, 700 /* weight */, false /* italic */));
+        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0f);
+        assertEquals(GLYPH_3EM_WIDTH, paint.measureText("c"), 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);
+
+        final Paint paint = new Paint();
+        // By default, the normal style font which weight is 400 is selected.
+        paint.setTypeface(family);
+        assertEquals(GLYPH_3EM_WIDTH, paint.measureText("a"), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0f);
+
+        // Draw with the italic font.
+        paint.setTypeface(Typeface.create(family, 400 /* weight */, true /* italic */));
+        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0f);
+        assertEquals(GLYPH_3EM_WIDTH, paint.measureText("b"), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("c"), 0f);
+
+        // Draw with the italic font which weigth is 700.
+        paint.setTypeface(Typeface.create(family, 700 /* weight */, true /* italic */));
+        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("a"), 0f);
+        assertEquals(GLYPH_1EM_WIDTH, paint.measureText("b"), 0f);
+        assertEquals(GLYPH_3EM_WIDTH, paint.measureText("c"), 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..e94d7d4 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanFeaturesTest.java
@@ -221,8 +221,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) {
diff --git a/tests/tests/hardware/Android.mk b/tests/tests/hardware/Android.mk
index 3adf811..3b6a0d1 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 1309c99..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
-
-# 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
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
-        ctstestrunner \
-	legacy-android-test
-
-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 ea24954..51c5d41 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)
 
@@ -48,7 +48,8 @@
 #
 # Uncomment when b/13282254 is fixed.
 # LOCAL_SDK_VERSION := current
-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 cfd9775..9d4345b 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..6e90050 100644
--- a/tests/tests/location/Android.mk
+++ b/tests/tests/location/Android.mk
@@ -26,6 +26,8 @@
 
 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
 
@@ -50,6 +52,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
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/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/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/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/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 5b9f327..326923f 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 e03eb76..0aab327 100644
--- a/tests/tests/media/Android.mk
+++ b/tests/tests/media/Android.mk
@@ -51,7 +51,6 @@
     ctstestrunner \
     ctstestserver \
     junit \
-    legacy-android-test \
     ndkaudio \
     testng
 
@@ -71,10 +70,13 @@
 
 LOCAL_PACKAGE_NAME := CtsMediaTestCases
 
-# uncomment when b/13249737 is fixed
+# This test uses private APIs
 #LOCAL_SDK_VERSION := current
 
-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/libmediandkjni/native-mediadrm-jni.cpp b/tests/tests/media/libmediandkjni/native-mediadrm-jni.cpp
index cff5c18..d1e63ec 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,
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/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index ab7e4b6..775fb35 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -38,8 +38,12 @@
 
 import android.app.ActivityManager;
 import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
 import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.res.Resources;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.content.pm.PackageManager;
 import android.media.AudioSystem;
@@ -144,6 +148,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 +247,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;
@@ -967,6 +1061,93 @@
         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);
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/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index 8fd1b7a..25e35b2 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -20,7 +20,6 @@
 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;
@@ -31,17 +30,33 @@
 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.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;
 
-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 +76,8 @@
         }
     };
 
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-
+    @Before
+    public void setUp() throws Exception {
         if (!hasMicrophone()) {
             return;
         }
@@ -100,13 +113,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 +127,7 @@
         mIsHandleMessageCalled = false;
     }
 
+    @Test
     public void testAudioRecordProperties() throws Exception {
         if (!hasMicrophone()) {
             return;
@@ -133,6 +146,7 @@
         assertTrue(bufferSize > 0);
     }
 
+    @Test
     public void testAudioRecordOP() throws Exception {
         if (!hasMicrophone()) {
             return;
@@ -235,6 +249,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 +258,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 +267,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 +286,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 +295,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 +304,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 +314,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 +327,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 +338,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 +353,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 +365,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 +395,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 +427,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 +469,7 @@
     }
 
     // Test AudioRecord to ensure we can build after a failure.
+    @Test
     public void testAudioRecordBufferSize() throws Exception {
         if (!hasMicrophone()) {
             return;
@@ -467,6 +505,7 @@
         assertTrue(TEST_NAME + ": buffer frame count", observedBufferSize2 > 0);
     }
 
+    @Test
     public void testTimestamp() throws Exception {
         if (!hasMicrophone()) {
             return;
@@ -562,6 +601,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;
@@ -743,11 +845,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 +1260,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 +1379,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
index d43dce1..5afb71d 100644
--- a/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
+++ b/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
@@ -41,6 +41,7 @@
 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;
@@ -79,6 +80,18 @@
     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(
@@ -100,7 +113,7 @@
     private Looper mLooper;
     private MediaCodecClearKeyPlayer mMediaCodecPlayer;
     private MediaDrm mDrm;
-    private Object mLock = new Object();
+    private final Object mLock = new Object();
     private SurfaceHolder mSurfaceHolder;
 
     @Override
@@ -348,7 +361,7 @@
             drm = startDrm(clearKeys, initDataType, drmSchemeUuid);
             if (!drm.isCryptoSchemeSupported(drmSchemeUuid)) {
                 stopDrm(drm);
-                throw new Error("Crypto scheme is not supported.");
+                throw new Error(ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED);
             }
             mSessionId = openSession(drm);
         }
@@ -435,7 +448,7 @@
         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.");
+            throw new Error(ERR_MSG_CRYPTO_SCHEME_NOT_SUPPORTED);
         }
 
         mSessionId = openSession(drm);
@@ -524,4 +537,156 @@
             MPEG2TS_CLEAR_URL, false,
             VIDEO_WIDTH_MPEG2TS, VIDEO_HEIGHT_MPEG2TS, false);
     }
+
+    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);
+        }
+    }
+
 }
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/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/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
index 45b2b8b..d29cfed 100644
--- a/tests/tests/media/src/android/media/cts/NativeClearKeySystemTest.java
+++ b/tests/tests/media/src/android/media/cts/NativeClearKeySystemTest.java
@@ -157,6 +157,7 @@
         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"));
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..ddb534a 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(
@@ -455,4 +487,260 @@
         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();
+    }
+
+    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..2f979b8
--- /dev/null
+++ b/tests/tests/nativemedia/aaudio/jni/test_session_id.cpp
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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);
+    ASSERT_LT(0, sessionId1); // Must be positive.
+
+    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 4aeab38..e348406 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/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..754e4f6 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,7 +603,7 @@
 
     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;
         }
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 1aa46cc..274bc06 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++
 
@@ -49,7 +49,8 @@
 
 # uncomment when b/13282254 is fixed
 #LOCAL_SDK_VERSION := current
-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..6cf3ded 100644
--- a/tests/tests/os/src/android/os/cts/StrictModeTest.java
+++ b/tests/tests/os/src/android/os/cts/StrictModeTest.java
@@ -16,340 +16,473 @@
 
 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 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/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..676465e 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.
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 b24774f..171283e 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++
 
@@ -43,7 +43,8 @@
 
 # uncomment when b/13249777 is fixed
 #LOCAL_SDK_VERSION := current
-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..99aab61 100644
--- a/tests/tests/permission/src/android/permission/cts/AppOpsTest.java
+++ b/tests/tests/permission/src/android/permission/cts/AppOpsTest.java
@@ -16,59 +16,220 @@
 
 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 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 android.Manifest.permission;
 import android.app.AppOpsManager;
 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 testCheckPackagePassesTest() throws Exception {
+        mAppOps.checkPackage(mMyUid, mOpPackageName);
+        mAppOps.checkPackage(Process.SYSTEM_UID, "android");
+    }
+
+    public void testCheckPackageDoesntPassTest() 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) {
+        }
+    }
+
+    @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 {
+        assertFalse(allowedOperationLogged(mOpPackageName, OPSTR_READ_SMS));
+        assertFalse(allowedOperationLogged(mOpPackageName, OPSTR_READ_CALENDAR));
+
+        setOpMode(mOpPackageName, OPSTR_READ_SMS, MODE_ALLOWED);
+        setOpMode(mOpPackageName, OPSTR_READ_CALENDAR, MODE_ERRORED);
+
+        // Note an op that's allowed.
+        mAppOps.noteOp(OPSTR_READ_SMS, mMyUid, mOpPackageName);
+        assertTrue(allowedOperationLogged(mOpPackageName, OPSTR_READ_SMS));
+
+        // Note another op that's not allowed.
+        mAppOps.noteOpNoThrow(OPSTR_READ_CALENDAR, mMyUid, mOpPackageName);
+        assertTrue(allowedOperationLogged(mOpPackageName, OPSTR_READ_SMS));
+        assertTrue(rejectedOperationLogged(mOpPackageName, OPSTR_READ_CALENDAR));
+    }
 }
diff --git a/tests/tests/permission2/Android.mk b/tests/tests/permission2/Android.mk
index 062aeb7..d1190a3 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 := telephony-common 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 a4dad83..f05ae9d 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -183,15 +183,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 +307,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 +319,16 @@
     <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.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="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 +407,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 +486,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 +504,8 @@
     <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.permission.GET_APP_GRANTED_URI_PERMISSIONS" />
     <protected-broadcast android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS" />
@@ -547,6 +564,9 @@
     <protected-broadcast android:name="android.media.tv.action.CHANNEL_BROWSABLE_REQUESTED" />
     <protected-broadcast android:name="com.android.server.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER" />
 
+    <!-- Made protected in P (was introduced in JB-MR2) -->
+    <protected-broadcast android:name="android.intent.action.GET_RESTRICTION_ENTRIES" />
+
     <!-- ====================================================================== -->
     <!--                          RUNTIME PERMISSIONS                           -->
     <!-- ====================================================================== -->
@@ -563,6 +583,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 +614,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 +645,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 +722,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 +784,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 +817,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,
@@ -943,6 +969,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 +1012,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 +1037,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
@@ -1591,6 +1620,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"
@@ -1712,7 +1746,7 @@
          @hide
     -->
     <permission android:name="android.permission.BIND_IMS_SERVICE"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged|vendorPrivileged" />
 
     <!-- Allows an application to manage embedded subscriptions (those on a eUICC) through
          EuiccManager APIs.
@@ -1763,6 +1797,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         -->
     <!-- ================================== -->
@@ -1860,11 +1903,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.
@@ -2030,6 +2073,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"
@@ -2270,7 +2318,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" />
 
@@ -2390,7 +2438,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" />
 
@@ -2415,10 +2464,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" />
@@ -2625,10 +2674,6 @@
     <permission android:name="android.permission.BIND_AUTOFILL_SERVICE"
         android:protectionLevel="signature" />
 
-    <!-- @hide TODO(b/37563972): remove once clients use BIND_AUTOFILL_SERVICE -->
-    <permission android:name="android.permission.BIND_AUTOFILL"
-        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> -->
@@ -2754,8 +2799,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"
@@ -2766,6 +2812,14 @@
     <!-- @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 clear user data.
@@ -2790,6 +2844,16 @@
         android:name="android.permission.CLEAR_APP_GRANTED_URI_PERMISSIONS"
         android:protectionLevel="signature" />
 
+    <!-- @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 Allows an application to delete cache files.
     <p>Not for use by third-party applications. -->
     <permission android:name="android.permission.DELETE_CACHE_FILES"
@@ -2877,6 +2941,19 @@
     <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 modify the display brightness configuration
+         @hide
+         @SystemApi -->
+    <permission android:name="android.permission.CONFIGURE_DISPLAY_BRIGHTNESS"
+        android:protectionLevel="signature|privileged|development" />
+
     <!-- @SystemApi Allows an application to control VPN.
          <p>Not for use by third-party applications.</p>
          @hide -->
@@ -3032,10 +3109,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.
@@ -3056,12 +3133,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 -->
@@ -3082,6 +3171,13 @@
     <permission android:name="android.permission.BIND_APPWIDGET"
         android:protectionLevel="signature|privileged" />
 
+    <!-- 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 -->
@@ -3126,6 +3222,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. -->
@@ -3199,6 +3300,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.
     -->
@@ -3285,6 +3390,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"
@@ -3430,6 +3541,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 -->
@@ -3458,11 +3570,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.
@@ -3548,6 +3668,20 @@
     <permission android:name="android.permission.INSTANT_APP_FOREGROUND_SERVICE"
         android:protectionLevel="signature|development|instant|appop" />
 
+    <!-- @hide Allows system components to access all app shortcuts. -->
+    <permission android:name="android.permission.ACCESS_SHORTCUTS"
+        android:protectionLevel="signature" />
+
+    <!-- @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" />
+
+    <!-- @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" />
+
     <application android:process="system"
                  android:persistent="true"
                  android:hasCode="false"
@@ -3605,7 +3739,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>
@@ -3772,6 +3906,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>
@@ -3788,14 +3930,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>
@@ -3836,6 +3970,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" />
@@ -3887,6 +4031,17 @@
                  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>
+</application>
 
 </manifest>
diff --git a/tests/tests/permission2/res/raw/automotive_android_manifest.xml b/tests/tests/permission2/res/raw/automotive_android_manifest.xml
new file mode 100644
index 0000000..4a68183
--- /dev/null
+++ b/tests/tests/permission2/res/raw/automotive_android_manifest.xml
@@ -0,0 +1,203 @@
+<?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="com.android.car"
+        coreApp="true"
+        android:sharedUserId="android.uid.system">
+
+    <original-package android:name="com.android.car" />
+
+    <permission-group
+        android:name="android.car.permission-group.CAR_INFORMATION"
+        android:icon="@drawable/car_ic_mode"
+        android:description="@string/car_permission_desc"
+        android:label="@string/car_permission_label" />
+    <permission
+        android:name="android.car.permission.CAR_CABIN"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_cabin"
+        android:description="@string/car_permission_desc_cabin" />
+    <permission
+        android:name="android.car.permission.CAR_CAMERA"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_camera"
+        android:description="@string/car_permission_desc_camera" />
+    <permission
+        android:name="android.car.permission.CAR_FUEL"
+        android:permissionGroup="android.car.permission-group.CAR_INFORMATION"
+        android:protectionLevel="dangerous"
+        android:label="@string/car_permission_label_fuel"
+        android:description="@string/car_permission_desc_fuel" />
+    <permission
+        android:name="android.car.permission.CAR_HVAC"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_hvac"
+        android:description="@string/car_permission_desc_hvac" />
+    <permission
+        android:name="android.car.permission.CAR_MILEAGE"
+        android:permissionGroup="android.car.permission-group.CAR_INFORMATION"
+        android:protectionLevel="dangerous"
+        android:label="@string/car_permission_label_mileage"
+        android:description="@string/car_permission_desc_mileage" />
+    <permission
+        android:name="android.car.permission.CAR_SPEED"
+        android:permissionGroup="android.permission-group.LOCATION"
+        android:protectionLevel="dangerous"
+        android:label="@string/car_permission_label_speed"
+        android:description="@string/car_permission_desc_speed" />
+    <permission
+        android:name="android.car.permission.VEHICLE_DYNAMICS_STATE"
+        android:permissionGroup="android.car.permission-group.CAR_INFORMATION"
+        android:protectionLevel="dangerous"
+        android:label="@string/car_permission_label_vehicle_dynamics_state"
+        android:description="@string/car_permission_desc_vehicle_dynamics_state" />
+    <permission
+        android:name="android.car.permission.CAR_VENDOR_EXTENSION"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_vendor_extension"
+        android:description="@string/car_permission_desc_vendor_extension" />
+    <permission
+        android:name="android.car.permission.CAR_RADIO"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_radio"
+        android:description="@string/car_permission_desc_radio" />
+    <permission
+        android:name="android.car.permission.CAR_PROJECTION"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_projection"
+        android:description="@string/car_permission_desc_projection" />
+    <permission
+        android:name="android.car.permission.CAR_MOCK_VEHICLE_HAL"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_mock_vehicle_hal"
+        android:description="@string/car_permission_desc_mock_vehicle_hal" />
+    <permission
+        android:name="android.car.permission.CAR_NAVIGATION_MANAGER"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_car_navigation_manager"
+        android:description="@string/car_permission_desc_car_navigation_manager" />
+    <permission
+      android:name="android.car.permission.DIAGNOSTIC_READ_ALL"
+      android:protectionLevel="system|signature"
+      android:label="@string/car_permission_label_diag_read"
+      android:description="@string/car_permission_desc_diag_read" />
+    <permission
+      android:name="android.car.permission.DIAGNOSTIC_CLEAR"
+      android:protectionLevel="system|signature"
+      android:label="@string/car_permission_label_diag_clear"
+      android:description="@string/car_permission_desc_diag_clear" />
+    <permission
+        android:name="android.car.permission.VMS_PUBLISHER"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_vms_publisher"
+        android:description="@string/car_permission_desc_vms_publisher" />
+    <permission
+        android:name="android.car.permission.VMS_SUBSCRIBER"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_vms_subscriber"
+        android:description="@string/car_permission_desc_vms_subscriber" />
+
+    <!--  may replace this with system permission if proper one is defined. -->
+    <permission
+        android:name="android.car.permission.CONTROL_APP_BLOCKING"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_control_app_blocking"
+        android:description="@string/car_permission_desc_control_app_blocking" />
+
+    <permission
+        android:name="android.car.permission.CAR_CONTROL_AUDIO_VOLUME"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_audio_volume"
+        android:description="@string/car_permission_desc_audio_volume" />
+
+    <permission
+        android:name="android.car.permission.CAR_CONTROL_AUDIO_SETTINGS"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_audio_settings"
+        android:description="@string/car_permission_desc_audio_settings" />
+
+    <permission
+            android:name="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"
+            android:protectionLevel="signature"
+            android:label="@string/car_permission_label_bind_instrument_cluster_rendering"
+            android:description="@string/car_permission_desc_bind_instrument_cluster_rendering"/>
+
+    <permission
+            android:name="android.car.permission.BIND_CAR_INPUT_SERVICE"
+            android:protectionLevel="signature"
+            android:label="@string/car_permission_label_bind_input_service"
+            android:description="@string/car_permission_desc_bind_input_service"/>
+
+    <permission
+            android:name="android.car.permission.CAR_DISPLAY_IN_CLUSTER"
+            android:protectionLevel="system|signature"
+            android:label="@string/car_permission_car_display_in_cluster"
+            android:description="@string/car_permission_desc_car_display_in_cluster" />
+
+    <permission android:name="android.car.permission.CAR_INSTRUMENT_CLUSTER_CONTROL"
+                android:protectionLevel="system|signature"
+                android:label="@string/car_permission_car_cluster_control"
+                android:description="@string/car_permission_desc_car_cluster_control" />
+
+    <permission android:name="android.car.permission.STORAGE_MONITORING"
+        android:protectionLevel="system|signature"
+        android:label="@string/car_permission_label_storage_monitoring"
+        android:description="@string/car_permission_desc_storage_monitoring" />
+
+    <uses-permission android:name="android.permission.CALL_PHONE" />
+    <uses-permission android:name="android.permission.DEVICE_POWER" />
+    <uses-permission android:name="android.permission.GRANT_RUNTIME_PERMISSIONS" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+    <uses-permission android:name="android.permission.MANAGE_ACTIVITY_STACKS" />
+    <uses-permission android:name="android.permission.MODIFY_AUDIO_ROUTING" />
+    <uses-permission android:name="android.permission.MODIFY_DAY_NIGHT_MODE" />
+    <uses-permission android:name="android.permission.MODIFY_PHONE_STATE" />
+    <uses-permission android:name="android.permission.READ_CALL_LOG" />
+    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.REAL_GET_TASKS" />
+    <uses-permission android:name="android.permission.REBOOT" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <uses-permission android:name="android.permission.REMOVE_TASKS" />
+    <uses-permission android:name="android.permission.WRITE_SETTINGS" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <uses-permission android:name="android.permission.BLUETOOTH" />
+    <uses-permission android:name="android.permission.MANAGE_USERS" />
+
+    <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"
+                android:singleUser="true">
+            <intent-filter>
+                <action android:name="android.car.ICar" />
+            </intent-filter>
+        </service>
+        <service android:name=".PerUserCarService" android:exported="false" />
+        <activity android:name="com.android.car.pm.ActivityBlockingActivity"
+                  android:excludeFromRecents="true"
+                  android:exported="false">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index 35a99da..68e29a6 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -16,8 +16,12 @@
 
 package android.permission2.cts;
 
+import static android.os.Build.VERSION.SECURITY_PATCH;
+
+import android.content.Context;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
 import android.test.AndroidTestCase;
@@ -26,10 +30,10 @@
 import android.util.ArraySet;
 import android.util.Log;
 import android.util.Xml;
+
 import org.xmlpull.v1.XmlPullParser;
 
 import java.io.InputStream;
-import java.lang.String;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
@@ -38,8 +42,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import static android.os.Build.VERSION.SECURITY_PATCH;
-
 /**
  * Tests for permission policy on the platform.
  */
@@ -54,6 +56,8 @@
 
     private static final String PLATFORM_ROOT_NAMESPACE = "android.";
 
+    private static final String AUTOMOTIVE_SERVICE_PACKAGE_NAME = "com.android.car";
+
     private static final String TAG_PERMISSION = "permission";
 
     private static final String ATTR_NAME = "name";
@@ -61,14 +65,10 @@
     private static final String ATTR_PROTECTION_LEVEL = "protectionLevel";
 
     public void testPlatformPermissionPolicyUnaltered() throws Exception {
-        PackageInfo platformPackage = getContext().getPackageManager()
-                .getPackageInfo(PLATFORM_PACKAGE_NAME, PackageManager.GET_PERMISSIONS);
-        Map<String, PermissionInfo> declaredPermissionsMap = new ArrayMap<>();
-        List<String> offendingList = new ArrayList<String>();
+        Map<String, PermissionInfo> declaredPermissionsMap =
+                getPermissionsForPackage(getContext(), PLATFORM_PACKAGE_NAME);
 
-        for (PermissionInfo declaredPermission : platformPackage.permissions) {
-            declaredPermissionsMap.put(declaredPermission.name, declaredPermission);
-        }
+        List<String> offendingList = new ArrayList<>();
 
         List<PermissionGroupInfo> declaredGroups = getContext().getPackageManager()
                 .getAllPermissionGroups(0);
@@ -77,9 +77,16 @@
             declaredGroupsSet.add(declaredGroup.name);
         }
 
-        Set<String> expectedPermissionGroups = new ArraySet<String>();
+        Set<String> expectedPermissionGroups = new ArraySet<>();
+        List<PermissionInfo> expectedPermissions = loadExpectedPermissions(R.raw.android_manifest);
 
-        for (PermissionInfo expectedPermission : loadExpectedPermissions()) {
+        if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            expectedPermissions.addAll(loadExpectedPermissions(R.raw.automotive_android_manifest));
+            declaredPermissionsMap.putAll(
+                    getPermissionsForPackage(getContext(), AUTOMOTIVE_SERVICE_PACKAGE_NAME));
+        }
+
+        for (PermissionInfo expectedPermission : expectedPermissions) {
             String expectedPermissionName = expectedPermission.name;
             if (shouldSkipPermission(expectedPermissionName)) {
                 continue;
@@ -132,7 +139,7 @@
 
                 if (!declaredGroupsSet.contains(declaredPermission.group)) {
                     offendingList.add(
-                            "Permission group " + expectedPermission.group + "must be defined");
+                            "Permission group " + expectedPermission.group + " must be defined");
                 }
             }
         }
@@ -140,7 +147,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");
             }
         }
 
@@ -151,11 +162,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");
                     }
                 }
             }
@@ -177,12 +186,9 @@
         assertTrue(errMsg, offendingList.isEmpty());
     }
 
-    private List<PermissionInfo> loadExpectedPermissions() throws Exception {
+    private List<PermissionInfo> loadExpectedPermissions(int resourceId) throws Exception {
         List<PermissionInfo> permissions = new ArrayList<>();
-        try (
-                InputStream in = getContext().getResources()
-                        .openRawResource(android.permission2.cts.R.raw.android_manifest)
-        ) {
+        try (InputStream in = getContext().getResources().openRawResource(resourceId)) {
             XmlPullParser parser = Xml.newPullParser();
             parser.setInput(in, null);
 
@@ -250,6 +256,9 @@
                 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;
@@ -264,6 +273,18 @@
         return protectionLevel;
     }
 
+    private static Map<String, PermissionInfo> getPermissionsForPackage(Context context, String pkg)
+            throws NameNotFoundException {
+        PackageInfo packageInfo = context.getPackageManager()
+                .getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
+        Map<String, PermissionInfo> declaredPermissionsMap = new ArrayMap<>();
+
+        for (PermissionInfo declaredPermission : packageInfo.permissions) {
+            declaredPermissionsMap.put(declaredPermission.name, declaredPermission);
+        }
+        return declaredPermissionsMap;
+    }
+
     private static Date parseDate(String date) {
         Date patchDate = new Date();
         try {
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/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..e8eb8cd 100644
--- a/tests/tests/print/AndroidTest.xml
+++ b/tests/tests/print/AndroidTest.xml
@@ -14,6 +14,7 @@
      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" />
 
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 f4fcea2..37faee6 100644
--- a/tests/tests/proto/Android.mk
+++ b/tests/tests/proto/Android.mk
@@ -33,7 +33,7 @@
 LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
 
 #LOCAL_SDK_VERSION := current
-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 d4b3e99..648c985 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 b22f6c7..f9763e1 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 fe4f779..4fefdab 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..68ec092 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",
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
new file mode 100644
index 0000000..d1b263f
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/OutputConfigurationTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.graphics.SurfaceTexture;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.os.Parcel;
+import android.platform.test.annotations.SecurityTest;
+import android.test.AndroidTestCase;
+import android.util.Size;
+import android.view.Surface;
+import android.view.TextureView;
+
+/**
+ * Verify that OutputConfiguration's fields propagate through parcel properly.
+ */
+@SecurityTest
+public class OutputConfigurationTest extends AndroidTestCase {
+    public void testSharedSurfaceOutputConfigurationBasic() throws Exception {
+        SurfaceTexture outputTexture = new SurfaceTexture(/* random texture ID */ 5);
+        Surface surface = new Surface(outputTexture);
+
+        //Test OutputConfiguration with a surface.
+        OutputConfiguration outputConfig = new OutputConfiguration(surface);
+        outputConfig.enableSurfaceSharing();
+        Parcel p;
+        p = Parcel.obtain();
+        outputConfig.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        OutputConfiguration parcelledOutput = OutputConfiguration.CREATOR.createFromParcel(p);
+        assertEquals("Number of surfaces should be 1",
+                parcelledOutput.getSurfaces().size(),
+                1);
+        assertEquals("Number of surfaces shouldn't change",
+                parcelledOutput.getSurfaces().size(),
+                outputConfig.getSurfaces().size());
+        assertEquals("surfaceGroupId shouldn't change",
+                parcelledOutput.getSurfaceGroupId(),
+                outputConfig.getSurfaceGroupId());
+        // addSurface shouldn't throw exception because surface sharing is enabled.
+        SurfaceTexture outputTexture2 = new SurfaceTexture(/* random texture ID */ 6);
+        Surface surface2 = new Surface(outputTexture2);
+        parcelledOutput.addSurface(surface2);
+        p.recycle();
+
+        //Test OutputConfiguration with surface group id.
+        outputConfig = new OutputConfiguration(
+                /* random surface groupd id */1, surface);
+        p = Parcel.obtain();
+        outputConfig.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        parcelledOutput = OutputConfiguration.CREATOR.createFromParcel(p);
+        assertEquals("Number of surfaces should be 1",
+                parcelledOutput.getSurfaces().size(),
+                1);
+        assertEquals("Number of surfaces shouldn't change",
+                parcelledOutput.getSurfaces().size(),
+                outputConfig.getSurfaces().size());
+        assertEquals("surfaceGroupdId shouldn't change",
+                parcelledOutput.getSurfaceGroupId(),
+                outputConfig.getSurfaceGroupId());
+        try {
+            parcelledOutput.addSurface(surface);
+            fail("should get IllegalStateException due to OutputConfiguration not shared");
+        } catch (IllegalStateException e) {
+            // expected exception
+        }
+        p.recycle();
+
+        //Test OutputConfiguration with deferred surface.
+        Size surfaceSize = new Size(10, 10);
+        outputConfig = new OutputConfiguration(
+                surfaceSize, SurfaceTexture.class);
+        p = Parcel.obtain();
+        outputConfig.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        parcelledOutput = OutputConfiguration.CREATOR.createFromParcel(p);
+        assertEquals("Number of surfaces should be 0",
+                parcelledOutput.getSurfaces().size(), 0);
+        assertEquals("Number of surfaces shouldn't change",
+                parcelledOutput.getSurfaces().size(),
+                outputConfig.getSurfaces().size());
+        assertEquals("surfaceGroupdId shouldn't change",
+                parcelledOutput.getSurfaceGroupId(),
+                outputConfig.getSurfaceGroupId());
+        p.recycle();
+    }
+}
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 9a97459..3e75ea6 100644
--- 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.util.ArrayList;
 import java.util.HashMap;
@@ -446,12 +449,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 doStagefrightTestANR(final int rid) throws Exception {
         doStagefrightTestMediaPlayerANR(rid, null);
     }
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..b13e613
--- /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 \
+    legacy-android-test \
+
+LOCAL_JAVA_LIBRARIES := 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_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..6de5250
--- /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 \
+    legacy-android-test \
+
+LOCAL_JAVA_LIBRARIES := 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_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..6c515f2
--- /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 \
+    legacy-android-test \
+
+LOCAL_JAVA_LIBRARIES := 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_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..be57f61 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)
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/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..4410b74
--- /dev/null
+++ b/tests/tests/slice/Android.mk
@@ -0,0 +1,44 @@
+# 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
+
+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..87253c5
--- /dev/null
+++ b/tests/tests/slice/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?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" />
+    </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/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/SliceProvider.java b/tests/tests/slice/src/android/slice/cts/SliceProvider.java
new file mode 100644
index 0000000..19968ab
--- /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":
+                SliceTest.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/slice/src/android/slice/cts/SliceTest.java b/tests/tests/slice/src/android/slice/cts/SliceTest.java
new file mode 100644
index 0000000..f573bca
--- /dev/null
+++ b/tests/tests/slice/src/android/slice/cts/SliceTest.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 SliceTest {
+
+    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/speech/Android.mk b/tests/tests/speech/Android.mk
index 05f9388..147b137 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/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 6ba1e77..4d96171 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 ea29552..321d0b5 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -100,6 +100,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();
         }
@@ -571,7 +572,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 7bd88fe..4d6922b 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
 
@@ -44,7 +43,8 @@
 
 # uncomment when b/13250611 is fixed
 #LOCAL_SDK_VERSION := current
-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/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..748b6963 100644
--- a/tests/tests/telephony/EmbmsMiddlewareTestApp/Android.mk
+++ b/tests/tests/telephony/EmbmsMiddlewareTestApp/Android.mk
@@ -14,7 +14,7 @@
 
 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
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/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 9803a45..37fcb01 100644
--- a/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/TelephonyManagerTest.java
@@ -196,6 +196,7 @@
         mTelephonyManager.getPhoneCount();
         mTelephonyManager.getDataEnabled();
         mTelephonyManager.getNetworkSpecifier();
+        mTelephonyManager.getNai();
         TelecomManager telecomManager = (TelecomManager) getContext()
                 .getSystemService(Context.TELECOM_SERVICE);
         PhoneAccountHandle defaultAccount = telecomManager
@@ -364,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) {
@@ -515,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/telephony2/Android.mk b/tests/tests/telephony2/Android.mk
index ec72916..c755c2a 100644
--- a/tests/tests/telephony2/Android.mk
+++ b/tests/tests/telephony2/Android.mk
@@ -24,8 +24,7 @@
 
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctstestrunner \
-    compatibility-device-util \
-    legacy-android-test
+    compatibility-device-util
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
@@ -34,6 +33,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..0f85e1f
--- /dev/null
+++ b/tests/tests/text/OWNERS
@@ -0,0 +1,4 @@
+siyamed@google.com
+nona@google.com
+clarabayarri@google.com
+toki@google.com
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/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/MeasuredTextTest.java b/tests/tests/text/src/android/text/cts/MeasuredTextTest.java
new file mode 100644
index 0000000..0ecd284
--- /dev/null
+++ b/tests/tests/text/src/android/text/cts/MeasuredTextTest.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.text.Layout;
+import android.text.MeasuredText;
+import android.text.SpannableStringBuilder;
+import android.text.Spanned;
+import android.text.TextDirectionHeuristic;
+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 MeasuredTextTest {
+
+    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();
+
+    private static final TextDirectionHeuristic LTR = TextDirectionHeuristics.LTR;
+
+    @Test
+    public void testBuilder() {
+        assertNotNull(new MeasuredText.Builder(STRING, PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE).build());
+        assertNotNull(new MeasuredText.Builder(SPANNED, PAINT)
+                .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE)
+                .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE).build());
+    }
+
+    @Test
+    public void testBuilder_withNull() {
+        try {
+            new MeasuredText.Builder(NULL_CHAR_SEQUENCE, PAINT);
+            fail();
+        } catch (NullPointerException e) {
+            // pass
+        }
+        try {
+            new MeasuredText.Builder(STRING, null);
+            fail();
+        } catch (NullPointerException e) {
+            // pass
+        }
+        try {
+            new MeasuredText.Builder(STRING, PAINT).setTextDirection(null);
+            fail();
+        } catch (NullPointerException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testBuilder_setRange() {
+        assertNotNull(new MeasuredText.Builder(STRING, PAINT).setRange(0, STRING.length()).build());
+        assertNotNull(new MeasuredText.Builder(STRING, PAINT)
+                .setRange(1, STRING.length() - 1).build());
+        assertNotNull(new MeasuredText.Builder(SPANNED, PAINT)
+                .setRange(0, SPANNED.length()).build());
+        assertNotNull(new MeasuredText.Builder(SPANNED, PAINT)
+                .setRange(1, SPANNED.length() - 1).build());
+        try {
+            new MeasuredText.Builder(STRING, PAINT).setRange(-1, -1);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+        try {
+            new MeasuredText.Builder(STRING, PAINT).setRange(100000, 100000);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+        try {
+            new MeasuredText.Builder(STRING, PAINT).setRange(STRING.length() - 1, 0);
+            fail();
+        } catch (IllegalArgumentException e) {
+            // pass
+        }
+    }
+
+    @Test
+    public void testCharSequenceInteferface() {
+        final CharSequence s = new MeasuredText.Builder(STRING, PAINT).build();
+        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());
+
+        // Even measure the part of the text, the CharSequence interface still works for original
+        // text.
+        // TODO: Should this work like substring?
+        final CharSequence s2 = new MeasuredText.Builder(STRING, PAINT)
+                .setRange(7, STRING.length()).build();
+        assertEquals(STRING.length(), s2.length());
+        assertEquals('H', s2.charAt(0));
+        assertEquals('e', s2.charAt(1));
+        assertEquals('l', s2.charAt(2));
+        assertEquals('l', s2.charAt(3));
+        assertEquals('o', s2.charAt(4));
+        assertEquals(',', s2.charAt(5));
+        assertEquals("Hello, World!", s2.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 Spanned s = new MeasuredText.Builder(SPANNED, PAINT).build();
+        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));
+
+        final Spanned s2 = new MeasuredText.Builder(SPANNED, PAINT)
+                .setRange(7, SPANNED.length()).build();
+        final LocaleSpan[] spans2 = s2.getSpans(0, s2.length(), LocaleSpan.class);
+        assertNotNull(spans2);
+        assertEquals(1, spans2.length);
+        assertEquals(SPAN, spans2[0]);
+
+        assertEquals(SPAN_START, s2.getSpanStart(SPAN));
+        assertEquals(SPAN_END, s2.getSpanEnd(SPAN));
+        assertTrue((s2.getSpanFlags(SPAN) & Spanned.SPAN_INCLUSIVE_EXCLUSIVE) != 0);
+
+        assertEquals(SPAN_START, s2.nextSpanTransition(0, s2.length(), LocaleSpan.class));
+        assertEquals(SPAN_END, s2.nextSpanTransition(SPAN_START, s2.length(), LocaleSpan.class));
+    }
+
+    @Test
+    public void testSpannedInterface_String() {
+        final Spanned s = new MeasuredText.Builder(STRING, PAINT).build();
+        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() {
+        assertSame(STRING, new MeasuredText.Builder(STRING, PAINT).build().getText());
+        assertSame(SPANNED, new MeasuredText.Builder(SPANNED, PAINT).build().getText());
+
+        assertSame(STRING, new MeasuredText.Builder(STRING, PAINT)
+                .setRange(1, 5).build().getText());
+        assertSame(SPANNED, new MeasuredText.Builder(SPANNED, PAINT)
+                .setRange(1, 5).build().getText());
+    }
+
+    @Test
+    public void testGetStartEnd() {
+        assertEquals(0, new MeasuredText.Builder(STRING, PAINT).build().getStart());
+        assertEquals(STRING.length(), new MeasuredText.Builder(STRING, PAINT).build().getEnd());
+
+        assertEquals(1, new MeasuredText.Builder(STRING, PAINT).setRange(1, 5).build().getStart());
+        assertEquals(5, new MeasuredText.Builder(STRING, PAINT).setRange(1, 5).build().getEnd());
+
+        assertEquals(0, new MeasuredText.Builder(SPANNED, PAINT).build().getStart());
+        assertEquals(SPANNED.length(), new MeasuredText.Builder(SPANNED, PAINT).build().getEnd());
+
+        assertEquals(1, new MeasuredText.Builder(SPANNED, PAINT).setRange(1, 5).build().getStart());
+        assertEquals(5, new MeasuredText.Builder(SPANNED, PAINT).setRange(1, 5).build().getEnd());
+    }
+
+    @Test
+    public void testGetTextDir() {
+        assertSame(TextDirectionHeuristics.FIRSTSTRONG_LTR,
+                new MeasuredText.Builder(STRING, PAINT).build().getTextDir());
+        assertSame(TextDirectionHeuristics.LTR,
+                new MeasuredText.Builder(SPANNED, PAINT)
+                        .setTextDirection(TextDirectionHeuristics.LTR).build().getTextDir());
+    }
+
+    @Test
+    public void testGetPaint() {
+        // No Paint equality functions. Check only not null.
+        assertNotNull(new MeasuredText.Builder(STRING, PAINT).build().getPaint());
+        assertNotNull(new MeasuredText.Builder(SPANNED, PAINT).build().getPaint());
+    }
+
+    @Test
+    public void testGetBreakStrategy() {
+        assertEquals(Layout.BREAK_STRATEGY_HIGH_QUALITY,
+                new MeasuredText.Builder(STRING, PAINT).build().getBreakStrategy());
+        assertEquals(Layout.BREAK_STRATEGY_SIMPLE,
+                new MeasuredText.Builder(STRING, PAINT)
+                        .setBreakStrategy(Layout.BREAK_STRATEGY_SIMPLE).build().getBreakStrategy());
+    }
+
+    @Test
+    public void testGetHyphenationFrequency() {
+        assertEquals(Layout.HYPHENATION_FREQUENCY_NORMAL,
+                new MeasuredText.Builder(STRING, PAINT).build().getHyphenationFrequency());
+        assertEquals(Layout.HYPHENATION_FREQUENCY_NONE,
+                new MeasuredText.Builder(STRING, PAINT)
+                        .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE).build()
+                                .getHyphenationFrequency());
+    }
+
+    @Test
+    public void testGetParagraphCount() {
+        final MeasuredText pm = new MeasuredText.Builder(STRING, PAINT).build();
+        assertEquals(1, pm.getParagraphCount());
+        assertEquals(0, pm.getParagraphStart(0));
+        assertEquals(STRING.length(), pm.getParagraphEnd(0));
+
+        final MeasuredText pm2 = new MeasuredText.Builder(STRING, PAINT).setRange(1, 9).build();
+        assertEquals(1, pm2.getParagraphCount());
+        assertEquals(1, pm2.getParagraphStart(0));
+        assertEquals(9, pm2.getParagraphEnd(0));
+
+        final MeasuredText pm3 = new MeasuredText.Builder(MULTIPARA_STRING, PAINT).build();
+        assertEquals(2, pm3.getParagraphCount());
+        assertEquals(0, pm3.getParagraphStart(0));
+        assertEquals(7, pm3.getParagraphEnd(0));
+        assertEquals(7, pm3.getParagraphStart(1));
+        assertEquals(pm3.length(), pm3.getParagraphEnd(1));
+
+        final MeasuredText pm4 = new MeasuredText.Builder(MULTIPARA_STRING, PAINT)
+                .setRange(1, 5).build();
+        assertEquals(1, pm4.getParagraphCount());
+        assertEquals(1, pm4.getParagraphStart(0));
+        assertEquals(5, pm4.getParagraphEnd(0));
+
+        final MeasuredText pm5 = new MeasuredText.Builder(MULTIPARA_STRING, PAINT)
+                .setRange(1, 9).build();
+        assertEquals(2, pm5.getParagraphCount());
+        assertEquals(1, pm5.getParagraphStart(0));
+        assertEquals(7, pm5.getParagraphEnd(0));
+        assertEquals(7, pm5.getParagraphStart(1));
+        assertEquals(9, pm5.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..fe05167 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
@@ -22,22 +22,29 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.content.res.ColorStateList;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
 import android.graphics.Typeface;
 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.MeasuredText;
 import android.text.SpannableString;
 import android.text.SpannableStringBuilder;
 import android.text.Spanned;
 import android.text.SpannedString;
 import android.text.StaticLayout;
+import android.text.TextDirectionHeuristics;
 import android.text.TextPaint;
 import android.text.TextUtils;
 import android.text.TextUtils.TruncateAt;
 import android.text.method.cts.EditorState;
 import android.text.style.StyleSpan;
+import android.text.style.TextAppearanceSpan;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -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,77 @@
                 .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;
+    }
+
+    @Test
+    public void testPremeasured() {
+        final float wholeWidth = mDefaultPaint.measureText(LOREM_IPSUM);
+        final int lineWidth = (int) (wholeWidth / 10.0f);  // Make 10 lines per paragraph.
+
+        final ColorStateList textColor = ColorStateList.valueOf(0x88FF0000);
+        final TextAppearanceSpan span = new TextAppearanceSpan(
+                "serif", Typeface.BOLD, 64 /* text size */, textColor, textColor);
+        final SpannableStringBuilder ssb = new SpannableStringBuilder(
+                LOREM_IPSUM + "\n" + LOREM_IPSUM);
+        ssb.setSpan(span, 0, LOREM_IPSUM.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
+
+        final Layout layout = StaticLayout.Builder.obtain(ssb, 0, ssb.length(), mDefaultPaint,
+                lineWidth).build();
+
+        final MeasuredText premeasuredText = new MeasuredText.Builder(ssb, mDefaultPaint).build();
+        final Layout premLayout = StaticLayout.Builder.obtain(premeasuredText, 0,
+                premeasuredText.length(), mDefaultPaint, lineWidth)
+                .setTextDirection(TextDirectionHeuristics.FIRSTSTRONG_LTR).build();
+
+        assertEquals(layout.getHeight(), premLayout.getHeight(), 0.0f);
+
+        final Bitmap bmp = drawToBitmap(layout);
+        final Bitmap premBmp = drawToBitmap(premLayout);
+
+        assertTrue(bmp.sameAs(premBmp));  // Need to be pixel perfect.
+    }
 }
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/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 cba0b45..5b721e0 100644
--- a/tests/tests/transition/Android.mk
+++ b/tests/tests/transition/Android.mk
@@ -31,14 +31,16 @@
     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)
 
 # 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/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..914442e 100644
--- a/tests/tests/transition/src/android/transition/cts/FadeTest.java
+++ b/tests/tests/transition/src/android/transition/cts/FadeTest.java
@@ -81,12 +81,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 +94,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 +112,7 @@
         resetListener();
         startTransition(R.layout.scene4);
         verify(mListener, never()).onTransitionEnd(any());
-        waitForEnd(400);
+        waitForEnd(1000);
     }
 
     @Test
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/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/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/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/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/LayerTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayerTests.java
index ed0110a..ae8f57e 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;
 
@@ -294,9 +295,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/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/util/Android.mk b/tests/tests/util/Android.mk
index ed7a61c..4fa2a39 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 533d98b..4538de3 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 7c35627..6cddaf3 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -288,15 +288,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">
@@ -348,6 +339,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/src/android/view/cts/DragDropTest.java b/tests/tests/view/src/android/view/cts/DragDropTest.java
index 39ae2d3..d74acef 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,81 @@
     private CountDownLatch mStartReceived;
     private CountDownLatch mEndReceived;
 
-    private static boolean equal(ClipDescription d1, ClipDescription d2) {
-        if ((d1 == null) != (d2 == null)) {
-            return false;
-        }
-        if (d1 == null) {
+    /**
+     * 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 +148,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 +169,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 +198,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 +211,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 +234,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 +269,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 +294,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");
                 }
             }
@@ -321,7 +352,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 +616,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 +636,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/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/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..8def4b0 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,89 @@
 @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();
+
+        MotionEvent event = mActivity.removeOldestButtonEvent();
+        assertNotNull(event);
+        assertEquals(MotionEvent.ACTION_DOWN, event.getAction());
+        event.recycle();
+        event = mActivity.removeOldestButtonEvent();
+        assertNotNull(event);
+        assertEquals(MotionEvent.ACTION_CANCEL, event.getAction());
+        event.recycle();
+        assertNull(mActivity.removeOldestButtonEvent());
+        assertEquals(0, mActivity.getButtonClickCount());
+        assertEquals(0, mActivity.getParentClickCount());
+    }
+
+    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();
+        });
     }
 }
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..0b0bb50 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -99,6 +99,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 +134,7 @@
     private ViewTestCtsActivity mActivity;
     private Resources mResources;
     private MockViewParent mMockParent;
+    private Context mContext;
 
     @Rule
     public ActivityTestRule<ViewTestCtsActivity> mActivityRule =
@@ -145,11 +147,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 +344,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 +535,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 +2445,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 +4047,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();
@@ -4340,6 +4505,42 @@
         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);
+    }
+
     private static class MockDrawable extends Drawable {
         private boolean mCalledSetTint = false;
 
@@ -4691,6 +4892,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/textclassifier/cts/TextClassificationManagerTest.java b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
index 71ca678..f066898 100644
--- a/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
+++ b/tests/tests/view/src/android/view/textclassifier/cts/TextClassificationManagerTest.java
@@ -29,6 +29,7 @@
 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 org.junit.Before;
@@ -42,7 +43,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;
@@ -57,12 +61,14 @@
 
     @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 +76,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,6 +96,11 @@
     }
 
     @Test
+    public void testGenerateLinks() {
+        assertValidResult(mClassifier.generateLinks(TEXT, null));
+    }
+
+    @Test
     public void testSetTextClassifier() {
         final TextClassifier classifier = mock(TextClassifier.class);
         mManager.setTextClassifier(classifier);
@@ -96,6 +109,8 @@
 
     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 +119,7 @@
             assertTrue(confidenceScore >= 0);
             assertTrue(confidenceScore <= 1);
         }
+        assertNotNull(selection.getSignature());
     }
 
     private static void assertValidResult(TextClassification classification) {
@@ -116,6 +132,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/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 c56f00e..a5f01b2 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..5168cda 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,6 +56,14 @@
             </intent-filter>
         </activity>
 
+        <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" />
+
         <meta-data android:name="android.webkit.WebView.EnableSafeBrowsing" android:value="false" />
 
     </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/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..b61420c
--- /dev/null
+++ b/tests/tests/webkit/src/android/webkit/cts/TracingControllerTest.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.util.concurrent.Callable;
+
+
+public class TracingControllerTest extends ActivityInstrumentationTestCase2<WebViewCtsActivity> {
+
+    public static class TracingReceiver implements TracingController.TracingOutputStream {
+        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 complete() {
+            validateThread();
+            mComplete = true;
+        }
+
+        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();
+    }
+
+    // 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/WebSettingsTest.java b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
index b02deec..f10e7fd 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
@@ -20,10 +20,13 @@
 import android.net.http.SslError;
 import android.os.Build;
 import android.test.ActivityInstrumentationTestCase2;
+import android.util.Base64;
 import android.util.Log;
 import android.webkit.ConsoleMessage;
 import android.webkit.SslErrorHandler;
 import android.webkit.WebIconDatabase;
+import android.webkit.WebResourceResponse;
+import android.webkit.WebResourceRequest;
 import android.webkit.WebSettings;
 import android.webkit.WebSettings.TextSize;
 import android.webkit.WebStorage;
@@ -35,7 +38,9 @@
 import com.android.compatibility.common.util.NullWebViewUtils;
 import com.android.compatibility.common.util.PollingCheck;
 
+import java.io.ByteArrayInputStream;
 import java.io.FileOutputStream;
+import java.nio.charset.StandardCharsets;
 import java.util.Locale;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -944,61 +949,77 @@
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
         }
-        final class SslWebViewClient extends WaitForLoadedClient {
-            public SslWebViewClient() {
+
+        final String INSECURE_BASE_URL = "http://www.example.com/";
+        final String INSECURE_JS_URL = INSECURE_BASE_URL + "insecure.js";
+        final String INSECURE_IMG_URL = INSECURE_BASE_URL + "insecure.png";
+        final String SECURE_URL = "/secure.html";
+        final String JS_HTML = "<script src=\"" + INSECURE_JS_URL + "\"></script>";
+        final String IMG_HTML = "<img src=\"" + INSECURE_IMG_URL + "\" />";
+        final String SECURE_HTML = "<body>" + IMG_HTML + " " + JS_HTML + "</body>";
+        final String JS_CONTENT = "window.loaded_js = 42;";
+        final String IMG_CONTENT = "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7";
+
+        final class InterceptClient extends WaitForLoadedClient {
+            public int mInsecureJsCounter;
+            public int mInsecureImgCounter;
+
+            public InterceptClient() {
                 super(mOnUiThread);
             }
+
             @Override
-            public void onReceivedSslError(WebView view,
-                    SslErrorHandler handler, SslError error) {
+            public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
                 handler.proceed();
             }
+
+            @Override
+            public WebResourceResponse shouldInterceptRequest(
+                    WebView view, WebResourceRequest request) {
+                if (request.getUrl().toString().equals(INSECURE_JS_URL)) {
+                    mInsecureJsCounter++;
+                    return new WebResourceResponse("text/javascript", "utf-8",
+                        new ByteArrayInputStream(JS_CONTENT.getBytes(StandardCharsets.UTF_8)));
+                } else if (request.getUrl().toString().equals(INSECURE_IMG_URL)) {
+                    mInsecureImgCounter++;
+                    return new WebResourceResponse("image/gif", "utf-8",
+                        new ByteArrayInputStream(Base64.decode(IMG_CONTENT, Base64.DEFAULT)));
+                }
+
+                if (request.getUrl().toString().startsWith(INSECURE_BASE_URL)) {
+                    return new WebResourceResponse("text/html", "UTF-8", null);
+                }
+                return null;
+            }
         }
 
+        InterceptClient interceptClient = new InterceptClient();
+        mOnUiThread.setWebViewClient(interceptClient);
         mSettings.setJavaScriptEnabled(true);
         TestWebServer httpsServer = null;
-        TestWebServer httpServer = null;
         try {
             httpsServer = new TestWebServer(true);
-            httpServer = new TestWebServer(false);
-            final String JS_URL = "/insecure.js";
-            final String IMG_URL = "/insecure.png";
-            final String SECURE_URL = "/secure.html";
-            final String JS_HTML = "<script src=\"" + httpServer.getResponseUrl(JS_URL) +
-                "\"></script>";
-            final String IMG_HTML = "<img src=\"" + httpServer.getResponseUrl(IMG_URL) + "\" />";
-            final String SECURE_HTML = "<body>" + IMG_HTML + " " + JS_HTML + "</body>";
-            httpServer.setResponse(JS_URL, "window.loaded_js = 42;", null);
-            httpServer.setResponseBase64(IMG_URL,
-                    "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
-                    null);
             String secureUrl = httpsServer.setResponse(SECURE_URL, SECURE_HTML, null);
-
             mOnUiThread.clearSslPreferences();
 
-            mOnUiThread.setWebViewClient(new SslWebViewClient());
-
             mSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_NEVER_ALLOW);
             mOnUiThread.loadUrlAndWaitForCompletion(secureUrl);
             assertEquals(1, httpsServer.getRequestCount(SECURE_URL));
-            assertEquals(0, httpServer.getRequestCount(JS_URL));
-            assertEquals(0, httpServer.getRequestCount(IMG_URL));
+            assertEquals(0, interceptClient.mInsecureJsCounter);
+            assertEquals(0, interceptClient.mInsecureImgCounter);
 
             mSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
             mOnUiThread.loadUrlAndWaitForCompletion(secureUrl);
             assertEquals(2, httpsServer.getRequestCount(SECURE_URL));
-            assertEquals(1, httpServer.getRequestCount(JS_URL));
-            assertEquals(1, httpServer.getRequestCount(IMG_URL));
+            assertEquals(1, interceptClient.mInsecureJsCounter);
+            assertEquals(1, interceptClient.mInsecureImgCounter);
 
             mSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE);
             mOnUiThread.loadUrlAndWaitForCompletion(secureUrl);
             assertEquals(3, httpsServer.getRequestCount(SECURE_URL));
-            assertEquals(1, httpServer.getRequestCount(JS_URL));
-            assertEquals(2, httpServer.getRequestCount(IMG_URL));
+            assertEquals(1, interceptClient.mInsecureJsCounter);
+            assertEquals(2, interceptClient.mInsecureImgCounter);
         } finally {
-            if (httpServer != null) {
-                httpServer.shutdown();
-            }
             if (httpsServer != null) {
                 httpsServer.shutdown();
             }
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..84740e3
--- 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;
@@ -212,6 +214,7 @@
         StrictMode.setThreadPolicy(oldPolicy);
     }
 
+    @Presubmit
     @UiThreadTest
     public void testConstructor() {
         if (!NullWebViewUtils.isWebViewAvailable()) {
@@ -272,7 +275,7 @@
         assertNotNull(CookieSyncManager.getInstance());
     }
 
-    @UiThreadTest
+    // Static methods should be safe to call on non-UI threads
     public void testFindAddress() {
         if (!NullWebViewUtils.isWebViewAvailable()) {
             return;
@@ -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
diff --git a/tests/tests/widget/Android.mk b/tests/tests/widget/Android.mk
index ec104d8..f939c43 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/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/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_layout.xml b/tests/tests/widget/res/layout/magnifier_layout.xml
new file mode 100644
index 0000000..989d33d
--- /dev/null
+++ b/tests/tests/widget/res/layout/magnifier_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_layout"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content">
+</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/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..abb85ad 100644
--- a/tests/tests/widget/res/values/styles.xml
+++ b/tests/tests/widget/res/values/styles.xml
@@ -109,6 +109,52 @@
         <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="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..4704e37 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;
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..a828c3d
--- /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_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..46d3fe5
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/MagnifierTest.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.widget.cts;
+
+import android.app.Activity;
+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.view.View;
+import android.widget.LinearLayout;
+import android.widget.LinearLayout.LayoutParams;
+import android.widget.Magnifier;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for {@link Magnifier}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MagnifierTest {
+    private Activity mActivity;
+    private LinearLayout mMagnifierLayout;
+
+    @Rule
+    public ActivityTestRule<MagnifierCtsActivity> mActivityRule =
+            new ActivityTestRule<>(MagnifierCtsActivity.class);
+
+    @Before
+    public void setup() {
+        mActivity = mActivityRule.getActivity();
+        PollingCheck.waitFor(mActivity::hasWindowFocus);
+        mMagnifierLayout = mActivity.findViewById(R.id.magnifier_layout);
+    }
+
+    @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() {
+        View view = new View(mActivity);
+        mMagnifierLayout.addView(view, new LayoutParams(200, 200));
+        Magnifier magnifier = new Magnifier(view);
+        // Valid coordinates.
+        magnifier.show(0, 0);
+        // Invalid coordinates, should both be clamped to 0.
+        magnifier.show(-1, -1);
+        // Valid coordinates.
+        magnifier.show(10, 10);
+        // Same valid coordinate as above, should skip making another copy request.
+        magnifier.show(10, 10);
+    }
+
+    @Test
+    @UiThreadTest
+    public void testDismiss() {
+        View view = new View(mActivity);
+        mMagnifierLayout.addView(view, new LayoutParams(200, 200));
+        Magnifier magnifier = new Magnifier(view);
+        // Valid coordinates.
+        magnifier.show(10, 10);
+        magnifier.dismiss();
+        // Should be no-op.
+        magnifier.dismiss();
+    }
+}
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..84f2ef0
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/OWNERS
@@ -0,0 +1,8 @@
+per-file TextView*.java = siyamed@google.com
+per-file TextView*.java = nona@google.com
+per-file TextView*.java = clarabayarri@google.com
+per-file TextView*.java = toki@google.com
+per-file EditText*.java = siyamed@google.com
+per-file EditText*.java = nona@google.com
+per-file EditText*.java = clarabayarri@google.com
+per-file EditText*.java = toki@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/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/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index ac90cb7..992f05e 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.MeasuredText;
 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,18 @@
         }
     }
 
+    @UiThreadTest
+    @Test
+    public void testSetText_MeasuredText() {
+        final TextView tv = findTextView(R.id.textview_text);
+        final MeasuredText measured = new MeasuredText.Builder(
+                "This is an example text.", new TextPaint())
+                .setBreakStrategy(tv.getBreakStrategy())
+                .setHyphenationFrequency(tv.getHyphenationFrequency()).build();
+        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 +4388,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 +4595,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 +7962,70 @@
         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());
+    }
+
     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/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..7b56e21 100644
--- a/tools/cts-api-coverage/Android.mk
+++ b/tools/cts-api-coverage/Android.mk
@@ -14,23 +14,51 @@
 
 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 += \
+        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..c4f55d4
--- /dev/null
+++ b/tools/cts-api-coverage/proto/testsuite.proto
@@ -0,0 +1,165 @@
+// Copyright (C) 2018 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+// [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;
+
+        message Class {
+            string name = 1;
+            string type = 2;
+            string super_class = 3;
+            string interface = 4;
+
+            enum Type {
+                UNKNOWN = 0;
+                JUNIT3 = 1;
+                JUNIT4 = 2;
+                PARAMETERIZED = 3;
+                JAVAHOST = 4;
+                GTEST = 5;
+                DEQP = 6;
+            }
+            Type test_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 = 2;
+        string op_codes = 3;
+    }
+    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;
+}
+// [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/CtsReportHandler.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsReportHandler.java
new file mode 100644
index 0000000..d0247e2
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsReportHandler.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 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 mModuleBld;
+    private CtsReport.TestPackage.TestSuite.TestCase.Builder mTestCaseBld;
+
+    CtsReportHandler(String configFileName) {
+        mCtsReportBld = CtsReport.newBuilder();
+        mTestPackageBld = CtsReport.TestPackage.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)) {
+            mModuleBld = CtsReport.TestPackage.TestSuite.newBuilder();
+            mModuleBld.setName(attributes.getValue(ABI_TAG)+","+attributes.getValue(NAME_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(mModuleBld);
+            mModuleBld = null;
+        } else if (TEST_CASE_TAG.equalsIgnoreCase(localName)) {
+            mModuleBld.addTestCase(mTestCaseBld);
+            mTestCaseBld = null;
+        }
+    }
+
+    public CtsReport getCtsReport() {
+        mCtsReportBld.addTestPackage(mTestPackageBld);
+        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..e094a34
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsReportParser.java
@@ -0,0 +1,168 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.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.File;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.FilenameFilter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+/**
+ * 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();
+        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("no,Abi,Module,Class,Test,Result");
+        int i = 1;
+        for (CtsReport.TestPackage tPkg : ctsReport.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()) {
+                        System.out.printf("%d,%s,%s,%s,%s\n",
+                                i++,
+                                module.getName(),
+                                testCase.getName(),
+                                test.getName(),
+                                test.getResult());
+                    }
+                }
+            }
+        }
+    }
+
+    static void printCtsReportSummary(CtsReport ctsReport, String fName)
+        throws IOException{
+
+        FileWriter fWriter = new FileWriter(fName);
+        PrintWriter pWriter = new PrintWriter(fWriter);
+
+        //Header
+        pWriter.print("no,Module,Test#\n");
+
+        for (CtsReport.TestPackage tPkg : ctsReport.getTestPackageList()) {
+            int moduleCnt = 0;
+            for (CtsReport.TestPackage.TestSuite module : tPkg.getTestSuiteList()) {
+                int testCaseCnt = 0;
+                for (CtsReport.TestPackage.TestSuite.TestCase testCase : module.getTestCaseList()) {
+                    for (CtsReport.TestPackage.TestSuite.TestCase.Test test : testCase.getTestList()) {
+                        testCaseCnt++;
+                    }
+                }
+                pWriter.printf("%d,%s,%d\n",
+                        moduleCnt++,
+                        module.getName(),
+                        testCaseCnt);
+            }
+        }
+
+        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..7cc42b0
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestCaseReport.java
@@ -0,0 +1,371 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.jf.dexlib2.AccessFlags;
+import org.jf.dexlib2.DexFileFactory;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.iface.Annotation;
+import org.jf.dexlib2.iface.AnnotationElement;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.DexFile;
+import org.jf.dexlib2.iface.Method;
+import org.jf.dexlib2.iface.value.StringEncodedValue;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+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.List;
+import java.util.Set;
+
+class TestCaseReport {
+    // JUNIT3 Test suffix
+    private static final String TEST_TAG = "Test;";
+    private static final String TEST_PREFIX_TAG = "test";
+    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 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_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 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";
+
+    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 TestSuite.Package.Class.Type chkTestType(ClassDef classDef) {
+        for (Annotation annotation : classDef.getAnnotations()) {
+            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.Type.JUNIT4;
+                    } else if (aName.equals(PARAMETERIZED_TAG)) {
+                        return TestSuite.Package.Class.Type.PARAMETERIZED;
+                    }
+                }
+
+                return TestSuite.Package.Class.Type.JUNIT4;
+            }
+        }
+
+        if (classDef.getType().endsWith(TEST_TAG)) {
+            return TestSuite.Package.Class.Type.JUNIT3;
+        } else {
+            return TestSuite.Package.Class.Type.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);
+                }
+                if (isTargetClass(classList, className)){
+                    if ((classDef.getAccessFlags() & AccessFlags.PUBLIC.getValue()) != 0) {
+                        TestSuite.Package.Class.Builder tClassBuilder = TestSuite.Package.Class.newBuilder();
+                        tClassBuilder.setTestType(chkTestType(classDef));
+                        tClassBuilder.setApk(apkName);
+                        tClassBuilder.setName(className);
+                        if (TestSuite.Package.Class.Type.JUNIT3 == tClassBuilder.getTestType()) {
+                            for (Method method : classDef.getMethods()) {
+                                if ((method.getAccessFlags() & AccessFlags.PUBLIC.getValue()) != 0) {
+                                    String mName = method.getName();
+                                    if (mName.startsWith(TEST_PREFIX_TAG)) {
+                                        TestSuite.Package.Class.Method.Builder methodBuilder =  TestSuite.Package.Class.Method.newBuilder();
+                                        methodBuilder.setName(mName);
+                                        tClassBuilder.addMethods(methodBuilder);
+                                    }
+                                }
+                            }
+                        } else if (TestSuite.Package.Class.Type.JUNIT4 == tClassBuilder.getTestType()) {
+                            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;
+    }
+
+    // Iterates though all test suite content and prints them.
+    static TestSuite.Builder listTestCases(TestSuiteContent tsContent, String tsPath)
+            throws Exception {
+        TestSuite.Builder tsBuilder = TestSuite.newBuilder();
+
+        int i = 1;
+        for (Entry entry: tsContent.getFileEntriesList()) {
+            if (Entry.EntryType.CONFIG == entry.getType()) {
+                ConfigMetadata config = entry.getFileMetadata().getConfigMetadata();
+
+                // getting package/class list from Test Module Configuration
+                ArrayList<String> testClassList = new ArrayList<String> ();
+                List<Option> optList;
+                List<ConfigMetadata.TestClass> testClassesList = config.getTestClassesList();
+                for (ConfigMetadata.TestClass tClass : testClassesList) {
+                    optList = tClass.getOptionsList();
+                    for (Option opt : optList) {
+                        if (PACKAGE_TAG.equalsIgnoreCase(opt.getName())) {
+                            testClassList.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 =
+                        parseApkTestCase(testApkList, testClassList, tsPath, 27);
+                tsPkgBuilder.setName(entry.getName().replaceAll(CONFIG_REGEX, ""));
+                tsBuilder.addPackages(tsPkgBuilder);
+            }
+        }
+        return tsBuilder;
+    }
+
+    // Iterates though all test suite content and prints them.
+    static void printTestSuite(TestSuite ts) {
+        //Header
+        System.out.println("no,Module,Class,Test,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()) {
+                    System.out.printf("%d,%s,%s,%s,%s,%s\n",
+                            i++,
+                            pkg.getName(),
+                            cls.getName(),
+                            mtd.getName(),
+                            cls.getApk(),
+                            cls.getTestType());
+                }
+            }
+        }
+    }
+
+    // Iterates though all test suite content and prints them.
+    static void printTestSuiteSummary(TestSuite ts, String fName)
+        throws IOException{
+        FileWriter fWriter = new FileWriter(fName);
+        PrintWriter pWriter = new PrintWriter(fWriter);
+
+        //Header
+        pWriter.print("no,Module,Test#,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()) {
+                    methodCnt++;
+                }
+                classCnt++;
+            }
+            pWriter.printf("%d,%s,%d,%d\n",
+                    i++,
+                    pkg.getName(),
+                    methodCnt,
+                    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 = "";
+
+        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 ("-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).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);
+        printTestSuiteSummary(ts1, outputSummaryFilePath);
+    }
+}
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..dae80ed
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TestSuiteContentReport.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.jf.dexlib2.DexFileFactory;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.iface.Annotation;
+import org.jf.dexlib2.iface.AnnotationElement;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.DexFile;
+import org.jf.dexlib2.iface.Method;
+import org.jf.dexlib2.iface.value.StringEncodedValue;
+import org.xml.sax.InputSource;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.XMLReaderFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStreamReader;
+import java.io.IOException;
+import java.io.PrintStream;
+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.List;
+
+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 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();
+            }
+        }
+    }
+
+    // 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);
+                    }
+                } 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");
+        }
+    }
+
+    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 2f11db9..d803c34 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-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/retry.xml b/tools/cts-tradefed/res/config/retry.xml
index cbecada..e488410 100644
--- a/tools/cts-tradefed/res/config/retry.xml
+++ b/tools/cts-tradefed/res/config/retry.xml
@@ -18,6 +18,10 @@
     <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" />
+
     <logger class="com.android.tradefed.log.FileLogger">
         <option name="log-level-display" value="WARN" />
     </logger>
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/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" />