Merge AU_LINUX_ANDROID_LA.BF.1.1.1_RB1.05.00.02.042.024 on remote branch

Change-Id: I5391addf628a8f908305523b46fb783f98463c96
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 31dc2fd..2ef9b62 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -34,6 +34,7 @@
     CtsSplitApp_arm64-v8a \
     CtsSplitApp_mips64 \
     CtsSplitApp_mips \
+    CtsSplitAppDiffRevision \
     CtsSplitAppDiffVersion \
     CtsSplitAppDiffCert \
     CtsSplitAppFeature \
@@ -68,11 +69,15 @@
     CtsDeviceTaskswitchingAppB \
     CtsDeviceTaskswitchingControl \
     CtsDeviceUi \
+    CtsHostsideNetworkTestsApp \
     CtsIntentReceiverApp \
     CtsIntentSenderApp \
+    CtsLauncherAppsTests \
+    CtsLauncherAppsTestsSupport \
     CtsManagedProfileApp \
     CtsMonkeyApp \
     CtsMonkeyApp2 \
+    CtsSimpleApp \
     CtsSomeAccessibilityServices \
     CtsThemeDeviceApp \
     TestDeviceSetup \
@@ -100,6 +105,7 @@
     CtsDeviceBrowserBench \
     CtsDeviceVideoPerf \
     CtsDeviceOpenGl \
+    CtsDeviceTvProviderPerf \
     CtsAccelerationTestCases \
     CtsAccountManagerTestCases \
     CtsAccessibilityServiceTestCases \
@@ -107,6 +113,7 @@
     CtsAdminTestCases \
     CtsAnimationTestCases \
     CtsAppTestCases \
+    CtsAppWidgetTestCases \
     CtsBluetoothTestCases \
     CtsCalendarcommon2TestCases \
     CtsContentTestCases \
@@ -171,7 +178,9 @@
     CtsAdbTests \
     CtsAppSecurityTests \
     CtsDevicePolicyManagerTestCases \
+    CtsDumpsysHostTestCases \
     CtsHostJank \
+    CtsHostsideNetworkTests \
     CtsHostUi \
     CtsMonkeyTestCases \
     CtsThemeHostTestCases \
diff --git a/apps/CameraITS/.gitignore b/apps/CameraITS/.gitignore
new file mode 100644
index 0000000..259969b
--- /dev/null
+++ b/apps/CameraITS/.gitignore
@@ -0,0 +1,11 @@
+# Ignore files that are created asa result of running the ITS tests.
+
+*.json
+*.yuv
+*.jpg
+*.jpeg
+*.png
+*.pyc
+its.target.cfg
+.DS_Store
+
diff --git a/apps/CameraITS/Android.mk b/apps/CameraITS/Android.mk
new file mode 100644
index 0000000..8f7ed7c
--- /dev/null
+++ b/apps/CameraITS/Android.mk
@@ -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.
+#
+
+its-dir-name := CameraITS
+its-dir := $(HOST_OUT)/$(its-dir-name)
+its-build-stamp := $(its-dir)/build_stamp
+
+camera-its: $(its-build-stamp)
+
+.PHONY: camera-its
+
+$(its-dir): $(its-build-stamp)
+
+$(its-build-stamp): $(ACP)
+	echo $(its_dir)
+	mkdir -p $(its-dir)
+	$(ACP) -rfp cts/apps/$(its-dir-name)/* $(its-dir)
+	rm $(its-dir)/Android.mk
+	touch $@
diff --git a/apps/CameraITS/CameraITS.pdf b/apps/CameraITS/CameraITS.pdf
new file mode 100644
index 0000000..0d10bae
--- /dev/null
+++ b/apps/CameraITS/CameraITS.pdf
Binary files differ
diff --git a/apps/CameraITS/build/envsetup.sh b/apps/CameraITS/build/envsetup.sh
new file mode 100644
index 0000000..6069341
--- /dev/null
+++ b/apps/CameraITS/build/envsetup.sh
@@ -0,0 +1,45 @@
+# 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.
+
+# This file should be sourced from bash. Sets environment variables for
+# running tests, and also checks that a number of dependences are present
+# and that the unit tests for the modules passed (indicating that the setup
+# is correct).
+
+[[ "${BASH_SOURCE[0]}" != "${0}" ]] || \
+    { echo ">> Script must be sourced with 'source $0'" >&2; exit 1; }
+
+command -v adb >/dev/null 2>&1 || \
+    echo ">> Require adb executable to be in path" >&2
+
+command -v python >/dev/null 2>&1 || \
+    echo ">> Require python executable to be in path" >&2
+
+python -V 2>&1 | grep -q "Python 2.7" || \
+    echo ">> Require python 2.7" >&2
+
+for M in numpy PIL Image matplotlib pylab cv2 scipy.stats scipy.spatial
+do
+    python -c "import $M" >/dev/null 2>&1 || \
+        echo ">> Require Python $M module" >&2
+done
+
+export PYTHONPATH="$PWD/pymodules:$PYTHONPATH"
+
+for M in device objects image caps dng target error
+do
+    python "pymodules/its/$M.py" 2>&1 | grep -q "OK" || \
+        echo ">> Unit test for $M failed" >&2
+done
+
diff --git a/apps/CameraITS/pymodules/its/__init__.py b/apps/CameraITS/pymodules/its/__init__.py
new file mode 100644
index 0000000..59058be
--- /dev/null
+++ b/apps/CameraITS/pymodules/its/__init__.py
@@ -0,0 +1,14 @@
+# 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.
+
diff --git a/apps/CameraITS/pymodules/its/caps.py b/apps/CameraITS/pymodules/its/caps.py
new file mode 100644
index 0000000..24f4e75
--- /dev/null
+++ b/apps/CameraITS/pymodules/its/caps.py
@@ -0,0 +1,232 @@
+# 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 unittest
+import its.objects
+import sys
+
+
+def skip_unless(cond):
+    """Skips the test if the condition is false.
+
+    If a test is skipped, then it is exited and returns the special code
+    of 101 to the calling shell, which can be used by an external test
+    harness to differentiate a skip from a pass or fail.
+
+    Args:
+        cond: Boolean, which must be true for the test to not skip.
+
+    Returns:
+        Nothing.
+    """
+    SKIP_RET_CODE = 101
+
+    if not cond:
+        print "Test skipped"
+        sys.exit(SKIP_RET_CODE)
+
+
+def full(props):
+    """Returns whether a device is a FULL capability camera2 device.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    return props.has_key("android.info.supportedHardwareLevel") and \
+           props["android.info.supportedHardwareLevel"] == 1
+
+def limited(props):
+    """Returns whether a device is a LIMITED capability camera2 device.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    return props.has_key("android.info.supportedHardwareLevel") and \
+           props["android.info.supportedHardwareLevel"] == 0
+
+def legacy(props):
+    """Returns whether a device is a LEGACY capability camera2 device.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    return props.has_key("android.info.supportedHardwareLevel") and \
+           props["android.info.supportedHardwareLevel"] == 2
+
+def manual_sensor(props):
+    """Returns whether a device supports MANUAL_SENSOR capabilities.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    return    props.has_key("android.request.availableCapabilities") and \
+              1 in props["android.request.availableCapabilities"] \
+           or full(props)
+
+def manual_post_proc(props):
+    """Returns whether a device supports MANUAL_POST_PROCESSING capabilities.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    return    props.has_key("android.request.availableCapabilities") and \
+              2 in props["android.request.availableCapabilities"] \
+           or full(props)
+
+def raw(props):
+    """Returns whether a device supports RAW capabilities.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    return props.has_key("android.request.availableCapabilities") and \
+           3 in props["android.request.availableCapabilities"]
+
+def raw16(props):
+    """Returns whether a device supports RAW16 output.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    return len(its.objects.get_available_output_sizes("raw", props)) > 0
+
+def raw10(props):
+    """Returns whether a device supports RAW10 output.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    return len(its.objects.get_available_output_sizes("raw10", props)) > 0
+
+def sensor_fusion(props):
+    """Returns whether the camera and motion sensor timestamps for the device
+    are in the same time domain and can be compared directly.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    return props.has_key("android.sensor.info.timestampSource") and \
+           props["android.sensor.info.timestampSource"] == 1
+
+def read_3a(props):
+    """Return whether a device supports reading out the following 3A settings:
+        sensitivity
+        exposure time
+        awb gain
+        awb cct
+        focus distance
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    # TODO: check available result keys explicitly
+    return manual_sensor(props) and manual_post_proc(props)
+
+def compute_target_exposure(props):
+    """Return whether a device supports target exposure computation in its.target module.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        Boolean.
+    """
+    return manual_sensor(props) and manual_post_proc(props)
+
+def freeform_crop(props):
+    """Returns whether a device supports freefrom cropping.
+
+    Args:
+        props: Camera properties object.
+
+    Return:
+        Boolean.
+    """
+    return props.has_key("android.scaler.croppingType") and \
+           props["android.scaler.croppingType"] == 1
+
+def flash(props):
+    """Returns whether a device supports flash control.
+
+    Args:
+        props: Camera properties object.
+
+    Return:
+        Boolean.
+    """
+    return props.has_key("android.flash.info.available") and \
+           props["android.flash.info.available"] == 1
+
+def per_frame_control(props):
+    """Returns whether a device supports per frame control
+
+    Args:
+        props: Camera properties object.
+
+    Return:
+        Boolean.
+    """
+    return props.has_key("android.sync.maxLatency") and \
+           props["android.sync.maxLatency"] == 0
+
+def ev_compensation(props):
+    """Returns whether a device supports ev compensation
+
+    Args:
+        props: Camera properties object.
+
+    Return:
+        Boolean.
+    """
+    return props.has_key("android.control.aeCompensationRange") and \
+           props["android.control.aeCompensationRange"] != [0, 0]
+
+class __UnitTest(unittest.TestCase):
+    """Run a suite of unit tests on this module.
+    """
+    # TODO: Add more unit tests.
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
new file mode 100644
index 0000000..beba0ae
--- /dev/null
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -0,0 +1,545 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.error
+import os
+import os.path
+import sys
+import re
+import json
+import time
+import unittest
+import socket
+import subprocess
+import hashlib
+import numpy
+
+class ItsSession(object):
+    """Controls a device over adb to run ITS scripts.
+
+    The script importing this module (on the host machine) prepares JSON
+    objects encoding CaptureRequests, specifying sets of parameters to use
+    when capturing an image using the Camera2 APIs. This class encapsulates
+    sending the requests to the device, monitoring the device's progress, and
+    copying the resultant captures back to the host machine when done. TCP
+    forwarded over adb is the transport mechanism used.
+
+    The device must have CtsVerifier.apk installed.
+
+    Attributes:
+        sock: The open socket.
+    """
+
+    # Open a connection to localhost:6000, forwarded to port 6000 on the device.
+    # TODO: Support multiple devices running over different TCP ports.
+    IPADDR = '127.0.0.1'
+    PORT = 6000
+    BUFFER_SIZE = 4096
+
+    # Seconds timeout on each socket operation.
+    SOCK_TIMEOUT = 10.0
+
+    PACKAGE = 'com.android.cts.verifier.camera.its'
+    INTENT_START = 'com.android.cts.verifier.camera.its.START'
+    ACTION_ITS_RESULT = 'com.android.cts.verifier.camera.its.ACTION_ITS_RESULT'
+    EXTRA_SUCCESS = 'camera.its.extra.SUCCESS'
+
+    # TODO: Handle multiple connected devices.
+    ADB = "adb -d"
+
+    # Definitions for some of the common output format options for do_capture().
+    # Each gets images of full resolution for each requested format.
+    CAP_RAW = {"format":"raw"}
+    CAP_DNG = {"format":"dng"}
+    CAP_YUV = {"format":"yuv"}
+    CAP_JPEG = {"format":"jpeg"}
+    CAP_RAW_YUV = [{"format":"raw"}, {"format":"yuv"}]
+    CAP_DNG_YUV = [{"format":"dng"}, {"format":"yuv"}]
+    CAP_RAW_JPEG = [{"format":"raw"}, {"format":"jpeg"}]
+    CAP_DNG_JPEG = [{"format":"dng"}, {"format":"jpeg"}]
+    CAP_YUV_JPEG = [{"format":"yuv"}, {"format":"jpeg"}]
+    CAP_RAW_YUV_JPEG = [{"format":"raw"}, {"format":"yuv"}, {"format":"jpeg"}]
+    CAP_DNG_YUV_JPEG = [{"format":"dng"}, {"format":"yuv"}, {"format":"jpeg"}]
+
+    # Method to handle the case where the service isn't already running.
+    # This occurs when a test is invoked directly from the command line, rather
+    # than as a part of a separate test harness which is setting up the device
+    # and the TCP forwarding.
+    def __pre_init(self):
+
+        # This also includes the optional reboot handling: if the user
+        # provides a "reboot" or "reboot=N" arg, then reboot the device,
+        # waiting for N seconds (default 30) before returning.
+        for s in sys.argv[1:]:
+            if s[:6] == "reboot":
+                duration = 30
+                if len(s) > 7 and s[6] == "=":
+                    duration = int(s[7:])
+                print "Rebooting device"
+                _run("%s reboot" % (ItsSession.ADB));
+                _run("%s wait-for-device" % (ItsSession.ADB))
+                time.sleep(duration)
+                print "Reboot complete"
+
+        # TODO: Figure out why "--user 0" is needed, and fix the problem.
+        _run('%s shell am force-stop --user 0 %s' % (ItsSession.ADB, self.PACKAGE))
+        _run(('%s shell am startservice --user 0 -t text/plain '
+              '-a %s') % (ItsSession.ADB, self.INTENT_START))
+
+        # Wait until the socket is ready to accept a connection.
+        proc = subprocess.Popen(
+                ItsSession.ADB.split() + ["logcat"],
+                stdout=subprocess.PIPE)
+        logcat = proc.stdout
+        while True:
+            line = logcat.readline().strip()
+            if line.find('ItsService ready') >= 0:
+                break
+        proc.kill()
+
+        # Setup the TCP-over-ADB forwarding.
+        _run('%s forward tcp:%d tcp:%d' % (ItsSession.ADB,self.PORT,self.PORT))
+
+    def __init__(self):
+        if "noinit" not in sys.argv:
+            self.__pre_init()
+        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.sock.connect((self.IPADDR, self.PORT))
+        self.sock.settimeout(self.SOCK_TIMEOUT)
+        self.__close_camera()
+        self.__open_camera()
+
+    def __del__(self):
+        if hasattr(self, 'sock') and self.sock:
+            self.__close_camera()
+            self.sock.close()
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, type, value, traceback):
+        return False
+
+    def __read_response_from_socket(self):
+        # Read a line (newline-terminated) string serialization of JSON object.
+        chars = []
+        while len(chars) == 0 or chars[-1] != '\n':
+            ch = self.sock.recv(1)
+            if len(ch) == 0:
+                # Socket was probably closed; otherwise don't get empty strings
+                raise its.error.Error('Problem with socket on device side')
+            chars.append(ch)
+        line = ''.join(chars)
+        jobj = json.loads(line)
+        # Optionally read a binary buffer of a fixed size.
+        buf = None
+        if jobj.has_key("bufValueSize"):
+            n = jobj["bufValueSize"]
+            buf = bytearray(n)
+            view = memoryview(buf)
+            while n > 0:
+                nbytes = self.sock.recv_into(view, n)
+                view = view[nbytes:]
+                n -= nbytes
+            buf = numpy.frombuffer(buf, dtype=numpy.uint8)
+        return jobj, buf
+
+    def __open_camera(self):
+        # Get the camera ID to open as an argument.
+        camera_id = 0
+        for s in sys.argv[1:]:
+            if s[:7] == "camera=" and len(s) > 7:
+                camera_id = int(s[7:])
+        cmd = {"cmdName":"open", "cameraId":camera_id}
+        self.sock.send(json.dumps(cmd) + "\n")
+        data,_ = self.__read_response_from_socket()
+        if data['tag'] != 'cameraOpened':
+            raise its.error.Error('Invalid command response')
+
+    def __close_camera(self):
+        cmd = {"cmdName":"close"}
+        self.sock.send(json.dumps(cmd) + "\n")
+        data,_ = self.__read_response_from_socket()
+        if data['tag'] != 'cameraClosed':
+            raise its.error.Error('Invalid command response')
+
+    def do_vibrate(self, pattern):
+        """Cause the device to vibrate to a specific pattern.
+
+        Args:
+            pattern: Durations (ms) for which to turn on or off the vibrator.
+                The first value indicates the number of milliseconds to wait
+                before turning the vibrator on. The next value indicates the
+                number of milliseconds for which to keep the vibrator on
+                before turning it off. Subsequent values alternate between
+                durations in milliseconds to turn the vibrator off or to turn
+                the vibrator on.
+
+        Returns:
+            Nothing.
+        """
+        cmd = {}
+        cmd["cmdName"] = "doVibrate"
+        cmd["pattern"] = pattern
+        self.sock.send(json.dumps(cmd) + "\n")
+        data,_ = self.__read_response_from_socket()
+        if data['tag'] != 'vibrationStarted':
+            raise its.error.Error('Invalid command response')
+
+    def start_sensor_events(self):
+        """Start collecting sensor events on the device.
+
+        See get_sensor_events for more info.
+
+        Returns:
+            Nothing.
+        """
+        cmd = {}
+        cmd["cmdName"] = "startSensorEvents"
+        self.sock.send(json.dumps(cmd) + "\n")
+        data,_ = self.__read_response_from_socket()
+        if data['tag'] != 'sensorEventsStarted':
+            raise its.error.Error('Invalid command response')
+
+    def get_sensor_events(self):
+        """Get a trace of all sensor events on the device.
+
+        The trace starts when the start_sensor_events function is called. If
+        the test runs for a long time after this call, then the device's
+        internal memory can fill up. Calling get_sensor_events gets all events
+        from the device, and then stops the device from collecting events and
+        clears the internal buffer; to start again, the start_sensor_events
+        call must be used again.
+
+        Events from the accelerometer, compass, and gyro are returned; each
+        has a timestamp and x,y,z values.
+
+        Note that sensor events are only produced if the device isn't in its
+        standby mode (i.e.) if the screen is on.
+
+        Returns:
+            A Python dictionary with three keys ("accel", "mag", "gyro") each
+            of which maps to a list of objects containing "time","x","y","z"
+            keys.
+        """
+        cmd = {}
+        cmd["cmdName"] = "getSensorEvents"
+        self.sock.send(json.dumps(cmd) + "\n")
+        data,_ = self.__read_response_from_socket()
+        if data['tag'] != 'sensorEvents':
+            raise its.error.Error('Invalid command response')
+        return data['objValue']
+
+    def get_camera_properties(self):
+        """Get the camera properties object for the device.
+
+        Returns:
+            The Python dictionary object for the CameraProperties object.
+        """
+        cmd = {}
+        cmd["cmdName"] = "getCameraProperties"
+        self.sock.send(json.dumps(cmd) + "\n")
+        data,_ = self.__read_response_from_socket()
+        if data['tag'] != 'cameraProperties':
+            raise its.error.Error('Invalid command response')
+        return data['objValue']['cameraProperties']
+
+    def do_3a(self, regions_ae=[[0,0,1,1,1]],
+                    regions_awb=[[0,0,1,1,1]],
+                    regions_af=[[0,0,1,1,1]],
+                    do_ae=True, do_awb=True, do_af=True,
+                    lock_ae=False, lock_awb=False,
+                    get_results=False,
+                    ev_comp=0):
+        """Perform a 3A operation on the device.
+
+        Triggers some or all of AE, AWB, and AF, and returns once they have
+        converged. Uses the vendor 3A that is implemented inside the HAL.
+
+        Throws an assertion if 3A fails to converge.
+
+        Args:
+            regions_ae: List of weighted AE regions.
+            regions_awb: List of weighted AWB regions.
+            regions_af: List of weighted AF regions.
+            do_ae: Trigger AE and wait for it to converge.
+            do_awb: Wait for AWB to converge.
+            do_af: Trigger AF and wait for it to converge.
+            lock_ae: Request AE lock after convergence, and wait for it.
+            lock_awb: Request AWB lock after convergence, and wait for it.
+            get_results: Return the 3A results from this function.
+            ev_comp: An EV compensation value to use when running AE.
+
+        Region format in args:
+            Arguments are lists of weighted regions; each weighted region is a
+            list of 5 values, [x,y,w,h, wgt], and each argument is a list of
+            these 5-value lists. The coordinates are given as normalized
+            rectangles (x,y,w,h) specifying the region. For example:
+                [[0.0, 0.0, 1.0, 0.5, 5], [0.0, 0.5, 1.0, 0.5, 10]].
+            Weights are non-negative integers.
+
+        Returns:
+            Five values are returned if get_results is true::
+            * AE sensitivity; None if do_ae is False
+            * AE exposure time; None if do_ae is False
+            * AWB gains (list); None if do_awb is False
+            * AWB transform (list); None if do_awb is false
+            * AF focus position; None if do_af is false
+            Otherwise, it returns five None values.
+        """
+        print "Running vendor 3A on device"
+        cmd = {}
+        cmd["cmdName"] = "do3A"
+        cmd["regions"] = {"ae": sum(regions_ae, []),
+                          "awb": sum(regions_awb, []),
+                          "af": sum(regions_af, [])}
+        cmd["triggers"] = {"ae": do_ae, "af": do_af}
+        if lock_ae:
+            cmd["aeLock"] = True
+        if lock_awb:
+            cmd["awbLock"] = True
+        if ev_comp != 0:
+            cmd["evComp"] = ev_comp
+        self.sock.send(json.dumps(cmd) + "\n")
+
+        # Wait for each specified 3A to converge.
+        ae_sens = None
+        ae_exp = None
+        awb_gains = None
+        awb_transform = None
+        af_dist = None
+        converged = False
+        while True:
+            data,_ = self.__read_response_from_socket()
+            vals = data['strValue'].split()
+            if data['tag'] == 'aeResult':
+                ae_sens, ae_exp = [int(i) for i in vals]
+            elif data['tag'] == 'afResult':
+                af_dist = float(vals[0])
+            elif data['tag'] == 'awbResult':
+                awb_gains = [float(f) for f in vals[:4]]
+                awb_transform = [float(f) for f in vals[4:]]
+            elif data['tag'] == '3aConverged':
+                converged = True
+            elif data['tag'] == '3aDone':
+                break
+            else:
+                raise its.error.Error('Invalid command response')
+        if converged and not get_results:
+            return None,None,None,None,None
+        if (do_ae and ae_sens == None or do_awb and awb_gains == None
+                or do_af and af_dist == None or not converged):
+            raise its.error.Error('3A failed to converge')
+        return ae_sens, ae_exp, awb_gains, awb_transform, af_dist
+
+    def do_capture(self, cap_request, out_surfaces=None):
+        """Issue capture request(s), and read back the image(s) and metadata.
+
+        The main top-level function for capturing one or more images using the
+        device. Captures a single image if cap_request is a single object, and
+        captures a burst if it is a list of objects.
+
+        The out_surfaces field can specify the width(s), height(s), and
+        format(s) of the captured image. The formats may be "yuv", "jpeg",
+        "dng", "raw", or "raw10". The default is a YUV420 frame ("yuv")
+        corresponding to a full sensor frame.
+
+        Note that one or more surfaces can be specified, allowing a capture to
+        request images back in multiple formats (e.g.) raw+yuv, raw+jpeg,
+        yuv+jpeg, raw+yuv+jpeg. If the size is omitted for a surface, the
+        default is the largest resolution available for the format of that
+        surface. At most one output surface can be specified for a given format,
+        and raw+dng, raw10+dng, and raw+raw10 are not supported as combinations.
+
+        Example of a single capture request:
+
+            {
+                "android.sensor.exposureTime": 100*1000*1000,
+                "android.sensor.sensitivity": 100
+            }
+
+        Example of a list of capture requests:
+
+            [
+                {
+                    "android.sensor.exposureTime": 100*1000*1000,
+                    "android.sensor.sensitivity": 100
+                },
+                {
+                    "android.sensor.exposureTime": 100*1000*1000,
+                    "android.sensor.sensitivity": 200
+                }
+            ]
+
+        Examples of output surface specifications:
+
+            {
+                "width": 640,
+                "height": 480,
+                "format": "yuv"
+            }
+
+            [
+                {
+                    "format": "jpeg"
+                },
+                {
+                    "format": "raw"
+                }
+            ]
+
+        The following variables defined in this class are shortcuts for
+        specifying one or more formats where each output is the full size for
+        that format; they can be used as values for the out_surfaces arguments:
+
+            CAP_RAW
+            CAP_DNG
+            CAP_YUV
+            CAP_JPEG
+            CAP_RAW_YUV
+            CAP_DNG_YUV
+            CAP_RAW_JPEG
+            CAP_DNG_JPEG
+            CAP_YUV_JPEG
+            CAP_RAW_YUV_JPEG
+            CAP_DNG_YUV_JPEG
+
+        If multiple formats are specified, then this function returns multiple
+        capture objects, one for each requested format. If multiple formats and
+        multiple captures (i.e. a burst) are specified, then this function
+        returns multiple lists of capture objects. In both cases, the order of
+        the returned objects matches the order of the requested formats in the
+        out_surfaces parameter. For example:
+
+            yuv_cap            = do_capture( req1                           )
+            yuv_cap            = do_capture( req1,        yuv_fmt           )
+            yuv_cap,  raw_cap  = do_capture( req1,        [yuv_fmt,raw_fmt] )
+            yuv_caps           = do_capture( [req1,req2], yuv_fmt           )
+            yuv_caps, raw_caps = do_capture( [req1,req2], [yuv_fmt,raw_fmt] )
+
+        Args:
+            cap_request: The Python dict/list specifying the capture(s), which
+                will be converted to JSON and sent to the device.
+            out_surfaces: (Optional) specifications of the output image formats
+                and sizes to use for each capture.
+
+        Returns:
+            An object, list of objects, or list of lists of objects, where each
+            object contains the following fields:
+            * data: the image data as a numpy array of bytes.
+            * width: the width of the captured image.
+            * height: the height of the captured image.
+            * format: image the format, in ["yuv","jpeg","raw","raw10","dng"].
+            * metadata: the capture result object (Python dictionary).
+        """
+        cmd = {}
+        cmd["cmdName"] = "doCapture"
+        if not isinstance(cap_request, list):
+            cmd["captureRequests"] = [cap_request]
+        else:
+            cmd["captureRequests"] = cap_request
+        if out_surfaces is not None:
+            if not isinstance(out_surfaces, list):
+                cmd["outputSurfaces"] = [out_surfaces]
+            else:
+                cmd["outputSurfaces"] = out_surfaces
+            formats = [c["format"] if c.has_key("format") else "yuv"
+                       for c in cmd["outputSurfaces"]]
+            formats = [s if s != "jpg" else "jpeg" for s in formats]
+        else:
+            formats = ['yuv']
+        ncap = len(cmd["captureRequests"])
+        nsurf = 1 if out_surfaces is None else len(cmd["outputSurfaces"])
+        if len(formats) > len(set(formats)):
+            raise its.error.Error('Duplicate format requested')
+        if "dng" in formats and "raw" in formats or \
+                "dng" in formats and "raw10" in formats or \
+                "raw" in formats and "raw10" in formats:
+            raise its.error.Error('Different raw formats not supported')
+        print "Capturing %d frame%s with %d format%s [%s]" % (
+                  ncap, "s" if ncap>1 else "", nsurf, "s" if nsurf>1 else "",
+                  ",".join(formats))
+        self.sock.send(json.dumps(cmd) + "\n")
+
+        # Wait for ncap*nsurf images and ncap metadata responses.
+        # Assume that captures come out in the same order as requested in
+        # the burst, however individual images of different formats can come
+        # out in any order for that capture.
+        nbufs = 0
+        bufs = {"yuv":[], "raw":[], "raw10":[], "dng":[], "jpeg":[]}
+        mds = []
+        widths = None
+        heights = None
+        while nbufs < ncap*nsurf or len(mds) < ncap:
+            jsonObj,buf = self.__read_response_from_socket()
+            if jsonObj['tag'] in ['jpegImage', 'yuvImage', 'rawImage', \
+                    'raw10Image', 'dngImage'] and buf is not None:
+                fmt = jsonObj['tag'][:-5]
+                bufs[fmt].append(buf)
+                nbufs += 1
+            elif jsonObj['tag'] == 'captureResults':
+                mds.append(jsonObj['objValue']['captureResult'])
+                outputs = jsonObj['objValue']['outputs']
+                widths = [out['width'] for out in outputs]
+                heights = [out['height'] for out in outputs]
+            else:
+                # Just ignore other tags
+                None
+        rets = []
+        for j,fmt in enumerate(formats):
+            objs = []
+            for i in range(ncap):
+                obj = {}
+                obj["data"] = bufs[fmt][i]
+                obj["width"] = widths[j]
+                obj["height"] = heights[j]
+                obj["format"] = fmt
+                obj["metadata"] = mds[i]
+                objs.append(obj)
+            rets.append(objs if ncap>1 else objs[0])
+        return rets if len(rets)>1 else rets[0]
+
+def report_result(camera_id, success):
+    """Send a pass/fail result to the device, via an intent.
+
+    Args:
+        camera_id: The ID string of the camera for which to report pass/fail.
+        success: Boolean, indicating if the result was pass or fail.
+
+    Returns:
+        Nothing.
+    """
+    resultstr = "%s=%s" % (camera_id, 'True' if success else 'False')
+    _run(('%s shell am broadcast '
+          '-a %s --es %s %s') % (ItsSession.ADB, ItsSession.ACTION_ITS_RESULT,
+          ItsSession.EXTRA_SUCCESS, resultstr))
+
+
+def _run(cmd):
+    """Replacement for os.system, with hiding of stdout+stderr messages.
+    """
+    with open(os.devnull, 'wb') as devnull:
+        subprocess.check_call(
+                cmd.split(), stdout=devnull, stderr=subprocess.STDOUT)
+
+class __UnitTest(unittest.TestCase):
+    """Run a suite of unit tests on this module.
+    """
+
+    # TODO: Add some unit tests.
+    None
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/apps/CameraITS/pymodules/its/dng.py b/apps/CameraITS/pymodules/its/dng.py
new file mode 100644
index 0000000..f331d02
--- /dev/null
+++ b/apps/CameraITS/pymodules/its/dng.py
@@ -0,0 +1,174 @@
+# 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 numpy
+import numpy.linalg
+import unittest
+
+# Illuminant IDs
+A = 0
+D65 = 1
+
+def compute_cm_fm(illuminant, gains, ccm, cal):
+    """Compute the ColorMatrix (CM) and ForwardMatrix (FM).
+
+    Given a captured shot of a grey chart illuminated by either a D65 or a
+    standard A illuminant, the HAL will produce the WB gains and transform,
+    in the android.colorCorrection.gains and android.colorCorrection.transform
+    tags respectively. These values have both golden module and per-unit
+    calibration baked in.
+
+    This function is used to take the per-unit gains, ccm, and calibration
+    matrix, and compute the values that the DNG ColorMatrix and ForwardMatrix
+    for the specified illuminant should be. These CM and FM values should be
+    the same for all DNG files captured by all units of the same model (e.g.
+    all Nexus 5 units). The calibration matrix should be the same for all DNGs
+    saved by the same unit, but will differ unit-to-unit.
+
+    Args:
+        illuminant: 0 (A) or 1 (D65).
+        gains: White balance gains, as a list of 4 floats.
+        ccm: White balance transform matrix, as a list of 9 floats.
+        cal: Per-unit calibration matrix, as a list of 9 floats.
+
+    Returns:
+        CM: The 3x3 ColorMatrix for the specified illuminant, as a numpy array
+        FM: The 3x3 ForwardMatrix for the specified illuminant, as a numpy array
+    """
+
+    ###########################################################################
+    # Standard matrices.
+
+    # W is the matrix that maps sRGB to XYZ.
+    # See: http://www.brucelindbloom.com/
+    W = numpy.array([
+        [ 0.4124564,  0.3575761,  0.1804375],
+        [ 0.2126729,  0.7151522,  0.0721750],
+        [ 0.0193339,  0.1191920,  0.9503041]])
+
+    # HH is the chromatic adaptation matrix from D65 (since sRGB's ref white is
+    # D65) to D50 (since CIE XYZ's ref white is D50).
+    HH = numpy.array([
+        [ 1.0478112,  0.0228866, -0.0501270],
+        [ 0.0295424,  0.9904844, -0.0170491],
+        [-0.0092345,  0.0150436,  0.7521316]])
+
+    # H is a chromatic adaptation matrix from D65 (because sRGB's reference
+    # white is D65) to the calibration illuminant (which is a standard matrix
+    # depending on the illuminant). For a D65 illuminant, the matrix is the
+    # identity. For the A illuminant, the matrix uses the linear Bradford
+    # adaptation method to map from D65 to A.
+    # See: http://www.brucelindbloom.com/
+    H_D65 = numpy.array([
+        [ 1.0,        0.0,        0.0],
+        [ 0.0,        1.0,        0.0],
+        [ 0.0,        0.0,        1.0]])
+    H_A = numpy.array([
+        [ 1.2164557,  0.1109905, -0.1549325],
+        [ 0.1533326,  0.9152313, -0.0559953],
+        [-0.0239469,  0.0358984,  0.3147529]])
+    H = [H_A, H_D65][illuminant]
+
+    ###########################################################################
+    # Per-model matrices (that should be the same for all units of a particular
+    # phone/camera. These are statics in the HAL camera properties.
+
+    # G is formed by taking the r,g,b gains and putting them into a
+    # diagonal matrix.
+    G = numpy.array([[gains[0],0,0], [0,gains[1],0], [0,0,gains[3]]])
+
+    # S is just the CCM.
+    S = numpy.array([ccm[0:3], ccm[3:6], ccm[6:9]])
+
+    ###########################################################################
+    # Per-unit matrices.
+
+    # The per-unit calibration matrix for the given illuminant.
+    CC = numpy.array([cal[0:3],cal[3:6],cal[6:9]])
+
+    ###########################################################################
+    # Derived matrices. These should match up with DNG-related matrices
+    # provided by the HAL.
+
+    # The color matrix and forward matrix are computed as follows:
+    #   CM = inv(H * W * S * G * CC)
+    #   FM = HH * W * S
+    CM = numpy.linalg.inv(
+            numpy.dot(numpy.dot(numpy.dot(numpy.dot(H, W), S), G), CC))
+    FM = numpy.dot(numpy.dot(HH, W), S)
+
+    # The color matrix is normalized so that it maps the D50 (PCS) white
+    # point to a maximum component value of 1.
+    CM = CM / max(numpy.dot(CM, (0.9642957, 1.0, 0.8251046)))
+
+    return CM, FM
+
+def compute_asn(illuminant, cal, CM):
+    """Compute the AsShotNeutral DNG value.
+
+    This value is the only dynamic DNG value; the ForwardMatrix, ColorMatrix,
+    and CalibrationMatrix values should be the same for every DNG saved by
+    a given unit. The AsShotNeutral depends on the scene white balance
+    estimate.
+
+    This function computes what the DNG AsShotNeutral values should be, for
+    a given ColorMatrix (which is computed from the WB gains and CCM for a
+    shot taken of a grey chart under either A or D65 illuminants) and the
+    per-unit calibration matrix.
+
+    Args:
+        illuminant: 0 (A) or 1 (D65).
+        cal: Per-unit calibration matrix, as a list of 9 floats.
+        CM: The computed 3x3 ColorMatrix for the illuminant, as a numpy array.
+
+    Returns:
+        ASN: The AsShotNeutral value, as a length-3 numpy array.
+    """
+
+    ###########################################################################
+    # Standard matrices.
+
+    # XYZCAL is the  XYZ coordinate of calibration illuminant (so A or D65).
+    # See: Wyszecki & Stiles, "Color Science", second edition.
+    XYZCAL_A = numpy.array([1.098675, 1.0, 0.355916])
+    XYZCAL_D65 = numpy.array([0.950456, 1.0, 1.089058])
+    XYZCAL = [XYZCAL_A, XYZCAL_D65][illuminant]
+
+    ###########################################################################
+    # Per-unit matrices.
+
+    # The per-unit calibration matrix for the given illuminant.
+    CC = numpy.array([cal[0:3],cal[3:6],cal[6:9]])
+
+    ###########################################################################
+    # Derived matrices.
+
+    # The AsShotNeutral value is then the product of this final color matrix
+    # with the XYZ coordinate of calibration illuminant.
+    #   ASN = CC * CM * XYZCAL
+    ASN = numpy.dot(numpy.dot(CC, CM), XYZCAL)
+
+    # Normalize so the max vector element is 1.0.
+    ASN = ASN / max(ASN)
+
+    return ASN
+
+class __UnitTest(unittest.TestCase):
+    """Run a suite of unit tests on this module.
+    """
+    # TODO: Add more unit tests.
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/apps/CameraITS/pymodules/its/error.py b/apps/CameraITS/pymodules/its/error.py
new file mode 100644
index 0000000..884389b
--- /dev/null
+++ b/apps/CameraITS/pymodules/its/error.py
@@ -0,0 +1,26 @@
+# 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 unittest
+
+class Error(Exception):
+    pass
+
+class __UnitTest(unittest.TestCase):
+    """Run a suite of unit tests on this module.
+    """
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/apps/CameraITS/pymodules/its/image.py b/apps/CameraITS/pymodules/its/image.py
new file mode 100644
index 0000000..b3bdb65
--- /dev/null
+++ b/apps/CameraITS/pymodules/its/image.py
@@ -0,0 +1,747 @@
+# 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 matplotlib
+matplotlib.use('Agg')
+
+import its.error
+import pylab
+import sys
+import Image
+import numpy
+import math
+import unittest
+import cStringIO
+import scipy.stats
+import copy
+
+DEFAULT_YUV_TO_RGB_CCM = numpy.matrix([
+                                [1.000,  0.000,  1.402],
+                                [1.000, -0.344, -0.714],
+                                [1.000,  1.772,  0.000]])
+
+DEFAULT_YUV_OFFSETS = numpy.array([0, 128, 128])
+
+DEFAULT_GAMMA_LUT = numpy.array(
+        [math.floor(65535 * math.pow(i/65535.0, 1/2.2) + 0.5)
+         for i in xrange(65536)])
+
+DEFAULT_INVGAMMA_LUT = numpy.array(
+        [math.floor(65535 * math.pow(i/65535.0, 2.2) + 0.5)
+         for i in xrange(65536)])
+
+MAX_LUT_SIZE = 65536
+
+def convert_capture_to_rgb_image(cap,
+                                 ccm_yuv_to_rgb=DEFAULT_YUV_TO_RGB_CCM,
+                                 yuv_off=DEFAULT_YUV_OFFSETS,
+                                 props=None):
+    """Convert a captured image object to a RGB image.
+
+    Args:
+        cap: A capture object as returned by its.device.do_capture.
+        ccm_yuv_to_rgb: (Optional) the 3x3 CCM to convert from YUV to RGB.
+        yuv_off: (Optional) offsets to subtract from each of Y,U,V values.
+        props: (Optional) camera properties object (of static values);
+            required for processing raw images.
+
+    Returns:
+        RGB float-3 image array, with pixel values in [0.0, 1.0].
+    """
+    w = cap["width"]
+    h = cap["height"]
+    if cap["format"] == "raw10":
+        assert(props is not None)
+        cap = unpack_raw10_capture(cap, props)
+    if cap["format"] == "yuv":
+        y = cap["data"][0:w*h]
+        u = cap["data"][w*h:w*h*5/4]
+        v = cap["data"][w*h*5/4:w*h*6/4]
+        return convert_yuv420_to_rgb_image(y, u, v, w, h)
+    elif cap["format"] == "jpeg":
+        return decompress_jpeg_to_rgb_image(cap["data"])
+    elif cap["format"] == "raw":
+        assert(props is not None)
+        r,gr,gb,b = convert_capture_to_planes(cap, props)
+        return convert_raw_to_rgb_image(r,gr,gb,b, props, cap["metadata"])
+    else:
+        raise its.error.Error('Invalid format %s' % (cap["format"]))
+
+def unpack_raw10_capture(cap, props):
+    """Unpack a raw-10 capture to a raw-16 capture.
+
+    Args:
+        cap: A raw-10 capture object.
+        props: Camera properties object.
+
+    Returns:
+        New capture object with raw-16 data.
+    """
+    # Data is packed as 4x10b pixels in 5 bytes, with the first 4 bytes holding
+    # the MSPs of the pixels, and the 5th byte holding 4x2b LSBs.
+    w,h = cap["width"], cap["height"]
+    if w % 4 != 0:
+        raise its.error.Error('Invalid raw-10 buffer width')
+    cap = copy.deepcopy(cap)
+    cap["data"] = unpack_raw10_image(cap["data"].reshape(h,w*5/4))
+    cap["format"] = "raw"
+    return cap
+
+def unpack_raw10_image(img):
+    """Unpack a raw-10 image to a raw-16 image.
+
+    Output image will have the 10 LSBs filled in each 16b word, and the 6 MSBs
+    will be set to zero.
+
+    Args:
+        img: A raw-10 image, as a uint8 numpy array.
+
+    Returns:
+        Image as a uint16 numpy array, with all row padding stripped.
+    """
+    if img.shape[1] % 5 != 0:
+        raise its.error.Error('Invalid raw-10 buffer width')
+    w = img.shape[1]*4/5
+    h = img.shape[0]
+    # Cut out the 4x8b MSBs and shift to bits [10:2] in 16b words.
+    msbs = numpy.delete(img, numpy.s_[4::5], 1)
+    msbs = msbs.astype(numpy.uint16)
+    msbs = numpy.left_shift(msbs, 2)
+    msbs = msbs.reshape(h,w)
+    # Cut out the 4x2b LSBs and put each in bits [2:0] of their own 8b words.
+    lsbs = img[::, 4::5].reshape(h,w/4)
+    lsbs = numpy.right_shift(
+            numpy.packbits(numpy.unpackbits(lsbs).reshape(h,w/4,4,2),3), 6)
+    lsbs = lsbs.reshape(h,w)
+    # Fuse the MSBs and LSBs back together
+    img16 = numpy.bitwise_or(msbs, lsbs).reshape(h,w)
+    return img16
+
+def convert_capture_to_planes(cap, props=None):
+    """Convert a captured image object to separate image planes.
+
+    Decompose an image into multiple images, corresponding to different planes.
+
+    For YUV420 captures ("yuv"):
+        Returns Y,U,V planes, where the Y plane is full-res and the U,V planes
+        are each 1/2 x 1/2 of the full res.
+
+    For Bayer captures ("raw" or "raw10"):
+        Returns planes in the order R,Gr,Gb,B, regardless of the Bayer pattern
+        layout. Each plane is 1/2 x 1/2 of the full res.
+
+    For JPEG captures ("jpeg"):
+        Returns R,G,B full-res planes.
+
+    Args:
+        cap: A capture object as returned by its.device.do_capture.
+        props: (Optional) camera properties object (of static values);
+            required for processing raw images.
+
+    Returns:
+        A tuple of float numpy arrays (one per plane), consisting of pixel
+            values in the range [0.0, 1.0].
+    """
+    w = cap["width"]
+    h = cap["height"]
+    if cap["format"] == "raw10":
+        assert(props is not None)
+        cap = unpack_raw10_capture(cap, props)
+    if cap["format"] == "yuv":
+        y = cap["data"][0:w*h]
+        u = cap["data"][w*h:w*h*5/4]
+        v = cap["data"][w*h*5/4:w*h*6/4]
+        return ((y.astype(numpy.float32) / 255.0).reshape(h, w, 1),
+                (u.astype(numpy.float32) / 255.0).reshape(h/2, w/2, 1),
+                (v.astype(numpy.float32) / 255.0).reshape(h/2, w/2, 1))
+    elif cap["format"] == "jpeg":
+        rgb = decompress_jpeg_to_rgb_image(cap["data"]).reshape(w*h*3)
+        return (rgb[::3].reshape(h,w,1),
+                rgb[1::3].reshape(h,w,1),
+                rgb[2::3].reshape(h,w,1))
+    elif cap["format"] == "raw":
+        assert(props is not None)
+        white_level = float(props['android.sensor.info.whiteLevel'])
+        img = numpy.ndarray(shape=(h*w,), dtype='<u2',
+                            buffer=cap["data"][0:w*h*2])
+        img = img.astype(numpy.float32).reshape(h,w) / white_level
+        imgs = [img[::2].reshape(w*h/2)[::2].reshape(h/2,w/2,1),
+                img[::2].reshape(w*h/2)[1::2].reshape(h/2,w/2,1),
+                img[1::2].reshape(w*h/2)[::2].reshape(h/2,w/2,1),
+                img[1::2].reshape(w*h/2)[1::2].reshape(h/2,w/2,1)]
+        idxs = get_canonical_cfa_order(props)
+        return [imgs[i] for i in idxs]
+    else:
+        raise its.error.Error('Invalid format %s' % (cap["format"]))
+
+def get_canonical_cfa_order(props):
+    """Returns a mapping from the Bayer 2x2 top-left grid in the CFA to
+    the standard order R,Gr,Gb,B.
+
+    Args:
+        props: Camera properties object.
+
+    Returns:
+        List of 4 integers, corresponding to the positions in the 2x2 top-
+            left Bayer grid of R,Gr,Gb,B, where the 2x2 grid is labeled as
+            0,1,2,3 in row major order.
+    """
+    # Note that raw streams aren't croppable, so the cropRegion doesn't need
+    # to be considered when determining the top-left pixel color.
+    cfa_pat = props['android.sensor.info.colorFilterArrangement']
+    if cfa_pat == 0:
+        # RGGB
+        return [0,1,2,3]
+    elif cfa_pat == 1:
+        # GRBG
+        return [1,0,3,2]
+    elif cfa_pat == 2:
+        # GBRG
+        return [2,3,0,1]
+    elif cfa_pat == 3:
+        # BGGR
+        return [3,2,1,0]
+    else:
+        raise its.error.Error("Not supported")
+
+def get_gains_in_canonical_order(props, gains):
+    """Reorders the gains tuple to the canonical R,Gr,Gb,B order.
+
+    Args:
+        props: Camera properties object.
+        gains: List of 4 values, in R,G_even,G_odd,B order.
+
+    Returns:
+        List of gains values, in R,Gr,Gb,B order.
+    """
+    cfa_pat = props['android.sensor.info.colorFilterArrangement']
+    if cfa_pat in [0,1]:
+        # RGGB or GRBG, so G_even is Gr
+        return gains
+    elif cfa_pat in [2,3]:
+        # GBRG or BGGR, so G_even is Gb
+        return [gains[0], gains[2], gains[1], gains[3]]
+    else:
+        raise its.error.Error("Not supported")
+
+def convert_raw_to_rgb_image(r_plane, gr_plane, gb_plane, b_plane,
+                             props, cap_res):
+    """Convert a Bayer raw-16 image to an RGB image.
+
+    Includes some extremely rudimentary demosaicking and color processing
+    operations; the output of this function shouldn't be used for any image
+    quality analysis.
+
+    Args:
+        r_plane,gr_plane,gb_plane,b_plane: Numpy arrays for each color plane
+            in the Bayer image, with pixels in the [0.0, 1.0] range.
+        props: Camera properties object.
+        cap_res: Capture result (metadata) object.
+
+    Returns:
+        RGB float-3 image array, with pixel values in [0.0, 1.0]
+    """
+    # Values required for the RAW to RGB conversion.
+    assert(props is not None)
+    white_level = float(props['android.sensor.info.whiteLevel'])
+    black_levels = props['android.sensor.blackLevelPattern']
+    gains = cap_res['android.colorCorrection.gains']
+    ccm = cap_res['android.colorCorrection.transform']
+
+    # Reorder black levels and gains to R,Gr,Gb,B, to match the order
+    # of the planes.
+    idxs = get_canonical_cfa_order(props)
+    black_levels = [black_levels[i] for i in idxs]
+    gains = get_gains_in_canonical_order(props, gains)
+
+    # Convert CCM from rational to float, as numpy arrays.
+    ccm = numpy.array(its.objects.rational_to_float(ccm)).reshape(3,3)
+
+    # Need to scale the image back to the full [0,1] range after subtracting
+    # the black level from each pixel.
+    scale = white_level / (white_level - max(black_levels))
+
+    # Three-channel black levels, normalized to [0,1] by white_level.
+    black_levels = numpy.array([b/white_level for b in [
+            black_levels[i] for i in [0,1,3]]])
+
+    # Three-channel gains.
+    gains = numpy.array([gains[i] for i in [0,1,3]])
+
+    h,w = r_plane.shape[:2]
+    img = numpy.dstack([r_plane,(gr_plane+gb_plane)/2.0,b_plane])
+    img = (((img.reshape(h,w,3) - black_levels) * scale) * gains).clip(0.0,1.0)
+    img = numpy.dot(img.reshape(w*h,3), ccm.T).reshape(h,w,3).clip(0.0,1.0)
+    return img
+
+def convert_yuv420_to_rgb_image(y_plane, u_plane, v_plane,
+                                w, h,
+                                ccm_yuv_to_rgb=DEFAULT_YUV_TO_RGB_CCM,
+                                yuv_off=DEFAULT_YUV_OFFSETS):
+    """Convert a YUV420 8-bit planar image to an RGB image.
+
+    Args:
+        y_plane: The packed 8-bit Y plane.
+        u_plane: The packed 8-bit U plane.
+        v_plane: The packed 8-bit V plane.
+        w: The width of the image.
+        h: The height of the image.
+        ccm_yuv_to_rgb: (Optional) the 3x3 CCM to convert from YUV to RGB.
+        yuv_off: (Optional) offsets to subtract from each of Y,U,V values.
+
+    Returns:
+        RGB float-3 image array, with pixel values in [0.0, 1.0].
+    """
+    y = numpy.subtract(y_plane, yuv_off[0])
+    u = numpy.subtract(u_plane, yuv_off[1]).view(numpy.int8)
+    v = numpy.subtract(v_plane, yuv_off[2]).view(numpy.int8)
+    u = u.reshape(h/2, w/2).repeat(2, axis=1).repeat(2, axis=0)
+    v = v.reshape(h/2, w/2).repeat(2, axis=1).repeat(2, axis=0)
+    yuv = numpy.dstack([y, u.reshape(w*h), v.reshape(w*h)])
+    flt = numpy.empty([h, w, 3], dtype=numpy.float32)
+    flt.reshape(w*h*3)[:] = yuv.reshape(h*w*3)[:]
+    flt = numpy.dot(flt.reshape(w*h,3), ccm_yuv_to_rgb.T).clip(0, 255)
+    rgb = numpy.empty([h, w, 3], dtype=numpy.uint8)
+    rgb.reshape(w*h*3)[:] = flt.reshape(w*h*3)[:]
+    return rgb.astype(numpy.float32) / 255.0
+
+def load_yuv420_to_rgb_image(yuv_fname,
+                             w, h,
+                             ccm_yuv_to_rgb=DEFAULT_YUV_TO_RGB_CCM,
+                             yuv_off=DEFAULT_YUV_OFFSETS):
+    """Load a YUV420 image file, and return as an RGB image.
+
+    Args:
+        yuv_fname: The path of the YUV420 file.
+        w: The width of the image.
+        h: The height of the image.
+        ccm_yuv_to_rgb: (Optional) the 3x3 CCM to convert from YUV to RGB.
+        yuv_off: (Optional) offsets to subtract from each of Y,U,V values.
+
+    Returns:
+        RGB float-3 image array, with pixel values in [0.0, 1.0].
+    """
+    with open(yuv_fname, "rb") as f:
+        y = numpy.fromfile(f, numpy.uint8, w*h, "")
+        v = numpy.fromfile(f, numpy.uint8, w*h/4, "")
+        u = numpy.fromfile(f, numpy.uint8, w*h/4, "")
+        return convert_yuv420_to_rgb_image(y,u,v,w,h,ccm_yuv_to_rgb,yuv_off)
+
+def load_yuv420_to_yuv_planes(yuv_fname, w, h):
+    """Load a YUV420 image file, and return separate Y, U, and V plane images.
+
+    Args:
+        yuv_fname: The path of the YUV420 file.
+        w: The width of the image.
+        h: The height of the image.
+
+    Returns:
+        Separate Y, U, and V images as float-1 Numpy arrays, pixels in [0,1].
+        Note that pixel (0,0,0) is not black, since U,V pixels are centered at
+        0.5, and also that the Y and U,V plane images returned are different
+        sizes (due to chroma subsampling in the YUV420 format).
+    """
+    with open(yuv_fname, "rb") as f:
+        y = numpy.fromfile(f, numpy.uint8, w*h, "")
+        v = numpy.fromfile(f, numpy.uint8, w*h/4, "")
+        u = numpy.fromfile(f, numpy.uint8, w*h/4, "")
+        return ((y.astype(numpy.float32) / 255.0).reshape(h, w, 1),
+                (u.astype(numpy.float32) / 255.0).reshape(h/2, w/2, 1),
+                (v.astype(numpy.float32) / 255.0).reshape(h/2, w/2, 1))
+
+def decompress_jpeg_to_rgb_image(jpeg_buffer):
+    """Decompress a JPEG-compressed image, returning as an RGB image.
+
+    Args:
+        jpeg_buffer: The JPEG stream.
+
+    Returns:
+        A numpy array for the RGB image, with pixels in [0,1].
+    """
+    img = Image.open(cStringIO.StringIO(jpeg_buffer))
+    w = img.size[0]
+    h = img.size[1]
+    return numpy.array(img).reshape(h,w,3) / 255.0
+
+def apply_lut_to_image(img, lut):
+    """Applies a LUT to every pixel in a float image array.
+
+    Internally converts to a 16b integer image, since the LUT can work with up
+    to 16b->16b mappings (i.e. values in the range [0,65535]). The lut can also
+    have fewer than 65536 entries, however it must be sized as a power of 2
+    (and for smaller luts, the scale must match the bitdepth).
+
+    For a 16b lut of 65536 entries, the operation performed is:
+
+        lut[r * 65535] / 65535 -> r'
+        lut[g * 65535] / 65535 -> g'
+        lut[b * 65535] / 65535 -> b'
+
+    For a 10b lut of 1024 entries, the operation becomes:
+
+        lut[r * 1023] / 1023 -> r'
+        lut[g * 1023] / 1023 -> g'
+        lut[b * 1023] / 1023 -> b'
+
+    Args:
+        img: Numpy float image array, with pixel values in [0,1].
+        lut: Numpy table encoding a LUT, mapping 16b integer values.
+
+    Returns:
+        Float image array after applying LUT to each pixel.
+    """
+    n = len(lut)
+    if n <= 0 or n > MAX_LUT_SIZE or (n & (n - 1)) != 0:
+        raise its.error.Error('Invalid arg LUT size: %d' % (n))
+    m = float(n-1)
+    return (lut[(img * m).astype(numpy.uint16)] / m).astype(numpy.float32)
+
+def apply_matrix_to_image(img, mat):
+    """Multiplies a 3x3 matrix with each float-3 image pixel.
+
+    Each pixel is considered a column vector, and is left-multiplied by
+    the given matrix:
+
+        [     ]   r    r'
+        [ mat ] * g -> g'
+        [     ]   b    b'
+
+    Args:
+        img: Numpy float image array, with pixel values in [0,1].
+        mat: Numpy 3x3 matrix.
+
+    Returns:
+        The numpy float-3 image array resulting from the matrix mult.
+    """
+    h = img.shape[0]
+    w = img.shape[1]
+    img2 = numpy.empty([h, w, 3], dtype=numpy.float32)
+    img2.reshape(w*h*3)[:] = (numpy.dot(img.reshape(h*w, 3), mat.T)
+                             ).reshape(w*h*3)[:]
+    return img2
+
+def get_image_patch(img, xnorm, ynorm, wnorm, hnorm):
+    """Get a patch (tile) of an image.
+
+    Args:
+        img: Numpy float image array, with pixel values in [0,1].
+        xnorm,ynorm,wnorm,hnorm: Normalized (in [0,1]) coords for the tile.
+
+    Returns:
+        Float image array of the patch.
+    """
+    hfull = img.shape[0]
+    wfull = img.shape[1]
+    xtile = math.ceil(xnorm * wfull)
+    ytile = math.ceil(ynorm * hfull)
+    wtile = math.floor(wnorm * wfull)
+    htile = math.floor(hnorm * hfull)
+    return img[ytile:ytile+htile,xtile:xtile+wtile,:].copy()
+
+def compute_image_means(img):
+    """Calculate the mean of each color channel in the image.
+
+    Args:
+        img: Numpy float image array, with pixel values in [0,1].
+
+    Returns:
+        A list of mean values, one per color channel in the image.
+    """
+    means = []
+    chans = img.shape[2]
+    for i in xrange(chans):
+        means.append(numpy.mean(img[:,:,i], dtype=numpy.float64))
+    return means
+
+def compute_image_variances(img):
+    """Calculate the variance of each color channel in the image.
+
+    Args:
+        img: Numpy float image array, with pixel values in [0,1].
+
+    Returns:
+        A list of mean values, one per color channel in the image.
+    """
+    variances = []
+    chans = img.shape[2]
+    for i in xrange(chans):
+        variances.append(numpy.var(img[:,:,i], dtype=numpy.float64))
+    return variances
+
+def write_image(img, fname, apply_gamma=False):
+    """Save a float-3 numpy array image to a file.
+
+    Supported formats: PNG, JPEG, and others; see PIL docs for more.
+
+    Image can be 3-channel, which is interpreted as RGB, or can be 1-channel,
+    which is greyscale.
+
+    Can optionally specify that the image should be gamma-encoded prior to
+    writing it out; this should be done if the image contains linear pixel
+    values, to make the image look "normal".
+
+    Args:
+        img: Numpy image array data.
+        fname: Path of file to save to; the extension specifies the format.
+        apply_gamma: (Optional) apply gamma to the image prior to writing it.
+    """
+    if apply_gamma:
+        img = apply_lut_to_image(img, DEFAULT_GAMMA_LUT)
+    (h, w, chans) = img.shape
+    if chans == 3:
+        Image.fromarray((img * 255.0).astype(numpy.uint8), "RGB").save(fname)
+    elif chans == 1:
+        img3 = (img * 255.0).astype(numpy.uint8).repeat(3).reshape(h,w,3)
+        Image.fromarray(img3, "RGB").save(fname)
+    else:
+        raise its.error.Error('Unsupported image type')
+
+def downscale_image(img, f):
+    """Shrink an image by a given integer factor.
+
+    This function computes output pixel values by averaging over rectangular
+    regions of the input image; it doesn't skip or sample pixels, and all input
+    image pixels are evenly weighted.
+
+    If the downscaling factor doesn't cleanly divide the width and/or height,
+    then the remaining pixels on the right or bottom edge are discarded prior
+    to the downscaling.
+
+    Args:
+        img: The input image as an ndarray.
+        f: The downscaling factor, which should be an integer.
+
+    Returns:
+        The new (downscaled) image, as an ndarray.
+    """
+    h,w,chans = img.shape
+    f = int(f)
+    assert(f >= 1)
+    h = (h/f)*f
+    w = (w/f)*f
+    img = img[0:h:,0:w:,::]
+    chs = []
+    for i in xrange(chans):
+        ch = img.reshape(h*w*chans)[i::chans].reshape(h,w)
+        ch = ch.reshape(h,w/f,f).mean(2).reshape(h,w/f)
+        ch = ch.T.reshape(w/f,h/f,f).mean(2).T.reshape(h/f,w/f)
+        chs.append(ch.reshape(h*w/(f*f)))
+    img = numpy.vstack(chs).T.reshape(h/f,w/f,chans)
+    return img
+
+def __get_color_checker_patch(img, xc,yc, patch_size):
+    r = patch_size/2
+    tile = img[yc-r:yc+r:, xc-r:xc+r:, ::]
+    return tile
+
+def __measure_color_checker_patch(img, xc,yc, patch_size):
+    tile = __get_color_checker_patch(img, xc,yc, patch_size)
+    means = tile.mean(1).mean(0)
+    return means
+
+def get_color_checker_chart_patches(img, debug_fname_prefix=None):
+    """Return the center coords of each patch in a color checker chart.
+
+    Assumptions:
+    * Chart is vertical or horizontal w.r.t. camera, but not diagonal.
+    * Chart is (roughly) planar-parallel to the camera.
+    * Chart is centered in frame (roughly).
+    * Around/behind chart is white/grey background.
+    * The only black pixels in the image are from the chart.
+    * Chart is 100% visible and contained within image.
+    * No other objects within image.
+    * Image is well-exposed.
+    * Standard color checker chart with standard-sized black borders.
+
+    The values returned are in the coordinate system of the chart; that is,
+    patch (0,0) is the brown patch that is in the chart's top-left corner when
+    it is in the normal upright/horizontal orientation. (The chart may be any
+    of the four main orientations in the image.)
+
+    Args:
+        img: Input image, as a numpy array with pixels in [0,1].
+        debug_fname_prefix: If not None, the (string) name of a file prefix to
+            use to save a number of debug images for visualizing the output of
+            this function; can be used to see if the patches are being found
+            successfully.
+
+    Returns:
+        6x4 list of lists of integer (x,y) coords of the center of each patch,
+        ordered in the "chart order" (6x4 row major).
+    """
+
+    # Shrink the original image.
+    DOWNSCALE_FACTOR = 4
+    img_small = downscale_image(img, DOWNSCALE_FACTOR)
+
+    # Make a threshold image, which is 1.0 where the image is black,
+    # and 0.0 elsewhere.
+    BLACK_PIXEL_THRESH = 0.2
+    mask_img = scipy.stats.threshold(
+                img_small.max(2), BLACK_PIXEL_THRESH, 1.1, 0.0)
+    mask_img = 1.0 - scipy.stats.threshold(mask_img, -0.1, 0.1, 1.0)
+
+    if debug_fname_prefix is not None:
+        h,w = mask_img.shape
+        write_image(img, debug_fname_prefix+"_0.jpg")
+        write_image(mask_img.repeat(3).reshape(h,w,3),
+                debug_fname_prefix+"_1.jpg")
+
+    # Mask image flattened to a single row or column (by averaging).
+    # Also apply a threshold to these arrays.
+    FLAT_PIXEL_THRESH = 0.05
+    flat_row = mask_img.mean(0)
+    flat_col = mask_img.mean(1)
+    flat_row = [0 if v < FLAT_PIXEL_THRESH else 1 for v in flat_row]
+    flat_col = [0 if v < FLAT_PIXEL_THRESH else 1 for v in flat_col]
+
+    # Start and end of the non-zero region of the flattened row/column.
+    flat_row_nonzero = [i for i in range(len(flat_row)) if flat_row[i]>0]
+    flat_col_nonzero = [i for i in range(len(flat_col)) if flat_col[i]>0]
+    flat_row_min, flat_row_max = min(flat_row_nonzero), max(flat_row_nonzero)
+    flat_col_min, flat_col_max = min(flat_col_nonzero), max(flat_col_nonzero)
+
+    # Orientation of chart, and number of grid cells horz. and vertically.
+    orient = "h" if flat_row_max-flat_row_min>flat_col_max-flat_col_min else "v"
+    xgrids = 6 if orient=="h" else 4
+    ygrids = 6 if orient=="v" else 4
+
+    # Get better bounds on the patches region, lopping off some of the excess
+    # black border.
+    HRZ_BORDER_PAD_FRAC = 0.0138
+    VERT_BORDER_PAD_FRAC = 0.0395
+    xpad = HRZ_BORDER_PAD_FRAC if orient=="h" else VERT_BORDER_PAD_FRAC
+    ypad = HRZ_BORDER_PAD_FRAC if orient=="v" else VERT_BORDER_PAD_FRAC
+    xchart = flat_row_min + (flat_row_max - flat_row_min) * xpad
+    ychart = flat_col_min + (flat_col_max - flat_col_min) * ypad
+    wchart = (flat_row_max - flat_row_min) * (1 - 2*xpad)
+    hchart = (flat_col_max - flat_col_min) * (1 - 2*ypad)
+
+    # Get the colors of the 4 corner patches, in clockwise order, by measuring
+    # the average value of a small patch at each of the 4 patch centers.
+    colors = []
+    centers = []
+    for (x,y) in [(0,0), (xgrids-1,0), (xgrids-1,ygrids-1), (0,ygrids-1)]:
+        xc = xchart + (x + 0.5)*wchart/xgrids
+        yc = ychart + (y + 0.5)*hchart/ygrids
+        xc = int(xc * DOWNSCALE_FACTOR + 0.5)
+        yc = int(yc * DOWNSCALE_FACTOR + 0.5)
+        centers.append((xc,yc))
+        chan_means = __measure_color_checker_patch(img, xc,yc, 32)
+        colors.append(sum(chan_means) / len(chan_means))
+
+    # The brightest corner is the white patch, the darkest is the black patch.
+    # The black patch should be counter-clockwise from the white patch.
+    white_patch_index = None
+    for i in range(4):
+        if colors[i] == max(colors) and \
+                colors[(i-1+4)%4] == min(colors):
+            white_patch_index = i%4
+    assert(white_patch_index is not None)
+
+    # Return the coords of the origin (top-left when the chart is in the normal
+    # upright orientation) patch's center, and the vector displacement to the
+    # center of the second patch on the first row of the chart (when in the
+    # normal upright orientation).
+    origin_index = (white_patch_index+1)%4
+    prev_index = (origin_index-1+4)%4
+    next_index = (origin_index+1)%4
+    origin_center = centers[origin_index]
+    prev_center = centers[prev_index]
+    next_center = centers[next_index]
+    vec_across = tuple([(next_center[i]-origin_center[i])/5.0 for i in [0,1]])
+    vec_down = tuple([(prev_center[i]-origin_center[i])/3.0 for i in [0,1]])
+
+    # Compute the center of each patch.
+    patches = [[],[],[],[]]
+    for yi in range(4):
+        for xi in range(6):
+            x0,y0 = origin_center
+            dxh,dyh = vec_across
+            dxv,dyv = vec_down
+            xc = int(x0 + dxh*xi + dxv*yi)
+            yc = int(y0 + dyh*xi + dyv*yi)
+            patches[yi].append((xc,yc))
+
+    # Sanity check: test that the R,G,B,black,white patches are correct.
+    sanity_failed = False
+    patch_info = [(2,2,[0]), # Red
+                  (2,1,[1]), # Green
+                  (2,0,[2]), # Blue
+                  (3,0,[0,1,2]), # White
+                  (3,5,[])] # Black
+    for i in range(len(patch_info)):
+        yi,xi,high_chans = patch_info[i]
+        low_chans = [i for i in [0,1,2] if i not in high_chans]
+        xc,yc = patches[yi][xi]
+        means = __measure_color_checker_patch(img, xc,yc, 64)
+        if (min([means[i] for i in high_chans]+[1]) < \
+                max([means[i] for i in low_chans]+[0])):
+            sanity_failed = True
+
+    if debug_fname_prefix is not None:
+        gridimg = numpy.zeros([4*(32+2), 6*(32+2), 3])
+        for yi in range(4):
+            for xi in range(6):
+                xc,yc = patches[yi][xi]
+                tile = __get_color_checker_patch(img, xc,yc, 32)
+                gridimg[yi*(32+2)+1:yi*(32+2)+1+32,
+                        xi*(32+2)+1:xi*(32+2)+1+32, :] = tile
+        write_image(gridimg, debug_fname_prefix+"_2.png")
+
+    assert(not sanity_failed)
+
+    return patches
+
+class __UnitTest(unittest.TestCase):
+    """Run a suite of unit tests on this module.
+    """
+
+    # TODO: Add more unit tests.
+
+    def test_apply_matrix_to_image(self):
+        """Unit test for apply_matrix_to_image.
+
+        Test by using a canned set of values on a 1x1 pixel image.
+
+            [ 1 2 3 ]   [ 0.1 ]   [ 1.4 ]
+            [ 4 5 6 ] * [ 0.2 ] = [ 3.2 ]
+            [ 7 8 9 ]   [ 0.3 ]   [ 5.0 ]
+               mat         x         y
+        """
+        mat = numpy.array([[1,2,3],[4,5,6],[7,8,9]])
+        x = numpy.array([0.1,0.2,0.3]).reshape(1,1,3)
+        y = apply_matrix_to_image(x, mat).reshape(3).tolist()
+        y_ref = [1.4,3.2,5.0]
+        passed = all([math.fabs(y[i] - y_ref[i]) < 0.001 for i in xrange(3)])
+        self.assertTrue(passed)
+
+    def test_apply_lut_to_image(self):
+        """ Unit test for apply_lut_to_image.
+
+        Test by using a canned set of values on a 1x1 pixel image. The LUT will
+        simply double the value of the index:
+
+            lut[x] = 2*x
+        """
+        lut = numpy.array([2*i for i in xrange(65536)])
+        x = numpy.array([0.1,0.2,0.3]).reshape(1,1,3)
+        y = apply_lut_to_image(x, lut).reshape(3).tolist()
+        y_ref = [0.2,0.4,0.6]
+        passed = all([math.fabs(y[i] - y_ref[i]) < 0.001 for i in xrange(3)])
+        self.assertTrue(passed)
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/apps/CameraITS/pymodules/its/objects.py b/apps/CameraITS/pymodules/its/objects.py
new file mode 100644
index 0000000..22540b8
--- /dev/null
+++ b/apps/CameraITS/pymodules/its/objects.py
@@ -0,0 +1,274 @@
+# 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 os.path
+import sys
+import re
+import json
+import tempfile
+import time
+import unittest
+import subprocess
+import math
+
+def int_to_rational(i):
+    """Function to convert Python integers to Camera2 rationals.
+
+    Args:
+        i: Python integer or list of integers.
+
+    Returns:
+        Python dictionary or list of dictionaries representing the given int(s)
+        as rationals with denominator=1.
+    """
+    if isinstance(i, list):
+        return [{"numerator":val, "denominator":1} for val in i]
+    else:
+        return {"numerator":i, "denominator":1}
+
+def float_to_rational(f, denom=128):
+    """Function to convert Python floats to Camera2 rationals.
+
+    Args:
+        f: Python float or list of floats.
+        denom: (Optonal) the denominator to use in the output rationals.
+
+    Returns:
+        Python dictionary or list of dictionaries representing the given
+        float(s) as rationals.
+    """
+    if isinstance(f, list):
+        return [{"numerator":math.floor(val*denom+0.5), "denominator":denom}
+                for val in f]
+    else:
+        return {"numerator":math.floor(f*denom+0.5), "denominator":denom}
+
+def rational_to_float(r):
+    """Function to convert Camera2 rational objects to Python floats.
+
+    Args:
+        r: Rational or list of rationals, as Python dictionaries.
+
+    Returns:
+        Float or list of floats.
+    """
+    if isinstance(r, list):
+        return [float(val["numerator"]) / float(val["denominator"])
+                for val in r]
+    else:
+        return float(r["numerator"]) / float(r["denominator"])
+
+def manual_capture_request(sensitivity, exp_time, linear_tonemap=False):
+    """Return a capture request with everything set to manual.
+
+    Uses identity/unit color correction, and the default tonemap curve.
+    Optionally, the tonemap can be specified as being linear.
+
+    Args:
+        sensitivity: The sensitivity value to populate the request with.
+        exp_time: The exposure time, in nanoseconds, to populate the request
+            with.
+        linear_tonemap: [Optional] whether a linear tonemap should be used
+            in this request.
+
+    Returns:
+        The default manual capture request, ready to be passed to the
+        its.device.do_capture function.
+    """
+    req = {
+        "android.control.captureIntent": 6,
+        "android.control.mode": 0,
+        "android.control.aeMode": 0,
+        "android.control.awbMode": 0,
+        "android.control.afMode": 0,
+        "android.control.effectMode": 0,
+        "android.sensor.frameDuration": 0,
+        "android.sensor.sensitivity": sensitivity,
+        "android.sensor.exposureTime": exp_time,
+        "android.colorCorrection.mode": 0,
+        "android.colorCorrection.transform":
+                int_to_rational([1,0,0, 0,1,0, 0,0,1]),
+        "android.colorCorrection.gains": [1,1,1,1],
+        "android.tonemap.mode": 1,
+        "android.shading.mode": 1
+        }
+    if linear_tonemap:
+        req["android.tonemap.mode"] = 0
+        req["android.tonemap.curveRed"] = [0.0,0.0, 1.0,1.0]
+        req["android.tonemap.curveGreen"] = [0.0,0.0, 1.0,1.0]
+        req["android.tonemap.curveBlue"] = [0.0,0.0, 1.0,1.0]
+    return req
+
+def auto_capture_request():
+    """Return a capture request with everything set to auto.
+    """
+    return {
+        "android.control.mode": 1,
+        "android.control.aeMode": 1,
+        "android.control.awbMode": 1,
+        "android.control.afMode": 1,
+        "android.colorCorrection.mode": 1,
+        "android.tonemap.mode": 1,
+        }
+
+def fastest_auto_capture_request(props):
+    """Return an auto capture request for the fastest capture.
+
+    Args:
+        props: the object returned from its.device.get_camera_properties().
+
+    Returns:
+        A capture request with everything set to auto and all filters that
+            may slow down capture set to OFF or FAST if possible
+    """
+    req = auto_capture_request()
+    turn_slow_filters_off(props, req)
+
+    return req
+
+def get_available_output_sizes(fmt, props):
+    """Return a sorted list of available output sizes for a given format.
+
+    Args:
+        fmt: the output format, as a string in ["jpg", "yuv", "raw"].
+        props: the object returned from its.device.get_camera_properties().
+
+    Returns:
+        A sorted list of (w,h) tuples (sorted large-to-small).
+    """
+    fmt_codes = {"raw":0x20, "raw10":0x25, "yuv":0x23, "jpg":0x100, "jpeg":0x100}
+    configs = props['android.scaler.streamConfigurationMap']\
+                   ['availableStreamConfigurations']
+    fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_codes[fmt]]
+    out_configs = [cfg for cfg in fmt_configs if cfg['input'] == False]
+    out_sizes = [(cfg['width'],cfg['height']) for cfg in out_configs]
+    out_sizes.sort(reverse=True)
+    return out_sizes
+
+def set_filter_off_or_fast_if_possible(props, req, available_modes, filter):
+    """Check and set controlKey to off or fast in req.
+
+    Args:
+        props: the object returned from its.device.get_camera_properties().
+        req: the input request. filter will be set to OFF or FAST if possible.
+        available_modes: the key to check available modes.
+        filter: the filter key
+
+    Returns:
+        Nothing.
+    """
+    if props.has_key(available_modes):
+        if 0 in props[available_modes]:
+            req[filter] = 0
+        elif 1 in props[available_modes]:
+            req[filter] = 1
+
+def turn_slow_filters_off(props, req):
+    """Turn filters that may slow FPS down to OFF or FAST in input request.
+
+    This function modifies the request argument, such that filters that may
+    reduce the frames-per-second throughput of the camera device will be set to
+    OFF or FAST if possible.
+
+    Args:
+        props: the object returned from its.device.get_camera_properties().
+        req: the input request.
+
+    Returns:
+        Nothing.
+    """
+    set_filter_off_or_fast_if_possible(props, req,
+        "android.noiseReduction.availableNoiseReductionModes",
+        "android.noiseReduction.mode")
+    set_filter_off_or_fast_if_possible(props, req,
+        "android.colorCorrection.availableAberrationModes",
+        "android.colorCorrection.aberrationMode")
+    set_filter_off_or_fast_if_possible(props, req,
+        "android.hotPixel.availableHotPixelModes",
+        "android.hotPixel.mode")
+    set_filter_off_or_fast_if_possible(props, req,
+        "android.edge.availableEdgeModes",
+        "android.edge.mode")
+
+def get_fastest_manual_capture_settings(props):
+    """Return a capture request and format spec for the fastest capture.
+
+    Args:
+        props: the object returned from its.device.get_camera_properties().
+
+    Returns:
+        Two values, the first is a capture request, and the second is an output
+        format specification, for the fastest possible (legal) capture that
+        can be performed on this device (with the smallest output size).
+    """
+    fmt = "yuv"
+    size = get_available_output_sizes(fmt, props)[-1]
+    out_spec = {"format":fmt, "width":size[0], "height":size[1]}
+    s = min(props['android.sensor.info.sensitivityRange'])
+    e = min(props['android.sensor.info.exposureTimeRange'])
+    req = manual_capture_request(s,e)
+
+    turn_slow_filters_off(props, req)
+
+    return req, out_spec
+
+def get_max_digital_zoom(props):
+    """Returns the maximum amount of zooming possible by the camera device.
+
+    Args:
+        props: the object returned from its.device.get_camera_properties().
+
+    Return:
+        A float indicating the maximum amount of zooming possible by the
+        camera device.
+    """
+
+    maxz = 1.0
+
+    if props.has_key("android.scaler.availableMaxDigitalZoom"):
+        maxz = props["android.scaler.availableMaxDigitalZoom"]
+
+    return maxz
+
+
+class __UnitTest(unittest.TestCase):
+    """Run a suite of unit tests on this module.
+    """
+
+    def test_int_to_rational(self):
+        """Unit test for int_to_rational.
+        """
+        self.assertEqual(int_to_rational(10),
+                         {"numerator":10,"denominator":1})
+        self.assertEqual(int_to_rational([1,2]),
+                         [{"numerator":1,"denominator":1},
+                          {"numerator":2,"denominator":1}])
+
+    def test_float_to_rational(self):
+        """Unit test for float_to_rational.
+        """
+        self.assertEqual(float_to_rational(0.5001, 64),
+                        {"numerator":32, "denominator":64})
+
+    def test_rational_to_float(self):
+        """Unit test for rational_to_float.
+        """
+        self.assertTrue(
+                abs(rational_to_float({"numerator":32,"denominator":64})-0.5)
+                < 0.0001)
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/apps/CameraITS/pymodules/its/target.py b/apps/CameraITS/pymodules/its/target.py
new file mode 100644
index 0000000..3715f34
--- /dev/null
+++ b/apps/CameraITS/pymodules/its/target.py
@@ -0,0 +1,266 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.device
+import its.image
+import its.objects
+import os
+import os.path
+import sys
+import json
+import unittest
+import json
+
+CACHE_FILENAME = "its.target.cfg"
+
+def __do_target_exposure_measurement(its_session):
+    """Use device 3A and captured shots to determine scene exposure.
+
+    Creates a new ITS device session (so this function should not be called
+    while another session to the device is open).
+
+    Assumes that the camera is pointed at a scene that is reasonably uniform
+    and reasonably lit -- that is, an appropriate target for running the ITS
+    tests that assume such uniformity.
+
+    Measures the scene using device 3A and then by taking a shot to hone in on
+    the exact exposure level that will result in a center 10% by 10% patch of
+    the scene having a intensity level of 0.5 (in the pixel range of [0,1])
+    when a linear tonemap is used. That is, the pixels coming off the sensor
+    should be at approximately 50% intensity (however note that it's actually
+    the luma value in the YUV image that is being targeted to 50%).
+
+    The computed exposure value is the product of the sensitivity (ISO) and
+    exposure time (ns) to achieve that sensor exposure level.
+
+    Args:
+        its_session: Holds an open device session.
+
+    Returns:
+        The measured product of sensitivity and exposure time that results in
+            the luma channel of captured shots having an intensity of 0.5.
+    """
+    print "Measuring target exposure"
+
+    # Get AE+AWB lock first, so the auto values in the capture result are
+    # populated properly.
+    r = [[0.45, 0.45, 0.1, 0.1, 1]]
+    sens, exp_time, gains, xform, _ \
+            = its_session.do_3a(r,r,r,do_af=False,get_results=True)
+
+    # Convert the transform to rational.
+    xform_rat = [{"numerator":int(100*x),"denominator":100} for x in xform]
+
+    # Linear tonemap
+    tmap = sum([[i/63.0,i/63.0] for i in range(64)], [])
+
+    # Capture a manual shot with this exposure, using a linear tonemap.
+    # Use the gains+transform returned by the AWB pass.
+    req = its.objects.manual_capture_request(sens, exp_time)
+    req["android.tonemap.mode"] = 0
+    req["android.tonemap.curveRed"] = tmap
+    req["android.tonemap.curveGreen"] = tmap
+    req["android.tonemap.curveBlue"] = tmap
+    req["android.colorCorrection.transform"] = xform_rat
+    req["android.colorCorrection.gains"] = gains
+    cap = its_session.do_capture(req)
+
+    # Compute the mean luma of a center patch.
+    yimg,uimg,vimg = its.image.convert_capture_to_planes(cap)
+    tile = its.image.get_image_patch(yimg, 0.45, 0.45, 0.1, 0.1)
+    luma_mean = its.image.compute_image_means(tile)
+
+    # Compute the exposure value that would result in a luma of 0.5.
+    return sens * exp_time * 0.5 / luma_mean[0]
+
+def __set_cached_target_exposure(exposure):
+    """Saves the given exposure value to a cached location.
+
+    Once a value is cached, a call to __get_cached_target_exposure will return
+    the value, even from a subsequent test/script run. That is, the value is
+    persisted.
+
+    The value is persisted in a JSON file in the current directory (from which
+    the script calling this function is run).
+
+    Args:
+        exposure: The value to cache.
+    """
+    print "Setting cached target exposure"
+    with open(CACHE_FILENAME, "w") as f:
+        f.write(json.dumps({"exposure":exposure}))
+
+def __get_cached_target_exposure():
+    """Get the cached exposure value.
+
+    Returns:
+        The cached exposure value, or None if there is no valid cached value.
+    """
+    try:
+        with open(CACHE_FILENAME, "r") as f:
+            o = json.load(f)
+            return o["exposure"]
+    except:
+        return None
+
+def clear_cached_target_exposure():
+    """If there is a cached exposure value, clear it.
+    """
+    if os.path.isfile(CACHE_FILENAME):
+        os.remove(CACHE_FILENAME)
+
+def set_hardcoded_exposure(exposure):
+    """Set a hard-coded exposure value, rather than relying on measurements.
+
+    The exposure value is the product of sensitivity (ISO) and eposure time
+    (ns) that will result in a center-patch luma value of 0.5 (using a linear
+    tonemap) for the scene that the camera is pointing at.
+
+    If bringing up a new HAL implementation and the ability use the device to
+    measure the scene isn't there yet (e.g. device 3A doesn't work), then a
+    cache file of the appropriate name can be manually created and populated
+    with a hard-coded value using this function.
+
+    Args:
+        exposure: The hard-coded exposure value to set.
+    """
+    __set_cached_target_exposure(exposure)
+
+def get_target_exposure(its_session=None):
+    """Get the target exposure to use.
+
+    If there is a cached value and if the "target" command line parameter is
+    present, then return the cached value. Otherwise, measure a new value from
+    the scene, cache it, then return it.
+
+    Args:
+        its_session: Optional, holding an open device session.
+
+    Returns:
+        The target exposure value.
+    """
+    cached_exposure = None
+    for s in sys.argv[1:]:
+        if s == "target":
+            cached_exposure = __get_cached_target_exposure()
+    if cached_exposure is not None:
+        print "Using cached target exposure"
+        return cached_exposure
+    if its_session is None:
+        with its.device.ItsSession() as cam:
+            measured_exposure = __do_target_exposure_measurement(cam)
+    else:
+        measured_exposure = __do_target_exposure_measurement(its_session)
+    __set_cached_target_exposure(measured_exposure)
+    return measured_exposure
+
+def get_target_exposure_combos(its_session=None):
+    """Get a set of legal combinations of target (exposure time, sensitivity).
+
+    Gets the target exposure value, which is a product of sensitivity (ISO) and
+    exposure time, and returns equivalent tuples of (exposure time,sensitivity)
+    that are all legal and that correspond to the four extrema in this 2D param
+    space, as well as to two "middle" points.
+
+    Will open a device session if its_session is None.
+
+    Args:
+        its_session: Optional, holding an open device session.
+
+    Returns:
+        Object containing six legal (exposure time, sensitivity) tuples, keyed
+        by the following strings:
+            "minExposureTime"
+            "midExposureTime"
+            "maxExposureTime"
+            "minSensitivity"
+            "midSensitivity"
+            "maxSensitivity
+    """
+    if its_session is None:
+        with its.device.ItsSession() as cam:
+            exposure = get_target_exposure(cam)
+            props = cam.get_camera_properties()
+    else:
+        exposure = get_target_exposure(its_session)
+        props = its_session.get_camera_properties()
+
+    sens_range = props['android.sensor.info.sensitivityRange']
+    exp_time_range = props['android.sensor.info.exposureTimeRange']
+
+    # Combo 1: smallest legal exposure time.
+    e1_expt = exp_time_range[0]
+    e1_sens = exposure / e1_expt
+    if e1_sens > sens_range[1]:
+        e1_sens = sens_range[1]
+        e1_expt = exposure / e1_sens
+
+    # Combo 2: largest legal exposure time.
+    e2_expt = exp_time_range[1]
+    e2_sens = exposure / e2_expt
+    if e2_sens < sens_range[0]:
+        e2_sens = sens_range[0]
+        e2_expt = exposure / e2_sens
+
+    # Combo 3: smallest legal sensitivity.
+    e3_sens = sens_range[0]
+    e3_expt = exposure / e3_sens
+    if e3_expt > exp_time_range[1]:
+        e3_expt = exp_time_range[1]
+        e3_sens = exposure / e3_expt
+
+    # Combo 4: largest legal sensitivity.
+    e4_sens = sens_range[1]
+    e4_expt = exposure / e4_sens
+    if e4_expt < exp_time_range[0]:
+        e4_expt = exp_time_range[0]
+        e4_sens = exposure / e4_expt
+
+    # Combo 5: middle exposure time.
+    e5_expt = (exp_time_range[0] + exp_time_range[1]) / 2.0
+    e5_sens = exposure / e5_expt
+    if e5_sens > sens_range[1]:
+        e5_sens = sens_range[1]
+        e5_expt = exposure / e5_sens
+    if e5_sens < sens_range[0]:
+        e5_sens = sens_range[0]
+        e5_expt = exposure / e5_sens
+
+    # Combo 6: middle sensitivity.
+    e6_sens = (sens_range[0] + sens_range[1]) / 2.0
+    e6_expt = exposure / e6_sens
+    if e6_expt > exp_time_range[1]:
+        e6_expt = exp_time_range[1]
+        e6_sens = exposure / e6_expt
+    if e6_expt < exp_time_range[0]:
+        e6_expt = exp_time_range[0]
+        e6_sens = exposure / e6_expt
+
+    return {
+        "minExposureTime" : (int(e1_expt), int(e1_sens)),
+        "maxExposureTime" : (int(e2_expt), int(e2_sens)),
+        "minSensitivity" : (int(e3_expt), int(e3_sens)),
+        "maxSensitivity" : (int(e4_expt), int(e4_sens)),
+        "midExposureTime" : (int(e5_expt), int(e5_sens)),
+        "midSensitivity" : (int(e6_expt), int(e6_sens))
+        }
+
+class __UnitTest(unittest.TestCase):
+    """Run a suite of unit tests on this module.
+    """
+    # TODO: Add some unit tests.
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/apps/CameraITS/tests/dng_noise_model/DngNoiseModel.pdf b/apps/CameraITS/tests/dng_noise_model/DngNoiseModel.pdf
new file mode 100644
index 0000000..01389fa
--- /dev/null
+++ b/apps/CameraITS/tests/dng_noise_model/DngNoiseModel.pdf
Binary files differ
diff --git a/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py b/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
new file mode 100644
index 0000000..19b6c92
--- /dev/null
+++ b/apps/CameraITS/tests/dng_noise_model/dng_noise_model.py
@@ -0,0 +1,187 @@
+# 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.device
+import its.objects
+import its.image
+import pprint
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+import numpy
+import math
+
+def main():
+    """Compute the DNG noise model from a color checker chart.
+
+    TODO: Make this more robust; some manual futzing may be needed.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    with its.device.ItsSession() as cam:
+
+        props = cam.get_camera_properties()
+
+        white_level = float(props['android.sensor.info.whiteLevel'])
+        black_levels = props['android.sensor.blackLevelPattern']
+        idxs = its.image.get_canonical_cfa_order(props)
+        black_levels = [black_levels[i] for i in idxs]
+
+        # Expose for the scene with min sensitivity
+        sens_min, sens_max = props['android.sensor.info.sensitivityRange']
+        s_ae,e_ae,awb_gains,awb_ccm,_  = cam.do_3a(get_results=True)
+        s_e_prod = s_ae * e_ae
+
+        # Make the image brighter since the script looks at linear Bayer
+        # raw patches rather than gamma-encoded YUV patches (and the AE
+        # probably under-exposes a little for this use-case).
+        s_e_prod *= 2
+
+        # Capture raw frames across the full sensitivity range.
+        NUM_SENS_STEPS = 9
+        sens_step = int((sens_max - sens_min - 1) / float(NUM_SENS_STEPS))
+        reqs = []
+        sens = []
+        for s in range(sens_min, sens_max, sens_step):
+            e = int(s_e_prod / float(s))
+            req = its.objects.manual_capture_request(s, e)
+            req["android.colorCorrection.transform"] = \
+                    its.objects.float_to_rational(awb_ccm)
+            req["android.colorCorrection.gains"] = awb_gains
+            reqs.append(req)
+            sens.append(s)
+
+        caps = cam.do_capture(reqs, cam.CAP_RAW)
+
+        # A list of the (x,y) coords of the center pixel of a collection of
+        # patches of a color checker chart. Each patch should be uniform,
+        # however the actual color doesn't matter. Note that the coords are
+        # relative to the *converted* RGB image, which is 1/2 x 1/2 of the
+        # full size; convert back to full.
+        img = its.image.convert_capture_to_rgb_image(caps[0], props=props)
+        patches = its.image.get_color_checker_chart_patches(img, NAME+"_debug")
+        patches = [(2*x,2*y) for (x,y) in sum(patches,[])]
+
+        lines = []
+        for iouter, (s,cap) in enumerate(zip(sens,caps)):
+            # For each capture, compute the mean value in each patch, for each
+            # Bayer plane; discard patches where pixels are close to clamped.
+            # Also compute the variance.
+            CLAMP_THRESH = 0.2
+            planes = its.image.convert_capture_to_planes(cap, props)
+            points = []
+            for i,plane in enumerate(planes):
+                plane = (plane * white_level - black_levels[i]) / (
+                        white_level - black_levels[i])
+                for j,(x,y) in enumerate(patches):
+                    tile = plane[y/2-16:y/2+16:,x/2-16:x/2+16:,::]
+                    mean = its.image.compute_image_means(tile)[0]
+                    var = its.image.compute_image_variances(tile)[0]
+                    if (mean > CLAMP_THRESH and mean < 1.0-CLAMP_THRESH):
+                        # Each point is a (mean,variance) tuple for a patch;
+                        # for a given ISO, there should be a linear
+                        # relationship between these values.
+                        points.append((mean,var))
+
+            # Fit a line to the points, with a line equation: y = mx + b.
+            # This line is the relationship between mean and variance (i.e.)
+            # between signal level and noise, for this particular sensor.
+            # In the DNG noise model, the gradient (m) is "S", and the offset
+            # (b) is "O".
+            points.sort()
+            xs = [x for (x,y) in points]
+            ys = [y for (x,y) in points]
+            m,b = numpy.polyfit(xs, ys, 1)
+            lines.append((s,m,b))
+            print s, "->", m, b
+
+            # TODO: Clean up these checks (which currently fail in some cases).
+            # Some sanity checks:
+            # * Noise levels should increase with brightness.
+            # * Extrapolating to a black image, the noise should be positive.
+            # Basically, the "b" value should correspond to the read noise,
+            # which is the noise level if the sensor was operating in zero
+            # light.
+            #assert(m > 0)
+            #assert(b >= 0)
+
+            if iouter == 0:
+                pylab.plot(xs, ys, 'r', label="Measured")
+                pylab.plot([0,xs[-1]],[b,m*xs[-1]+b],'b', label="Fit")
+            else:
+                pylab.plot(xs, ys, 'r')
+                pylab.plot([0,xs[-1]],[b,m*xs[-1]+b],'b')
+
+        pylab.xlabel("Mean")
+        pylab.ylabel("Variance")
+        pylab.legend()
+        matplotlib.pyplot.savefig("%s_plot_mean_vs_variance.png" % (NAME))
+
+        # Now fit a line across the (m,b) line parameters for each sensitivity.
+        # The gradient (m) params are fit to the "S" line, and the offset (b)
+        # params are fit to the "O" line, both as a function of sensitivity.
+        gains = [d[0] for d in lines]
+        Ss = [d[1] for d in lines]
+        Os = [d[2] for d in lines]
+        mS,bS = numpy.polyfit(gains, Ss, 1)
+        mO,bO = numpy.polyfit(gains, Os, 1)
+
+        # Plot curve "O" as 10x, so it fits in the same scale as curve "S".
+        fig = matplotlib.pyplot.figure()
+        pylab.plot(gains, [10*o for o in Os], 'r', label="Measured")
+        pylab.plot([gains[0],gains[-1]],
+                [10*mO*gains[0]+10*bO, 10*mO*gains[-1]+10*bO],'r--',label="Fit")
+        pylab.plot(gains, Ss, 'b', label="Measured")
+        pylab.plot([gains[0],gains[-1]], [mS*gains[0]+bS,mS*gains[-1]+bS],'b--',
+                label="Fit")
+        pylab.xlabel("Sensitivity")
+        pylab.ylabel("Model parameter: S (blue), O x10 (red)")
+        pylab.legend()
+        matplotlib.pyplot.savefig("%s_plot_S_O.png" % (NAME))
+
+        print """
+        /* Generated test code to dump a table of data for external validation
+         * of the noise model parameters.
+         */
+        #include <stdio.h>
+        #include <assert.h>
+        double compute_noise_model_entry_S(int sens);
+        double compute_noise_model_entry_O(int sens);
+        int main(void) {
+            int sens;
+            for (sens = %d; sens <= %d; sens += 100) {
+                double o = compute_noise_model_entry_O(sens);
+                double s = compute_noise_model_entry_S(sens);
+                printf("%%d,%%lf,%%lf\\n", sens, o, s);
+            }
+            return 0;
+        }
+
+        /* Generated functions to map a given sensitivity to the O and S noise
+         * model parameters in the DNG noise model.
+         */
+        double compute_noise_model_entry_S(int sens) {
+            double s = %e * sens + %e;
+            return s < 0.0 ? 0.0 : s;
+        }
+        double compute_noise_model_entry_O(int sens) {
+            double o = %e * sens + %e;
+            return o < 0.0 ? 0.0 : o;
+        }
+        """%(sens_min,sens_max,mS,bS,mO,bO)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/inprog/scene2/README b/apps/CameraITS/tests/inprog/scene2/README
new file mode 100644
index 0000000..3a0953f
--- /dev/null
+++ b/apps/CameraITS/tests/inprog/scene2/README
@@ -0,0 +1,8 @@
+Scene 2 requires a camera lab with controlled illuminants, for example
+light sources capable of producing D65, D50, A, TL84, etc. illumination.
+Specific charts may also be required, for example grey cards, color
+checker charts, and resolution charts. The individual tests will specify
+the setup that they require.
+
+If a test requires that the camera be in any particular orientaion, it will
+specify this too. Otherwise, the camara can be in either portrait or lanscape.
diff --git a/apps/CameraITS/tests/inprog/scene2/test_dng_tags.py b/apps/CameraITS/tests/inprog/scene2/test_dng_tags.py
new file mode 100644
index 0000000..0c96ca7
--- /dev/null
+++ b/apps/CameraITS/tests/inprog/scene2/test_dng_tags.py
@@ -0,0 +1,94 @@
+# 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.dng
+import its.objects
+import numpy
+import os.path
+
+def main():
+    """Test that the DNG tags are internally self-consistent.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+
+        # Assumes that illuminant 1 is D65, and illuminant 2 is standard A.
+        # TODO: Generalize DNG tags check for any provided illuminants.
+        illum_code = [21, 17] # D65, A
+        illum_str = ['D65', 'A']
+        ref_str = ['android.sensor.referenceIlluminant%d'%(i) for i in [1,2]]
+        cm_str = ['android.sensor.colorTransform%d'%(i) for i in [1,2]]
+        fm_str = ['android.sensor.forwardMatrix%d'%(i) for i in [1,2]]
+        cal_str = ['android.sensor.calibrationTransform%d'%(i) for i in [1,2]]
+        dng_illum = [its.dng.D65, its.dng.A]
+
+        for i in [0,1]:
+            assert(props[ref_str[i]] == illum_code[i])
+            raw_input("\n[Point camera at grey card under %s and press ENTER]"%(
+                    illum_str[i]))
+
+            cam.do_3a(do_af=False)
+            cap = cam.do_capture(its.objects.auto_capture_request())
+            gains = cap["metadata"]["android.colorCorrection.gains"]
+            ccm = its.objects.rational_to_float(
+                    cap["metadata"]["android.colorCorrection.transform"])
+            cal = its.objects.rational_to_float(props[cal_str[i]])
+            print "HAL reported gains:\n", numpy.array(gains)
+            print "HAL reported ccm:\n", numpy.array(ccm).reshape(3,3)
+            print "HAL reported cal:\n", numpy.array(cal).reshape(3,3)
+
+            # Dump the image.
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(img, "%s_%s.jpg" % (NAME, illum_str[i]))
+
+            # Compute the matrices that are expected under this illuminant from
+            # the HAL-reported WB gains, CCM, and calibration matrix.
+            cm, fm = its.dng.compute_cm_fm(dng_illum[i], gains, ccm, cal)
+            asn = its.dng.compute_asn(dng_illum[i], cal, cm)
+            print "Expected ColorMatrix:\n", cm
+            print "Expected ForwardMatrix:\n", fm
+            print "Expected AsShotNeutral:\n", asn
+
+            # Get the matrices that are reported by the HAL for this
+            # illuminant.
+            cm_ref = numpy.array(its.objects.rational_to_float(
+                    props[cm_str[i]])).reshape(3,3)
+            fm_ref = numpy.array(its.objects.rational_to_float(
+                    props[fm_str[i]])).reshape(3,3)
+            asn_ref = numpy.array(its.objects.rational_to_float(
+                    cap['metadata']['android.sensor.neutralColorPoint']))
+            print "Reported ColorMatrix:\n", cm_ref
+            print "Reported ForwardMatrix:\n", fm_ref
+            print "Reported AsShotNeutral:\n", asn_ref
+
+            # The color matrix may be scaled (between the reported and
+            # expected values).
+            cm_scale = cm.mean(1).mean(0) / cm_ref.mean(1).mean(0)
+            print "ColorMatrix scale factor:", cm_scale
+
+            # Compute the deltas between reported and expected.
+            print "Ratios in ColorMatrix:\n", cm / cm_ref
+            print "Deltas in ColorMatrix (after normalizing):\n", cm/cm_scale - cm_ref
+            print "Deltas in ForwardMatrix:\n", fm - fm_ref
+            print "Deltas in AsShotNeutral:\n", asn - asn_ref
+
+            # TODO: Add pass/fail test on DNG matrices.
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/inprog/test_3a_remote.py b/apps/CameraITS/tests/inprog/test_3a_remote.py
new file mode 100644
index 0000000..c76ff6d
--- /dev/null
+++ b/apps/CameraITS/tests/inprog/test_3a_remote.py
@@ -0,0 +1,70 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.device
+import its.objects
+import os.path
+import pprint
+import math
+import numpy
+import matplotlib.pyplot
+import mpl_toolkits.mplot3d
+
+def main():
+    """Run 3A remotely (from this script).
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        w_map = props["android.lens.info.shadingMapSize"]["width"]
+        h_map = props["android.lens.info.shadingMapSize"]["height"]
+
+        # TODO: Test for 3A convergence, and exit this test once converged.
+
+        triggered = False
+        while True:
+            req = its.objects.auto_capture_request()
+            req["android.statistics.lensShadingMapMode"] = 1
+            req['android.control.aePrecaptureTrigger'] = (0 if triggered else 1)
+            req['android.control.afTrigger'] = (0 if triggered else 1)
+            triggered = True
+
+            cap = cam.do_capture(req)
+
+            ae_state = cap["metadata"]["android.control.aeState"]
+            awb_state = cap["metadata"]["android.control.awbState"]
+            af_state = cap["metadata"]["android.control.afState"]
+            gains = cap["metadata"]["android.colorCorrection.gains"]
+            transform = cap["metadata"]["android.colorCorrection.transform"]
+            exp_time = cap["metadata"]['android.sensor.exposureTime']
+            lsc_map = cap["metadata"]["android.statistics.lensShadingMap"]
+            foc_dist = cap["metadata"]['android.lens.focusDistance']
+            foc_range = cap["metadata"]['android.lens.focusRange']
+
+            print "States (AE,AWB,AF):", ae_state, awb_state, af_state
+            print "Gains:", gains
+            print "Transform:", [its.objects.rational_to_float(t)
+                                 for t in transform]
+            print "AE region:", cap["metadata"]['android.control.aeRegions']
+            print "AF region:", cap["metadata"]['android.control.afRegions']
+            print "AWB region:", cap["metadata"]['android.control.awbRegions']
+            print "LSC map:", w_map, h_map, lsc_map[:8]
+            print "Focus (dist,range):", foc_dist, foc_range
+            print ""
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/inprog/test_black_level.py b/apps/CameraITS/tests/inprog/test_black_level.py
new file mode 100644
index 0000000..37dab94
--- /dev/null
+++ b/apps/CameraITS/tests/inprog/test_black_level.py
@@ -0,0 +1,99 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.device
+import its.objects
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+import numpy
+
+def main():
+    """Black level consistence test.
+
+    Test: capture dark frames and check if black level correction is done
+    correctly.
+    1. Black level should be roughly consistent for repeating shots.
+    2. Noise distribution should be roughly centered at black level.
+
+    Shoot with the camera covered (i.e.) dark/black. The test varies the
+    sensitivity parameter.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    NUM_REPEAT = 3
+    NUM_STEPS = 3
+
+    # Only check the center part where LSC has little effects.
+    R = 200
+
+    # The most frequent pixel value in each image; assume this is the black
+    # level, since the images are all dark (shot with the lens covered).
+    ymodes = []
+    umodes = []
+    vmodes = []
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        sens_range = props['android.sensor.info.sensitivityRange']
+        sens_step = (sens_range[1] - sens_range[0]) / float(NUM_STEPS-1)
+        sensitivities = [sens_range[0] + i*sens_step for i in range(NUM_STEPS)]
+        print "Sensitivities:", sensitivities
+
+        for si, s in enumerate(sensitivities):
+            for rep in xrange(NUM_REPEAT):
+                req = its.objects.manual_capture_request(100, 1*1000*1000)
+                req["android.blackLevel.lock"] = True
+                req["android.sensor.sensitivity"] = s
+                cap = cam.do_capture(req)
+                yimg,uimg,vimg = its.image.convert_capture_to_planes(cap)
+                w = cap["width"]
+                h = cap["height"]
+
+                # Magnify the noise in saved images to help visualize.
+                its.image.write_image(yimg * 2,
+                                      "%s_s=%05d_y.jpg" % (NAME, s), True)
+                its.image.write_image(numpy.absolute(uimg - 0.5) * 2,
+                                      "%s_s=%05d_u.jpg" % (NAME, s), True)
+
+                yimg = yimg[w/2-R:w/2+R, h/2-R:h/2+R]
+                uimg = uimg[w/4-R/2:w/4+R/2, w/4-R/2:w/4+R/2]
+                vimg = vimg[w/4-R/2:w/4+R/2, w/4-R/2:w/4+R/2]
+                yhist,_ = numpy.histogram(yimg*255, 256, (0,256))
+                ymodes.append(numpy.argmax(yhist))
+                uhist,_ = numpy.histogram(uimg*255, 256, (0,256))
+                umodes.append(numpy.argmax(uhist))
+                vhist,_ = numpy.histogram(vimg*255, 256, (0,256))
+                vmodes.append(numpy.argmax(vhist))
+
+                # Take 32 bins from Y, U, and V.
+                # Histograms of U and V are cropped at the center of 128.
+                pylab.plot(range(32), yhist.tolist()[0:32], 'rgb'[si])
+                pylab.plot(range(32), uhist.tolist()[112:144], 'rgb'[si]+'--')
+                pylab.plot(range(32), vhist.tolist()[112:144], 'rgb'[si]+'--')
+
+    pylab.xlabel("DN: Y[0:32], U[112:144], V[112:144]")
+    pylab.ylabel("Pixel count")
+    pylab.title("Histograms for different sensitivities")
+    matplotlib.pyplot.savefig("%s_plot_histograms.png" % (NAME))
+
+    print "Y black levels:", ymodes
+    print "U black levels:", umodes
+    print "V black levels:", vmodes
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/inprog/test_blc_lsc.py b/apps/CameraITS/tests/inprog/test_blc_lsc.py
new file mode 100644
index 0000000..ce120a2
--- /dev/null
+++ b/apps/CameraITS/tests/inprog/test_blc_lsc.py
@@ -0,0 +1,106 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.device
+import its.objects
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Test that BLC and LSC look reasonable.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    r_means_center = []
+    g_means_center = []
+    b_means_center = []
+    r_means_corner = []
+    g_means_corner = []
+    b_means_corner = []
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        expt_range = props['android.sensor.info.exposureTimeRange']
+
+        # Get AE+AWB lock first, so the auto values in the capture result are
+        # populated properly.
+        r = [[0,0,1,1,1]]
+        ae_sen,ae_exp,awb_gains,awb_transform,_ \
+                = cam.do_3a(r,r,r,do_af=False,get_results=True)
+        print "AE:", ae_sen, ae_exp / 1000000.0
+        print "AWB:", awb_gains, awb_transform
+
+        # Set analog gain (sensitivity) to 800
+        ae_exp = ae_exp * ae_sen / 800
+        ae_sen = 800
+
+        # Capture range of exposures from 1/100x to 4x of AE estimate.
+        exposures = [ae_exp*x/100.0 for x in [1]+range(10,401,40)]
+        exposures = [e for e in exposures
+                     if e >= expt_range[0] and e <= expt_range[1]]
+
+        # Convert the transform back to rational.
+        awb_transform_rat = its.objects.float_to_rational(awb_transform)
+
+        # Linear tonemap
+        tmap = sum([[i/63.0,i/63.0] for i in range(64)], [])
+
+        reqs = []
+        for e in exposures:
+            req = its.objects.manual_capture_request(ae_sen,e)
+            req["android.tonemap.mode"] = 0
+            req["android.tonemap.curveRed"] = tmap
+            req["android.tonemap.curveGreen"] = tmap
+            req["android.tonemap.curveBlue"] = tmap
+            req["android.colorCorrection.transform"] = awb_transform_rat
+            req["android.colorCorrection.gains"] = awb_gains
+            reqs.append(req)
+
+        caps = cam.do_capture(reqs)
+        for i,cap in enumerate(caps):
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(img, "%s_i=%d.jpg"%(NAME, i))
+
+            tile_center = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            rgb_means = its.image.compute_image_means(tile_center)
+            r_means_center.append(rgb_means[0])
+            g_means_center.append(rgb_means[1])
+            b_means_center.append(rgb_means[2])
+
+            tile_corner = its.image.get_image_patch(img, 0.0, 0.0, 0.1, 0.1)
+            rgb_means = its.image.compute_image_means(tile_corner)
+            r_means_corner.append(rgb_means[0])
+            g_means_corner.append(rgb_means[1])
+            b_means_corner.append(rgb_means[2])
+
+    fig = matplotlib.pyplot.figure()
+    pylab.plot(exposures, r_means_center, 'r')
+    pylab.plot(exposures, g_means_center, 'g')
+    pylab.plot(exposures, b_means_center, 'b')
+    pylab.ylim([0,1])
+    matplotlib.pyplot.savefig("%s_plot_means_center.png" % (NAME))
+
+    fig = matplotlib.pyplot.figure()
+    pylab.plot(exposures, r_means_corner, 'r')
+    pylab.plot(exposures, g_means_corner, 'g')
+    pylab.plot(exposures, b_means_corner, 'b')
+    pylab.ylim([0,1])
+    matplotlib.pyplot.savefig("%s_plot_means_corner.png" % (NAME))
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/inprog/test_burst_sameness_auto.py b/apps/CameraITS/tests/inprog/test_burst_sameness_auto.py
new file mode 100644
index 0000000..87500c7
--- /dev/null
+++ b/apps/CameraITS/tests/inprog/test_burst_sameness_auto.py
@@ -0,0 +1,91 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import os.path
+import numpy
+
+def main():
+    """Take long bursts of images and check that they're all identical.
+
+    Assumes a static scene. Can be used to idenfity if there are sporadic
+    frames that are processed differently or have artifacts, or if 3A isn't
+    stable, since this test converges 3A at the start but doesn't lock 3A
+    throughout capture.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    BURST_LEN = 50
+    BURSTS = 5
+    FRAMES = BURST_LEN * BURSTS
+
+    SPREAD_THRESH = 0.03
+
+    with its.device.ItsSession() as cam:
+
+        # Capture at the smallest resolution.
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.manual_sensor(props))
+
+        _, fmt = its.objects.get_fastest_manual_capture_settings(props)
+        w,h = fmt["width"], fmt["height"]
+
+        # Converge 3A prior to capture.
+        cam.do_3a(lock_ae=True, lock_awb=True)
+
+        # After 3A has converged, lock AE+AWB for the duration of the test.
+        req = its.objects.fastest_auto_capture_request(props)
+        req["android.blackLevel.lock"] = True
+        req["android.control.awbLock"] = True
+        req["android.control.aeLock"] = True
+
+        # Capture bursts of YUV shots.
+        # Get the mean values of a center patch for each.
+        # Also build a 4D array, which is an array of all RGB images.
+        r_means = []
+        g_means = []
+        b_means = []
+        imgs = numpy.empty([FRAMES,h,w,3])
+        for j in range(BURSTS):
+            caps = cam.do_capture([req]*BURST_LEN, [fmt])
+            for i,cap in enumerate(caps):
+                n = j*BURST_LEN + i
+                imgs[n] = its.image.convert_capture_to_rgb_image(cap)
+                tile = its.image.get_image_patch(imgs[n], 0.45, 0.45, 0.1, 0.1)
+                means = its.image.compute_image_means(tile)
+                r_means.append(means[0])
+                g_means.append(means[1])
+                b_means.append(means[2])
+
+        # Dump all images.
+        print "Dumping images"
+        for i in range(FRAMES):
+            its.image.write_image(imgs[i], "%s_frame%03d.jpg"%(NAME,i))
+
+        # The mean image.
+        img_mean = imgs.mean(0)
+        its.image.write_image(img_mean, "%s_mean.jpg"%(NAME))
+
+        # Pass/fail based on center patch similarity.
+        for means in [r_means, g_means, b_means]:
+            spread = max(means) - min(means)
+            print spread
+            assert(spread < SPREAD_THRESH)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/inprog/test_burst_sameness_fullres_auto.py b/apps/CameraITS/tests/inprog/test_burst_sameness_fullres_auto.py
new file mode 100644
index 0000000..932c051
--- /dev/null
+++ b/apps/CameraITS/tests/inprog/test_burst_sameness_fullres_auto.py
@@ -0,0 +1,91 @@
+# 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
+import numpy
+import pylab
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Take long bursts of images and check that they're all identical.
+
+    Assumes a static scene. Can be used to idenfity if there are sporadic
+    frames that are processed differently or have artifacts, or if 3A isn't
+    stable, since this test converges 3A at the start but doesn't lock 3A
+    throughout capture.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    BURST_LEN = 6
+    BURSTS = 2
+    FRAMES = BURST_LEN * BURSTS
+
+    DELTA_THRESH = 0.1
+
+    with its.device.ItsSession() as cam:
+
+        # Capture at full resolution.
+        props = cam.get_camera_properties()
+        w,h = its.objects.get_available_output_sizes("yuv", props)[0]
+
+        # Converge 3A prior to capture.
+        cam.do_3a(lock_ae=True, lock_awb=True)
+
+        # After 3A has converged, lock AE+AWB for the duration of the test.
+        req = its.objects.fastest_auto_capture_request(props)
+        req["android.blackLevel.lock"] = True
+        req["android.control.awbLock"] = True
+        req["android.control.aeLock"] = True
+
+        # Capture bursts of YUV shots.
+        # Build a 4D array, which is an array of all RGB images after down-
+        # scaling them by a factor of 4x4.
+        imgs = numpy.empty([FRAMES,h/4,w/4,3])
+        for j in range(BURSTS):
+            caps = cam.do_capture([req]*BURST_LEN)
+            for i,cap in enumerate(caps):
+                n = j*BURST_LEN + i
+                imgs[n] = its.image.downscale_image(
+                        its.image.convert_capture_to_rgb_image(cap), 4)
+
+        # Dump all images.
+        print "Dumping images"
+        for i in range(FRAMES):
+            its.image.write_image(imgs[i], "%s_frame%03d.jpg"%(NAME,i))
+
+        # The mean image.
+        img_mean = imgs.mean(0)
+        its.image.write_image(img_mean, "%s_mean.jpg"%(NAME))
+
+        # Compute the deltas of each image from the mean image; this test
+        # passes if none of the deltas are large.
+        print "Computing frame differences"
+        delta_maxes = []
+        for i in range(FRAMES):
+            deltas = (imgs[i] - img_mean).reshape(h*w*3/16)
+            delta_max_pos = numpy.max(deltas)
+            delta_max_neg = numpy.min(deltas)
+            delta_maxes.append(max(abs(delta_max_pos), abs(delta_max_neg)))
+        max_delta_max = max(delta_maxes)
+        print "Frame %d has largest diff %f" % (
+                delta_maxes.index(max_delta_max), max_delta_max)
+        assert(max_delta_max < DELTA_THRESH)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/inprog/test_crop_region.py b/apps/CameraITS/tests/inprog/test_crop_region.py
new file mode 100644
index 0000000..396603f
--- /dev/null
+++ b/apps/CameraITS/tests/inprog/test_crop_region.py
@@ -0,0 +1,67 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import its.image
+import its.device
+import its.objects
+
+
+def main():
+    """Takes shots with different sensor crop regions.
+    """
+    name = os.path.basename(__file__).split(".")[0]
+
+    # Regions specified here in x,y,w,h normalized form.
+    regions = [[0.0, 0.0, 0.5, 0.5], # top left
+               [0.0, 0.5, 0.5, 0.5], # bottom left
+               [0.1, 0.9, 0.5, 1.0]] # right side (top + bottom)
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        r = props['android.sensor.info.pixelArraySize']
+        w = r['width']
+        h = r['height']
+
+        # Capture a full frame first.
+        reqs = [its.objects.auto_capture_request()]
+        print "Capturing img0 with the full sensor region"
+
+        # Capture a frame for each of the regions.
+        for i,region in enumerate(regions):
+            req = its.objects.auto_capture_request()
+            req['android.scaler.cropRegion'] = {
+                    "left": int(region[0] * w),
+                    "top": int(region[1] * h),
+                    "right": int((region[0]+region[2])*w),
+                    "bottom": int((region[1]+region[3])*h)}
+            reqs.append(req)
+            crop = req['android.scaler.cropRegion']
+            print "Capturing img%d with crop: %d,%d %dx%d"%(i+1,
+                    crop["left"],crop["top"],
+                    crop["right"]-crop["left"],crop["bottom"]-crop["top"])
+
+        cam.do_3a()
+        caps = cam.do_capture(reqs)
+
+        for i,cap in enumerate(caps):
+            img = its.image.convert_capture_to_rgb_image(cap)
+            crop = cap["metadata"]['android.scaler.cropRegion']
+            its.image.write_image(img, "%s_img%d.jpg"%(name,i))
+            print "Captured img%d with crop: %d,%d %dx%d"%(i,
+                    crop["left"],crop["top"],
+                    crop["right"]-crop["left"],crop["bottom"]-crop["top"])
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CameraITS/tests/inprog/test_faces.py b/apps/CameraITS/tests/inprog/test_faces.py
new file mode 100644
index 0000000..228dac8
--- /dev/null
+++ b/apps/CameraITS/tests/inprog/test_faces.py
@@ -0,0 +1,41 @@
+# 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 face detection.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    with its.device.ItsSession() as cam:
+        cam.do_3a()
+        req = its.objects.auto_capture_request()
+        req['android.statistics.faceDetectMode'] = 2
+        caps = cam.do_capture([req]*5)
+        for i,cap in enumerate(caps):
+            md = cap['metadata']
+            print "Frame %d face metadata:" % i
+            print "  Ids:", md['android.statistics.faceIds']
+            print "  Landmarks:", md['android.statistics.faceLandmarks']
+            print "  Rectangles:", md['android.statistics.faceRectangles']
+            print "  Scores:", md['android.statistics.faceScores']
+            print ""
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/inprog/test_param_black_level_lock.py b/apps/CameraITS/tests/inprog/test_param_black_level_lock.py
new file mode 100644
index 0000000..7d0be92
--- /dev/null
+++ b/apps/CameraITS/tests/inprog/test_param_black_level_lock.py
@@ -0,0 +1,76 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.device
+import its.objects
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+import numpy
+
+def main():
+    """Test that when the black level is locked, it doesn't change.
+
+    Shoot with the camera covered (i.e.) dark/black. The test varies the
+    sensitivity parameter and checks if the black level changes.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    NUM_STEPS = 5
+
+    req = {
+        "android.blackLevel.lock": True,
+        "android.control.mode": 0,
+        "android.control.aeMode": 0,
+        "android.control.awbMode": 0,
+        "android.control.afMode": 0,
+        "android.sensor.frameDuration": 0,
+        "android.sensor.exposureTime": 10*1000*1000
+        }
+
+    # The most frequent pixel value in each image; assume this is the black
+    # level, since the images are all dark (shot with the lens covered).
+    modes = []
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        sens_range = props['android.sensor.info.sensitivityRange']
+        sensitivities = range(sens_range[0],
+                              sens_range[1]+1,
+                              int((sens_range[1] - sens_range[0]) / NUM_STEPS))
+        for si, s in enumerate(sensitivities):
+            req["android.sensor.sensitivity"] = s
+            cap = cam.do_capture(req)
+            yimg,_,_ = its.image.convert_capture_to_planes(cap)
+            hist,_ = numpy.histogram(yimg*255, 256, (0,256))
+            modes.append(numpy.argmax(hist))
+
+            # Add this histogram to a plot; solid for shots without BL
+            # lock, dashes for shots with BL lock
+            pylab.plot(range(16), hist.tolist()[:16])
+
+    pylab.xlabel("Luma DN, showing [0:16] out of full [0:256] range")
+    pylab.ylabel("Pixel count")
+    pylab.title("Histograms for different sensitivities")
+    matplotlib.pyplot.savefig("%s_plot_histograms.png" % (NAME))
+
+    # Check that the black levels are all the same.
+    print "Black levels:", modes
+    assert(all([modes[i] == modes[0] for i in range(len(modes))]))
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/inprog/test_param_edge_mode.py b/apps/CameraITS/tests/inprog/test_param_edge_mode.py
new file mode 100644
index 0000000..e928f21
--- /dev/null
+++ b/apps/CameraITS/tests/inprog/test_param_edge_mode.py
@@ -0,0 +1,48 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.device
+import its.objects
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Test that the android.edge.mode parameter is applied.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    req = {
+        "android.control.mode": 0,
+        "android.control.aeMode": 0,
+        "android.control.awbMode": 0,
+        "android.control.afMode": 0,
+        "android.sensor.frameDuration": 0,
+        "android.sensor.exposureTime": 30*1000*1000,
+        "android.sensor.sensitivity": 100
+        }
+
+    with its.device.ItsSession() as cam:
+        sens, exp, gains, xform, focus = cam.do_3a(get_results=True)
+        for e in [0,1,2]:
+            req["android.edge.mode"] = e
+            cap = cam.do_capture(req)
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(img, "%s_mode=%d.jpg" % (NAME, e))
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/inprog/test_test_patterns.py b/apps/CameraITS/tests/inprog/test_test_patterns.py
new file mode 100644
index 0000000..f75b141
--- /dev/null
+++ b/apps/CameraITS/tests/inprog/test_test_patterns.py
@@ -0,0 +1,41 @@
+# 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_camera_properties.py b/apps/CameraITS/tests/scene0/test_camera_properties.py
new file mode 100644
index 0000000..eb638f0
--- /dev/null
+++ b/apps/CameraITS/tests/scene0/test_camera_properties.py
@@ -0,0 +1,43 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.caps
+import its.device
+import its.objects
+import pprint
+
+def main():
+    """Basic test to query and print out camera properties.
+    """
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+
+        pprint.pprint(props)
+
+        its.caps.skip_unless(its.caps.manual_sensor(props))
+
+        # Test that a handful of required keys are present.
+        assert(props.has_key('android.sensor.info.sensitivityRange'))
+        assert(props.has_key('android.sensor.orientation'))
+        assert(props.has_key('android.scaler.streamConfigurationMap'))
+        assert(props.has_key('android.lens.facing'))
+
+        print "JPG sizes:", its.objects.get_available_output_sizes("jpg", props)
+        print "RAW sizes:", its.objects.get_available_output_sizes("raw", props)
+        print "YUV sizes:", its.objects.get_available_output_sizes("yuv", props)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene0/test_capture_result_dump.py b/apps/CameraITS/tests/scene0/test_capture_result_dump.py
new file mode 100644
index 0000000..6646557
--- /dev/null
+++ b/apps/CameraITS/tests/scene0/test_capture_result_dump.py
@@ -0,0 +1,40 @@
+# 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.caps
+import its.image
+import its.device
+import its.objects
+import its.target
+import pprint
+
+def main():
+    """Test that a capture result is returned from a manual capture; dump it.
+    """
+
+    with its.device.ItsSession() as cam:
+        # Arbitrary capture request exposure values; image content is not
+        # important for this test, only the metadata.
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.manual_sensor(props))
+
+        req,fmt = its.objects.get_fastest_manual_capture_settings(props)
+        cap = cam.do_capture(req, fmt)
+        pprint.pprint(cap["metadata"])
+
+        # No pass/fail check; test passes if it completes.
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene0/test_gyro_bias.py b/apps/CameraITS/tests/scene0/test_gyro_bias.py
new file mode 100644
index 0000000..7ea90c3
--- /dev/null
+++ b/apps/CameraITS/tests/scene0/test_gyro_bias.py
@@ -0,0 +1,78 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import time
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+import numpy
+
+def main():
+    """Test if the gyro has stable output when device is stationary.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    # Number of samples averaged together, in the plot.
+    N = 20
+
+    # Pass/fail thresholds for gyro drift
+    MEAN_THRESH = 0.01
+    VAR_THRESH = 0.001
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        # Only run test if the appropriate caps are claimed.
+        its.caps.skip_unless(its.caps.sensor_fusion(props))
+
+        print "Collecting gyro events"
+        cam.start_sensor_events()
+        time.sleep(5)
+        gyro_events = cam.get_sensor_events()["gyro"]
+
+    nevents = (len(gyro_events) / N) * N
+    gyro_events = gyro_events[:nevents]
+    times = numpy.array([(e["time"] - gyro_events[0]["time"])/1000000000.0
+                         for e in gyro_events])
+    xs = numpy.array([e["x"] for e in gyro_events])
+    ys = numpy.array([e["y"] for e in gyro_events])
+    zs = numpy.array([e["z"] for e in gyro_events])
+
+    # Group samples into size-N groups and average each together, to get rid
+    # of individual random spikes in the data.
+    times = times[N/2::N]
+    xs = xs.reshape(nevents/N, N).mean(1)
+    ys = ys.reshape(nevents/N, N).mean(1)
+    zs = zs.reshape(nevents/N, N).mean(1)
+
+    pylab.plot(times, xs, 'r', label="x")
+    pylab.plot(times, ys, 'g', label="y")
+    pylab.plot(times, zs, 'b', label="z")
+    pylab.xlabel("Time (seconds)")
+    pylab.ylabel("Gyro readings (mean of %d samples)"%(N))
+    pylab.legend()
+    matplotlib.pyplot.savefig("%s_plot.png" % (NAME))
+
+    for samples in [xs,ys,zs]:
+        assert(samples.mean() < MEAN_THRESH)
+        assert(numpy.var(samples) < VAR_THRESH)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene0/test_jitter.py b/apps/CameraITS/tests/scene0/test_jitter.py
new file mode 100644
index 0000000..c519792
--- /dev/null
+++ b/apps/CameraITS/tests/scene0/test_jitter.py
@@ -0,0 +1,66 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import os.path
+import pylab
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Measure jitter in camera timestamps.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    # Pass/fail thresholds
+    MIN_AVG_FRAME_DELTA = 30 # at least 30ms delta between frames
+    MAX_VAR_FRAME_DELTA = 0.01 # variance of frame deltas
+    MAX_FRAME_DELTA_JITTER = 0.3 # max ms gap from the average frame delta
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.manual_sensor(props) and
+                             its.caps.sensor_fusion(props))
+
+        req, fmt = its.objects.get_fastest_manual_capture_settings(props)
+        caps = cam.do_capture([req]*50, [fmt])
+
+        # Print out the millisecond delta between the start of each exposure
+        tstamps = [c['metadata']['android.sensor.timestamp'] for c in caps]
+        deltas = [tstamps[i]-tstamps[i-1] for i in range(1,len(tstamps))]
+        deltas_ms = [d/1000000.0 for d in deltas]
+        avg = sum(deltas_ms) / len(deltas_ms)
+        var = sum([d*d for d in deltas_ms]) / len(deltas_ms) - avg * avg
+        range0 = min(deltas_ms) - avg
+        range1 = max(deltas_ms) - avg
+        print "Average:", avg
+        print "Variance:", var
+        print "Jitter range:", range0, "to", range1
+
+        # Draw a plot.
+        pylab.plot(range(len(deltas_ms)), deltas_ms)
+        matplotlib.pyplot.savefig("%s_deltas.png" % (NAME))
+
+        # Test for pass/fail.
+        assert(avg > MIN_AVG_FRAME_DELTA)
+        assert(var < MAX_VAR_FRAME_DELTA)
+        assert(abs(range0) < MAX_FRAME_DELTA_JITTER)
+        assert(abs(range1) < MAX_FRAME_DELTA_JITTER)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene0/test_metadata.py b/apps/CameraITS/tests/scene0/test_metadata.py
new file mode 100644
index 0000000..b4ca4cb
--- /dev/null
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -0,0 +1,98 @@
+# 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 its.target
+import its.caps
+
+def main():
+    """Test the validity of some metadata entries.
+
+    Looks at capture results and at the camera characteristics objects.
+    """
+    global md, props, failed
+
+    with its.device.ItsSession() as cam:
+        # Arbitrary capture request exposure values; image content is not
+        # important for this test, only the metadata.
+        props = cam.get_camera_properties()
+        auto_req = its.objects.auto_capture_request()
+        cap = cam.do_capture(auto_req)
+        md = cap["metadata"]
+
+    print "Hardware level"
+    print "  Legacy:", its.caps.legacy(props)
+    print "  Limited:", its.caps.limited(props)
+    print "  Full:", its.caps.full(props)
+    print "Capabilities"
+    print "  Manual sensor:", its.caps.manual_sensor(props)
+    print "  Manual post-proc:", its.caps.manual_post_proc(props)
+    print "  Raw:", its.caps.raw(props)
+    print "  Sensor fusion:", its.caps.sensor_fusion(props)
+
+    # Test: hardware level should be a valid value.
+    check('props.has_key("android.info.supportedHardwareLevel")')
+    check('props["android.info.supportedHardwareLevel"] is not None')
+    check('props["android.info.supportedHardwareLevel"] in [0,1,2]')
+    full = getval('props["android.info.supportedHardwareLevel"]') == 1
+
+    # Test: rollingShutterSkew, and frameDuration tags must all be present,
+    # and rollingShutterSkew must be greater than zero and smaller than all
+    # of the possible frame durations.
+    check('md.has_key("android.sensor.frameDuration")')
+    check('md["android.sensor.frameDuration"] is not None')
+    check('md.has_key("android.sensor.rollingShutterSkew")')
+    check('md["android.sensor.rollingShutterSkew"] is not None')
+    check('md["android.sensor.frameDuration"] > '
+          'md["android.sensor.rollingShutterSkew"] > 0')
+
+    # Test: timestampSource must be a valid value.
+    check('props.has_key("android.sensor.info.timestampSource")')
+    check('props["android.sensor.info.timestampSource"] is not None')
+    check('props["android.sensor.info.timestampSource"] in [0,1]')
+
+    # Test: croppingType must be a valid value, and for full devices, it
+    # must be FREEFORM=1.
+    check('props.has_key("android.scaler.croppingType")')
+    check('props["android.scaler.croppingType"] is not None')
+    check('props["android.scaler.croppingType"] in [0,1]')
+    if full:
+        check('props["android.scaler.croppingType"] == 1')
+
+    assert(not failed)
+
+def getval(expr, default=None):
+    try:
+        return eval(expr)
+    except:
+        return default
+
+failed = False
+def check(expr):
+    global md, props, failed
+    try:
+        if eval(expr):
+            print "Passed>", expr
+        else:
+            print "Failed>>", expr
+            failed = True
+    except:
+        print "Failed>>", expr
+        failed = True
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
new file mode 100644
index 0000000..c3c2147
--- /dev/null
+++ b/apps/CameraITS/tests/scene0/test_param_sensitivity_burst.py
@@ -0,0 +1,50 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+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%
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.manual_sensor(props) and
+                             its.caps.per_frame_control(props))
+
+        sens_range = props['android.sensor.info.sensitivityRange']
+        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)
+
+        caps = cam.do_capture(reqs, fmt)
+        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)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene0/test_sensor_events.py b/apps/CameraITS/tests/scene0/test_sensor_events.py
new file mode 100644
index 0000000..5973de2
--- /dev/null
+++ b/apps/CameraITS/tests/scene0/test_sensor_events.py
@@ -0,0 +1,42 @@
+# 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.device
+import its.caps
+import time
+
+def main():
+    """Basic test to query and print out sensor events.
+
+    Test will only work if the screen is on (i.e.) the device isn't in standby.
+    Pass if some of each event are received.
+    """
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        # Only run test if the appropriate caps are claimed.
+        its.caps.skip_unless(its.caps.sensor_fusion(props))
+
+        cam.start_sensor_events()
+        time.sleep(1)
+        events = cam.get_sensor_events()
+        print "Events over 1s: %d gyro, %d accel, %d mag"%(
+                len(events["gyro"]), len(events["accel"]), len(events["mag"]))
+        assert(len(events["gyro"]) > 0)
+        assert(len(events["accel"]) > 0)
+        assert(len(events["mag"]) > 0)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene0/test_unified_timestamps.py b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
new file mode 100644
index 0000000..019e6c5
--- /dev/null
+++ b/apps/CameraITS/tests/scene0/test_unified_timestamps.py
@@ -0,0 +1,65 @@
+# 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.device
+import its.objects
+import its.caps
+import time
+
+def main():
+    """Test if image and motion sensor events are in the same time domain.
+    """
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+
+        # Only run test if the appropriate caps are claimed.
+        its.caps.skip_unless(its.caps.sensor_fusion(props))
+
+        # Get the timestamp of a captured image.
+        req, fmt = its.objects.get_fastest_manual_capture_settings(props)
+        cap = cam.do_capture(req, fmt)
+        ts_image0 = cap['metadata']['android.sensor.timestamp']
+
+        # Get the timestamps of motion events.
+        print "Reading sensor measurements"
+        cam.start_sensor_events()
+        time.sleep(0.5)
+        events = cam.get_sensor_events()
+        assert(len(events["gyro"]) > 0)
+        assert(len(events["accel"]) > 0)
+        assert(len(events["mag"]) > 0)
+        ts_gyro0 = events["gyro"][0]["time"]
+        ts_gyro1 = events["gyro"][-1]["time"]
+        ts_accel0 = events["accel"][0]["time"]
+        ts_accel1 = events["accel"][-1]["time"]
+        ts_mag0 = events["mag"][0]["time"]
+        ts_mag1 = events["mag"][-1]["time"]
+
+        # Get the timestamp of another image.
+        cap = cam.do_capture(req, fmt)
+        ts_image1 = cap['metadata']['android.sensor.timestamp']
+
+        print "Image timestamps:", ts_image0, ts_image1
+        print "Gyro timestamps:", ts_gyro0, ts_gyro1
+        print "Accel timestamps:", ts_accel0, ts_accel1
+        print "Mag timestamps:", ts_mag0, ts_mag1
+
+        # The motion timestamps must be between the two image timestamps.
+        assert ts_image0 < min(ts_gyro0, ts_accel0, ts_mag0) < ts_image1
+        assert ts_image0 < max(ts_gyro1, ts_accel1, ts_mag1) < ts_image1
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_3a.py b/apps/CameraITS/tests/scene1/test_3a.py
new file mode 100644
index 0000000..08cd747
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_3a.py
@@ -0,0 +1,40 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.device
+import its.caps
+
+def main():
+    """Basic test for bring-up of 3A.
+
+    To pass, 3A must converge. Check that the returned 3A values are legal.
+    """
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        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)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py b/apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py
new file mode 100644
index 0000000..bb91c9a
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_ae_precapture_trigger.py
@@ -0,0 +1,85 @@
+# 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.device
+import its.caps
+import its.objects
+import its.target
+
+AE_FRAMES_PER_ITERATION = 8
+AE_CONVERGE_ITERATIONS = 3
+# AE must converge within this number of auto requests under scene1
+THRESH_AE_CONVERGE = AE_FRAMES_PER_ITERATION * AE_CONVERGE_ITERATIONS
+
+def main():
+    """Test the AE state machine when using the precapture trigger.
+    """
+
+    INACTIVE = 0
+    SEARCHING = 1
+    CONVERGED = 2
+    LOCKED = 3
+    FLASHREQUIRED = 4
+    PRECAPTURE = 5
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.per_frame_control(props))
+
+        _,fmt = its.objects.get_fastest_manual_capture_settings(props)
+
+        # Capture 5 manual requests, with AE disabled, and the last request
+        # has an AE precapture trigger (which should be ignored since AE is
+        # disabled).
+        manual_reqs = []
+        e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
+        manual_req = its.objects.manual_capture_request(s,e)
+        manual_req['android.control.aeMode'] = 0 # Off
+        manual_reqs += [manual_req]*4
+        precap_req = its.objects.manual_capture_request(s,e)
+        precap_req['android.control.aeMode'] = 0 # Off
+        precap_req['android.control.aePrecaptureTrigger'] = 1 # Start
+        manual_reqs.append(precap_req)
+        caps = cam.do_capture(manual_reqs, fmt)
+        for cap in caps:
+            assert(cap['metadata']['android.control.aeState'] == INACTIVE)
+
+        # Capture an auto request and verify the AE state; no trigger.
+        auto_req = its.objects.auto_capture_request()
+        auto_req['android.control.aeMode'] = 1  # On
+        cap = cam.do_capture(auto_req, fmt)
+        state = cap['metadata']['android.control.aeState']
+        print "AE state after auto request:", state
+        assert(state in [SEARCHING, CONVERGED])
+
+        # Capture with auto request with a precapture trigger.
+        auto_req['android.control.aePrecaptureTrigger'] = 1  # Start
+        cap = cam.do_capture(auto_req, fmt)
+        state = cap['metadata']['android.control.aeState']
+        print "AE state after auto request with precapture trigger:", state
+        assert(state in [SEARCHING, CONVERGED, PRECAPTURE])
+
+        # Capture some more auto requests, and AE should converge.
+        auto_req['android.control.aePrecaptureTrigger'] = 0
+        for i in range(AE_CONVERGE_ITERATIONS):
+            caps = cam.do_capture([auto_req] * AE_FRAMES_PER_ITERATION, fmt)
+            state = caps[-1]['metadata']['android.control.aeState']
+            print "AE state after auto request:", state
+            if state == CONVERGED:
+                return
+        assert(state == CONVERGED)
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CameraITS/tests/scene1/test_auto_vs_manual.py b/apps/CameraITS/tests/scene1/test_auto_vs_manual.py
new file mode 100644
index 0000000..a9efa0b
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_auto_vs_manual.py
@@ -0,0 +1,94 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import os.path
+import math
+
+def main():
+    """Capture auto and manual shots that should look the same.
+
+    Manual shots taken with just manual WB, and also with manual WB+tonemap.
+
+    In all cases, the general color/look of the shots should be the same,
+    however there can be variations in brightness/contrast due to different
+    "auto" ISP blocks that may be disabled in the manual flows.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.manual_sensor(props) and
+                             its.caps.manual_post_proc(props) and
+                             its.caps.per_frame_control(props))
+
+        # Converge 3A and get the estimates.
+        sens, exp, gains, xform, focus = cam.do_3a(get_results=True)
+        xform_rat = its.objects.float_to_rational(xform)
+        print "AE sensitivity %d, exposure %dms" % (sens, exp/1000000.0)
+        print "AWB gains", gains
+        print "AWB transform", xform
+        print "AF distance", focus
+
+        # Auto capture.
+        req = its.objects.auto_capture_request()
+        cap_auto = cam.do_capture(req)
+        img_auto = its.image.convert_capture_to_rgb_image(cap_auto)
+        its.image.write_image(img_auto, "%s_auto.jpg" % (NAME))
+        xform_a = its.objects.rational_to_float(
+                cap_auto["metadata"]["android.colorCorrection.transform"])
+        gains_a = cap_auto["metadata"]["android.colorCorrection.gains"]
+        print "Auto gains:", gains_a
+        print "Auto transform:", xform_a
+
+        # Manual capture 1: WB
+        req = its.objects.manual_capture_request(sens, exp)
+        req["android.colorCorrection.transform"] = xform_rat
+        req["android.colorCorrection.gains"] = gains
+        cap_man1 = cam.do_capture(req)
+        img_man1 = its.image.convert_capture_to_rgb_image(cap_man1)
+        its.image.write_image(img_man1, "%s_manual_wb.jpg" % (NAME))
+        xform_m1 = its.objects.rational_to_float(
+                cap_man1["metadata"]["android.colorCorrection.transform"])
+        gains_m1 = cap_man1["metadata"]["android.colorCorrection.gains"]
+        print "Manual wb gains:", gains_m1
+        print "Manual wb transform:", xform_m1
+
+        # Manual capture 2: WB + tonemap
+        gamma = sum([[i/63.0,math.pow(i/63.0,1/2.2)] for i in xrange(64)],[])
+        req["android.tonemap.mode"] = 0
+        req["android.tonemap.curveRed"] = gamma
+        req["android.tonemap.curveGreen"] = gamma
+        req["android.tonemap.curveBlue"] = gamma
+        cap_man2 = cam.do_capture(req)
+        img_man2 = its.image.convert_capture_to_rgb_image(cap_man2)
+        its.image.write_image(img_man2, "%s_manual_wb_tm.jpg" % (NAME))
+        xform_m2 = its.objects.rational_to_float(
+                cap_man2["metadata"]["android.colorCorrection.transform"])
+        gains_m2 = cap_man2["metadata"]["android.colorCorrection.gains"]
+        print "Manual wb+tm gains:", gains_m2
+        print "Manual wb+tm transform:", xform_m2
+
+        # Check that the WB gains and transform reported in each capture
+        # result match with the original AWB estimate from do_3a.
+        for g,x in [(gains_a,xform_a),(gains_m1,xform_m1),(gains_m2,xform_m2)]:
+            assert(all([abs(xform[i] - x[i]) < 0.05 for i in range(9)]))
+            assert(all([abs(gains[i] - g[i]) < 0.05 for i in range(4)]))
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_black_white.py b/apps/CameraITS/tests/scene1/test_black_white.py
new file mode 100644
index 0000000..68d7de6
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_black_white.py
@@ -0,0 +1,85 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Test that the device will produce full black+white images.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    r_means = []
+    g_means = []
+    b_means = []
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.manual_sensor(props) and
+                             its.caps.per_frame_control(props))
+
+        expt_range = props['android.sensor.info.exposureTimeRange']
+        sens_range = props['android.sensor.info.sensitivityRange']
+
+        # Take a shot with very low ISO and exposure time. Expect it to
+        # be black.
+        print "Black shot: sens = %d, exp time = %.4fms" % (
+                sens_range[0], expt_range[0]/1000000.0)
+        req = its.objects.manual_capture_request(sens_range[0], expt_range[0])
+        cap = cam.do_capture(req)
+        img = its.image.convert_capture_to_rgb_image(cap)
+        its.image.write_image(img, "%s_black.jpg" % (NAME))
+        tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+        black_means = its.image.compute_image_means(tile)
+        r_means.append(black_means[0])
+        g_means.append(black_means[1])
+        b_means.append(black_means[2])
+        print "Dark pixel means:", black_means
+
+        # Take a shot with very high ISO and exposure time. Expect it to
+        # be white.
+        print "White shot: sens = %d, exp time = %.2fms" % (
+                sens_range[1], expt_range[1]/1000000.0)
+        req = its.objects.manual_capture_request(sens_range[1], expt_range[1])
+        cap = cam.do_capture(req)
+        img = its.image.convert_capture_to_rgb_image(cap)
+        its.image.write_image(img, "%s_white.jpg" % (NAME))
+        tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+        white_means = its.image.compute_image_means(tile)
+        r_means.append(white_means[0])
+        g_means.append(white_means[1])
+        b_means.append(white_means[2])
+        print "Bright pixel means:", white_means
+
+        # Draw a plot.
+        pylab.plot([0,1], r_means, 'r')
+        pylab.plot([0,1], g_means, 'g')
+        pylab.plot([0,1], b_means, 'b')
+        pylab.ylim([0,1])
+        matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
+        for val in black_means:
+            assert(val < 0.025)
+        for val in white_means:
+            assert(val > 0.975)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_burst_sameness_manual.py b/apps/CameraITS/tests/scene1/test_burst_sameness_manual.py
new file mode 100644
index 0000000..edb8995
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_burst_sameness_manual.py
@@ -0,0 +1,85 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import os.path
+import numpy
+
+def main():
+    """Take long bursts of images and check that they're all identical.
+
+    Assumes a static scene. Can be used to idenfity if there are sporadic
+    frames that are processed differently or have artifacts. Uses manual
+    capture settings.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    BURST_LEN = 50
+    BURSTS = 5
+    FRAMES = BURST_LEN * BURSTS
+
+    SPREAD_THRESH = 0.03
+
+    with its.device.ItsSession() as cam:
+
+        # Capture at the smallest resolution.
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.manual_sensor(props) and
+                             its.caps.per_frame_control(props))
+
+        _, fmt = its.objects.get_fastest_manual_capture_settings(props)
+        e, s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
+        req = its.objects.manual_capture_request(s, e)
+        w,h = fmt["width"], fmt["height"]
+
+        # Capture bursts of YUV shots.
+        # Get the mean values of a center patch for each.
+        # Also build a 4D array, which is an array of all RGB images.
+        r_means = []
+        g_means = []
+        b_means = []
+        imgs = numpy.empty([FRAMES,h,w,3])
+        for j in range(BURSTS):
+            caps = cam.do_capture([req]*BURST_LEN, [fmt])
+            for i,cap in enumerate(caps):
+                n = j*BURST_LEN + i
+                imgs[n] = its.image.convert_capture_to_rgb_image(cap)
+                tile = its.image.get_image_patch(imgs[n], 0.45, 0.45, 0.1, 0.1)
+                means = its.image.compute_image_means(tile)
+                r_means.append(means[0])
+                g_means.append(means[1])
+                b_means.append(means[2])
+
+        # Dump all images.
+        print "Dumping images"
+        for i in range(FRAMES):
+            its.image.write_image(imgs[i], "%s_frame%03d.jpg"%(NAME,i))
+
+        # The mean image.
+        img_mean = imgs.mean(0)
+        its.image.write_image(img_mean, "%s_mean.jpg"%(NAME))
+
+        # Pass/fail based on center patch similarity.
+        for means in [r_means, g_means, b_means]:
+            spread = max(means) - min(means)
+            print spread
+            assert(spread < SPREAD_THRESH)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_capture_result.py b/apps/CameraITS/tests/scene1/test_capture_result.py
new file mode 100644
index 0000000..331d1cd
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_capture_result.py
@@ -0,0 +1,213 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import os.path
+import numpy
+import matplotlib.pyplot
+
+# Required for 3d plot to work
+import mpl_toolkits.mplot3d
+
+def main():
+    """Test that valid data comes back in CaptureResult objects.
+    """
+    global NAME, auto_req, manual_req, w_map, h_map
+    global manual_tonemap, manual_transform, manual_gains, manual_region
+    global manual_exp_time, manual_sensitivity, manual_gains_ok
+
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.manual_sensor(props) and
+                             its.caps.manual_post_proc(props) and
+                             its.caps.per_frame_control(props))
+
+        manual_tonemap = [0,0, 1,1] # Linear
+        manual_transform = its.objects.int_to_rational([1,2,3, 4,5,6, 7,8,9])
+        manual_gains = [1,2,3,4]
+        manual_region = [{"x":8,"y":8,"width":128,"height":128,"weight":1}]
+        manual_exp_time = min(props['android.sensor.info.exposureTimeRange'])
+        manual_sensitivity = min(props['android.sensor.info.sensitivityRange'])
+
+        # The camera HAL may not support different gains for two G channels.
+        manual_gains_ok = [[1,2,3,4],[1,2,2,4],[1,3,3,4]]
+
+        auto_req = its.objects.auto_capture_request()
+        auto_req["android.statistics.lensShadingMapMode"] = 1
+
+        manual_req = {
+            "android.control.mode": 0,
+            "android.control.aeMode": 0,
+            "android.control.awbMode": 0,
+            "android.control.afMode": 0,
+            "android.sensor.frameDuration": 0,
+            "android.sensor.sensitivity": manual_sensitivity,
+            "android.sensor.exposureTime": manual_exp_time,
+            "android.colorCorrection.mode": 0,
+            "android.colorCorrection.transform": manual_transform,
+            "android.colorCorrection.gains": manual_gains,
+            "android.tonemap.mode": 0,
+            "android.tonemap.curveRed": manual_tonemap,
+            "android.tonemap.curveGreen": manual_tonemap,
+            "android.tonemap.curveBlue": manual_tonemap,
+            "android.control.aeRegions": manual_region,
+            "android.control.afRegions": manual_region,
+            "android.control.awbRegions": manual_region,
+            "android.statistics.lensShadingMapMode":1
+            }
+
+        w_map = props["android.lens.info.shadingMapSize"]["width"]
+        h_map = props["android.lens.info.shadingMapSize"]["height"]
+
+        print "Testing auto capture results"
+        lsc_map_auto = test_auto(cam, w_map, h_map)
+        print "Testing manual capture results"
+        test_manual(cam, w_map, h_map, lsc_map_auto)
+        print "Testing auto capture results again"
+        test_auto(cam, w_map, h_map)
+
+# A very loose definition for two floats being close to each other;
+# there may be different interpolation and rounding used to get the
+# two values, and all this test is looking at is whether there is
+# something obviously broken; it's not looking for a perfect match.
+def is_close_float(n1, n2):
+    return abs(n1 - n2) < 0.05
+
+def is_close_rational(n1, n2):
+    return is_close_float(its.objects.rational_to_float(n1),
+                          its.objects.rational_to_float(n2))
+
+def draw_lsc_plot(w_map, h_map, lsc_map, name):
+    for ch in range(4):
+        fig = matplotlib.pyplot.figure()
+        ax = fig.gca(projection='3d')
+        xs = numpy.array([range(w_map)] * h_map).reshape(h_map, w_map)
+        ys = numpy.array([[i]*w_map for i in range(h_map)]).reshape(
+                h_map, w_map)
+        zs = numpy.array(lsc_map[ch::4]).reshape(h_map, w_map)
+        ax.plot_wireframe(xs, ys, zs)
+        matplotlib.pyplot.savefig("%s_plot_lsc_%s_ch%d.png"%(NAME,name,ch))
+
+def test_auto(cam, w_map, h_map):
+    # Get 3A lock first, so the auto values in the capture result are
+    # populated properly.
+    rect = [[0,0,1,1,1]]
+    cam.do_3a(rect, rect, rect, do_af=False)
+
+    cap = cam.do_capture(auto_req)
+    cap_res = cap["metadata"]
+
+    gains = cap_res["android.colorCorrection.gains"]
+    transform = cap_res["android.colorCorrection.transform"]
+    exp_time = cap_res['android.sensor.exposureTime']
+    lsc_map = cap_res["android.statistics.lensShadingMap"]
+    ctrl_mode = cap_res["android.control.mode"]
+
+    print "Control mode:", ctrl_mode
+    print "Gains:", gains
+    print "Transform:", [its.objects.rational_to_float(t)
+                         for t in transform]
+    print "AE region:", cap_res['android.control.aeRegions']
+    print "AF region:", cap_res['android.control.afRegions']
+    print "AWB region:", cap_res['android.control.awbRegions']
+    print "LSC map:", w_map, h_map, lsc_map[:8]
+
+    assert(ctrl_mode == 1)
+
+    # Color correction gain and transform must be valid.
+    assert(len(gains) == 4)
+    assert(len(transform) == 9)
+    assert(all([g > 0 for g in gains]))
+    assert(all([t["denominator"] != 0 for t in transform]))
+
+    # Color correction should not match the manual settings.
+    assert(any([not is_close_float(gains[i], manual_gains[i])
+                for i in xrange(4)]))
+    assert(any([not is_close_rational(transform[i], manual_transform[i])
+                for i in xrange(9)]))
+
+    # Exposure time must be valid.
+    assert(exp_time > 0)
+
+    # Lens shading map must be valid.
+    assert(w_map > 0 and h_map > 0 and w_map * h_map * 4 == len(lsc_map))
+    assert(all([m >= 1 for m in lsc_map]))
+
+    draw_lsc_plot(w_map, h_map, lsc_map, "auto")
+
+    return lsc_map
+
+def test_manual(cam, w_map, h_map, lsc_map_auto):
+    cap = cam.do_capture(manual_req)
+    cap_res = cap["metadata"]
+
+    gains = cap_res["android.colorCorrection.gains"]
+    transform = cap_res["android.colorCorrection.transform"]
+    curves = [cap_res["android.tonemap.curveRed"],
+              cap_res["android.tonemap.curveGreen"],
+              cap_res["android.tonemap.curveBlue"]]
+    exp_time = cap_res['android.sensor.exposureTime']
+    lsc_map = cap_res["android.statistics.lensShadingMap"]
+    ctrl_mode = cap_res["android.control.mode"]
+
+    print "Control mode:", ctrl_mode
+    print "Gains:", gains
+    print "Transform:", [its.objects.rational_to_float(t)
+                         for t in transform]
+    print "Tonemap:", curves[0][1::16]
+    print "AE region:", cap_res['android.control.aeRegions']
+    print "AF region:", cap_res['android.control.afRegions']
+    print "AWB region:", cap_res['android.control.awbRegions']
+    print "LSC map:", w_map, h_map, lsc_map[:8]
+
+    assert(ctrl_mode == 0)
+
+    # Color correction gain and transform must be valid.
+    # Color correction gains and transform should be the same size and
+    # values as the manually set values.
+    assert(len(gains) == 4)
+    assert(len(transform) == 9)
+    assert( all([is_close_float(gains[i], manual_gains_ok[0][i])
+                 for i in xrange(4)]) or
+            all([is_close_float(gains[i], manual_gains_ok[1][i])
+                 for i in xrange(4)]) or
+            all([is_close_float(gains[i], manual_gains_ok[2][i])
+                 for i in xrange(4)]))
+    assert(all([is_close_rational(transform[i], manual_transform[i])
+                for i in xrange(9)]))
+
+    # Tonemap must be valid.
+    # The returned tonemap must be linear.
+    for c in curves:
+        assert(len(c) > 0)
+        assert(all([is_close_float(c[i], c[i+1])
+                    for i in xrange(0,len(c),2)]))
+
+    # Exposure time must be close to the requested exposure time.
+    assert(is_close_float(exp_time/1000000.0, manual_exp_time/1000000.0))
+
+    # Lens shading map must be valid.
+    assert(w_map > 0 and h_map > 0 and w_map * h_map * 4 == len(lsc_map))
+    assert(all([m >= 1 for m in lsc_map]))
+
+    draw_lsc_plot(w_map, h_map, lsc_map, "manual")
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_crop_region_raw.py b/apps/CameraITS/tests/scene1/test_crop_region_raw.py
new file mode 100644
index 0000000..189e987
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_crop_region_raw.py
@@ -0,0 +1,149 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import numpy
+import os.path
+
+def main():
+    """Test that raw streams are not croppable.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    DIFF_THRESH = 0.05
+    CROP_REGION_ERROR_THRESHOLD = 0.01
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.raw16(props) and
+                             its.caps.per_frame_control(props))
+
+        # Calculate the active sensor region for a full (non-cropped) image.
+        a = props['android.sensor.info.activeArraySize']
+        ax, ay = a["left"], a["top"]
+        aw, ah = a["right"] - a["left"], a["bottom"] - a["top"]
+        print "Active sensor region: (%d,%d %dx%d)" % (ax, ay, aw, ah)
+
+        full_region = {
+            "left": 0,
+            "top": 0,
+            "right": aw,
+            "bottom": ah
+        }
+
+        # Calculate a center crop region.
+        zoom = min(3.0, its.objects.get_max_digital_zoom(props))
+        assert(zoom >= 1)
+        cropw = aw / zoom
+        croph = ah / zoom
+
+        crop_region = {
+            "left": aw / 2 - cropw / 2,
+            "top": ah / 2 - croph / 2,
+            "right": aw / 2 + cropw / 2,
+            "bottom": ah / 2 + croph / 2
+        }
+
+        # Capture without a crop region.
+        # Use a manual request with a linear tonemap so that the YUV and RAW
+        # should look the same (once converted by the its.image module).
+        e, s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
+        req = its.objects.manual_capture_request(s,e, True)
+        cap1_raw, cap1_yuv = cam.do_capture(req, cam.CAP_RAW_YUV)
+
+        # Capture with a crop region.
+        req["android.scaler.cropRegion"] = crop_region
+        cap2_raw, cap2_yuv = cam.do_capture(req, cam.CAP_RAW_YUV)
+
+        # Check the metadata related to crop regions.
+        # When both YUV and RAW are requested, the crop region that's
+        # applied to YUV should be reported.
+        # Note that the crop region returned by the cropped captures doesn't
+        # need to perfectly match the one that was requested.
+        imgs = {}
+        for s, cap, cr_expected, err_delta in [
+                ("yuv_full",cap1_yuv,full_region,0),
+                ("raw_full",cap1_raw,full_region,0),
+                ("yuv_crop",cap2_yuv,crop_region,CROP_REGION_ERROR_THRESHOLD),
+                ("raw_crop",cap2_raw,crop_region,CROP_REGION_ERROR_THRESHOLD)]:
+
+            # Convert the capture to RGB and dump to a file.
+            img = its.image.convert_capture_to_rgb_image(cap, props=props)
+            its.image.write_image(img, "%s_%s.jpg" % (NAME, s))
+            imgs[s] = img
+
+            # Get the crop region that is reported in the capture result.
+            cr_reported = cap["metadata"]["android.scaler.cropRegion"]
+            x, y = cr_reported["left"], cr_reported["top"]
+            w = cr_reported["right"] - cr_reported["left"]
+            h = cr_reported["bottom"] - cr_reported["top"]
+            print "Crop reported on %s: (%d,%d %dx%d)" % (s, x, y, w, h)
+
+            # Test that the reported crop region is the same as the expected
+            # one, for a non-cropped capture, and is close to the expected one,
+            # for a cropped capture.
+            ex = aw * err_delta
+            ey = ah * err_delta
+            assert ((abs(cr_expected["left"] - cr_reported["left"]) <= ex) and
+                    (abs(cr_expected["right"] - cr_reported["right"]) <= ex) and
+                    (abs(cr_expected["top"] - cr_reported["top"]) <= ey) and
+                    (abs(cr_expected["bottom"] - cr_reported["bottom"]) <= ey))
+
+        # Also check the image content; 3 of the 4 shots should match.
+        # Note that all the shots are RGB below; the variable names correspond
+        # to what was captured.
+
+        # Shrink the YUV images 2x2 -> 1 to account for the size reduction that
+        # the raw images went through in the RGB conversion.
+        imgs2 = {}
+        for s,img in imgs.iteritems():
+            h,w,ch = img.shape
+            if s in ["yuv_full", "yuv_crop"]:
+                img = img.reshape(h/2,2,w/2,2,3).mean(3).mean(1)
+                img = img.reshape(h/2,w/2,3)
+            imgs2[s] = img
+
+        # Strip any border pixels from the raw shots (since the raw images may
+        # be larger than the YUV images). Assume a symmetric padded border.
+        xpad = (imgs2["raw_full"].shape[1] - imgs2["yuv_full"].shape[1]) / 2
+        ypad = (imgs2["raw_full"].shape[0] - imgs2["yuv_full"].shape[0]) / 2
+        wyuv = imgs2["yuv_full"].shape[1]
+        hyuv = imgs2["yuv_full"].shape[0]
+        imgs2["raw_full"]=imgs2["raw_full"][ypad:ypad+hyuv:,xpad:xpad+wyuv:,::]
+        imgs2["raw_crop"]=imgs2["raw_crop"][ypad:ypad+hyuv:,xpad:xpad+wyuv:,::]
+        print "Stripping padding before comparison:", xpad, ypad
+
+        for s,img in imgs2.iteritems():
+            its.image.write_image(img, "%s_comp_%s.jpg" % (NAME, s))
+
+        # Compute diffs between images of the same type.
+        # The raw_crop and raw_full shots should be identical (since the crop
+        # doesn't apply to raw images), and the yuv_crop and yuv_full shots
+        # should be different.
+        diff_yuv = numpy.fabs((imgs2["yuv_full"] - imgs2["yuv_crop"])).mean()
+        diff_raw = numpy.fabs((imgs2["raw_full"] - imgs2["raw_crop"])).mean()
+        print "YUV diff (crop vs. non-crop):", diff_yuv
+        print "RAW diff (crop vs. non-crop):", diff_raw
+
+        assert(diff_yuv > DIFF_THRESH)
+        assert(diff_raw < DIFF_THRESH)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_crop_regions.py b/apps/CameraITS/tests/scene1/test_crop_regions.py
new file mode 100644
index 0000000..6d3dad1
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_crop_regions.py
@@ -0,0 +1,106 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import os.path
+import numpy
+
+def main():
+    """Test that crop regions work.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    # A list of 5 regions, specified in normalized (x,y,w,h) coords.
+    # The regions correspond to: TL, TR, BL, BR, CENT
+    REGIONS = [(0.0, 0.0, 0.5, 0.5),
+               (0.5, 0.0, 0.5, 0.5),
+               (0.0, 0.5, 0.5, 0.5),
+               (0.5, 0.5, 0.5, 0.5),
+               (0.25, 0.25, 0.5, 0.5)]
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.freeform_crop(props) and
+                             its.caps.per_frame_control(props))
+
+        a = props['android.sensor.info.activeArraySize']
+        ax, ay = a["left"], a["top"]
+        aw, ah = a["right"] - a["left"], a["bottom"] - a["top"]
+        e, s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
+        print "Active sensor region (%d,%d %dx%d)" % (ax, ay, aw, ah)
+
+        # Uses a 2x digital zoom.
+        assert(its.objects.get_max_digital_zoom(props) >= 2)
+
+        # Capture a full frame.
+        req = its.objects.manual_capture_request(s,e)
+        cap_full = cam.do_capture(req)
+        img_full = its.image.convert_capture_to_rgb_image(cap_full)
+        its.image.write_image(img_full, "%s_full.jpg" % (NAME))
+        wfull, hfull = cap_full["width"], cap_full["height"]
+
+        # Capture a burst of crop region frames.
+        # Note that each region is 1/2x1/2 of the full frame, and is digitally
+        # zoomed into the full size output image, so must be downscaled (below)
+        # by 2x when compared to a tile of the full image.
+        reqs = []
+        for x,y,w,h in REGIONS:
+            req = its.objects.manual_capture_request(s,e)
+            req["android.scaler.cropRegion"] = {
+                    "top": int(ah * y),
+                    "left": int(aw * x),
+                    "right": int(aw * (x + w)),
+                    "bottom": int(ah * (y + h))}
+            reqs.append(req)
+        caps_regions = cam.do_capture(reqs)
+        match_failed = False
+        for i,cap in enumerate(caps_regions):
+            a = cap["metadata"]["android.scaler.cropRegion"]
+            ax, ay = a["left"], a["top"]
+            aw, ah = a["right"] - a["left"], a["bottom"] - a["top"]
+
+            # Match this crop image against each of the five regions of
+            # the full image, to find the best match (which should be
+            # the region that corresponds to this crop image).
+            img_crop = its.image.convert_capture_to_rgb_image(cap)
+            img_crop = its.image.downscale_image(img_crop, 2)
+            its.image.write_image(img_crop, "%s_crop%d.jpg" % (NAME, i))
+            min_diff = None
+            min_diff_region = None
+            for j,(x,y,w,h) in enumerate(REGIONS):
+                tile_full = its.image.get_image_patch(img_full, x,y,w,h)
+                wtest = min(tile_full.shape[1], aw)
+                htest = min(tile_full.shape[0], ah)
+                tile_full = tile_full[0:htest:, 0:wtest:, ::]
+                tile_crop = img_crop[0:htest:, 0:wtest:, ::]
+                its.image.write_image(tile_full, "%s_fullregion%d.jpg"%(NAME,j))
+                diff = numpy.fabs(tile_full - tile_crop).mean()
+                if min_diff is None or diff < min_diff:
+                    min_diff = diff
+                    min_diff_region = j
+            if i != min_diff_region:
+                match_failed = True
+            print "Crop image %d (%d,%d %dx%d) best match with region %d"%(
+                    i, ax, ay, aw, ah, min_diff_region)
+
+        assert(not match_failed)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_dng_noise_model.py b/apps/CameraITS/tests/scene1/test_dng_noise_model.py
new file mode 100644
index 0000000..51270b6
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_dng_noise_model.py
@@ -0,0 +1,114 @@
+# 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.device
+import its.caps
+import its.objects
+import its.image
+import os.path
+import pylab
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Verify that the DNG raw model parameters are correct.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    NUM_STEPS = 4
+
+    # Pass if the difference between expected and computed variances is small,
+    # defined as being within an absolute variance delta of 0.0005, or within
+    # 20% of the expected variance, whichever is larger; this is to allow the
+    # test to pass in the presence of some randomness (since this test is
+    # measuring noise of a small patch) and some imperfect scene conditions
+    # (since ITS doesn't require a perfectly uniformly lit scene).
+    DIFF_THRESH = 0.0005
+    FRAC_THRESH = 0.2
+
+    with its.device.ItsSession() as cam:
+
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.raw(props) and
+                             its.caps.raw16(props) and
+                             its.caps.manual_sensor(props) and
+                             its.caps.read_3a(props) and
+                             its.caps.per_frame_control(props))
+
+        white_level = float(props['android.sensor.info.whiteLevel'])
+        black_levels = props['android.sensor.blackLevelPattern']
+        cfa_idxs = its.image.get_canonical_cfa_order(props)
+        black_levels = [black_levels[i] for i in cfa_idxs]
+
+        # Expose for the scene with min sensitivity
+        sens_min, sens_max = props['android.sensor.info.sensitivityRange']
+        sens_step = (sens_max - sens_min) / NUM_STEPS
+        s_ae,e_ae,_,_,_  = cam.do_3a(get_results=True)
+        s_e_prod = s_ae * e_ae
+        sensitivities = range(sens_min, sens_max, sens_step)
+
+        var_expected = [[],[],[],[]]
+        var_measured = [[],[],[],[]]
+        for sens in sensitivities:
+
+            # Capture a raw frame with the desired sensitivity.
+            exp = int(s_e_prod / float(sens))
+            req = its.objects.manual_capture_request(sens, exp)
+            cap = cam.do_capture(req, cam.CAP_RAW)
+
+            # Test each raw color channel (R, GR, GB, B):
+            noise_profile = cap["metadata"]["android.sensor.noiseProfile"]
+            assert((len(noise_profile)) == 4)
+            for ch in range(4):
+                # Get the noise model parameters for this channel of this shot.
+                s,o = noise_profile[cfa_idxs[ch]]
+
+                # Get a center tile of the raw channel, and compute the mean.
+                # Use a very small patch to ensure gross uniformity (i.e. so
+                # non-uniform lighting or vignetting doesn't affect the variance
+                # calculation).
+                plane = its.image.convert_capture_to_planes(cap, props)[ch]
+                plane = (plane * white_level - black_levels[ch]) / (
+                        white_level - black_levels[ch])
+                tile = its.image.get_image_patch(plane, 0.49,0.49,0.02,0.02)
+                mean = tile.mean()
+
+                # Calculate the expected variance based on the model, and the
+                # measured variance from the tile.
+                var_measured[ch].append(
+                        its.image.compute_image_variances(tile)[0])
+                var_expected[ch].append(s * mean + o)
+
+    for ch in range(4):
+        pylab.plot(sensitivities, var_expected[ch], "rgkb"[ch],
+                label=["R","GR","GB","B"][ch]+" expected")
+        pylab.plot(sensitivities, var_measured[ch], "rgkb"[ch]+"--",
+                label=["R", "GR", "GB", "B"][ch]+" measured")
+    pylab.xlabel("Sensitivity")
+    pylab.ylabel("Center patch variance")
+    pylab.legend(loc=2)
+    matplotlib.pyplot.savefig("%s_plot.png" % (NAME))
+
+    # Pass/fail check.
+    for ch in range(4):
+        diffs = [var_measured[ch][i] - var_expected[ch][i]
+                 for i in range(NUM_STEPS)]
+        print "Diffs (%s):"%(["R","GR","GB","B"][ch]), diffs
+        for i,diff in enumerate(diffs):
+            thresh = max(DIFF_THRESH, FRAC_THRESH * var_expected[ch][i])
+            assert(diff <= thresh)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py b/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
new file mode 100644
index 0000000..9b43a74
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_ev_compensation_advanced.py
@@ -0,0 +1,88 @@
+# 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.caps
+import its.objects
+import os.path
+import pylab
+import matplotlib
+import matplotlib.pyplot
+import numpy
+
+def main():
+    """Tests that EV compensation is applied.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    MAX_LUMA_DELTA_THRESH = 0.02
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.manual_sensor(props) and
+                             its.caps.manual_post_proc(props) and
+                             its.caps.per_frame_control(props) and
+                             its.caps.ev_compensation(props))
+
+        ev_compensation_range = props['android.control.aeCompensationRange']
+        range_min = ev_compensation_range[0]
+        range_max = ev_compensation_range[1]
+        ev_per_step = its.objects.rational_to_float(
+                props['android.control.aeCompensationStep'])
+        steps_per_ev = int(1.0 / ev_per_step)
+        evs = range(range_min, range_max + 1, steps_per_ev)
+        lumas = []
+        for ev in evs:
+            # Re-converge 3A, and lock AE once converged. skip AF trigger as
+            # dark/bright scene could make AF convergence fail and this test
+            # doesn't care the image sharpness.
+            cam.do_3a(ev_comp=ev, lock_ae=True, do_af=False)
+
+            # Capture a single shot with the same EV comp and locked AE.
+            req = its.objects.auto_capture_request()
+            req['android.control.aeExposureCompensation'] = ev
+            req["android.control.aeLock"] = True
+            # Use linear tone curve to avoid brightness being impacted
+            # by tone curves.
+            req["android.tonemap.mode"] = 0
+            req["android.tonemap.curveRed"] = [0.0,0.0, 1.0,1.0]
+            req["android.tonemap.curveGreen"] = [0.0,0.0, 1.0,1.0]
+            req["android.tonemap.curveBlue"] = [0.0,0.0, 1.0,1.0]
+            cap = cam.do_capture(req)
+            y = its.image.convert_capture_to_planes(cap)[0]
+            tile = its.image.get_image_patch(y, 0.45,0.45,0.1,0.1)
+            lumas.append(its.image.compute_image_means(tile)[0])
+
+        luma_increase_per_step = pow(2, ev_per_step)
+        print "ev_step_size_in_stops", ev_per_step
+        imid = len(lumas) / 2
+        expected_lumas = [lumas[imid] / pow(luma_increase_per_step, i)
+                          for i in range(imid , 0, -1)]  + \
+                         [lumas[imid] * pow(luma_increase_per_step, i-imid)
+                          for i in range(imid, len(evs))]
+
+        pylab.plot(evs, lumas, 'r')
+        pylab.plot(evs, expected_lumas, 'b')
+        matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
+        luma_diffs = [expected_lumas[i] - lumas[i] for i in range(len(evs))]
+        max_diff = max(abs(i) for i in luma_diffs)
+        avg_diff = abs(numpy.array(luma_diffs)).mean()
+        print "Max delta between modeled and measured lumas:", max_diff
+        print "Avg delta between modeled and measured lumas:", avg_diff
+        assert(max_diff < MAX_LUMA_DELTA_THRESH)
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py b/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
new file mode 100644
index 0000000..d09f2fd
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_ev_compensation_basic.py
@@ -0,0 +1,70 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import os.path
+import pylab
+import matplotlib
+import matplotlib.pyplot
+import numpy
+
+def main():
+    """Tests that EV compensation is applied.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.ev_compensation(props))
+
+        ev_per_step = its.objects.rational_to_float(
+                props['android.control.aeCompensationStep'])
+        steps_per_ev = int(1.0 / ev_per_step)
+        evs = range(-2 * steps_per_ev, 2 * steps_per_ev + 1, steps_per_ev)
+        lumas = []
+        for ev in evs:
+            # Re-converge 3A, and lock AE once converged. skip AF trigger as
+            # dark/bright scene could make AF convergence fail and this test
+            # doesn't care the image sharpness.
+            cam.do_3a(ev_comp=ev, lock_ae=True, do_af=False)
+
+            # Capture a single shot with the same EV comp and locked AE.
+            req = its.objects.auto_capture_request()
+            req['android.control.aeExposureCompensation'] = ev
+            req["android.control.aeLock"] = True
+            cap = cam.do_capture(req)
+            y = its.image.convert_capture_to_planes(cap)[0]
+            tile = its.image.get_image_patch(y, 0.45,0.45,0.1,0.1)
+            lumas.append(its.image.compute_image_means(tile)[0])
+
+        pylab.plot(evs, lumas, 'r')
+        matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
+        # trim trailing 1.0s (for saturated image)
+        while lumas and lumas[-1] == 1.0:
+            lumas.pop(-1)
+        # Only allow positive EVs to give saturated image
+        assert(len(lumas) > 2)
+        luma_diffs = numpy.diff(lumas)
+        min_luma_diffs = min(luma_diffs)
+        print "Min of the luma value difference between adjacent ev comp: ", \
+                min_luma_diffs
+        # All luma brightness should be increasing with increasing ev comp.
+        assert(min_luma_diffs > 0)
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CameraITS/tests/scene1/test_exposure.py b/apps/CameraITS/tests/scene1/test_exposure.py
new file mode 100644
index 0000000..c55e7ad
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_exposure.py
@@ -0,0 +1,91 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import pylab
+import numpy
+import os.path
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Test that a constant exposure is seen as ISO and exposure time vary.
+
+    Take a series of shots that have ISO and exposure time chosen to balance
+    each other; result should be the same brightness, but over the sequence
+    the images should get noisier.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    THRESHOLD_MAX_OUTLIER_DIFF = 0.1
+    THRESHOLD_MIN_LEVEL = 0.1
+    THRESHOLD_MAX_LEVEL = 0.9
+    THRESHOLD_MAX_ABS_GRAD = 0.001
+
+    mults = []
+    r_means = []
+    g_means = []
+    b_means = []
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.per_frame_control(props))
+
+        e,s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
+        expt_range = props['android.sensor.info.exposureTimeRange']
+        sens_range = props['android.sensor.info.sensitivityRange']
+
+        m = 1
+        while s*m < sens_range[1] and e/m > expt_range[0]:
+            mults.append(m)
+            req = its.objects.manual_capture_request(s*m, e/m)
+            cap = cam.do_capture(req)
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(img, "%s_mult=%02d.jpg" % (NAME, m))
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            rgb_means = its.image.compute_image_means(tile)
+            r_means.append(rgb_means[0])
+            g_means.append(rgb_means[1])
+            b_means.append(rgb_means[2])
+            m = m + 4
+
+    # Draw a plot.
+    pylab.plot(mults, r_means, 'r')
+    pylab.plot(mults, g_means, 'g')
+    pylab.plot(mults, b_means, 'b')
+    pylab.ylim([0,1])
+    matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
+    # Check for linearity. For each R,G,B channel, fit a line y=mx+b, and
+    # assert that the gradient is close to 0 (flat) and that there are no
+    # crazy outliers. Also ensure that the images aren't clamped to 0 or 1
+    # (which would make them look like flat lines).
+    for chan in xrange(3):
+        values = [r_means, g_means, b_means][chan]
+        m, b = numpy.polyfit(mults, values, 1).tolist()
+        print "Channel %d line fit (y = mx+b): m = %f, b = %f" % (chan, m, b)
+        assert(abs(m) < THRESHOLD_MAX_ABS_GRAD)
+        assert(b > THRESHOLD_MIN_LEVEL and b < THRESHOLD_MAX_LEVEL)
+        for v in values:
+            assert(v > THRESHOLD_MIN_LEVEL and v < THRESHOLD_MAX_LEVEL)
+            assert(abs(v - b) < THRESHOLD_MAX_OUTLIER_DIFF)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_format_combos.py b/apps/CameraITS/tests/scene1/test_format_combos.py
new file mode 100644
index 0000000..1b40826
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_format_combos.py
@@ -0,0 +1,124 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.error
+import its.target
+import sys
+import os
+import os.path
+
+# Change this to True, to have the test break at the first failure.
+stop_at_first_failure = False
+
+def main():
+    """Test different combinations of output formats.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    with its.device.ItsSession() as cam:
+
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.raw16(props))
+
+        successes = []
+        failures = []
+
+        # Two different requests: auto, and manual.
+        e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
+        req_aut = its.objects.auto_capture_request()
+        req_man = its.objects.manual_capture_request(s, e)
+        reqs = [req_aut, # R0
+                req_man] # R1
+
+        # 10 different combos of output formats; some are single surfaces, and
+        # some are multiple surfaces.
+        wyuv,hyuv = its.objects.get_available_output_sizes("yuv", props)[-1]
+        wjpg,hjpg = its.objects.get_available_output_sizes("jpg", props)[-1]
+        fmt_yuv_prev = {"format":"yuv", "width":wyuv, "height":hyuv}
+        fmt_yuv_full = {"format":"yuv"}
+        fmt_jpg_prev = {"format":"jpeg","width":wjpg, "height":hjpg}
+        fmt_jpg_full = {"format":"jpeg"}
+        fmt_raw_full = {"format":"raw"}
+        fmt_combos =[
+                [fmt_yuv_prev],                             # F0
+                [fmt_yuv_full],                             # F1
+                [fmt_jpg_prev],                             # F2
+                [fmt_jpg_full],                             # F3
+                [fmt_raw_full],                             # F4
+                [fmt_yuv_prev, fmt_jpg_prev],               # F5
+                [fmt_yuv_prev, fmt_jpg_full],               # F6
+                [fmt_yuv_prev, fmt_raw_full],               # F7
+                [fmt_yuv_prev, fmt_jpg_prev, fmt_raw_full], # F8
+                [fmt_yuv_prev, fmt_jpg_full, fmt_raw_full]] # F9
+
+        # Two different burst lengths: single frame, and 3 frames.
+        burst_lens = [1, # B0
+                      3] # B1
+
+        # There are 2x10x2=40 different combinations. Run through them all.
+        n = 0
+        for r,req in enumerate(reqs):
+            for f,fmt_combo in enumerate(fmt_combos):
+                for b,burst_len in enumerate(burst_lens):
+                    try:
+                        caps = cam.do_capture([req]*burst_len, fmt_combo)
+                        successes.append((n,r,f,b))
+                        print "==> Success[%02d]: R%d F%d B%d" % (n,r,f,b)
+
+                        # Dump the captures out to jpegs.
+                        if not isinstance(caps, list):
+                            caps = [caps]
+                        elif isinstance(caps[0], list):
+                            caps = sum(caps, [])
+                        for c,cap in enumerate(caps):
+                            img = its.image.convert_capture_to_rgb_image(cap,
+                                    props=props)
+                            its.image.write_image(img,
+                                    "%s_n%02d_r%d_f%d_b%d_c%d.jpg"%(NAME,n,r,f,b,c))
+
+                    except Exception as e:
+                        print e
+                        print "==> Failure[%02d]: R%d F%d B%d" % (n,r,f,b)
+                        failures.append((n,r,f,b))
+                        if stop_at_first_failure:
+                            sys.exit(0)
+                    n += 1
+
+        num_fail = len(failures)
+        num_success = len(successes)
+        num_total = len(reqs)*len(fmt_combos)*len(burst_lens)
+        num_not_run = num_total - num_success - num_fail
+
+        print "\nFailures (%d / %d):" % (num_fail, num_total)
+        for (n,r,f,b) in failures:
+            print "  %02d: R%d F%d B%d" % (n,r,f,b)
+        print "\nSuccesses (%d / %d):" % (num_success, num_total)
+        for (n,r,f,b) in successes:
+            print "  %02d: R%d F%d B%d" % (n,r,f,b)
+        if num_not_run > 0:
+            print "\nNumber of tests not run: %d / %d" % (num_not_run, num_total)
+        print ""
+
+        # The test passes if all the combinations successfully capture.
+        assert(num_fail == 0)
+        assert(num_success == num_total)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_jpeg.py b/apps/CameraITS/tests/scene1/test_jpeg.py
new file mode 100644
index 0000000..25c2038
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_jpeg.py
@@ -0,0 +1,63 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import os.path
+import math
+
+def main():
+    """Test that converted YUV images and device JPEG images look the same.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    THRESHOLD_MAX_RMS_DIFF = 0.01
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.per_frame_control(props))
+
+        e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
+        req = its.objects.manual_capture_request(s, e, True)
+
+        # YUV
+        size = its.objects.get_available_output_sizes("yuv", props)[0]
+        out_surface = {"width":size[0], "height":size[1], "format":"yuv"}
+        cap = cam.do_capture(req, out_surface)
+        img = its.image.convert_capture_to_rgb_image(cap)
+        its.image.write_image(img, "%s_fmt=yuv.jpg" % (NAME))
+        tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+        rgb0 = its.image.compute_image_means(tile)
+
+        # JPEG
+        size = its.objects.get_available_output_sizes("jpg", props)[0]
+        out_surface = {"width":size[0], "height":size[1], "format":"jpg"}
+        cap = cam.do_capture(req, out_surface)
+        img = its.image.decompress_jpeg_to_rgb_image(cap["data"])
+        its.image.write_image(img, "%s_fmt=jpg.jpg" % (NAME))
+        tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+        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)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_latching.py b/apps/CameraITS/tests/scene1/test_latching.py
new file mode 100644
index 0000000..3bc4356
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_latching.py
@@ -0,0 +1,90 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Test that settings latch on the right frame.
+
+    Takes a bunch of shots using back-to-back requests, varying the capture
+    request parameters between shots. Checks that the images that come back
+    have the expected properties.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.full(props) and
+                             its.caps.per_frame_control(props))
+
+        _,fmt = its.objects.get_fastest_manual_capture_settings(props)
+        e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
+        e /= 2.0
+
+        r_means = []
+        g_means = []
+        b_means = []
+
+        reqs = [
+            its.objects.manual_capture_request(s,  e,   True),
+            its.objects.manual_capture_request(s,  e,   True),
+            its.objects.manual_capture_request(s*2,e,   True),
+            its.objects.manual_capture_request(s*2,e,   True),
+            its.objects.manual_capture_request(s,  e,   True),
+            its.objects.manual_capture_request(s,  e,   True),
+            its.objects.manual_capture_request(s,  e*2, True),
+            its.objects.manual_capture_request(s,  e,   True),
+            its.objects.manual_capture_request(s*2,e,   True),
+            its.objects.manual_capture_request(s,  e,   True),
+            its.objects.manual_capture_request(s,  e*2, True),
+            its.objects.manual_capture_request(s,  e,   True),
+            its.objects.manual_capture_request(s,  e*2, True),
+            its.objects.manual_capture_request(s,  e*2, True),
+            ]
+
+        caps = cam.do_capture(reqs, fmt)
+        for i,cap in enumerate(caps):
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(img, "%s_i=%02d.jpg" % (NAME, i))
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            rgb_means = its.image.compute_image_means(tile)
+            r_means.append(rgb_means[0])
+            g_means.append(rgb_means[1])
+            b_means.append(rgb_means[2])
+
+        # Draw a plot.
+        idxs = range(len(r_means))
+        pylab.plot(idxs, r_means, 'r')
+        pylab.plot(idxs, g_means, 'g')
+        pylab.plot(idxs, b_means, 'b')
+        pylab.ylim([0,1])
+        matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
+        g_avg = sum(g_means) / len(g_means)
+        g_ratios = [g / g_avg for g in g_means]
+        g_hilo = [g>1.0 for g in g_ratios]
+        assert(g_hilo == [False, False, True, True, False, False, True,
+                          False, True, False, True, False, True, True])
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_linearity.py b/apps/CameraITS/tests/scene1/test_linearity.py
new file mode 100644
index 0000000..2176f5e
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_linearity.py
@@ -0,0 +1,98 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import numpy
+import math
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Test that device processing can be inverted to linear pixels.
+
+    Captures a sequence of shots with the device pointed at a uniform
+    target. Attempts to invert all the ISP processing to get back to
+    linear R,G,B pixel data.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    RESIDUAL_THRESHOLD = 0.0003 # approximately each sample is off by 2/255
+
+    # The HAL3.2 spec requires that curves up to 64 control points in length
+    # must be supported.
+    L = 64
+    LM1 = float(L-1)
+
+    gamma_lut = numpy.array(
+            sum([[i/LM1, math.pow(i/LM1, 1/2.2)] for i in xrange(L)], []))
+    inv_gamma_lut = numpy.array(
+            sum([[i/LM1, math.pow(i/LM1, 2.2)] for i in xrange(L)], []))
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.per_frame_control(props))
+
+        e,s = its.target.get_target_exposure_combos(cam)["midSensitivity"]
+        s /= 2
+        sens_range = props['android.sensor.info.sensitivityRange']
+        sensitivities = [s*1.0/3.0, s*2.0/3.0, s, s*4.0/3.0, s*5.0/3.0]
+        sensitivities = [s for s in sensitivities
+                if s > sens_range[0] and s < sens_range[1]]
+
+        req = its.objects.manual_capture_request(0, e)
+        req["android.blackLevel.lock"] = True
+        req["android.tonemap.mode"] = 0
+        req["android.tonemap.curveRed"] = gamma_lut.tolist()
+        req["android.tonemap.curveGreen"] = gamma_lut.tolist()
+        req["android.tonemap.curveBlue"] = gamma_lut.tolist()
+
+        r_means = []
+        g_means = []
+        b_means = []
+
+        for sens in sensitivities:
+            req["android.sensor.sensitivity"] = sens
+            cap = cam.do_capture(req)
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(
+                    img, "%s_sens=%04d.jpg" % (NAME, sens))
+            img = its.image.apply_lut_to_image(img, inv_gamma_lut[1::2] * LM1)
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            rgb_means = its.image.compute_image_means(tile)
+            r_means.append(rgb_means[0])
+            g_means.append(rgb_means[1])
+            b_means.append(rgb_means[2])
+
+        pylab.plot(sensitivities, r_means, 'r')
+        pylab.plot(sensitivities, g_means, 'g')
+        pylab.plot(sensitivities, b_means, 'b')
+        pylab.ylim([0,1])
+        matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
+        # Check that each plot is actually linear.
+        for means in [r_means, g_means, b_means]:
+            line,residuals,_,_,_  = numpy.polyfit(range(5),means,1,full=True)
+            print "Line: m=%f, b=%f, resid=%f"%(line[0], line[1], residuals[0])
+            assert(residuals[0] < RESIDUAL_THRESHOLD)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_locked_burst.py b/apps/CameraITS/tests/scene1/test_locked_burst.py
new file mode 100644
index 0000000..90662db
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_locked_burst.py
@@ -0,0 +1,92 @@
+# 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
+import numpy
+import pylab
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Test 3A lock + YUV burst (using auto settings).
+
+    This is a test that is designed to pass even on limited devices that
+    don't have MANUAL_SENSOR or PER_FRAME_CONTROLS. (They must be able to
+    capture bursts with full res @ full frame rate to pass, however).
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    BURST_LEN = 8
+    SPREAD_THRESH = 0.005
+    FPS_MAX_DIFF = 2.0
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+
+        # Converge 3A prior to capture.
+        cam.do_3a(do_af=True, lock_ae=True, lock_awb=True)
+
+        # After 3A has converged, lock AE+AWB for the duration of the test.
+        req = its.objects.fastest_auto_capture_request(props)
+        req["android.control.awbLock"] = True
+        req["android.control.aeLock"] = True
+
+        # Capture bursts of YUV shots.
+        # Get the mean values of a center patch for each.
+        r_means = []
+        g_means = []
+        b_means = []
+        caps = cam.do_capture([req]*BURST_LEN)
+        for i,cap in enumerate(caps):
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(img, "%s_frame%d.jpg"%(NAME,i))
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            means = its.image.compute_image_means(tile)
+            r_means.append(means[0])
+            g_means.append(means[1])
+            b_means.append(means[2])
+
+        # Pass/fail based on center patch similarity.
+        for means in [r_means, g_means, b_means]:
+            spread = max(means) - min(means)
+            print "Patch mean spread", spread, \
+                   " (min/max: ",  min(means), "/", max(means), ")"
+            assert(spread < SPREAD_THRESH)
+
+        # Also ensure that the burst was at full frame rate.
+        fmt_code = 0x23
+        configs = props['android.scaler.streamConfigurationMap']\
+                       ['availableStreamConfigurations']
+        min_duration = None
+        for cfg in configs:
+            if cfg['format'] == fmt_code and cfg['input'] == False and \
+                    cfg['width'] == caps[0]["width"] and \
+                    cfg['height'] == caps[0]["height"]:
+                min_duration = cfg["minFrameDuration"]
+        assert(min_duration is not None)
+        tstamps = [c['metadata']['android.sensor.timestamp'] for c in caps]
+        deltas = [tstamps[i]-tstamps[i-1] for i in range(1,len(tstamps))]
+        actual_fps = 1.0 / (max(deltas) / 1000000000.0)
+        actual_fps_max = 1.0 / (min(deltas) / 1000000000.0)
+        max_fps = 1.0 / (min_duration / 1000000000.0)
+        print "Measure FPS min/max", actual_fps, "/", actual_fps_max
+        print "FPS measured %.1f, max advertized %.1f" %(actual_fps, max_fps)
+        assert(max_fps - FPS_MAX_DIFF <= actual_fps <= max_fps + FPS_MAX_DIFF)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_param_color_correction.py b/apps/CameraITS/tests/scene1/test_param_color_correction.py
new file mode 100644
index 0000000..b7fdc7b
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_param_color_correction.py
@@ -0,0 +1,104 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Test that the android.colorCorrection.* params are applied when set.
+
+    Takes shots with different transform and gains values, and tests that
+    they look correspondingly different. The transform and gains are chosen
+    to make the output go redder or bluer.
+
+    Uses a linear tonemap.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    THRESHOLD_MAX_DIFF = 0.1
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.per_frame_control(props))
+
+        # Baseline request
+        e, s = its.target.get_target_exposure_combos(cam)["midSensitivity"]
+        req = its.objects.manual_capture_request(s, e, True)
+        req["android.colorCorrection.mode"] = 0
+
+        # Transforms:
+        # 1. Identity
+        # 2. Identity
+        # 3. Boost blue
+        transforms = [its.objects.int_to_rational([1,0,0, 0,1,0, 0,0,1]),
+                      its.objects.int_to_rational([1,0,0, 0,1,0, 0,0,1]),
+                      its.objects.int_to_rational([1,0,0, 0,1,0, 0,0,2])]
+
+        # Gains:
+        # 1. Unit
+        # 2. Boost red
+        # 3. Unit
+        gains = [[1,1,1,1], [2,1,1,1], [1,1,1,1]]
+
+        r_means = []
+        g_means = []
+        b_means = []
+
+        # Capture requests:
+        # 1. With unit gains, and identity transform.
+        # 2. With a higher red gain, and identity transform.
+        # 3. With unit gains, and a transform that boosts blue.
+        for i in range(len(transforms)):
+            req["android.colorCorrection.transform"] = transforms[i]
+            req["android.colorCorrection.gains"] = gains[i]
+            cap = cam.do_capture(req)
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(img, "%s_req=%d.jpg" % (NAME, i))
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            rgb_means = its.image.compute_image_means(tile)
+            r_means.append(rgb_means[0])
+            g_means.append(rgb_means[1])
+            b_means.append(rgb_means[2])
+            ratios = [rgb_means[0] / rgb_means[1], rgb_means[2] / rgb_means[1]]
+            print "Means = ", rgb_means, "   Ratios =", ratios
+
+        # Draw a plot.
+        domain = range(len(transforms))
+        pylab.plot(domain, r_means, 'r')
+        pylab.plot(domain, g_means, 'g')
+        pylab.plot(domain, b_means, 'b')
+        pylab.ylim([0,1])
+        matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
+        # Expect G0 == G1 == G2, R0 == 0.5*R1 == R2, B0 == B1 == 0.5*B2
+        # Also need to ensure that the image is not clamped to white/black.
+        assert(all(g_means[i] > 0.2 and g_means[i] < 0.8 for i in xrange(3)))
+        assert(abs(g_means[1] - g_means[0]) < THRESHOLD_MAX_DIFF)
+        assert(abs(g_means[2] - g_means[1]) < THRESHOLD_MAX_DIFF)
+        assert(abs(r_means[2] - r_means[0]) < THRESHOLD_MAX_DIFF)
+        assert(abs(r_means[1] - 2.0 * r_means[0]) < THRESHOLD_MAX_DIFF)
+        assert(abs(b_means[1] - b_means[0]) < THRESHOLD_MAX_DIFF)
+        assert(abs(b_means[2] - 2.0 * b_means[0]) < THRESHOLD_MAX_DIFF)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_param_exposure_time.py b/apps/CameraITS/tests/scene1/test_param_exposure_time.py
new file mode 100644
index 0000000..e6078d9
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_param_exposure_time.py
@@ -0,0 +1,68 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Test that the android.sensor.exposureTime parameter is applied.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    exp_times = []
+    r_means = []
+    g_means = []
+    b_means = []
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.per_frame_control(props))
+
+        e,s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
+        for i,e_mult in enumerate([0.8, 0.9, 1.0, 1.1, 1.2]):
+            req = its.objects.manual_capture_request(s, e * e_mult, True)
+            cap = cam.do_capture(req)
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(
+                    img, "%s_frame%d.jpg" % (NAME, i))
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            rgb_means = its.image.compute_image_means(tile)
+            exp_times.append(e * e_mult)
+            r_means.append(rgb_means[0])
+            g_means.append(rgb_means[1])
+            b_means.append(rgb_means[2])
+
+    # Draw a plot.
+    pylab.plot(exp_times, r_means, 'r')
+    pylab.plot(exp_times, g_means, 'g')
+    pylab.plot(exp_times, b_means, 'b')
+    pylab.ylim([0,1])
+    matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
+    # Test for pass/fail: check that each shot is brighter than the previous.
+    for means in [r_means, g_means, b_means]:
+        for i in range(len(means)-1):
+            assert(means[i+1] > means[i])
+
+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
new file mode 100644
index 0000000..aae56aa
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_param_flash_mode.py
@@ -0,0 +1,66 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import os.path
+
+def main():
+    """Test that the android.flash.mode parameter is applied.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.flash(props) and
+                             its.caps.per_frame_control(props))
+
+        flash_modes_reported = []
+        flash_states_reported = []
+        g_means = []
+
+        # 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
+        # linear tonemap.
+        e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
+        e /= 4
+        req = its.objects.manual_capture_request(s, e, True)
+
+        for f in [0,1,2]:
+            req["android.flash.mode"] = f
+            cap = cam.do_capture(req)
+            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])
+
+        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])
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_param_noise_reduction.py b/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
new file mode 100644
index 0000000..f5176a7
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
@@ -0,0 +1,99 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Test that the android.noiseReduction.mode param is applied when set.
+
+    Capture images with the camera dimly lit. Uses a high analog gain to
+    ensure the captured image is noisy.
+
+    Captures three images, for NR off, "fast", and "high quality".
+    Also captures an image with low gain and NR off, and uses the variance
+    of this as the baseline.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    # List of variances for Y,U,V.
+    variances = [[],[],[]]
+
+    # Reference (baseline) variance for each of Y,U,V.
+    ref_variance = []
+
+    nr_modes_reported = []
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.per_frame_control(props))
+
+        # NR mode 0 with low gain
+        e, s = its.target.get_target_exposure_combos(cam)["minSensitivity"]
+        req = its.objects.manual_capture_request(s, e)
+        req["android.noiseReduction.mode"] = 0
+        cap = cam.do_capture(req)
+        its.image.write_image(
+                its.image.convert_capture_to_rgb_image(cap),
+                "%s_low_gain.jpg" % (NAME))
+        planes = its.image.convert_capture_to_planes(cap)
+        for j in range(3):
+            img = planes[j]
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            ref_variance.append(its.image.compute_image_variances(tile)[0])
+        print "Ref variances:", ref_variance
+
+        for i in range(3):
+            # NR modes 0, 1, 2 with high gain
+            e, s = its.target.get_target_exposure_combos(cam)["maxSensitivity"]
+            req = its.objects.manual_capture_request(s, e)
+            req["android.noiseReduction.mode"] = i
+            cap = cam.do_capture(req)
+            nr_modes_reported.append(
+                    cap["metadata"]["android.noiseReduction.mode"])
+            its.image.write_image(
+                    its.image.convert_capture_to_rgb_image(cap),
+                    "%s_high_gain_nr=%d.jpg" % (NAME, i))
+            planes = its.image.convert_capture_to_planes(cap)
+            for j in range(3):
+                img = planes[j]
+                tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+                variance = its.image.compute_image_variances(tile)[0]
+                variances[j].append(variance / ref_variance[j])
+        print "Variances with NR mode [0,1,2]:", variances
+
+    # Draw a plot.
+    for j in range(3):
+        pylab.plot(range(3), variances[j], "rgb"[j])
+    matplotlib.pyplot.savefig("%s_plot_variances.png" % (NAME))
+
+    assert(nr_modes_reported == [0,1,2])
+
+    # Check that the variance of the NR=0 image is higher than for the
+    # NR=1 and NR=2 images.
+    for j in range(3):
+        for i in range(1,3):
+            assert(variances[j][i] < variances[j][0])
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_param_sensitivity.py b/apps/CameraITS/tests/scene1/test_param_sensitivity.py
new file mode 100644
index 0000000..d6b44a2
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_param_sensitivity.py
@@ -0,0 +1,73 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Test that the android.sensor.sensitivity parameter is applied.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    NUM_STEPS = 5
+
+    sensitivities = None
+    r_means = []
+    g_means = []
+    b_means = []
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.per_frame_control(props))
+
+        expt,_ = its.target.get_target_exposure_combos(cam)["midSensitivity"]
+        sens_range = props['android.sensor.info.sensitivityRange']
+        sens_step = (sens_range[1] - sens_range[0]) / float(NUM_STEPS-1)
+        sensitivities = [sens_range[0] + i * sens_step for i in range(NUM_STEPS)]
+
+        for s in sensitivities:
+            req = its.objects.manual_capture_request(s, expt)
+            cap = cam.do_capture(req)
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(
+                    img, "%s_iso=%04d.jpg" % (NAME, s))
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            rgb_means = its.image.compute_image_means(tile)
+            r_means.append(rgb_means[0])
+            g_means.append(rgb_means[1])
+            b_means.append(rgb_means[2])
+
+    # Draw a plot.
+    pylab.plot(sensitivities, r_means, 'r')
+    pylab.plot(sensitivities, g_means, 'g')
+    pylab.plot(sensitivities, b_means, 'b')
+    pylab.ylim([0,1])
+    matplotlib.pyplot.savefig("%s_plot_means.png" % (NAME))
+
+    # Test for pass/fail: check that each shot is brighter than the previous.
+    for means in [r_means, g_means, b_means]:
+        for i in range(len(means)-1):
+            assert(means[i+1] > means[i])
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_param_tonemap_mode.py b/apps/CameraITS/tests/scene1/test_param_tonemap_mode.py
new file mode 100644
index 0000000..8c8e626
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_param_tonemap_mode.py
@@ -0,0 +1,103 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import os
+import os.path
+
+def main():
+    """Test that the android.tonemap.mode param is applied.
+
+    Applies different tonemap curves to each R,G,B channel, and checks
+    that the output images are modified as expected.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    THRESHOLD_RATIO_MIN_DIFF = 0.1
+    THRESHOLD_DIFF_MAX_DIFF = 0.05
+
+    # The HAL3.2 spec requires that curves up to 64 control points in length
+    # must be supported.
+    L = 32
+    LM1 = float(L-1)
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.per_frame_control(props))
+
+        e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
+        e /= 2
+
+        # Test 1: that the tonemap curves have the expected effect. Take two
+        # shots, with n in [0,1], where each has a linear tonemap, with the
+        # n=1 shot having a steeper gradient. The gradient for each R,G,B
+        # channel increases (i.e.) R[n=1] should be brighter than R[n=0],
+        # and G[n=1] should be brighter than G[n=0] by a larger margin, etc.
+        rgb_means = []
+
+        for n in [0,1]:
+            req = its.objects.manual_capture_request(s,e)
+            req["android.tonemap.mode"] = 0
+            req["android.tonemap.curveRed"] = (
+                    sum([[i/LM1, min(1.0,(1+0.5*n)*i/LM1)] for i in range(L)], []))
+            req["android.tonemap.curveGreen"] = (
+                    sum([[i/LM1, min(1.0,(1+1.0*n)*i/LM1)] for i in range(L)], []))
+            req["android.tonemap.curveBlue"] = (
+                    sum([[i/LM1, min(1.0,(1+1.5*n)*i/LM1)] for i in range(L)], []))
+            cap = cam.do_capture(req)
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(
+                    img, "%s_n=%d.jpg" %(NAME, n))
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            rgb_means.append(its.image.compute_image_means(tile))
+
+        rgb_ratios = [rgb_means[1][i] / rgb_means[0][i] for i in xrange(3)]
+        print "Test 1: RGB ratios:", rgb_ratios
+        assert(rgb_ratios[0] + THRESHOLD_RATIO_MIN_DIFF < rgb_ratios[1])
+        assert(rgb_ratios[1] + THRESHOLD_RATIO_MIN_DIFF < rgb_ratios[2])
+
+
+        # Test 2: that the length of the tonemap curve (i.e. number of control
+        # points) doesn't affect the output.
+        rgb_means = []
+
+        for size in [32,64]:
+            m = float(size-1)
+            curve = sum([[i/m, i/m] for i in range(size)], [])
+            req = its.objects.manual_capture_request(s,e)
+            req["android.tonemap.mode"] = 0
+            req["android.tonemap.curveRed"] = curve
+            req["android.tonemap.curveGreen"] = curve
+            req["android.tonemap.curveBlue"] = curve
+            cap = cam.do_capture(req)
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(
+                    img, "%s_size=%02d.jpg" %(NAME, size))
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            rgb_means.append(its.image.compute_image_means(tile))
+
+        rgb_diffs = [rgb_means[1][i] - rgb_means[0][i] for i in xrange(3)]
+        print "Test 2: RGB diffs:", rgb_diffs
+        assert(abs(rgb_diffs[0]) < THRESHOLD_DIFF_MAX_DIFF)
+        assert(abs(rgb_diffs[1]) < THRESHOLD_DIFF_MAX_DIFF)
+        assert(abs(rgb_diffs[2]) < THRESHOLD_DIFF_MAX_DIFF)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py b/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
new file mode 100644
index 0000000..6c2b5c1
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_raw_burst_sensitivity.py
@@ -0,0 +1,85 @@
+# 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.device
+import its.caps
+import its.objects
+import its.image
+import os.path
+import pylab
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Capture a set of raw images with increasing gains and measure the noise.
+
+    Capture raw-only, in a burst.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    # Each shot must be 1% noisier (by the variance metric) than the previous
+    # one.
+    VAR_THRESH = 1.01
+
+    NUM_STEPS = 5
+
+    with its.device.ItsSession() as cam:
+
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.raw16(props) and
+                             its.caps.manual_sensor(props) and
+                             its.caps.read_3a(props) and
+                             its.caps.per_frame_control(props))
+
+        # Expose for the scene with min sensitivity
+        sens_min, sens_max = props['android.sensor.info.sensitivityRange']
+        sens_step = (sens_max - sens_min) / NUM_STEPS
+        s_ae,e_ae,_,_,_  = cam.do_3a(get_results=True)
+        s_e_prod = s_ae * e_ae
+
+        reqs = []
+        settings = []
+        for s in range(sens_min, sens_max, sens_step):
+            e = int(s_e_prod / float(s))
+            req = its.objects.manual_capture_request(s, e)
+            reqs.append(req)
+            settings.append((s,e))
+
+        caps = cam.do_capture(reqs, cam.CAP_RAW)
+
+        variances = []
+        for i,cap in enumerate(caps):
+            (s,e) = settings[i]
+
+            # Measure the variance. Each shot should be noisier than the
+            # previous shot (as the gain is increasing).
+            plane = its.image.convert_capture_to_planes(cap, props)[1]
+            tile = its.image.get_image_patch(plane, 0.45,0.45,0.1,0.1)
+            var = its.image.compute_image_variances(tile)[0]
+            variances.append(var)
+
+            img = its.image.convert_capture_to_rgb_image(cap, props=props)
+            its.image.write_image(img, "%s_s=%05d_var=%f.jpg" % (NAME,s,var))
+            print "s=%d, e=%d, var=%e"%(s,e,var)
+
+        pylab.plot(range(len(variances)), variances)
+        matplotlib.pyplot.savefig("%s_variances.png" % (NAME))
+
+        # Test that each shot is noisier than the previous one.
+        for i in range(len(variances) - 1):
+            assert(variances[i] < variances[i+1] / VAR_THRESH)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_raw_sensitivity.py b/apps/CameraITS/tests/scene1/test_raw_sensitivity.py
new file mode 100644
index 0000000..14c5eb0
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_raw_sensitivity.py
@@ -0,0 +1,78 @@
+# 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.device
+import its.caps
+import its.objects
+import its.image
+import os.path
+import pylab
+import matplotlib
+import matplotlib.pyplot
+
+def main():
+    """Capture a set of raw images with increasing gains and measure the noise.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    # Each shot must be 1% noisier (by the variance metric) than the previous
+    # one.
+    VAR_THRESH = 1.01
+
+    NUM_STEPS = 5
+
+    with its.device.ItsSession() as cam:
+
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.raw16(props) and
+                             its.caps.manual_sensor(props) and
+                             its.caps.read_3a(props) and
+                             its.caps.per_frame_control(props))
+
+        # Expose for the scene with min sensitivity
+        sens_min, sens_max = props['android.sensor.info.sensitivityRange']
+        sens_step = (sens_max - sens_min) / NUM_STEPS
+        s_ae,e_ae,_,_,_  = cam.do_3a(get_results=True)
+        s_e_prod = s_ae * e_ae
+
+        variances = []
+        for s in range(sens_min, sens_max, sens_step):
+
+            e = int(s_e_prod / float(s))
+            req = its.objects.manual_capture_request(s, e)
+
+            # Capture raw+yuv, but only look at the raw.
+            cap,_ = cam.do_capture(req, cam.CAP_RAW_YUV)
+
+            # Measure the variance. Each shot should be noisier than the
+            # previous shot (as the gain is increasing).
+            plane = its.image.convert_capture_to_planes(cap, props)[1]
+            tile = its.image.get_image_patch(plane, 0.45,0.45,0.1,0.1)
+            var = its.image.compute_image_variances(tile)[0]
+            variances.append(var)
+
+            img = its.image.convert_capture_to_rgb_image(cap, props=props)
+            its.image.write_image(img, "%s_s=%05d_var=%f.jpg" % (NAME,s,var))
+            print "s=%d, e=%d, var=%e"%(s,e,var)
+
+        pylab.plot(range(len(variances)), variances)
+        matplotlib.pyplot.savefig("%s_variances.png" % (NAME))
+
+        # Test that each shot is noisier than the previous one.
+        for i in range(len(variances) - 1):
+            assert(variances[i] < variances[i+1] / VAR_THRESH)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_tonemap_sequence.py b/apps/CameraITS/tests/scene1/test_tonemap_sequence.py
new file mode 100644
index 0000000..18ca506
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_tonemap_sequence.py
@@ -0,0 +1,70 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import os.path
+import numpy
+
+def main():
+    """Test a sequence of shots with different tonemap curves.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    # There should be 3 identical frames followed by a different set of
+    # 3 identical frames.
+    MAX_SAME_DELTA = 0.01
+    MIN_DIFF_DELTA = 0.10
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.manual_sensor(props) and
+                             its.caps.manual_post_proc(props) and
+                             its.caps.per_frame_control(props))
+
+        sens, exp_time, _,_,_ = cam.do_3a(do_af=False,get_results=True)
+
+        means = []
+
+        # Capture 3 manual shots with a linear tonemap.
+        req = its.objects.manual_capture_request(sens, exp_time, True)
+        for i in [0,1,2]:
+            cap = cam.do_capture(req)
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(img, "%s_i=%d.jpg" % (NAME, i))
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            means.append(tile.mean(0).mean(0))
+
+        # Capture 3 manual shots with the default tonemap.
+        req = its.objects.manual_capture_request(sens, exp_time, False)
+        for i in [3,4,5]:
+            cap = cam.do_capture(req)
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(img, "%s_i=%d.jpg" % (NAME, i))
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            means.append(tile.mean(0).mean(0))
+
+        # Compute the delta between each consecutive frame pair.
+        deltas = [numpy.max(numpy.fabs(means[i+1]-means[i])) \
+                  for i in range(len(means)-1)]
+        print "Deltas between consecutive frames:", deltas
+
+        assert(all([abs(deltas[i]) < MAX_SAME_DELTA for i in [0,1,3,4]]))
+        assert(abs(deltas[2]) > MIN_DIFF_DELTA)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py b/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py
new file mode 100644
index 0000000..1b278ef
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_yuv_jpeg_all.py
@@ -0,0 +1,84 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import os.path
+import math
+
+def main():
+    """Test that the reported sizes and formats for image capture work.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    THRESHOLD_MAX_RMS_DIFF = 0.03
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.per_frame_control(props))
+
+        # Use a manual request with a linear tonemap so that the YUV and JPEG
+        # should look the same (once converted by the its.image module).
+        e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
+        req = its.objects.manual_capture_request(s, e, True)
+
+        rgbs = []
+
+        for size in its.objects.get_available_output_sizes("yuv", props):
+            out_surface = {"width":size[0], "height":size[1], "format":"yuv"}
+            cap = cam.do_capture(req, out_surface)
+            assert(cap["format"] == "yuv")
+            assert(cap["width"] == size[0])
+            assert(cap["height"] == size[1])
+            print "Captured YUV %dx%d" % (cap["width"], cap["height"])
+            img = its.image.convert_capture_to_rgb_image(cap)
+            its.image.write_image(img, "%s_yuv_w%d_h%d.jpg"%(
+                    NAME,size[0],size[1]))
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            rgb = its.image.compute_image_means(tile)
+            rgbs.append(rgb)
+
+        for size in its.objects.get_available_output_sizes("jpg", props):
+            out_surface = {"width":size[0], "height":size[1], "format":"jpg"}
+            cap = cam.do_capture(req, out_surface)
+            assert(cap["format"] == "jpeg")
+            assert(cap["width"] == size[0])
+            assert(cap["height"] == size[1])
+            img = its.image.decompress_jpeg_to_rgb_image(cap["data"])
+            its.image.write_image(img, "%s_jpg_w%d_h%d.jpg"%(
+                    NAME,size[0], size[1]))
+            assert(img.shape[0] == size[1])
+            assert(img.shape[1] == size[0])
+            assert(img.shape[2] == 3)
+            print "Captured JPEG %dx%d" % (cap["width"], cap["height"])
+            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+            rgb = its.image.compute_image_means(tile)
+            rgbs.append(rgb)
+
+        max_diff = 0
+        rgb0 = rgbs[0]
+        for rgb1 in rgbs[1:]:
+            rms_diff = math.sqrt(
+                    sum([pow(rgb0[i] - rgb1[i], 2.0) for i in range(3)]) / 3.0)
+            max_diff = max(max_diff, rms_diff)
+        print "Max RMS difference:", max_diff
+        assert(rms_diff < THRESHOLD_MAX_RMS_DIFF)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_dng.py b/apps/CameraITS/tests/scene1/test_yuv_plus_dng.py
new file mode 100644
index 0000000..33e7763
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_dng.py
@@ -0,0 +1,47 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import os.path
+
+def main():
+    """Test capturing a single frame as both DNG and YUV outputs.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.raw(props) and
+                             its.caps.read_3a(props))
+
+        cam.do_3a()
+
+        req = its.objects.auto_capture_request()
+        cap_dng, cap_yuv = cam.do_capture(req, cam.CAP_DNG_YUV)
+
+        img = its.image.convert_capture_to_rgb_image(cap_yuv)
+        its.image.write_image(img, "%s.jpg" % (NAME))
+
+        with open("%s.dng"%(NAME), "wb") as f:
+            f.write(cap_dng["data"])
+
+        # No specific pass/fail check; test is assumed to have succeeded if
+        # it completes.
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py b/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
new file mode 100644
index 0000000..6daa243
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_jpeg.py
@@ -0,0 +1,61 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import os.path
+import math
+
+def main():
+    """Test capturing a single frame as both YUV and JPEG outputs.
+    """
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    THRESHOLD_MAX_RMS_DIFF = 0.01
+
+    fmt_yuv =  {"format":"yuv"}
+    fmt_jpeg = {"format":"jpeg"}
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props))
+
+        # Use a manual request with a linear tonemap so that the YUV and JPEG
+        # should look the same (once converted by the its.image module).
+        e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
+        req = its.objects.manual_capture_request(s, e, True)
+
+        cap_yuv, cap_jpeg = cam.do_capture(req, [fmt_yuv, fmt_jpeg])
+
+        img = its.image.convert_capture_to_rgb_image(cap_yuv, True)
+        its.image.write_image(img, "%s_yuv.jpg" % (NAME))
+        tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+        rgb0 = its.image.compute_image_means(tile)
+
+        img = its.image.convert_capture_to_rgb_image(cap_jpeg, True)
+        its.image.write_image(img, "%s_jpeg.jpg" % (NAME))
+        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)
+
+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
new file mode 100644
index 0000000..eb01c1a
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw.py
@@ -0,0 +1,62 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import os.path
+import math
+
+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
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.raw16(props) and
+                             its.caps.per_frame_control(props))
+
+        # Use a manual request with a linear tonemap so that the YUV and RAW
+        # should look the same (once converted by the its.image module).
+        e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
+        req = its.objects.manual_capture_request(s, e, True)
+
+        cap_raw, cap_yuv = cam.do_capture(req, cam.CAP_RAW_YUV)
+
+        img = its.image.convert_capture_to_rgb_image(cap_yuv)
+        its.image.write_image(img, "%s_yuv.jpg" % (NAME), 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, so scale the
+        # tile appropriately.
+        img = its.image.convert_capture_to_rgb_image(cap_raw, props=props)
+        its.image.write_image(img, "%s_raw.jpg" % (NAME), True)
+        tile = its.image.get_image_patch(img, 0.475, 0.475, 0.05, 0.05)
+        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)
+
+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
new file mode 100644
index 0000000..910a8ea
--- /dev/null
+++ b/apps/CameraITS/tests/scene1/test_yuv_plus_raw10.py
@@ -0,0 +1,63 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.image
+import its.caps
+import its.device
+import its.objects
+import its.target
+import os.path
+import math
+
+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
+
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.compute_target_exposure(props) and
+                             its.caps.raw10(props) and
+                             its.caps.per_frame_control(props))
+
+        # Use a manual request with a linear tonemap so that the YUV and RAW
+        # should look the same (once converted by the its.image module).
+        e, s = its.target.get_target_exposure_combos(cam)["midExposureTime"]
+        req = its.objects.manual_capture_request(s, e, True)
+
+        cap_raw, cap_yuv = cam.do_capture(req,
+                [{"format":"raw10"}, {"format":"yuv"}])
+
+        img = its.image.convert_capture_to_rgb_image(cap_yuv)
+        its.image.write_image(img, "%s_yuv.jpg" % (NAME), 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, so scale the
+        # tile appropriately.
+        img = its.image.convert_capture_to_rgb_image(cap_raw, props=props)
+        its.image.write_image(img, "%s_raw.jpg" % (NAME), True)
+        tile = its.image.get_image_patch(img, 0.475, 0.475, 0.05, 0.05)
+        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)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/sensor_fusion/SensorFusion.pdf b/apps/CameraITS/tests/sensor_fusion/SensorFusion.pdf
new file mode 100644
index 0000000..2e390c7
--- /dev/null
+++ b/apps/CameraITS/tests/sensor_fusion/SensorFusion.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
new file mode 100644
index 0000000..efeb665
--- /dev/null
+++ b/apps/CameraITS/tests/sensor_fusion/test_sensor_fusion.py
@@ -0,0 +1,391 @@
+# 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 time
+import math
+import pylab
+import os.path
+import matplotlib
+import matplotlib.pyplot
+import json
+import Image
+import numpy
+import cv2
+import bisect
+import scipy.spatial
+import sys
+
+NAME = os.path.basename(__file__).split(".")[0]
+
+# Capture 210 QVGA frames (which is 7s at 30fps)
+N = 210
+W,H = 320,240
+
+FEATURE_PARAMS = dict( maxCorners = 50,
+                       qualityLevel = 0.3,
+                       minDistance = 7,
+                       blockSize = 7 )
+
+LK_PARAMS = dict( winSize  = (15, 15),
+                  maxLevel = 2,
+                  criteria = (cv2.TERM_CRITERIA_EPS | cv2.TERM_CRITERIA_COUNT,
+                        10, 0.03))
+
+# Constants to convert between different time units (for clarity).
+SEC_TO_NSEC = 1000*1000*1000.0
+SEC_TO_MSEC = 1000.0
+MSEC_TO_NSEC = 1000*1000.0
+MSEC_TO_SEC = 1/1000.0
+NSEC_TO_SEC = 1/(1000*1000*1000.0)
+NSEC_TO_MSEC = 1/(1000*1000.0)
+
+# Pass/fail thresholds.
+THRESH_MAX_CORR_DIST = 0.005
+THRESH_MAX_SHIFT_MS = 2
+THRESH_MIN_ROT = 0.001
+
+def main():
+    """Test if image and motion sensor events are well synchronized.
+
+    The instructions for running this test are in the SensorFusion.pdf file in
+    the same directory as this test.
+
+    The command-line argument "replay" may be optionally provided. Without this
+    argument, the test will collect a new set of camera+gyro data from the
+    device and then analyze it (and it will also dump this data to files in the
+    current directory). If the "replay" argument is provided, then the script
+    will instead load the dumped data from a previous run and analyze that
+    instead. This can be helpful for developers who are digging for additional
+    information on their measurements.
+    """
+
+    # 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
+    # RGB images as numpy arrays.
+    if "replay" not in sys.argv:
+        events, frames = collect_data()
+    else:
+        events, frames = load_data()
+
+    # Sanity check camera timestamps are enclosed by sensor timestamps
+    # This will catch bugs where camera and gyro timestamps go completely out
+    # of sync
+    cam_times = get_cam_times(events["cam"])
+    min_cam_time = min(cam_times) * NSEC_TO_SEC
+    max_cam_time = max(cam_times) * NSEC_TO_SEC
+    gyro_times = [e["time"] for e in events["gyro"]]
+    min_gyro_time = min(gyro_times) * NSEC_TO_SEC
+    max_gyro_time = max(gyro_times) * NSEC_TO_SEC
+    if not (min_cam_time > min_gyro_time and max_cam_time < max_gyro_time):
+        print "Test failed: camera timestamps [%f,%f] " \
+              "are not enclosed by gyro timestamps [%f, %f]" % (
+            min_cam_time, max_cam_time, min_gyro_time, max_gyro_time)
+        assert(0)
+
+    # Compute the camera rotation displacements (rad) between each pair of
+    # adjacent frames.
+    cam_rots = get_cam_rotations(frames)
+    if max(abs(cam_rots)) < THRESH_MIN_ROT:
+        print "Device wasn't moved enough"
+        assert(0)
+
+    # Find the best offset (time-shift) to align the gyro and camera motion
+    # traces; this function integrates the shifted gyro data between camera
+    # samples for a range of candidate shift values, and returns the shift that
+    # result in the best correlation.
+    offset = get_best_alignment_offset(cam_times, cam_rots, events["gyro"])
+
+    # Plot the camera and gyro traces after applying the best shift.
+    cam_times = cam_times + offset*SEC_TO_NSEC
+    gyro_rots = get_gyro_rotations(events["gyro"], cam_times)
+    plot_rotations(cam_rots, gyro_rots)
+
+    # Pass/fail based on the offset and also the correlation distance.
+    dist = scipy.spatial.distance.correlation(cam_rots,gyro_rots)
+    print "Best correlation of %f at shift of %.2fms"%(dist, offset*SEC_TO_MSEC)
+    assert(dist < THRESH_MAX_CORR_DIST)
+    assert(abs(offset) < THRESH_MAX_SHIFT_MS*MSEC_TO_SEC)
+
+def get_best_alignment_offset(cam_times, cam_rots, gyro_events):
+    """Find the best offset to align the camera and gyro traces.
+
+    Uses a correlation distance metric between the curves, where a smaller
+    value means that the curves are better-correlated.
+
+    Args:
+        cam_times: Array of N camera times, one for each frame.
+        cam_rots: Array of N-1 camera rotation displacements (rad).
+        gyro_events: List of gyro event objects.
+
+    Returns:
+        Offset (seconds) of the best alignment.
+    """
+    # Measure the corr. dist. over a shift of up to +/- 100ms (1ms step size).
+    # Get the shift corresponding to the best (lowest) score.
+    candidates = range(-100,101)
+    dists = []
+    for shift in candidates:
+        times = cam_times + shift*MSEC_TO_NSEC
+        gyro_rots = get_gyro_rotations(gyro_events, times)
+        dists.append(scipy.spatial.distance.correlation(cam_rots,gyro_rots))
+    best_corr_dist = min(dists)
+    best_shift = candidates[dists.index(best_corr_dist)]
+
+    # Fit a curve to the corr. dist. data to measure the minima more
+    # accurately, by looking at the correlation distances within a range of
+    # +/- 20ms from the measured best score; note that this will use fewer
+    # than the full +/- 20 range for the curve fit if the measured score
+    # (which is used as the center of the fit) is within 20ms of the edge of
+    # the +/- 100ms candidate range.
+    i = len(dists)/2 + best_shift
+    candidates = candidates[i-20:i+21]
+    dists = dists[i-20:i+21]
+    a,b,c = numpy.polyfit(candidates, dists, 2)
+    exact_best_shift = -b/(2*a)
+    if abs(best_shift - exact_best_shift) > 2.0 or a <= 0 or c <= 0:
+        print "Test failed; bad fit to time-shift curve"
+        assert(0)
+
+    xfit = [x/10.0 for x in xrange(candidates[0]*10,candidates[-1]*10)]
+    yfit = [a*x*x+b*x+c for x in xfit]
+    fig = matplotlib.pyplot.figure()
+    pylab.plot(candidates, dists, 'r', label="data")
+    pylab.plot(xfit, yfit, 'b', label="fit")
+    pylab.plot([exact_best_shift+x for x in [-0.1,0,0.1]], [0,0.01,0], 'b')
+    pylab.xlabel("Relative horizontal shift between curves (ms)")
+    pylab.ylabel("Correlation distance")
+    pylab.legend()
+    matplotlib.pyplot.savefig("%s_plot_shifts.png" % (NAME))
+
+    return exact_best_shift * MSEC_TO_SEC
+
+def plot_rotations(cam_rots, gyro_rots):
+    """Save a plot of the camera vs. gyro rotational measurements.
+
+    Args:
+        cam_rots: Array of N-1 camera rotation measurements (rad).
+        gyro_rots: Array of N-1 gyro rotation measurements (rad).
+    """
+    # For the plot, scale the rotations to be in degrees.
+    fig = matplotlib.pyplot.figure()
+    cam_rots = cam_rots * (360/(2*math.pi))
+    gyro_rots = gyro_rots * (360/(2*math.pi))
+    pylab.plot(range(len(cam_rots)), cam_rots, 'r', label="camera")
+    pylab.plot(range(len(gyro_rots)), gyro_rots, 'b', label="gyro")
+    pylab.legend()
+    pylab.xlabel("Camera frame number")
+    pylab.ylabel("Angular displacement between adjacent camera frames (deg)")
+    pylab.xlim([0, len(cam_rots)])
+    matplotlib.pyplot.savefig("%s_plot.png" % (NAME))
+
+def get_gyro_rotations(gyro_events, cam_times):
+    """Get the rotation values of the gyro.
+
+    Integrates the gyro data between each camera frame to compute an angular
+    displacement. Uses simple Euler approximation to implement the
+    integration.
+
+    Args:
+        gyro_events: List of gyro event objects.
+        cam_times: Array of N camera times, one for each frame.
+
+    Returns:
+        Array of N-1 gyro rotation measurements (rad).
+    """
+    all_times = numpy.array([e["time"] for e in gyro_events])
+    all_rots = numpy.array([e["z"] for e in gyro_events])
+    gyro_rots = []
+    # Integrate the gyro data between each pair of camera frame times.
+    for icam in range(len(cam_times)-1):
+        # Get the window of gyro samples within the current pair of frames.
+        tcam0 = cam_times[icam]
+        tcam1 = cam_times[icam+1]
+        igyrowindow0 = bisect.bisect(all_times, tcam0)
+        igyrowindow1 = bisect.bisect(all_times, tcam1)
+        sgyro = 0
+        # Integrate samples within the window.
+        for igyro in range(igyrowindow0, igyrowindow1):
+            vgyro0 = all_rots[igyro]
+            vgyro1 = all_rots[igyro+1]
+            tgyro0 = all_times[igyro]
+            tgyro1 = all_times[igyro+1]
+            vgyro = 0.5 * (vgyro0 + vgyro1)
+            deltatgyro = (tgyro1 - tgyro0) * NSEC_TO_SEC
+            sgyro += vgyro * deltatgyro
+        # Handle the fractional intervals at the sides of the window.
+        for side,igyro in enumerate([igyrowindow0-1, igyrowindow1]):
+            vgyro0 = all_rots[igyro]
+            vgyro1 = all_rots[igyro+1]
+            tgyro0 = all_times[igyro]
+            tgyro1 = all_times[igyro+1]
+            vgyro = 0.5 * (vgyro0 + vgyro1)
+            deltatgyro = (tgyro1 - tgyro0) * NSEC_TO_SEC
+            if side == 0:
+                f = (tcam0 - tgyro0) / (tgyro1 - tgyro0)
+                sgyro += vgyro * deltatgyro * (1.0 - f)
+            else:
+                f = (tcam1 - tgyro0) / (tgyro1 - tgyro0)
+                sgyro += vgyro * deltatgyro * f
+        gyro_rots.append(sgyro)
+    gyro_rots = numpy.array(gyro_rots)
+    return gyro_rots
+
+def get_cam_rotations(frames):
+    """Get the rotations of the camera between each pair of frames.
+
+    Takes N frames and returns N-1 angular displacements corresponding to the
+    rotations between adjacent pairs of frames, in radians.
+
+    Args:
+        frames: List of N images (as RGB numpy arrays).
+
+    Returns:
+        Array of N-1 camera rotation measurements (rad).
+    """
+    gframes = []
+    for frame in frames:
+        frame = (frame * 255.0).astype(numpy.uint8)
+        gframes.append(cv2.cvtColor(frame, cv2.COLOR_RGB2GRAY))
+    rots = []
+    for i in range(1,len(gframes)):
+        gframe0 = gframes[i-1]
+        gframe1 = gframes[i]
+        p0 = cv2.goodFeaturesToTrack(gframe0, mask=None, **FEATURE_PARAMS)
+        p1,st,_ = cv2.calcOpticalFlowPyrLK(gframe0, gframe1, p0, None,
+                **LK_PARAMS)
+        tform = procrustes_rotation(p0[st==1], p1[st==1])
+        # TODO: Choose the sign for the rotation so the cam matches the gyro
+        rot = -math.atan2(tform[0, 1], tform[0, 0])
+        rots.append(rot)
+        if i == 1:
+            # Save a debug visualization of the features that are being
+            # tracked in the first frame.
+            frame = frames[i]
+            for x,y in p0[st==1]:
+                cv2.circle(frame, (x,y), 3, (100,100,255), -1)
+            its.image.write_image(frame, "%s_features.jpg"%(NAME))
+    return numpy.array(rots)
+
+def get_cam_times(cam_events):
+    """Get the camera frame times.
+
+    Args:
+        cam_events: List of (start_exposure, exposure_time, readout_duration)
+            tuples, one per captured frame, with times in nanoseconds.
+
+    Returns:
+        frame_times: Array of N times, one corresponding to the "middle" of
+            the exposure of each frame.
+    """
+    # Assign a time to each frame that assumes that the image is instantly
+    # captured in the middle of its exposure.
+    starts = numpy.array([start for start,exptime,readout in cam_events])
+    exptimes = numpy.array([exptime for start,exptime,readout in cam_events])
+    readouts = numpy.array([readout for start,exptime,readout in cam_events])
+    frame_times = starts + (exptimes + readouts) / 2.0
+    return frame_times
+
+def load_data():
+    """Load a set of previously captured data.
+
+    Returns:
+        events: Dictionary containing all gyro events and cam timestamps.
+        frames: List of RGB images as numpy arrays.
+    """
+    with open("%s_events.txt"%(NAME), "r") as f:
+        events = json.loads(f.read())
+    n = len(events["cam"])
+    frames = []
+    for i in range(n):
+        img = Image.open("%s_frame%03d.jpg"%(NAME,i))
+        w,h = img.size[0:2]
+        frames.append(numpy.array(img).reshape(h,w,3) / 255.0)
+    return events, frames
+
+def collect_data():
+    """Capture a new set of data from the device.
+
+    Captures both motion data and camera frames, while the user is moving
+    the device in a proscribed manner.
+
+    Returns:
+        events: Dictionary containing all gyro events and cam timestamps.
+        frames: List of RGB images as numpy arrays.
+    """
+    with its.device.ItsSession() as cam:
+        print "Starting sensor event collection"
+        cam.start_sensor_events()
+
+        # Sleep a few seconds for gyro events to stabilize.
+        time.sleep(5)
+
+        # TODO: Ensure that OIS is disabled; set to DISABLE and wait some time.
+
+        # Capture the frames.
+        props = cam.get_camera_properties()
+        fmt = {"format":"yuv", "width":W, "height":H}
+        s,e,_,_,_ = cam.do_3a(get_results=True)
+        req = its.objects.manual_capture_request(s, e)
+        print "Capturing %dx%d with sens. %d, exp. time %.1fms" % (
+                W, H, s, e*NSEC_TO_MSEC)
+        caps = cam.do_capture([req]*N, fmt)
+
+        # Get the gyro events.
+        print "Reading out sensor events"
+        gyro = cam.get_sensor_events()["gyro"]
+
+        # Combine the events into a single structure.
+        print "Dumping event data"
+        starts = [c["metadata"]["android.sensor.timestamp"] for c in caps]
+        exptimes = [c["metadata"]["android.sensor.exposureTime"] for c in caps]
+        readouts = [c["metadata"]["android.sensor.rollingShutterSkew"]
+                    for c in caps]
+        events = {"gyro": gyro, "cam": zip(starts,exptimes,readouts)}
+        with open("%s_events.txt"%(NAME), "w") as f:
+            f.write(json.dumps(events))
+
+        # Convert the frames to RGB.
+        print "Dumping frames"
+        frames = []
+        for i,c in enumerate(caps):
+            img = its.image.convert_capture_to_rgb_image(c)
+            frames.append(img)
+            its.image.write_image(img, "%s_frame%03d.jpg"%(NAME,i))
+
+        return events, frames
+
+def procrustes_rotation(X, Y):
+    """
+    Procrustes analysis determines a linear transformation (translation,
+    reflection, orthogonal rotation and scaling) of the points in Y to best
+    conform them to the points in matrix X, using the sum of squared errors
+    as the goodness of fit criterion.
+
+    Args:
+        X, Y: Matrices of target and input coordinates.
+
+    Returns:
+        The rotation component of the transformation that maps X to Y.
+    """
+    X0 = (X-X.mean(0)) / numpy.sqrt(((X-X.mean(0))**2.0).sum())
+    Y0 = (Y-Y.mean(0)) / numpy.sqrt(((Y-Y.mean(0))**2.0).sum())
+    U,s,Vt = numpy.linalg.svd(numpy.dot(X0.T, Y0),full_matrices=False)
+    return numpy.dot(Vt.T, U.T)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tests/tutorial.py b/apps/CameraITS/tests/tutorial.py
new file mode 100644
index 0000000..c266d14
--- /dev/null
+++ b/apps/CameraITS/tests/tutorial.py
@@ -0,0 +1,188 @@
+# 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.
+
+# --------------------------------------------------------------------------- #
+# The Google Python style guide should be used for scripts:                   #
+# http://google-styleguide.googlecode.com/svn/trunk/pyguide.html              #
+# --------------------------------------------------------------------------- #
+
+# The ITS modules that are in the pymodules/its/ directory. To see formatted
+# docs, use the "pydoc" command:
+#
+# > pydoc its.image
+#
+import its.image
+import its.device
+import its.objects
+import its.target
+
+# Standard Python modules.
+import os.path
+import pprint
+import math
+
+# Modules from the numpy, scipy, and matplotlib libraries. These are used for
+# the image processing code, and images are represented as numpy arrays.
+import pylab
+import numpy
+import matplotlib
+import matplotlib.pyplot
+
+# Each script has a "main" function.
+def main():
+
+    # Each script has a string description of what it does. This is the first
+    # entry inside the main function.
+    """Tutorial script to show how to use the ITS infrastructure.
+    """
+
+    # A convention in each script is to use the filename (without the extension)
+    # as the name of the test, when printing results to the screen or dumping
+    # files.
+    NAME = os.path.basename(__file__).split(".")[0]
+
+    # The standard way to open a session with a connected camera device. This
+    # creates a cam object which encapsulates the session and which is active
+    # within the scope of the "with" block; when the block exits, the camera
+    # session is closed.
+    with its.device.ItsSession() as cam:
+
+        # Get the static properties of the camera device. Returns a Python
+        # associative array object; print it to the console.
+        props = cam.get_camera_properties()
+        pprint.pprint(props)
+
+        # Grab a YUV frame with manual exposure of sensitivity = 200, exposure
+        # duration = 50ms.
+        req = its.objects.manual_capture_request(200, 50*1000*1000)
+        cap = cam.do_capture(req)
+
+        # Print the properties of the captured frame; width and height are
+        # integers, and the metadata is a Python associative array object.
+        print "Captured image width:", cap["width"]
+        print "Captured image height:", cap["height"]
+        pprint.pprint(cap["metadata"])
+
+        # The captured image is YUV420. Convert to RGB, and save as a file.
+        rgbimg = its.image.convert_capture_to_rgb_image(cap)
+        its.image.write_image(rgbimg, "%s_rgb_1.jpg" % (NAME))
+
+        # Can also get the Y,U,V planes separately; save these to greyscale
+        # files.
+        yimg,uimg,vimg = its.image.convert_capture_to_planes(cap)
+        its.image.write_image(yimg, "%s_y_plane_1.jpg" % (NAME))
+        its.image.write_image(uimg, "%s_u_plane_1.jpg" % (NAME))
+        its.image.write_image(vimg, "%s_v_plane_1.jpg" % (NAME))
+
+        # Run 3A on the device. In this case, just use the entire image as the
+        # 3A region, and run each of AWB,AE,AF. Can also change the region and
+        # specify independently for each of AE,AWB,AF whether it should run.
+        #
+        # NOTE: This may fail, if the camera isn't pointed at a reasonable
+        # target scene. If it fails, the script will end. The logcat messages
+        # can be inspected to see the status of 3A running on the device.
+        #
+        # > adb logcat -s 'ItsService:v'
+        #
+        # If this keeps on failing, try also rebooting the device before
+        # running the test.
+        sens, exp, gains, xform, focus = cam.do_3a(get_results=True)
+        print "AE: sensitivity %d, exposure %dms" % (sens, exp/1000000.0)
+        print "AWB: gains", gains, "transform", xform
+        print "AF: distance", focus
+
+        # Grab a new manual frame, using the 3A values, and convert it to RGB
+        # and save it to a file too. Note that the "req" object is just a
+        # Python dictionary that is pre-populated by the its.objets module
+        # functions (in this case a default manual capture), and the key/value
+        # pairs in the object can be used to set any field of the capture
+        # request. Here, the AWB gains and transform (CCM) are being used.
+        # Note that the CCM transform is in a rational format in capture
+        # requests, meaning it is an object with integer numerators and
+        # denominators. The 3A routine returns simple floats instead, however,
+        # so a conversion from float to rational must be performed.
+        req = its.objects.manual_capture_request(sens, exp)
+        xform_rat = its.objects.float_to_rational(xform)
+
+        req["android.colorCorrection.transform"] = xform_rat
+        req["android.colorCorrection.gains"] = gains
+        cap = cam.do_capture(req)
+        rgbimg = its.image.convert_capture_to_rgb_image(cap)
+        its.image.write_image(rgbimg, "%s_rgb_2.jpg" % (NAME))
+
+        # Print out the actual capture request object that was used.
+        pprint.pprint(req)
+
+        # Images are numpy arrays. The dimensions are (h,w,3) when indexing,
+        # in the case of RGB images. Greyscale images are (h,w,1). Pixels are
+        # generally float32 values in the [0,1] range, however some of the
+        # helper functions in its.image deal with the packed YUV420 and other
+        # formats of images that come from the device (and convert them to
+        # float32).
+        # Print the dimensions of the image, and the top-left pixel value,
+        # which is an array of 3 floats.
+        print "RGB image dimensions:", rgbimg.shape
+        print "RGB image top-left pixel:", rgbimg[0,0]
+
+        # Grab a center tile from the image; this returns a new image. Save
+        # this tile image. In this case, the tile is the middle 10% x 10%
+        # rectangle.
+        tile = its.image.get_image_patch(rgbimg, 0.45, 0.45, 0.1, 0.1)
+        its.image.write_image(tile, "%s_rgb_2_tile.jpg" % (NAME))
+
+        # Compute the mean values of the center tile image.
+        rgb_means = its.image.compute_image_means(tile)
+        print "RGB means:", rgb_means
+
+        # Apply a lookup table to the image, and save the new version. The LUT
+        # is basically a tonemap, and can be used to implement a gamma curve.
+        # In this case, the LUT is used to double the value of each pixel.
+        lut = numpy.array([2*i for i in xrange(65536)])
+        rgbimg_lut = its.image.apply_lut_to_image(rgbimg, lut)
+        its.image.write_image(rgbimg_lut, "%s_rgb_2_lut.jpg" % (NAME))
+
+        # Apply a 3x3 matrix to the image, and save the new version. The matrix
+        # is a numpy array, in row major order, and the pixel values are right-
+        # multiplied to it (when considered as column vectors). The example
+        # matrix here just boosts the blue channel by 10%.
+        mat = numpy.array([[1, 0, 0  ],
+                           [0, 1, 0  ],
+                           [0, 0, 1.1]])
+        rgbimg_mat = its.image.apply_matrix_to_image(rgbimg, mat)
+        its.image.write_image(rgbimg_mat, "%s_rgb_2_mat.jpg" % (NAME))
+
+        # Compute a histogram of the luma image, in 256 buckets.
+        yimg,_,_ = its.image.convert_capture_to_planes(cap)
+        hist,_ = numpy.histogram(yimg*255, 256, (0,256))
+
+        # Plot the histogram using matplotlib, and save as a PNG image.
+        pylab.plot(range(256), hist.tolist())
+        pylab.xlabel("Luma DN")
+        pylab.ylabel("Pixel count")
+        pylab.title("Histogram of luma channel of captured image")
+        matplotlib.pyplot.savefig("%s_histogram.png" % (NAME))
+
+        # Capture a frame to be returned as a JPEG. Load it as an RGB image,
+        # then save it back as a JPEG.
+        cap = cam.do_capture(req, cam.CAP_JPEG)
+        rgbimg = its.image.convert_capture_to_rgb_image(cap)
+        its.image.write_image(rgbimg, "%s_jpg.jpg" % (NAME))
+        r,g,b = its.image.convert_capture_to_planes(cap)
+        its.image.write_image(r, "%s_r.jpg" % (NAME))
+
+# This is the standard boilerplate in each test that allows the script to both
+# be executed directly and imported as a module.
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tools/config.py b/apps/CameraITS/tools/config.py
new file mode 100644
index 0000000..6e83412
--- /dev/null
+++ b/apps/CameraITS/tools/config.py
@@ -0,0 +1,66 @@
+# Copyright 2013 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import its.device
+import its.target
+import sys
+
+def main():
+    """Set the target exposure.
+
+    This program is just a wrapper around the its.target module, to allow the
+    functions in it to be invoked from the command line.
+
+    Usage:
+        python config.py        - Measure the target exposure, and cache it.
+        python config.py EXP    - Hard-code (and cache) the target exposure.
+
+    The "reboot" or "reboot=<N>" and "camera=<N>" arguments may also be
+    provided, just as with all the test scripts. The "target" argument is
+    may also be provided but it has no effect on this script since the cached
+    exposure value is cleared regardless.
+
+    If no exposure value is provided, the camera will be used to measure
+    the scene and set a level that will result in the luma (with linear
+    tonemap) being at the 0.5 level. This requires camera 3A and capture
+    to be functioning.
+
+    For bring-up purposes, the exposure value may be manually set to a hard-
+    coded value, without the camera having to be able to perform 3A (or even
+    capture a shot reliably).
+    """
+
+    # Command line args, ignoring any args that will be passed down to the
+    # ItsSession constructor.
+    args = [s for s in sys.argv if s[:6] not in \
+            ["reboot", "camera", "target", "noinit"]]
+
+    if len(args) == 1:
+        with its.device.ItsSession() as cam:
+            # Automatically measure target exposure.
+            its.target.clear_cached_target_exposure()
+            exposure = its.target.get_target_exposure(cam)
+    elif len(args) == 2:
+        # Hard-code the target exposure.
+        exposure = int(args[1])
+        its.target.set_hardcoded_exposure(exposure)
+    else:
+        print "Usage: python %s [EXPOSURE]"
+        sys.exit(0)
+    print "New target exposure set to", exposure
+    print "This corresponds to %dms at ISO 100" % int(exposure/100/1000000.0)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
new file mode 100644
index 0000000..f8a7689
--- /dev/null
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -0,0 +1,116 @@
+# Copyright 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import os.path
+import tempfile
+import subprocess
+import time
+import sys
+import its.device
+
+def main():
+    """Run all the automated tests, saving intermediate files, and producing
+    a summary/report of the results.
+
+    Script should be run from the top-level CameraITS directory.
+    """
+
+    SKIP_RET_CODE = 101
+
+    # Not yet mandated tests
+    NOT_YET_MANDATED = {
+        "scene0":[
+            "test_jitter"
+        ],
+        "scene1":[
+            "test_ae_precapture_trigger",
+            "test_crop_region_raw",
+            "test_ev_compensation_advanced",
+            "test_ev_compensation_basic",
+            "test_yuv_plus_jpeg"
+        ]
+    }
+
+    # Get all the scene0 and scene1 tests, which can be run using the same
+    # physical setup.
+    scenes = ["scene0", "scene1"]
+    tests = []
+    for d in scenes:
+        tests += [(d,s[:-3],os.path.join("tests", d, s))
+                  for s in os.listdir(os.path.join("tests",d))
+                  if s[-3:] == ".py"]
+    tests.sort()
+
+    # Make output directories to hold the generated files.
+    topdir = tempfile.mkdtemp()
+    for d in scenes:
+        os.mkdir(os.path.join(topdir, d))
+    print "Saving output files to:", topdir, "\n"
+
+    # determine camera id
+    camera_id = 0
+    for s in sys.argv[1:]:
+        if s[:7] == "camera=" and len(s) > 7:
+            camera_id = s[7:]
+
+    # Run each test, capturing stdout and stderr.
+    numpass = 0
+    numskip = 0
+    numnotmandatedfail = 0
+    numfail = 0
+    for (scene,testname,testpath) in tests:
+        cmd = ['python', os.path.join(os.getcwd(),testpath)] + sys.argv[1:]
+        outdir = os.path.join(topdir,scene)
+        outpath = os.path.join(outdir,testname+"_stdout.txt")
+        errpath = os.path.join(outdir,testname+"_stderr.txt")
+        t0 = time.time()
+        with open(outpath,"w") as fout, open(errpath,"w") as ferr:
+            retcode = subprocess.call(cmd,stderr=ferr,stdout=fout,cwd=outdir)
+        t1 = time.time()
+
+        if retcode == 0:
+            retstr = "PASS "
+            numpass += 1
+        elif retcode == SKIP_RET_CODE:
+            retstr = "SKIP "
+            numskip += 1
+        elif retcode != 0 and testname in NOT_YET_MANDATED[scene]:
+            retstr = "FAIL*"
+            numnotmandatedfail += 1
+        else:
+            retstr = "FAIL "
+            numfail += 1
+
+        print "%s %s/%s [%.1fs]" % (retstr, scene, testname, t1-t0)
+
+    if numskip > 0:
+        skipstr = ", %d test%s skipped" % (numskip, "s" if numskip > 1 else "")
+    else:
+        skipstr = ""
+
+    print "\n%d / %d tests passed (%.1f%%)%s" % (
+            numpass + numnotmandatedfail, len(tests) - numskip,
+            100.0 * float(numpass + numnotmandatedfail) / (len(tests) - numskip)
+                if len(tests) != numskip else 100.0,
+            skipstr)
+
+    if numnotmandatedfail > 0:
+        print "(*) tests are not yet mandated"
+
+    its.device.report_result(camera_id, numfail == 0)
+
+if __name__ == '__main__':
+    main()
+
diff --git a/apps/CtsVerifier/Android.mk b/apps/CtsVerifier/Android.mk
index 460b88a..87f962f 100644
--- a/apps/CtsVerifier/Android.mk
+++ b/apps/CtsVerifier/Android.mk
@@ -25,7 +25,10 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
 
-LOCAL_STATIC_JAVA_LIBRARIES := cts-sensors-tests ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := android-ex-camera2 \
+                               compatibility-common-util-devicesidelib_v2 \
+                               cts-sensors-tests \
+                               ctstestrunner \
 
 LOCAL_PACKAGE_NAME := CtsVerifier
 
@@ -40,10 +43,13 @@
 
 include $(BUILD_PACKAGE)
 
+notification-bot := $(call intermediates-dir-for,APPS,NotificationBot)/package.apk
+
 # Builds and launches CTS Verifier on a device.
 .PHONY: cts-verifier
-cts-verifier: CtsVerifier adb
+cts-verifier: CtsVerifier adb NotificationBot
 	adb install -r $(PRODUCT_OUT)/data/app/CtsVerifier/CtsVerifier.apk \
+		&& adb install -r $(notification-bot) \
 		&& adb shell "am start -n com.android.cts.verifier/.CtsVerifierActivity"
 
 #
@@ -78,12 +84,16 @@
 ifeq ($(HOST_OS),linux)
 $(verifier-zip) : $(HOST_OUT)/bin/cts-usb-accessory
 endif
+$(verifier-zip) : $(HOST_OUT)/CameraITS
+$(verifier-zip) : $(notification-bot)
 $(verifier-zip) : $(call intermediates-dir-for,APPS,CtsVerifier)/package.apk | $(ACP)
 		$(hide) mkdir -p $(verifier-dir)
 		$(hide) $(ACP) -fp $< $(verifier-dir)/CtsVerifier.apk
+		$(ACP) -fp $(notification-bot) $(verifier-dir)/NotificationBot.apk
 ifeq ($(HOST_OS),linux)
 		$(hide) $(ACP) -fp $(HOST_OUT)/bin/cts-usb-accessory $(verifier-dir)/cts-usb-accessory
 endif
+		$(hide) $(ACP) -fpr $(HOST_OUT)/CameraITS $(verifier-dir)
 		$(hide) cd $(cts-dir) && zip -rq $(verifier-dir-name) $(verifier-dir-name)
 
 ifneq ($(filter cts, $(MAKECMDGOALS)),)
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index ef059b7..3b2eb46 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -17,12 +17,13 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
       package="com.android.cts.verifier"
-      android:versionCode="4"
-      android:versionName="5.0_r2">
+      android:versionCode="5"
+      android:versionName="5.1_r0.5">
 
     <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="21"/>
 
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
     <uses-permission android:name="android.permission.BLUETOOTH" />
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
@@ -34,6 +35,11 @@
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.NFC" />
     <uses-permission android:name="android.permission.VIBRATE" />
+    <uses-feature android:name="android.hardware.camera" android:required="false"/>
+    <uses-feature android:name="android.hardware.camera.flash" android:required="false"/>
+    <uses-feature android:name="android.hardware.sensor.accelerometer" android:required="false" />
+    <uses-feature android:name="android.hardware.sensor.compass" android:required="false" />
+    <uses-feature android:name="android.hardware.sensor.gyroscope" android:required="false" />
     <uses-feature android:name="android.hardware.camera.front"
                   android:required="false" />
     <uses-feature android:name="android.hardware.camera.autofocus"
@@ -49,6 +55,8 @@
     <uses-permission android:name="android.permission.READ_CONTACTS"/>
     <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
+    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
 
     <!-- Needed by the Audio Quality Verifier to store the sound samples that will be mailed. -->
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -234,8 +242,8 @@
         <service android:name=".bluetooth.BleScannerService"
                 android:label="@string/ble_scanner_service_name" />
 
-        <!-- TODO: Enable when test quality issues listed in b/18283088 is resolved -->
-        <!-- activity android:name=".bluetooth.BleClientTestActivity"
+        <!-- Uncomment until b/15657182, b/18283088 fixed
+        <activity android:name=".bluetooth.BleClientStartActivity"
                 android:label="@string/ble_client_test_name"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
@@ -246,98 +254,9 @@
             <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
             <meta-data android:name="test_required_features"
                        android:value="android.hardware.bluetooth_le"/>
-        </activity -->
-
-        <activity android:name=".bluetooth.BleClientConnectActivity"
-                android:label="@string/ble_client_connect_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
         </activity>
 
-        <activity android:name=".bluetooth.BleDiscoverServiceActivity"
-                android:label="@string/ble_discover_service_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
-        </activity>
-
-        <activity android:name=".bluetooth.BleClientCharacteristicActivity"
-                android:label="@string/ble_client_characteristic_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
-        </activity>
-
-        <activity android:name=".bluetooth.BleNotifyCharacteristicActivity"
-                android:label="@string/ble_notify_characteristic_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
-        </activity>
-
-        <activity android:name=".bluetooth.BleClientDescriptorActivity"
-                android:label="@string/ble_client_descriptor_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
-        </activity>
-
-        <activity android:name=".bluetooth.BleReliableWriteActivity"
-                android:label="@string/ble_reliable_write_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
-        </activity>
-
-        <activity android:name=".bluetooth.BleReadRssiActivity"
-                android:label="@string/ble_read_rssi_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
-        </activity>
-
-        <activity android:name=".bluetooth.BleClientDisconnectActivity"
-                android:label="@string/ble_client_disconnect_name"
-                android:configChanges="keyboardHidden|orientation|screenSize">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.cts.intent.category.MANUAL_TEST" />
-            </intent-filter>
-            <meta-data android:name="test_category" android:value="@string/bt_le" />
-            <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BleClientTestActivity" />
-        </activity>
-
-        <!-- TODO: Enable when test quality issues listed in b/18283088 is resolved -->
-        <!-- activity android:name=".bluetooth.BleServerStartActivity"
+        <activity android:name=".bluetooth.BleServerStartActivity"
                 android:label="@string/ble_server_start_name"
                 android:configChanges="keyboardHidden|orientation|screenSize">
             <intent-filter>
@@ -348,7 +267,7 @@
             <meta-data android:name="test_parent" android:value="com.android.cts.verifier.bluetooth.BluetoothTestActivity" />
             <meta-data android:name="test_required_features"
                        android:value="android.hardware.bluetooth_le"/>
-        </activity -->
+        </activity> -->
 
         <!-- TODO: Enable when test quality issues listed in b/18282549 is resolved -->
         <!-- activity android:name=".bluetooth.BleScannerTestActivity"
@@ -581,6 +500,10 @@
                 android:label="@string/nfc_hce_payment_dynamic_aids_emulator"
                 android:configChanges="keyboardHidden|orientation|screenSize" />
 
+        <activity android:name=".nfc.hce.LargeNumAidsEmulatorActivity"
+                  android:label="@string/nfc_hce_large_num_aids_emulator"
+                  android:configChanges="keyboardHidden|orientation|screenSize" />
+
         <activity android:name=".nfc.hce.PrefixPaymentEmulatorActivity"
                 android:label="@string/nfc_hce_payment_prefix_aids_emulator"
                 android:configChanges="keyboardHidden|orientation|screenSize" />
@@ -716,6 +639,24 @@
             </intent-filter>
             <meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/access_prefix_aid_list"/>
         </service>
+        <service android:name=".nfc.hce.LargeNumAidsService" android:exported="true"
+                 android:permission="android.permission.BIND_NFC_SERVICE"
+                 android:enabled="false">
+            <intent-filter>
+                <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+            <meta-data android:name="android.nfc.cardemulation.host_apdu_service" android:resource="@xml/payment_aid_list_1"/>
+        </service>
+
+        <!-- Service used for Camera ITS tests -->
+        <service android:name=".camera.its.ItsService" >
+            <intent-filter>
+                <action android:name="com.android.cts.verifier.camera.its.START"/>
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="text/plain" />
+            </intent-filter>
+        </service>
 
         <!--
             A DeviceAdmin receiver for sensor tests, it allows sensor tests to turn off the screen.
@@ -799,13 +740,7 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_sensors" />
             <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.stepcounter" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.stepdetector" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.proximity" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.light" />
+                       android:value="android.hardware.sensor.stepcounter:android.hardware.sensor.stepdetector:android.hardware.sensor.proximity:android.hardware.sensor.light" />
         </activity>
 
         <!-- TODO: enable when a more reliable way to identify time synchronization is available -->
@@ -830,13 +765,7 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_sensors"/>
             <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.accelerometer" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.compass" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.gyroscope" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.barometer" />
+                       android:value="android.hardware.sensor.accelerometer:android.hardware.sensor.compass:android.hardware.sensor.gyroscope:android.hardware.sensor.barometer" />
         </activity>
 
         <activity android:name=".sensors.SensorBatchingTestsActivity"
@@ -848,13 +777,7 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_sensors"/>
             <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.accelerometer" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.compass" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.gyroscope" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.barometer" />
+                       android:value="android.hardware.sensor.accelerometer:android.hardware.sensor.compass:android.hardware.sensor.gyroscope:android.hardware.sensor.barometer" />
         </activity>
 
         <activity android:name=".sensors.SensorIntegrationTestsActivity"
@@ -866,11 +789,7 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_sensors"/>
             <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.accelerometer" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.compass" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.gyroscope" />
+                       android:value="android.hardware.sensor.accelerometer:android.hardware.sensor.compass:android.hardware.sensor.gyroscope" />
         </activity>
 
         <activity android:name=".sensors.SensorTestActivity"
@@ -882,17 +801,7 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_sensors"/>
             <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.accelerometer" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.stepcounter" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.stepdetector" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.heartrate" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.compass" />
-            <meta-data android:name="test_applicable_features"
-                       android:value="android.hardware.sensor.ambient_temperature" />
+                       android:value="android.hardware.sensor.accelerometer:android.hardware.sensor.stepcounter:android.hardware.sensor.stepdetector:android.hardware.sensor.heartrate:android.hardware.sensor.compass:android.hardware.sensor.ambient_temperature" />
         </activity>
 
         <!-- End sensor tests definitions -->
@@ -1040,6 +949,17 @@
                     android:value="android.hardware.camera.any"/>
         </activity>
 
+        <activity android:name=".camera.its.ItsTestActivity"
+                  android:label="@string/camera_its_test"
+                  android:configChanges="keyboardHidden|orientation|screenSize">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_camera" />
+            <meta-data android:name="test_required_features" android:value="android.hardware.camera.any" />
+        </activity>
+
         <activity android:name=".usb.UsbAccessoryTestActivity"
                 android:label="@string/usb_accessory_test"
                 android:configChanges="keyboardHidden|orientation|screenSize">
@@ -1085,13 +1005,30 @@
             <meta-data android:name="test_category" android:value="@string/test_category_notifications" />
         </activity>
 
-        <activity android:name=".notifications.NotificationAttentionManagementVerifierActivity"
+        <activity android:name=".notifications.AttentionManagementVerifierActivity"
                 android:label="@string/attention_test">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_notifications" />
+            <meta-data android:name="test_excluded_features"
+                    android:value="android.hardware.type.watch" />
+        </activity>
+
+        <activity android:name=".notifications.PackagePriorityVerifierActivity"
+                android:label="@string/package_priority_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_notifications" />
+            <meta-data android:name="test_excluded_features"
+                    android:value="android.hardware.type.watch" />
+            <meta-data android:name="test_excluded_features"
+                    android:value="android.hardware.type.television" />
+            <meta-data android:name="test_excluded_features"
+                    android:value="android.software.leanback" />
         </activity>
 
         <service android:name=".notifications.MockListener"
@@ -1103,7 +1040,8 @@
             </intent-filter>
         </service>
 
-        <service  android:name=".notifications.NotificationListenerVerifierActivity$DismissService"/>
+        <service  android:name=".notifications.InteractiveVerifierActivity$DismissService"/>
+
         <activity android:name=".security.CAInstallNotificationVerifierActivity"
                 android:label="@string/cacert_test">
             <intent-filter>
@@ -1197,6 +1135,8 @@
             <meta-data android:name="test_category" android:value="@string/test_category_other" />
             <meta-data android:name="test_required_features"
                     android:value="android.software.app_widgets" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.software.leanback" />
         </activity>
 
         <activity android:name=".deskclock.DeskClockTestsActivity"
@@ -1308,7 +1248,8 @@
                 <category android:name="android.cts.intent.category.MANUAL_TEST" />
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_projection" />
-            <meta-data android:name="test_required_features" android:value="android.hardware.faketouch" />
+            <meta-data android:name="test_required_features"
+                       android:value="android.hardware.faketouch:android.hardware.touchscreen.multitouch" />
         </activity>
 
 
@@ -1338,8 +1279,7 @@
         </activity>
 
 
-        <!-- TODO: enable when the test can be executed without leaving marks -->
-        <!-- activity android:name=".managedprovisioning.ByodFlowTestActivity"
+        <activity android:name=".managedprovisioning.ByodFlowTestActivity"
                 android:launchMode="singleTask"
                 android:label="@string/provisioning_byod">
             <intent-filter>
@@ -1352,12 +1292,20 @@
             </intent-filter>
             <meta-data android:name="test_category" android:value="@string/test_category_managed_provisioning" />
             <meta-data android:name="test_required_features" android:value="android.software.managed_users:android.software.device_admin" />
-        </activity-->
+        </activity>
 
         <activity android:name=".managedprovisioning.ByodHelperActivity">
             <intent-filter>
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_QUERY" />
                 <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_REMOVE" />
+                <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK" />
+                <category android:name="android.intent.category.DEFAULT"></category>
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".managedprovisioning.ByodIconSamplerActivity">
+            <intent-filter>
+                <action android:name="com.android.cts.verifier.managedprovisioning.BYOD_SAMPLE_ICON" />
                 <category android:name="android.intent.category.DEFAULT"></category>
             </intent-filter>
         </activity>
@@ -1369,6 +1317,14 @@
             </intent-filter>
         </activity>
 
+        <activity android:name=".managedprovisioning.WorkNotificationTestActivity">
+            <intent-filter>
+                <action android:name="com.android.cts.verifier.managedprovisioning.WORK_NOTIFICATION" />
+                <action android:name="com.android.cts.verifier.managedprovisioning.CLEAR_WORK_NOTIFICATION" />
+                <category android:name="android.intent.category.DEFAULT"></category>
+            </intent-filter>
+        </activity>
+
         <receiver android:name=".managedprovisioning.DeviceAdminTestReceiver"
                 android:label="@string/provisioning_byod_device_admin"
                 android:permission="android.permission.BIND_DEVICE_ADMIN">
@@ -1419,6 +1375,80 @@
         <service android:name=".jobscheduler.MockJobService"
             android:permission="android.permission.BIND_JOB_SERVICE"/>
 
+        <!-- Used by the SensorTestScreenManipulator to reset the screen timeout after turn off. -->
+        <activity android:name=".os.TimeoutResetActivity"/>
+
+        <activity android:name=".tv.TvInputDiscoveryTestActivity"
+                android:label="@string/tv_input_discover_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_tv" />
+            <meta-data android:name="test_required_features"
+                    android:value="android.software.live_tv" />
+        </activity>
+
+        <activity android:name=".tv.ParentalControlTestActivity"
+                android:label="@string/tv_parental_control_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_tv" />
+            <meta-data android:name="test_required_features"
+                    android:value="android.software.live_tv" />
+        </activity>
+
+        <activity android:name=".tv.MultipleTracksTestActivity"
+                android:label="@string/tv_multiple_tracks_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_tv" />
+            <meta-data android:name="test_required_features"
+                    android:value="android.software.live_tv" />
+        </activity>
+
+        <activity android:name=".screenpinning.ScreenPinningTestActivity"
+            android:label="@string/screen_pinning_test">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_other" />
+        </activity>
+
+        <activity android:name=".tv.MockTvInputSettingsActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
+        <activity android:name=".tv.MockTvInputSetupActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+            </intent-filter>
+        </activity>
+
+        <service android:name=".tv.MockTvInputService"
+            android:permission="android.permission.BIND_TV_INPUT">
+            <intent-filter>
+                <action android:name="android.media.tv.TvInputService" />
+            </intent-filter>
+            <meta-data android:name="android.media.tv.input"
+                android:resource="@xml/mock_tv_input_service" />
+        </service>
+
+        <receiver android:name=".tv.TvInputReceiver">
+            <intent-filter>
+                <action android:name="android.media.tv.action.QUERY_CONTENT_RATING_SYSTEMS" />
+            </intent-filter>
+            <meta-data android:name="android.media.tv.metadata.CONTENT_RATING_SYSTEMS"
+                android:resource="@xml/mock_content_rating_systems" />
+        </receiver>
+
     </application>
 
 </manifest>
diff --git a/apps/CtsVerifier/assets/scripts/execute_power_tests.py b/apps/CtsVerifier/assets/scripts/execute_power_tests.py
index d1c2dac..d07cb36 100755
--- a/apps/CtsVerifier/assets/scripts/execute_power_tests.py
+++ b/apps/CtsVerifier/assets/scripts/execute_power_tests.py
@@ -25,57 +25,108 @@
 import pkgutil
 import threading
 import Queue
+import traceback
+import math
+import bisect
+from bisect import bisect_left
 
-# queue to signal thread to exit
-signal_exit_q = Queue.Queue()
-signal_abort = Queue.Queue()
+"""
+scipy, numpy and matplotlib are python packages that can be installed
+from: http://www.scipy.org/
+
+"""
+import scipy
+import matplotlib.pyplot as plt
 
 # let this script know about the power monitor implementations
 sys.path = [os.path.basename(__file__)] + sys.path
-available_monitors = [name for _, name, _ in pkgutil.iter_modules(
-    [os.path.join(os.path.dirname(__file__),'power_monitors')]) if not name.startswith('_')]
+available_monitors = [
+    name
+    for _, name, _ in pkgutil.iter_modules(
+        [os.path.join(os.path.dirname(__file__), "power_monitors")])
+    if not name.startswith("_")]
 
-APK = os.path.join( os.path.dirname(__file__), '..', "CtsVerifier.apk")
+APK = os.path.join(os.path.dirname(__file__), "..", "CtsVerifier.apk")
 
 FLAGS = flags.FLAGS
 
-# whether to use a strict delay to ensure screen is off, or attempt to use power measurements
-USE_STRICT_DELAY = False
-if USE_STRICT_DELAY:
-    DELAY_SCREEN_OFF = 30.0
-else:
-    DELAY_SCREEN_OFF = 2.0
+# DELAY_SCREEN_OFF is the number of seconds to wait for baseline state
+DELAY_SCREEN_OFF = 20.0
 
 # whether to log data collected to a file for each sensor run:
 LOG_DATA_TO_FILE = True
 
 logging.getLogger().setLevel(logging.ERROR)
 
+
 def do_import(name):
     """import a module by name dynamically"""
     mod = __import__(name)
-    components = name.split('.')
+    components = name.split(".")
     for comp in components[1:]:
         mod = getattr(mod, comp)
     return mod
 
+class PowerTestException(Exception):
+    """
+    Definition of specialized Exception class for CTS power tests
+    """
+    def __init__(self, message):
+        self._error_message = message
+    def __str__(self):
+        return self._error_message
 
 class PowerTest:
-    """Class to run a suite of power tests"""
+    """Class to run a suite of power tests. This has methods for obtaining
+    measurements from the power monitor (through the driver) and then
+    processing it to determine baseline and AP suspend state and
+    measure ampere draw of various sensors.
+    Ctrl+C causes a keyboard interrupt exception which terminates the test."""
 
     # Thresholds for max allowed power usage per sensor tested
-    MAX_ACCEL_POWER = 0.08  # Amps
-    MAX_MAG_POWER = 0.08  # Amps
-    MAX_GYRO_POWER = 0.08  # Amps
-    MAX_SIGMO_POWER = 0.08 # Amps
-    MAX_STEP_COUNTER_POWER = 0.08 # Amps
-    MAX_STEP_DETECTOR_POWER = 0.08 # Amps
+    # TODO: Accel, Mag and Gyro have no maximum power specified in the CDD;
+    # the following numbers are bogus and will be replaced soon by what
+    # the device reports (from Sensor.getPower())
+    MAX_ACCEL_AMPS = 0.08  # Amps
+    MAX_MAG_AMPS = 0.08  # Amps
+    MAX_GYRO_AMPS = 0.08  # Amps
+    MAX_SIGMO_AMPS = 0.08  # Amps
 
-
-    PORT = 0  # any available port
+    # TODO: The following numbers for step counter, etc must be replaced by
+    # the numbers specified in CDD for low-power sensors. The expected current
+    # draw must be computed from the specified power and the voltage used to
+    # power the device (specified from a config file).
+    MAX_STEP_COUNTER_AMPS = 0.08  # Amps
+    MAX_STEP_DETECTOR_AMPS = 0.08  # Amps
+    # The variable EXPECTED_AMPS_VARIATION_HALF_RANGE denotes the expected
+    # variation of  the ampere measurements
+    # around the mean value at baseline state. i.e. we expect most of the
+    # ampere measurements at baseline state to vary around the mean by
+    # between +/- of the number below
+    EXPECTED_AMPS_VARIATION_HALF_RANGE = 0.0005
+    # The variable THRESHOLD_BASELINE_SAMPLES_FRACTION denotes the minimum fraction of samples that must
+    # be in the range of variation defined by EXPECTED_AMPS_VARIATION_HALF_RANGE
+    # around the mean baseline for us to decide that the phone has settled into
+    # its baseline state
+    THRESHOLD_BASELINE_SAMPLES_FRACTION = 0.86
+    # The variable MAX_PERCENTILE_AP_SCREEN_OFF_AMPS denotes the maximum ampere
+    # draw that the device can consume when it has gone to suspend state with
+    # one or more sensors registered and batching samples (screen and AP are
+    # off in this case)
+    MAX_PERCENTILE_AP_SCREEN_OFF_AMPS = 0.030  # Amps
+    # The variable PERCENTILE_MAX_AP_SCREEN_OFF denotes the fraction of ampere
+    # measurements that must be below the specified maximum amperes
+    # MAX_PERCENTILE_AP_SCREEN_OFF_AMPS for us to decide that the phone has
+    # reached suspend state.
+    PERCENTILE_MAX_AP_SCREEN_OFF = 0.95
     DOMAIN_NAME = "/android/cts/powertest"
+    # SAMPLE_COUNT_NOMINAL denotes the typical number of measurements of amperes
+    # to collect from the power monitor
     SAMPLE_COUNT_NOMINAL = 1000
+    # RATE_NOMINAL denotes the nominal frequency at which ampere measurements
+    # are taken from the monsoon power monitor
     RATE_NOMINAL = 100
+    ENABLE_PLOTTING = False
 
     REQUEST_EXTERNAL_STORAGE = "EXTERNAL STORAGE?"
     REQUEST_EXIT = "EXIT"
@@ -87,103 +138,157 @@
     REQUEST_SCREEN_OFF = "SCREEN OFF"
     REQUEST_SHOW_MESSAGE = "MESSAGE %s"
 
+    NEGATIVE_AMPERE_ERROR_MESSAGE = (
+        "Negative ampere draw measured, possibly due to power "
+        "supply from USB cable. Check the setup of device and power "
+        "monitor to make sure that the device is not connected "
+        "to machine via USB directly. The device should be "
+        "connected to the USB slot in the power monitor. It is okay "
+        "to change the wiring when the test is in progress.")
 
-    def __init__(self):
+
+    def __init__(self, max_baseline_amps):
+        """
+        Args:
+            max_baseline_amps: The maximum value of baseline amperes
+                    that we expect the device to consume at baseline state.
+                    This can be different between models of phones.
+        """
         power_monitors = do_import("power_monitors.%s" % FLAGS.power_monitor)
         testid = time.strftime("%d_%m_%Y__%H__%M_%S")
         self._power_monitor = power_monitors.Power_Monitor(log_file_id = testid)
+        self._tcp_connect_port = 0  # any available port
         print ("Establishing connection to device...")
         self.setUsbEnabled(True)
         status = self._power_monitor.GetStatus()
         self._native_hz = status["sampleRate"] * 1000
+        # the following describes power test being run (i.e on what sensor
+        # and what type of test. This is used for logging.
         self._current_test = "None"
-        self._external_storage = self.executeOnDevice(PowerTest.REQUEST_EXTERNAL_STORAGE,
-                                                      reportErrors=True)
+        self._external_storage = self.executeOnDevice(PowerTest.REQUEST_EXTERNAL_STORAGE)
+        self._max_baseline_amps = max_baseline_amps
 
     def __del__(self):
         self.finalize()
 
     def finalize(self):
         """To be called upon termination of host connection to device"""
-        if PowerTest.PORT > 0:
-            # tell device side to exit connection loop, and remove the forwarding connection
-            self.executeOnDevice(PowerTest.REQUEST_EXIT, reportErrors=False)
-            self.executeLocal("adb forward --remove tcp:%d" % PowerTest.PORT)
-        PowerTest.PORT = 0
+        if self._tcp_connect_port > 0:
+            # tell device side to exit connection loop, and remove the forwarding
+            # connection
+            self.executeOnDevice(PowerTest.REQUEST_EXIT, reportErrors = False)
+            self.executeLocal("adb forward --remove tcp:%d" % self._tcp_connect_port)
+        self._tcp_connect_port = 0
         if self._power_monitor:
             self._power_monitor.Close()
             self._power_monitor = None
 
-    def _send(self, msg, report_errors=True):
+    def _send(self, msg, report_errors = True):
         """Connect to the device, send the given command, and then disconnect"""
-        if PowerTest.PORT == 0:
+        if self._tcp_connect_port == 0:
             # on first attempt to send a command, connect to device via any open port number,
             # forwarding that port to a local socket on the device via adb
             logging.debug("Seeking port for communication...")
             # discover an open port
             dummysocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
             dummysocket.bind(("localhost", 0))
-            (_, PowerTest.PORT) = dummysocket.getsockname()
+            (_, self._tcp_connect_port) = dummysocket.getsockname()
             dummysocket.close()
-            assert(PowerTest.PORT > 0)
+            assert(self._tcp_connect_port > 0)
+
             status = self.executeLocal("adb forward tcp:%d localabstract:%s" %
-                    (PowerTest.PORT, PowerTest.DOMAIN_NAME))
-            if report_errors:
-                self.reportErrorIf(status != 0, msg="Unable to forward requests to client over adb")
-            logging.info("Forwarding requests over local port %d" % PowerTest.PORT)
+                                       (self._tcp_connect_port, PowerTest.DOMAIN_NAME))
+            # If the status !=0, then the host machine is unable to
+            # forward requests to client over adb. Ending the test and logging error message
+            # to the console on the host.
+            self.endTestIfLostConnection(
+                status != 0,
+                "Unable to forward requests to client over adb")
+            logging.info("Forwarding requests over local port %d",
+                         self._tcp_connect_port)
 
         link = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 
         try:
             logging.debug("Connecting to device...")
-            link.connect (("localhost", PowerTest.PORT))
+            link.connect(("localhost", self._tcp_connect_port))
             logging.debug("Connected.")
+        except socket.error as serr:
+            print "Socket connection error: ", serr
+            print "Finalizing and exiting the test"
+            self.endTestIfLostConnection(
+                report_errors,
+                "Unable to communicate with device: connection refused")
         except:
-            if report_errors:
-                self.reportErrorIf(True, msg="Unable to communicate with device: connection refused")
-        logging.debug("Sending '%s'" % msg)
+            print "Non socket-related exception at this block in _send(); re-raising now."
+            raise
+        logging.debug("Sending '%s'", msg)
         link.sendall(msg)
         logging.debug("Getting response...")
         response = link.recv(4096)
-        logging.debug("Got response '%s'" % response)
+        logging.debug("Got response '%s'", response)
         link.close()
         return response
 
     def queryDevice(self, query):
         """Post a yes/no query to the device, return True upon successful query, False otherwise"""
-        logging.info("Querying device with '%s'" % query)
+        logging.info("Querying device with '%s'", query)
         return self._send(query) == "OK"
 
     # TODO: abstract device communication (and string commands) into its own class
-    def executeOnDevice(self, cmd , reportErrors=True):
+    def executeOnDevice(self, cmd, reportErrors = True):
         """Execute a (string) command on the remote device"""
-        return self._send(cmd , reportErrors)
+        return self._send(cmd, reportErrors)
 
-    def executeLocal(self, cmd, check_status=True):
+    def executeLocal(self, cmd, check_status = True):
         """execute a shell command locally (on the host)"""
         from subprocess import call
-        status = call(cmd.split(' '))
+        status = call(cmd.split(" "))
         if status != 0 and check_status:
-            logging.error("Failed to execute \"%s\"" % cmd)
+            logging.error("Failed to execute \"%s\"", cmd)
         else:
-            logging.debug("Executed \"%s\"" % cmd)
+            logging.debug("Executed \"%s\"", cmd)
         return status
 
-    def reportErrorIf(self, condition, msg):
+    def reportErrorRaiseExceptionIf(self, condition, msg):
         """Report an error condition to the device if condition is True.
-        Will raise an exception on the device if condition is True"""
+        Will raise an exception on the device if condition is True.
+        Args:
+            condition: If true, this reports error
+            msg: Message related to exception
+        Raises:
+            A PowerTestException encapsulating the message provided in msg
+        """
         if condition:
             try:
                 logging.error("Exiting on error: %s" % msg)
-                self.executeOnDevice(PowerTest.REQUEST_RAISE % (self._current_test, msg), False)
+                self.executeOnDevice(PowerTest.REQUEST_RAISE % (self._current_test, msg),
+                                     reportErrors = True)
             except:
-
-                logging.error("Unable to communicate with device to report error: %s" % msg)
+                logging.error("Unable to communicate with device to report "
+                              "error: %s" % msg)
                 self.finalize()
                 sys.exit(msg)
-            raise Exception(msg)
+            raise PowerTestException(msg)
 
-    def setUsbEnabled(self, enabled, verbose=True):
+    def endTestIfLostConnection(self, lost_connection, error_message):
+        """
+        This function ends the test if lost_connection was true,
+        which indicates that the connection to the device was lost.
+        Args:
+            lost_connection: boolean variable, if True it indicates that
+                connection to device was lost and the test must be terminated.
+            error_message: String to print to the host console before exiting the test
+                (if lost_connection is True)
+        Returns:
+            None.
+        """
+        if lost_connection:
+            logging.error(error_message)
+            self.finalize()
+            sys.exit(error_message)
+
+    def setUsbEnabled(self, enabled, verbose = True):
         if enabled:
             val = 1
         else:
@@ -193,6 +298,7 @@
 
         # Sometimes command won't go through first time, particularly if immediately after a data
         # collection, so allow for retries
+        # TODO: Move this retry mechanism to the power monitor driver.
         status = self._power_monitor.GetStatus()
         while status is None and tries < 5:
             tries += 1
@@ -203,59 +309,230 @@
             status = self._power_monitor.GetStatus()
 
         if enabled:
-            if verbose: print("...USB enabled, waiting for device")
-            self.executeLocal ("adb wait-for-device")
-            if verbose: print("...device online")
+            if verbose:
+                print("...USB enabled, waiting for device")
+            self.executeLocal("adb wait-for-device")
+            if verbose:
+                print("...device online")
         else:
-            if verbose: logging.info("...USB disabled")
+            if verbose:
+                logging.info("...USB disabled")
         # re-establish port forwarding
-        if enabled and PowerTest.PORT > 0:
+        if enabled and self._tcp_connect_port > 0:
             status = self.executeLocal("adb forward tcp:%d localabstract:%s" %
-                                       (PowerTest.PORT, PowerTest.DOMAIN_NAME))
-            self.reportErrorIf(status != 0, msg="Unable to forward requests to client over adb")
+                                       (self._tcp_connect_port, PowerTest.DOMAIN_NAME))
+            self.reportErrorRaiseExceptionIf(status != 0, msg = "Unable to forward requests to client over adb")
 
-    def waitForScreenOff(self):
-        # disconnect of USB will cause screen to go on, so must wait (1 second more than screen off
-        # timeout)
-        if USE_STRICT_DELAY:
-            time.sleep(DELAY_SCREEN_OFF)
-            return
+    def computeBaselineState(self, measurements):
+        """
+        Args:
+            measurements: List of floats containing ampere draw measurements
+                taken from the monsoon power monitor.
+                Must be atleast 100 measurements long
+        Returns:
+            A tuple (isBaseline, mean_current) where isBaseline is a
+            boolean that is True only if the baseline state for the phone is
+            detected. mean_current is an estimate of the average baseline
+            current for the device, which is valid only if baseline state is
+            detected (if not, it is set to -1).
+        """
 
-        # need at least 100 sequential clean low-power measurements to know screen is off
-        THRESHOLD_COUNT_LOW_POWER = 100
-        CURRENT_LOW_POWER_THRESHOLD = 0.060  # Amps
-        TIMEOUT_SCREEN_OFF = 30 # this many tries at most
-        count_good = 0
-        tries = 0
-        print("Waiting for screen off and application processor in suspend mode...")
-        while count_good < THRESHOLD_COUNT_LOW_POWER:
-            measurements = self.collectMeasurements(THRESHOLD_COUNT_LOW_POWER,
-                                                      PowerTest.RATE_NOMINAL,
-                                                      ensure_screen_off=False,
-                                                      verbose=False)
-            count_good = len([m for m in measurements
-                               if m < CURRENT_LOW_POWER_THRESHOLD])
-            tries += 1
-            if count_good < THRESHOLD_COUNT_LOW_POWER and measurements:
-                print("Current usage: %.2f mAmps. Device is probably not in suspend mode.   Waiting..." %
-                      (1000.0*(sum(measurements)/len(measurements))))
-            if tries >= TIMEOUT_SCREEN_OFF:
-                # TODO: dump the state of sensor service to identify if there are features using sensors
-                self.reportErrorIf(tries>=TIMEOUT_SCREEN_OFF,
-                    msg="Unable to determine application processor suspend mode status.")
+        # Looks at the measurements to see if it is in baseline state
+        if len(measurements) < 100:
+            print(
+                "Need at least 100 measurements to determine if baseline state has"
+                " been reached")
+            return (False, -1)
+
+        # Assumption: At baseline state, the power profile is Gaussian distributed
+        # with low-variance around the mean current draw.
+        # Ideally we should find the mode from a histogram bin to find an estimated mean.
+        # Assuming here that the median is very close to this value; later we check that the
+        # variance of the samples is low enough to validate baseline.
+        sorted_measurements = sorted(measurements)
+        number_measurements = len(measurements)
+        if not number_measurements % 2:
+            median_measurement = (sorted_measurements[(number_measurements - 1) / 2] +
+                                  sorted_measurements[(number_measurements + 1) / 2]) / 2
+        else:
+            median_measurement = sorted_measurements[number_measurements / 2]
+
+        # Assume that at baseline state, a large fraction of power measurements
+        # are within +/- EXPECTED_AMPS_VARIATION_HALF_RANGE milliAmperes of
+        # the average baseline current. Find all such measurements in the
+        # sorted measurement vector.
+        left_index = (
+            bisect_left(
+                sorted_measurements,
+                median_measurement -
+                PowerTest.EXPECTED_AMPS_VARIATION_HALF_RANGE))
+        right_index = (
+            bisect_left(
+                sorted_measurements,
+                median_measurement +
+                PowerTest.EXPECTED_AMPS_VARIATION_HALF_RANGE))
+
+        average_baseline_amps = scipy.mean(
+            sorted_measurements[left_index: (right_index - 1)])
+
+        detected_baseline = True
+        # We enforce that a fraction of more than 'THRESHOLD_BASELINE_SAMPLES_FRACTION'
+        # of samples must be within +/- EXPECTED_AMPS_VARIATION_HALF_RANGE
+        # milliAmperes of the mean baseline current, which we have estimated as
+        # the median.
+        if ((right_index - left_index) < PowerTest.THRESHOLD_BASELINE_SAMPLES_FRACTION * len(
+                measurements)):
+            detected_baseline = False
+
+        # We check for the maximum limit of the expected baseline
+        if median_measurement > self._max_baseline_amps:
+            detected_baseline = False
+        if average_baseline_amps < 0:
+            print PowerTest.NEGATIVE_AMPERE_ERROR_MESSAGE
+            detected_baseline = False
+
+        print("%s baseline state" % ("Could detect" if detected_baseline else "Could NOT detect"))
+        print(
+            "median amps = %f, avg amps = %f, fraction of good samples = %f" %
+            (median_measurement, average_baseline_amps,
+             float(right_index - left_index) / len(measurements)))
+        if PowerTest.ENABLE_PLOTTING:
+            plt.plot(measurements)
+            plt.show()
+            print("To continue test, please close the plot window manually.")
+        return (detected_baseline, average_baseline_amps)
+
+    def isApInSuspendState(self, measurements_amps, nominal_max_amps, test_percentile):
+        """
+        This function detects AP suspend and display off state of phone
+        after a sensor has been registered.
+
+        Because the power profile can be very different between sensors and
+        even across builds, it is difficult to specify a tight threshold for
+        mean current draw or mandate that the power measurements must have low
+        variance. We use a criteria that allows for a certain fraction of
+        peaks in power spectrum and checks that test_percentile fraction of
+        measurements must be below the specified value nominal_max_amps
+        Args:
+            measurements_amps: amperes draw measurements from power monitor
+            test_percentile: the fraction of measurements we require to be below
+                             a specified amps value
+            nominal_max_amps: the specified value of the max current draw
+        Returns:
+            returns a boolean which is True if and only if the AP suspend and
+            display off state is detected
+        """
+        count_good = len([m for m in measurements_amps if m < nominal_max_amps])
+        count_negative = len([m for m in measurements_amps if m < 0])
+        if count_negative > 0:
+            print PowerTest.NEGATIVE_AMPERE_ERROR_MESSAGE
+            return False;
+        return count_good > test_percentile * len(measurements_amps)
+
+    def getBaselineState(self):
+        """This function first disables all sensors, then collects measurements
+        through the power monitor and continuously evaluates if baseline state
+        is reached. Once baseline state is detected, it returns a tuple with
+        status information. If baseline is not detected in a preset maximum
+        number of trials, it returns as well.
+
+        Returns:
+            Returns a tuple (isBaseline, mean_current) where isBaseline is a
+            boolean that is True only if the baseline state for the phone is
+            detected. mean_current is an estimate of the average baseline current
+            for the device, which is valid only if baseline state is detected
+            (if not, it is set to -1)
+        """
+        self.setPowerOn("ALL", False)
+        self.setUsbEnabled(False)
+        print("Waiting %d seconds for baseline state" % DELAY_SCREEN_OFF)
+        time.sleep(DELAY_SCREEN_OFF)
+
+        MEASUREMENT_DURATION_SECONDS_BASELINE_DETECTION = 5  # seconds
+        NUMBER_MEASUREMENTS_BASELINE_DETECTION = (
+            PowerTest.RATE_NOMINAL *
+            MEASUREMENT_DURATION_SECONDS_BASELINE_DETECTION)
+        NUMBER_MEASUREMENTS_BASELINE_VERIFICATION = (
+            NUMBER_MEASUREMENTS_BASELINE_DETECTION * 5)
+        MAX_TRIALS = 50
+
+        collected_baseline_measurements = False
+
+        for tries in xrange(MAX_TRIALS):
+            print("Trial number %d of %d..." % (tries, MAX_TRIALS))
+            measurements = self.collectMeasurements(
+                NUMBER_MEASUREMENTS_BASELINE_DETECTION, PowerTest.RATE_NOMINAL,
+                verbose = False)
+            if self.computeBaselineState(measurements)[0] is True:
+                collected_baseline_measurements = True
                 break
-        if DELAY_SCREEN_OFF:
-            # add additional delay time if necessary
-            time.sleep(DELAY_SCREEN_OFF)
-        print("...Screen off and device in suspend mode.")
 
-    def collectMeasurements(self, measurementCount, rate , ensure_screen_off=True, verbose=True,
-                             plot_data = False):
-        assert(measurementCount > 0)
-        decimate_by = self._native_hz / rate  or 1
-        if ensure_screen_off:
-            self.waitForScreenOff()
-            print ("Taking measurements...")
+        if collected_baseline_measurements:
+            print("Verifying baseline state over a longer interval "
+                  "in order to double check baseline state")
+            measurements = self.collectMeasurements(
+                NUMBER_MEASUREMENTS_BASELINE_VERIFICATION, PowerTest.RATE_NOMINAL,
+                verbose = False)
+            self.reportErrorRaiseExceptionIf(
+                not measurements, "No background measurements could be taken")
+            retval = self.computeBaselineState(measurements)
+            if retval[0]:
+                print("Verified baseline.")
+                if measurements and LOG_DATA_TO_FILE:
+                    with open("/tmp/cts-power-tests-background-data.log", "w") as f:
+                        for m in measurements:
+                            f.write("%.4f\n" % m)
+            return retval
+        else:
+            return (False, -1)
+
+    def waitForApSuspendMode(self):
+        """This function repeatedly collects measurements until AP suspend and display off
+        mode is detected. After a maximum number of trials, if this state is not reached, it
+        raises an error.
+        Returns:
+            boolean which is True if device was detected to be in suspend state
+        Raises:
+            Power monitor-related exception
+        """
+        print("waitForApSuspendMode(): Sleeping for %d seconds" % DELAY_SCREEN_OFF)
+        time.sleep(DELAY_SCREEN_OFF)
+
+        NUMBER_MEASUREMENTS = 200
+        # Maximum trials for which to collect measurements to get to Ap suspend
+        # state
+        MAX_TRIALS = 50
+
+        got_to_suspend_state = False
+        for count in xrange(MAX_TRIALS):
+            print ("waitForApSuspendMode(): Trial %d of %d" % (count, MAX_TRIALS))
+            measurements = self.collectMeasurements(NUMBER_MEASUREMENTS,
+                                                    PowerTest.RATE_NOMINAL,
+                                                    verbose = False)
+            if self.isApInSuspendState(
+                    measurements, PowerTest.MAX_PERCENTILE_AP_SCREEN_OFF_AMPS,
+                    PowerTest.PERCENTILE_MAX_AP_SCREEN_OFF):
+                got_to_suspend_state = True
+                break
+        self.reportErrorRaiseExceptionIf(
+            got_to_suspend_state is False,
+            msg = "Unable to determine application processor suspend mode status.")
+        print("Got to AP suspend state")
+        return got_to_suspend_state
+
+    def collectMeasurements(self, measurementCount, rate, verbose = True):
+        """Args:
+            measurementCount: Number of measurements to collect from the power
+                              monitor
+            rate: The integer frequency in Hertz at which to collect measurements from
+                  the power monitor
+        Returns:
+            A list containing measurements from the power monitor; that has the
+            requested count of the number of measurements at the specified rate
+        """
+        assert (measurementCount > 0)
+        decimate_by = self._native_hz / rate or 1
+
         self._power_monitor.StartDataCollection()
         sub_measurements = []
         measurements = []
@@ -273,47 +550,70 @@
                     tries = 0
                     sub_measurements.extend(additional)
                     while len(sub_measurements) >= decimate_by:
-                        sub_avg = sum(sub_measurements) / len(sub_measurements)
+                        sub_avg = sum(sub_measurements[0:decimate_by]) / decimate_by
                         measurements.append(sub_avg)
                         sub_measurements = sub_measurements[decimate_by:]
                         if verbose:
+                            # "\33[1A\33[2K" is a special Linux console control
+                            # sequence for moving to the previous line, and
+                            # erasing it; and reprinting new text on that
+                            # erased line.
                             sys.stdout.write("\33[1A\33[2K")
-                            print ("MEASURED[%d]: %f" % (len(measurements),measurements[-1]))
+                            print ("MEASURED[%d]: %f" % (len(measurements), measurements[-1]))
         finally:
             self._power_monitor.StopDataCollection()
 
-        self.reportErrorIf(measurementCount > len(measurements),
-                            "Unable to collect all requested measurements")
+        self.reportErrorRaiseExceptionIf(measurementCount > len(measurements),
+                           "Unable to collect all requested measurements")
         return measurements
 
-    def request_user_acknowledgment(self, msg):
+    def requestUserAcknowledgment(self, msg):
         """Post message to user on screen and wait for acknowledgment"""
         response = self.executeOnDevice(PowerTest.REQUEST_USER_RESPONSE % msg)
-        self.reportErrorIf(response != "OK", "Unable to request user acknowledgment")
+        self.reportErrorRaiseExceptionIf(
+            response != "OK", "Unable to request user acknowledgment")
 
-    def setTestResult (self, testname, condition, msg):
-        if condition is False:
-            val = "FAIL"
-        elif condition is True:
-            val = "PASS"
-        else:
-            val = condition
-        print ("Test %s : %s" % (testname, val))
-        response = self.executeOnDevice(PowerTest.REQUEST_SET_TEST_RESULT % (testname, val, msg))
-        self.reportErrorIf(response != "OK", "Unable to send test status to Verifier")
+    def setTestResult(self, test_name, test_result, test_message):
+        """
+        Reports the result of a test to the device
+        Args:
+            test_name: name of the test
+            test_result: Boolean result of the test (True means Pass)
+            test_message: Relevant message
+        """
+        print ("Test %s : %s" % (test_name, test_result))
+
+        response = (
+            self.executeOnDevice(
+                PowerTest.REQUEST_SET_TEST_RESULT %
+                (test_name, test_result, test_message)))
+        self.reportErrorRaiseExceptionIf(
+            response != "OK", "Unable to send test status to Verifier")
 
     def setPowerOn(self, sensor, powered_on):
         response = self.executeOnDevice(PowerTest.REQUEST_SENSOR_SWITCH %
-                                        ({True:"ON", False:"OFF"}[powered_on], sensor))
-        self.reportErrorIf(response == "ERR", "Unable to set sensor %s state" % sensor)
-        logging.info("Set %s %s" % (sensor, {True:"ON", False:"OFF"}[powered_on]))
+            (("ON" if powered_on else "OFF"), sensor))
+        self.reportErrorRaiseExceptionIf(
+            response == "ERR", "Unable to set sensor %s state" % sensor)
+        logging.info("Set %s %s", sensor, ("ON" if powered_on else "OFF"))
         return response
 
-    def runPowerTest(self, sensor, max_power_allowed, user_request = None):
-        if not signal_abort.empty():
-            sys.exit( signal_abort.get() )
-        self._current_test = "%s_Power_Test_While_%s" % (sensor,
-                                    {True:"Under_Motion", False:"Still"}[user_request is not None])
+    def runSensorPowerTest(
+            self, sensor, max_amperes_allowed, baseline_amps, user_request = None):
+        """
+        Runs power test for a specific sensor; i.e. measures the amperes draw
+        of the phone using monsoon, with the specified sensor mregistered
+        and the phone in suspend state; and verifies that the incremental
+        consumed amperes is within expected bounds.
+        Args:
+            sensor: The specified sensor for which to run the power test
+            max_amperes_allowed: Maximum ampere draw of the device with the
+                    sensor registered and device in suspend state
+            baseline_amps: The power draw of the device when it is in baseline
+                    state (no sensors registered, display off, AP asleep)
+        """
+        self._current_test = ("%s_Power_Test_While_%s" % (
+            sensor, ("Under_Motion" if user_request is not None else "Still")))
         try:
             print ("\n\n---------------------------------")
             if user_request is not None:
@@ -321,248 +621,299 @@
             else:
                 print ("Running power test on %s while device is still." % sensor)
             print ("---------------------------------")
-            response = self.executeOnDevice(PowerTest.REQUEST_SENSOR_AVAILABILITY % sensor)
+            response = self.executeOnDevice(
+                PowerTest.REQUEST_SENSOR_AVAILABILITY % sensor)
             if response == "UNAVAILABLE":
-                self.setTestResult(self._current_test, condition="SKIPPED",
-                    msg="Sensor %s not available on this platform"%sensor)
+                self.setTestResult(
+                    self._current_test, test_result = "SKIPPED",
+                    test_message = "Sensor %s not available on this platform" % sensor)
             self.setPowerOn("ALL", False)
             if response == "UNAVAILABLE":
-                self.setTestResult(self._current_test, condition="SKIPPED",
-                                   msg="Sensor %s not available on this device"%sensor)
+                self.setTestResult(
+                    self._current_test, test_result = "SKIPPED",
+                    test_message = "Sensor %s not available on this device" % sensor)
                 return
-
-            self.reportErrorIf(response != "OK", "Unable to set all sensor off")
-            if not signal_abort.empty():
-                sys.exit( signal_abort.get() )
+            self.reportErrorRaiseExceptionIf(response != "OK", "Unable to set all sensor off")
             self.executeOnDevice(PowerTest.REQUEST_SCREEN_OFF)
             self.setUsbEnabled(False)
-            print("Collecting background measurements...")
-            measurements = self.collectMeasurements( PowerTest.SAMPLE_COUNT_NOMINAL,
-                                                     PowerTest.RATE_NOMINAL,
-                                                     plot_data = True)
-            if measurements and LOG_DATA_TO_FILE:
-                with open( "/tmp/cts-power-tests-%s-%s-background-data.log"%(sensor,
-                   {True:"Under_Motion", False:"Still"}[user_request is not None] ),'w') as f:
-                    for m in measurements:
-                        f.write( "%.4f\n"%m)
-            self.reportErrorIf(not measurements, "No background measurements could be taken")
-            backgnd = sum(measurements) / len(measurements)
             self.setUsbEnabled(True)
             self.setPowerOn(sensor, True)
             if user_request is not None:
                 print("===========================================\n" +
                       "==> Please follow the instructions presented on the device\n" +
-                      "==========================================="
-                     )
-                self.request_user_acknowledgment(user_request)
+                      "===========================================")
+                self.requestUserAcknowledgment(user_request)
             self.executeOnDevice(PowerTest.REQUEST_SCREEN_OFF)
             self.setUsbEnabled(False)
-            self.reportErrorIf(response != "OK", "Unable to set sensor %s ON" % sensor)
+            self.reportErrorRaiseExceptionIf(
+                response != "OK", "Unable to set sensor %s ON" % sensor)
+
+            self.waitForApSuspendMode()
             print ("Collecting sensor %s measurements" % sensor)
             measurements = self.collectMeasurements(PowerTest.SAMPLE_COUNT_NOMINAL,
                                                     PowerTest.RATE_NOMINAL)
 
             if measurements and LOG_DATA_TO_FILE:
-                with open( "/tmp/cts-power-tests-%s-%s-sensor-data.log"%(sensor,
-                   {True:"Under_Motion", False:"Still"}[user_request is not None] ),'w') as f:
+                with open("/tmp/cts-power-tests-%s-%s-sensor-data.log" % (sensor,
+                    ("Under_Motion" if user_request is not None else "Still")), "w") as f:
                     for m in measurements:
-                        f.write( "%.4f\n"%m)
+                        f.write("%.4f\n" % m)
                     self.setUsbEnabled(True, verbose = False)
                     print("Saving raw data files to device...")
                     self.executeLocal("adb shell mkdir -p %s" % self._external_storage, False)
                     self.executeLocal("adb push %s %s/." % (f.name, self._external_storage))
                     self.setUsbEnabled(False, verbose = False)
-            self.reportErrorIf(not measurements, "No measurements could be taken for %s" % sensor)
+            self.reportErrorRaiseExceptionIf(
+                not measurements, "No measurements could be taken for %s" % sensor)
             avg = sum(measurements) / len(measurements)
-            squared = [(m-avg)*(m-avg) for m in measurements]
+            squared = [(m - avg) * (m - avg) for m in measurements]
 
-            import math
-            stddev = math.sqrt(sum(squared)/len(squared))
-            current_diff = avg - backgnd
+            stddev = math.sqrt(sum(squared) / len(squared))
+            current_diff = avg - baseline_amps
             self.setUsbEnabled(True)
             max_power = max(measurements) - avg
-            if current_diff <= max_power_allowed:
+            if current_diff <= max_amperes_allowed:
                 # TODO: fail the test of background > current
-                message = ("Draw is within limits. Current:%f Background:%f   Measured: %f Stddev: %f  Peak: %f")%\
-                             ( current_diff*1000.0, backgnd*1000.0, avg*1000.0, stddev*1000.0, max_power*1000.0)
+                message = (
+                              "Draw is within limits. Sensor delta:%f mAmp   Baseline:%f "
+                              "mAmp   Sensor: %f mAmp  Stddev : %f mAmp  Peak: %f mAmp") % (
+                              current_diff * 1000.0, baseline_amps * 1000.0, avg * 1000.0,
+                              stddev * 1000.0, max_power * 1000.0)
             else:
-                message = ("Draw is too high. Current:%f Background:%f   Measured: %f Stddev: %f  Peak: %f")%\
-                             ( current_diff*1000.0, backgnd*1000.0, avg*1000.0, stddev*1000.0, max_power*1000.0)
-            self.setTestResult( testname = self._current_test,
-                                condition = current_diff <= max_power_allowed,
-                                msg = message)
-            print("Result: "+message)
+                message = (
+                              "Draw is too high. Current:%f Background:%f   Measured: %f "
+                              "Stddev: %f  Peak: %f") % (
+                              current_diff * 1000.0, baseline_amps * 1000.0, avg * 1000.0,
+                              stddev * 1000.0, max_power * 1000.0)
+            self.setTestResult(
+                self._current_test,
+                ("PASS" if (current_diff <= max_amperes_allowed) else "FAIL"),
+                message)
+            print("Result: " + message)
         except:
-            import traceback
             traceback.print_exc()
-            self.setTestResult(self._current_test, condition="FAIL",
-                               msg="Exception occurred during run of test.")
-
+            self.setTestResult(self._current_test, test_result = "FAIL",
+                               test_message = "Exception occurred during run of test.")
+            raise
 
     @staticmethod
-    def run_tests():
+    def runTests(max_baseline_amps):
         testrunner = None
         try:
-            GENERIC_MOTION_REQUEST = "\n===> Please press Next and when the screen is off, keep " + \
-                "the device under motion with only tiny, slow movements until the screen turns " + \
-                "on again.\nPlease refrain from interacting with the screen or pressing any side " + \
-                "buttons while measurements are taken."
-            USER_STEPS_REQUEST = "\n===> Please press Next and when the screen is off, then move " + \
-                "the device to simulate step motion until the screen turns on again.\nPlease " + \
-                "refrain from interacting with the screen or pressing any side buttons while " + \
-                "measurements are taken."
-            testrunner = PowerTest()
-            testrunner.executeOnDevice(PowerTest.REQUEST_SHOW_MESSAGE % "Connected.  Running tests...")
-            testrunner.runPowerTest("SIGNIFICANT_MOTION", PowerTest.MAX_SIGMO_POWER, user_request = GENERIC_MOTION_REQUEST)
-            testrunner.runPowerTest("STEP_DETECTOR", PowerTest.MAX_STEP_DETECTOR_POWER, user_request = USER_STEPS_REQUEST)
-            testrunner.runPowerTest("STEP_COUNTER", PowerTest.MAX_STEP_COUNTER_POWER, user_request = USER_STEPS_REQUEST)
-            testrunner.runPowerTest("ACCELEROMETER", PowerTest.MAX_ACCEL_POWER, user_request = GENERIC_MOTION_REQUEST)
-            testrunner.runPowerTest("MAGNETIC_FIELD", PowerTest.MAX_MAG_POWER, user_request = GENERIC_MOTION_REQUEST)
-            testrunner.runPowerTest("GYROSCOPE", PowerTest.MAX_GYRO_POWER, user_request = GENERIC_MOTION_REQUEST)
-            testrunner.runPowerTest("ACCELEROMETER", PowerTest.MAX_ACCEL_POWER, user_request = None)
-            testrunner.runPowerTest("MAGNETIC_FIELD", PowerTest.MAX_MAG_POWER, user_request = None)
-            testrunner.runPowerTest("GYROSCOPE", PowerTest.MAX_GYRO_POWER, user_request = None)
-            testrunner.runPowerTest("SIGNIFICANT_MOTION", PowerTest.MAX_SIGMO_POWER, user_request = None)
-            testrunner.runPowerTest("STEP_DETECTOR", PowerTest.MAX_STEP_DETECTOR_POWER, user_request = None)
-            testrunner.runPowerTest("STEP_COUNTER", PowerTest.MAX_STEP_COUNTER_POWER, user_request = None)
+            GENERIC_MOTION_REQUEST = ("\n===> Please press Next and when the "
+                "screen is off, keep the device under motion with only tiny, "
+                "slow movements until the screen turns on again.\nPlease "
+                "refrain from interacting with the screen or pressing any side "
+                "buttons while measurements are taken.")
+            USER_STEPS_REQUEST = ("\n===> Please press Next and when the "
+                "screen is off, then move the device to simulate step motion "
+                "until the screen turns on again.\nPlease refrain from "
+                "interacting with the screen or pressing any side buttons "
+                "while measurements are taken.")
+            testrunner = PowerTest(max_baseline_amps)
+            testrunner.executeOnDevice(
+                PowerTest.REQUEST_SHOW_MESSAGE % "Connected.  Running tests...")
+            is_baseline_success, baseline_amps = testrunner.getBaselineState()
+
+            if is_baseline_success:
+                testrunner.setUsbEnabled(True)
+                # TODO: Enable testing a single sensor
+                testrunner.runSensorPowerTest(
+                    "SIGNIFICANT_MOTION", PowerTest.MAX_SIGMO_AMPS, baseline_amps,
+                    user_request = GENERIC_MOTION_REQUEST)
+                testrunner.runSensorPowerTest(
+                    "STEP_DETECTOR", PowerTest.MAX_STEP_DETECTOR_AMPS, baseline_amps,
+                    user_request = USER_STEPS_REQUEST)
+                testrunner.runSensorPowerTest(
+                    "STEP_COUNTER", PowerTest.MAX_STEP_COUNTER_AMPS, baseline_amps,
+                    user_request = USER_STEPS_REQUEST)
+                testrunner.runSensorPowerTest(
+                    "ACCELEROMETER", PowerTest.MAX_ACCEL_AMPS, baseline_amps,
+                    user_request = GENERIC_MOTION_REQUEST)
+                testrunner.runSensorPowerTest(
+                    "MAGNETIC_FIELD", PowerTest.MAX_MAG_AMPS, baseline_amps,
+                    user_request = GENERIC_MOTION_REQUEST)
+                testrunner.runSensorPowerTest(
+                    "GYROSCOPE", PowerTest.MAX_GYRO_AMPS, baseline_amps,
+                    user_request = GENERIC_MOTION_REQUEST)
+                testrunner.runSensorPowerTest(
+                    "ACCELEROMETER", PowerTest.MAX_ACCEL_AMPS, baseline_amps,
+                    user_request = None)
+                testrunner.runSensorPowerTest(
+                    "MAGNETIC_FIELD", PowerTest.MAX_MAG_AMPS, baseline_amps,
+                    user_request = None)
+                testrunner.runSensorPowerTest(
+                    "GYROSCOPE", PowerTest.MAX_GYRO_AMPS, baseline_amps,
+                    user_request = None)
+                testrunner.runSensorPowerTest(
+                    "SIGNIFICANT_MOTION", PowerTest.MAX_SIGMO_AMPS, baseline_amps,
+                    user_request = None)
+                testrunner.runSensorPowerTest(
+                    "STEP_DETECTOR", PowerTest.MAX_STEP_DETECTOR_AMPS, baseline_amps,
+                    user_request = None)
+                testrunner.runSensorPowerTest(
+                    "STEP_COUNTER", PowerTest.MAX_STEP_COUNTER_AMPS, baseline_amps,
+                    user_request = None)
+            else:
+                print("Could not get to baseline state. This is either because "
+                      "in several trials, the monitor could not measure a set "
+                      "of power measurements that had the specified low "
+                      "variance or the mean measurements were below the "
+                      "expected value. None of the sensor power measurement "
+                      " tests were performed due to not being able to detect "
+                      "baseline state. Please re-run the power tests.")
+        except KeyboardInterrupt:
+            print "Keyboard interrupt from user."
+            raise
         except:
             import traceback
             traceback.print_exc()
         finally:
-            signal_exit_q.put(0) # anything will signal thread to terminate
             logging.info("TESTS COMPLETE")
             if testrunner:
                 try:
                     testrunner.finalize()
                 except socket.error:
-                    sys.exit("============================\nUnable to connect to device under " + \
-                             "test. Make sure the device is connected via the usb pass-through, " + \
-                             "the CtsVerifier app is running the SensorPowerTest on the device, " + \
-                             "and USB pass-through is enabled.\n===========================")
-
+                    sys.exit(
+                        "===================================================\n"
+                        "Unable to connect to device under test. Make sure \n"
+                        "the device is connected via the usb pass-through, \n"
+                        "the CtsVerifier app is running the SensorPowerTest on \n"
+                        "the device, and USB pass-through is enabled.\n"
+                        "===================================================")
 
 def main(argv):
-  """ Simple command-line interface for a power test application."""
-  useful_flags = ["voltage", "status", "usbpassthrough",
-                  "samples", "current", "log", "power_monitor"]
-  if not [f for f in useful_flags if FLAGS.get(f, None) is not None]:
-    print __doc__.strip()
-    print FLAGS.MainModuleHelp()
-    return
+    """ Simple command-line interface for a power test application."""
+    useful_flags = ["voltage", "status", "usbpassthrough",
+                    "samples", "current", "log", "power_monitor"]
+    if not [f for f in useful_flags if FLAGS.get(f, None) is not None]:
+        print __doc__.strip()
+        print FLAGS.MainModuleHelp()
+        return
 
-  if FLAGS.avg and FLAGS.avg < 0:
-    loggign.error("--avg must be greater than 0")
-    return
+    if FLAGS.avg and FLAGS.avg < 0:
+        logging.error("--avg must be greater than 0")
+        return
 
-  if FLAGS.voltage is not None:
-    if FLAGS.voltage > 5.5:
-        print("!!WARNING: Voltage higher than typical values!!!")
-    try:
-        response = raw_input("Voltage of %.3f requested.  Confirm this is correct (Y/N)"%FLAGS.voltage)
-        if response.upper() != "Y":
-            sys.exit("Aborting")
-    except:
-        sys.exit("Aborting.")
+    if FLAGS.voltage is not None:
+        if FLAGS.voltage > 5.5:
+            print("!!WARNING: Voltage higher than typical values!!!")
+        try:
+            response = raw_input(
+                "Voltage of %.3f requested.  Confirm this is correct (Y/N)" %
+                FLAGS.voltage)
+            if response.upper() != "Y":
+                sys.exit("Aborting")
+        except:
+            sys.exit("Aborting.")
 
-  if not FLAGS.power_monitor:
-      sys.exit("You must specify a '--power_monitor' option to specify which power monitor type " + \
-               "you are using.\nOne of:\n  \n  ".join(available_monitors))
-  power_monitors = do_import('power_monitors.%s' % FLAGS.power_monitor)
-  try:
-      mon = power_monitors.Power_Monitor(device=FLAGS.device)
-  except:
-      import traceback
-      traceback.print_exc()
-      sys.exit("No power monitors found")
-
-  if FLAGS.voltage is not None:
-
-    if FLAGS.ramp is not None:
-      mon.RampVoltage(mon.start_voltage, FLAGS.voltage)
-    else:
-      mon.SetVoltage(FLAGS.voltage)
-
-  if FLAGS.current is not None:
-    mon.SetMaxCurrent(FLAGS.current)
-
-  if FLAGS.status:
-    items = sorted(mon.GetStatus().items())
-    print "\n".join(["%s: %s" % item for item in items])
-
-  if FLAGS.usbpassthrough:
-    if FLAGS.usbpassthrough == 'off':
-      mon.SetUsbPassthrough(0)
-    elif FLAGS.usbpassthrough == 'on':
-      mon.SetUsbPassthrough(1)
-    elif FLAGS.usbpassthrough == 'auto':
-      mon.SetUsbPassthrough(2)
-    else:
-      mon.Close()
-      sys.exit('bad pass-through flag: %s' % FLAGS.usbpassthrough)
-
-  if FLAGS.samples:
-    # Make sure state is normal
-    mon.StopDataCollection()
-    status = mon.GetStatus()
-    native_hz = status["sampleRate"] * 1000
-
-    # Collect and average samples as specified
-    mon.StartDataCollection()
-
-    # In case FLAGS.hz doesn't divide native_hz exactly, use this invariant:
-    # 'offset' = (consumed samples) * FLAGS.hz - (emitted samples) * native_hz
-    # This is the error accumulator in a variation of Bresenham's algorithm.
-    emitted = offset = 0
-    collected = []
-    history_deque = collections.deque()  # past n samples for rolling average
-
-    try:
-      last_flush = time.time()
-      while emitted < FLAGS.samples or FLAGS.samples == -1:
-        # The number of raw samples to consume before emitting the next output
-        need = (native_hz - offset + FLAGS.hz - 1) / FLAGS.hz
-        if need > len(collected):  # still need more input samples
-          samples = mon.CollectData()
-          if not samples: break
-          collected.extend(samples)
-        else:
-          # Have enough data, generate output samples.
-          # Adjust for consuming 'need' input samples.
-          offset += need * FLAGS.hz
-          while offset >= native_hz:  # maybe multiple, if FLAGS.hz > native_hz
-            this_sample = sum(collected[:need]) / need
-
-            if FLAGS.timestamp: print int(time.time()),
-
-            if FLAGS.avg:
-              history_deque.appendleft(this_sample)
-              if len(history_deque) > FLAGS.avg: history_deque.pop()
-              print "%f %f" % (this_sample,
-                               sum(history_deque) / len(history_deque))
-            else:
-              print "%f" % this_sample
-            sys.stdout.flush()
-
-            offset -= native_hz
-            emitted += 1  # adjust for emitting 1 output sample
-          collected = collected[need:]
-          now = time.time()
-          if now - last_flush >= 0.99:  # flush every second
-            sys.stdout.flush()
-            last_flush = now
-    except KeyboardInterrupt:
-      print("interrupted")
-      return 1
-    finally:
-      mon.Close()
-    return 0
-
-  if FLAGS.run:
     if not FLAGS.power_monitor:
-        sys.exit("When running power tests, you must specify which type of power monitor to use" +
-                 " with '--power_monitor <type of power monitor>'")
-    PowerTest.run_tests()
+        sys.exit(
+            "You must specify a '--power_monitor' option to specify which power "
+            "monitor type " +
+            "you are using.\nOne of:\n  \n  ".join(available_monitors))
+    power_monitors = do_import("power_monitors.%s" % FLAGS.power_monitor)
+    try:
+        mon = power_monitors.Power_Monitor(device = FLAGS.device)
+    except:
+        import traceback
 
+        traceback.print_exc()
+        sys.exit("No power monitors found")
+
+    if FLAGS.voltage is not None:
+
+        if FLAGS.ramp is not None:
+            mon.RampVoltage(mon.start_voltage, FLAGS.voltage)
+        else:
+            mon.SetVoltage(FLAGS.voltage)
+
+    if FLAGS.current is not None:
+        mon.SetMaxCurrent(FLAGS.current)
+
+    if FLAGS.status:
+        items = sorted(mon.GetStatus().items())
+        print "\n".join(["%s: %s" % item for item in items])
+
+    if FLAGS.usbpassthrough:
+        if FLAGS.usbpassthrough == "off":
+            mon.SetUsbPassthrough(0)
+        elif FLAGS.usbpassthrough == "on":
+            mon.SetUsbPassthrough(1)
+        elif FLAGS.usbpassthrough == "auto":
+            mon.SetUsbPassthrough(2)
+        else:
+            mon.Close()
+            sys.exit("bad pass-through flag: %s" % FLAGS.usbpassthrough)
+
+    if FLAGS.samples:
+        # Make sure state is normal
+        mon.StopDataCollection()
+        status = mon.GetStatus()
+        native_hz = status["sampleRate"] * 1000
+
+        # Collect and average samples as specified
+        mon.StartDataCollection()
+
+        # In case FLAGS.hz doesn't divide native_hz exactly, use this invariant:
+        # 'offset' = (consumed samples) * FLAGS.hz - (emitted samples) * native_hz
+        # This is the error accumulator in a variation of Bresenham's algorithm.
+        emitted = offset = 0
+        collected = []
+        history_deque = collections.deque()  # past n samples for rolling average
+
+        # TODO: Complicated lines of code below. Refactoring needed
+        try:
+            last_flush = time.time()
+            while emitted < FLAGS.samples or FLAGS.samples == -1:
+                # The number of raw samples to consume before emitting the next output
+                need = (native_hz - offset + FLAGS.hz - 1) / FLAGS.hz
+                if need > len(collected):  # still need more input samples
+                    samples = mon.CollectData()
+                    if not samples: break
+                    collected.extend(samples)
+                else:
+                    # Have enough data, generate output samples.
+                    # Adjust for consuming 'need' input samples.
+                    offset += need * FLAGS.hz
+                    while offset >= native_hz:  # maybe multiple, if FLAGS.hz > native_hz
+                        this_sample = sum(collected[:need]) / need
+
+                        if FLAGS.timestamp: print int(time.time()),
+
+                        if FLAGS.avg:
+                            history_deque.appendleft(this_sample)
+                            if len(history_deque) > FLAGS.avg: history_deque.pop()
+                            print "%f %f" % (this_sample,
+                                             sum(history_deque) / len(history_deque))
+                        else:
+                            print "%f" % this_sample
+                        sys.stdout.flush()
+
+                        offset -= native_hz
+                        emitted += 1  # adjust for emitting 1 output sample
+                    collected = collected[need:]
+                    now = time.time()
+                    if now - last_flush >= 0.99:  # flush every second
+                        sys.stdout.flush()
+                        last_flush = now
+        except KeyboardInterrupt:
+            print("interrupted")
+            return 1
+        finally:
+            mon.Close()
+        return 0
+
+    if FLAGS.run:
+        if not FLAGS.power_monitor:
+            sys.exit(
+                "When running power tests, you must specify which type of power "
+                "monitor to use" +
+                " with '--power_monitor <type of power monitor>'")
+        try:
+            PowerTest.runTests(FLAGS.max_baseline_amps)
+        except KeyboardInterrupt:
+            print "Keyboard interrupt from user"
 
 if __name__ == "__main__":
     flags.DEFINE_boolean("status", None, "Print power meter status")
@@ -581,5 +932,6 @@
     flags.DEFINE_boolean("log", False, "Log progress to a file or not")
     flags.DEFINE_boolean("run", False, "Run the test suite for power")
     flags.DEFINE_string("power_monitor", None, "Type of power monitor to use")
+    flags.DEFINE_float("max_baseline_amps", 0.005,
+                       "Set maximum baseline current for device being tested")
     sys.exit(main(FLAGS(sys.argv)))
-
diff --git a/apps/CtsVerifier/res/drawable-hdpi/fs_clock.png b/apps/CtsVerifier/res/drawable-hdpi/fs_clock.png
new file mode 100644
index 0000000..209d78e
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/fs_clock.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-hdpi/ic_corp_icon.png b/apps/CtsVerifier/res/drawable-hdpi/ic_corp_icon.png
new file mode 100644
index 0000000..06c5135
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/ic_corp_icon.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-hdpi/ic_stat_alice.png b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_alice.png
new file mode 100644
index 0000000..e4eea4b
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_alice.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-hdpi/ic_stat_bob.png b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_bob.png
new file mode 100644
index 0000000..c67ff4f
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_bob.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-hdpi/ic_stat_charlie.png b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_charlie.png
new file mode 100644
index 0000000..71afa3e
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-hdpi/ic_stat_charlie.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/fs_clock.png b/apps/CtsVerifier/res/drawable-mdpi/fs_clock.png
new file mode 100644
index 0000000..209d78e
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/fs_clock.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/ic_corp_icon.png b/apps/CtsVerifier/res/drawable-mdpi/ic_corp_icon.png
new file mode 100644
index 0000000..79372b2
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/ic_corp_icon.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/ic_stat_alice.png b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_alice.png
new file mode 100644
index 0000000..3717827
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_alice.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/ic_stat_bob.png b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_bob.png
new file mode 100644
index 0000000..f266312
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_bob.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-mdpi/ic_stat_charlie.png b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_charlie.png
new file mode 100644
index 0000000..49c4b9a
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-mdpi/ic_stat_charlie.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-xhdpi/ic_corp_icon.png b/apps/CtsVerifier/res/drawable-xhdpi/ic_corp_icon.png
new file mode 100644
index 0000000..3626c7d
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-xhdpi/ic_corp_icon.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-xxhdpi/ic_corp_icon.png b/apps/CtsVerifier/res/drawable-xxhdpi/ic_corp_icon.png
new file mode 100644
index 0000000..d33319f
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-xxhdpi/ic_corp_icon.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable-xxxhdpi/ic_corp_icon.png b/apps/CtsVerifier/res/drawable-xxxhdpi/ic_corp_icon.png
new file mode 100644
index 0000000..359e210
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable-xxxhdpi/ic_corp_icon.png
Binary files differ
diff --git a/apps/CtsVerifier/res/drawable/badged_icon.png b/apps/CtsVerifier/res/drawable/badged_icon.png
new file mode 100644
index 0000000..bb748da
--- /dev/null
+++ b/apps/CtsVerifier/res/drawable/badged_icon.png
Binary files differ
diff --git a/apps/CtsVerifier/res/layout-land/sensor_test.xml b/apps/CtsVerifier/res/layout-land/sensor_test.xml
index 293b4b0..f547978 100644
--- a/apps/CtsVerifier/res/layout-land/sensor_test.xml
+++ b/apps/CtsVerifier/res/layout-land/sensor_test.xml
@@ -13,41 +13,46 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:orientation="vertical"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        >
-
-    <LinearLayout
-            android:orientation="horizontal"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
+            android:orientation="vertical"
             android:layout_width="match_parent"
-            android:layout_height="0dp"
-            android:layout_weight="1">
+            android:layout_height="match_parent"
+            >
 
-        <ScrollView
-                android:id="@+id/log_scroll_view"
-                android:fillViewport="true"
-                android:layout_height="match_parent"
-                android:layout_width="0dp"
+        <LinearLayout
+                android:orientation="horizontal"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
                 android:layout_weight="1">
 
-            <LinearLayout
-                    android:id="@+id/log_layout"
-                    android:orientation="vertical"
-                    android:layout_width="match_parent"
-                    android:layout_height="match_parent"/>
+            <ScrollView
+                    android:id="@+id/log_scroll_view"
+                    android:fillViewport="true"
+                    android:layout_height="match_parent"
+                    android:layout_width="0dp"
+                    android:layout_weight="1">
 
-        </ScrollView>
+                <LinearLayout
+                        android:id="@+id/log_layout"
+                        android:orientation="vertical"
+                        android:layout_width="match_parent"
+                        android:layout_height="match_parent"/>
 
-        <android.opengl.GLSurfaceView android:id="@+id/gl_surface_view"
-                android:visibility="gone"
-                android:layout_width="0dp"
-                android:layout_height="match_parent"
-                android:layout_weight="1"/>
+            </ScrollView>
+
+            <android.opengl.GLSurfaceView android:id="@+id/gl_surface_view"
+                    android:visibility="gone"
+                    android:layout_width="0dp"
+                    android:layout_height="match_parent"
+                    android:layout_weight="1"/>
+
+        </LinearLayout>
+
+        <include layout="@layout/snsr_next_button" />
 
     </LinearLayout>
-
-    <include layout="@layout/snsr_next_button" />
-
-</LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout-port/sensor_test.xml b/apps/CtsVerifier/res/layout-port/sensor_test.xml
index eac5357..b4eca4d 100644
--- a/apps/CtsVerifier/res/layout-port/sensor_test.xml
+++ b/apps/CtsVerifier/res/layout-port/sensor_test.xml
@@ -13,30 +13,35 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:orientation="vertical"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
 
-    <ScrollView android:id="@+id/log_scroll_view"
-            android:fillViewport="true"
-            android:layout_height="0dp"
-            android:layout_weight="1"
-            android:layout_width="match_parent">
+        <ScrollView android:id="@+id/log_scroll_view"
+                android:fillViewport="true"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                android:layout_width="match_parent">
 
-        <LinearLayout android:id="@+id/log_layout"
-                android:orientation="vertical"
-                android:layout_height="match_parent"
+            <LinearLayout android:id="@+id/log_layout"
+                    android:orientation="vertical"
+                    android:layout_height="match_parent"
+                    android:layout_width="match_parent"/>
+
+        </ScrollView>
+
+        <android.opengl.GLSurfaceView android:id="@+id/gl_surface_view"
+                android:visibility="gone"
+                android:layout_height="0dp"
+                android:layout_weight="1"
                 android:layout_width="match_parent"/>
 
-    </ScrollView>
+        <include layout="@layout/snsr_next_button"/>
 
-    <android.opengl.GLSurfaceView android:id="@+id/gl_surface_view"
-            android:visibility="gone"
-            android:layout_height="0dp"
-            android:layout_weight="1"
-            android:layout_width="match_parent"/>
-
-    <include layout="@layout/snsr_next_button"/>
-
-</LinearLayout>
+    </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_advertiser_hardware_scan_filter.xml b/apps/CtsVerifier/res/layout/ble_advertiser_hardware_scan_filter.xml
index ce3e1e1..a545727 100644
--- a/apps/CtsVerifier/res/layout/ble_advertiser_hardware_scan_filter.xml
+++ b/apps/CtsVerifier/res/layout/ble_advertiser_hardware_scan_filter.xml
@@ -19,63 +19,56 @@
         android:orientation="vertical"
         android:padding="10dip"
         >
-
-    <LinearLayout android:orientation="vertical"
+    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            >
-        <TextView android:text="@string/ble_advertiser_scannable"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-        />
-        <TextView android:text="@string/ble_advertiser_scannable_instruction"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-        />
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                >
-            <Button android:id="@+id/ble_advertiser_scannable_start"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/ble_advertiser_start"
-                    />
-            <Button android:id="@+id/ble_advertiser_scannable_stop"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/ble_advertiser_stop"
-                    />
-        </LinearLayout>
-        <TextView android:text="@string/ble_advertiser_unscannable"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-        />
-        <TextView android:text="@string/ble_advertiser_unscannable_instruction"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-        />
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                >
-            <Button android:id="@+id/ble_advertiser_unscannable_start"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/ble_advertiser_start"
-                    />
-            <Button android:id="@+id/ble_advertiser_unscannable_stop"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/ble_advertiser_stop"
-                    />
-        </LinearLayout>
-    </LinearLayout>
+            android:scrollbars="vertical">
 
-    <include android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            layout="@layout/pass_fail_buttons"
-            />
+        <LinearLayout android:orientation="vertical"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_centerInParent="true">
+            <TextView android:text="@string/ble_advertiser_scannable"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+            <TextView android:text="@string/ble_advertiser_scannable_instruction"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+            <LinearLayout android:orientation="horizontal"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+                <Button android:id="@+id/ble_advertiser_scannable_start"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/ble_advertiser_start"/>
+                <Button android:id="@+id/ble_advertiser_scannable_stop"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/ble_advertiser_stop"/>
+            </LinearLayout>
+            <TextView android:text="@string/ble_advertiser_unscannable"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+            <TextView android:text="@string/ble_advertiser_unscannable_instruction"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+            <LinearLayout android:orientation="horizontal"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+                <Button android:id="@+id/ble_advertiser_unscannable_start"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/ble_advertiser_start"/>
+                <Button android:id="@+id/ble_advertiser_unscannable_stop"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/ble_advertiser_stop"/>
+            </LinearLayout>
+
+            <include android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_alignParentBottom="true"
+                    layout="@layout/pass_fail_buttons"/>
+        </LinearLayout>
+    </ScrollView>
 </RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_advertiser_power_level.xml b/apps/CtsVerifier/res/layout/ble_advertiser_power_level.xml
index ec3284d..c8e0133 100644
--- a/apps/CtsVerifier/res/layout/ble_advertiser_power_level.xml
+++ b/apps/CtsVerifier/res/layout/ble_advertiser_power_level.xml
@@ -19,31 +19,34 @@
         android:orientation="vertical"
         android:padding="10dip"
         >
-
-    <TextView android:text="@string/ble_advertiser_power_level_instruction"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-    />
-    <LinearLayout android:orientation="horizontal"
+    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content">
+        <LinearLayout android:orientation="vertical"
             android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            >
-        <Button android:id="@+id/ble_power_level_start"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/ble_advertiser_start"
-                />
-        <Button android:id="@+id/ble_power_level_stop"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/ble_advertiser_stop"
-                />
-    </LinearLayout>
+            android:layout_height="wrap_content">
+            <TextView android:text="@string/ble_advertiser_power_level_instruction"
+                    android:id="@+id/ble_advertiser_power_level_instruction"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:scrollbars="vertical"/>
+            <LinearLayout android:orientation="horizontal"
+                    android:layout_below="@+id/ble_advertiser_power_level_instruction"
+                    android:layout_width="match_parent"
+                    android:layout_height="wrap_content">
+                <Button android:id="@+id/ble_power_level_start"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/ble_advertiser_start"/>
+                <Button android:id="@+id/ble_power_level_stop"
+                        android:layout_width="wrap_content"
+                        android:layout_height="wrap_content"
+                        android:text="@string/ble_advertiser_stop"/>
+            </LinearLayout>
 
-    <include android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            layout="@layout/pass_fail_buttons"
-            />
+            <include android:layout_width="match_parent"
+                    android:layout_height="wrap_content"
+                    layout="@layout/pass_fail_buttons"/>
+        </LinearLayout>
+    </ScrollView>
 </RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_client_connect.xml b/apps/CtsVerifier/res/layout/ble_client_connect.xml
deleted file mode 100644
index 54a0a99..0000000
--- a/apps/CtsVerifier/res/layout/ble_client_connect.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:padding="10dip"
-        >
-
-    <LinearLayout android:orientation="horizontal"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            >
-        <EditText android:id="@+id/ble_address"
-                android:layout_weight="1"
-                android:layout_width="0dp"
-                android:layout_height="wrap_content"
-                android:hint="@string/ble_address"
-                />
-        <Button android:id="@+id/ble_connect"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:text="@string/ble_connect"
-                />
-    </LinearLayout>
-
-    <include android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            layout="@layout/pass_fail_buttons"
-            />
-</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/ble_client_read_write.xml b/apps/CtsVerifier/res/layout/ble_client_read_write.xml
deleted file mode 100644
index 7edba62..0000000
--- a/apps/CtsVerifier/res/layout/ble_client_read_write.xml
+++ /dev/null
@@ -1,70 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:padding="10dip"
-        >
-    <LinearLayout android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            >
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                >
-            <EditText android:id="@+id/write_text"
-                    android:layout_width="0dp"
-                    android:layout_weight="1"
-                    android:layout_height="wrap_content"
-                    android:hint="@string/ble_write_hint"
-                    android:padding="10dip"
-                    />
-            <Button android:id="@+id/ble_write"
-                    android:layout_height="wrap_content"
-                    android:layout_width="wrap_content"
-                    android:text="@string/ble_write"
-                    />
-        </LinearLayout>
-
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                >
-            <TextView android:id="@+id/read_text"
-                    android:layout_width="0dp"
-                    android:layout_weight="1"
-                    android:layout_height="wrap_content"
-                    android:hint="@string/ble_read_hint"
-                    android:padding="10dip"
-                    android:textSize="18sp"
-                    />
-            <Button android:id="@+id/ble_read"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content"
-                    android:text="@string/ble_read"
-                    />
-        </LinearLayout>
-    </LinearLayout>
-
-    <include android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            layout="@layout/pass_fail_buttons"
-            />
-</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/ble_client_test.xml b/apps/CtsVerifier/res/layout/ble_client_start.xml
similarity index 79%
rename from apps/CtsVerifier/res/layout/ble_client_test.xml
rename to apps/CtsVerifier/res/layout/ble_client_start.xml
index 660abd5..c377ca1 100644
--- a/apps/CtsVerifier/res/layout/ble_client_test.xml
+++ b/apps/CtsVerifier/res/layout/ble_client_start.xml
@@ -19,15 +19,17 @@
         android:orientation="vertical"
         android:padding="10dip"
         >
-    <ListView android:id="@+id/ble_client_tests"
-            android:layout_height="fill_parent"
+    <include android:id="@+id/pass_fail_buttons"
             android:layout_width="match_parent"
-            android:padding="10dip"
-            />
-
-    <include android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_alignParentBottom="true"
             layout="@layout/pass_fail_buttons"
             />
-</RelativeLayout>
\ No newline at end of file
+    <ListView android:id="@+id/ble_server_tests"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+            android:layout_above="@id/pass_fail_buttons"
+            android:layout_alignParentTop="true"
+            android:padding="10dip"
+            />
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_notify_characteristic.xml b/apps/CtsVerifier/res/layout/ble_notify_characteristic.xml
deleted file mode 100644
index 786918a..0000000
--- a/apps/CtsVerifier/res/layout/ble_notify_characteristic.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:padding="10dip"
-        >
-    <RelativeLayout android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            >
-        <Button android:id="@+id/ble_notify"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_centerHorizontal="true"
-                android:text="@string/ble_begin_notification"
-                android:padding="10dip"
-                />
-        <TextView android:id="@+id/ble_notify_text"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_centerHorizontal="true"
-                android:layout_below="@id/ble_notify"
-                android:textSize="20sp"
-                android:padding="10dip"
-                />
-    </RelativeLayout>
-
-    <include android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            layout="@layout/pass_fail_buttons"
-            />
-</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/ble_read_rssi.xml b/apps/CtsVerifier/res/layout/ble_read_rssi.xml
deleted file mode 100644
index 8aa3193..0000000
--- a/apps/CtsVerifier/res/layout/ble_read_rssi.xml
+++ /dev/null
@@ -1,49 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:padding="10dip"
-        >
-    <RelativeLayout android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            >
-        <Button android:id="@+id/ble_read_rssi"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_centerHorizontal="true"
-                android:text="@string/ble_read_rssi"
-                android:padding="10dip"
-                />
-        <TextView android:id="@+id/ble_rssi_text"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_centerHorizontal="true"
-                android:layout_below="@id/ble_read_rssi"
-                android:textSize="20sp"
-                android:padding="10dip"
-                />
-    </RelativeLayout>
-
-    <include android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            layout="@layout/pass_fail_buttons"
-            />
-</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/ble_reliable_write.xml b/apps/CtsVerifier/res/layout/ble_reliable_write.xml
deleted file mode 100644
index 7db78ff..0000000
--- a/apps/CtsVerifier/res/layout/ble_reliable_write.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:orientation="vertical"
-        android:padding="10dip"
-        >
-    <LinearLayout android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            >
-        <EditText android:id="@+id/write_text"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:hint="@string/ble_write_hint"
-                android:padding="5dip"
-                />
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                >
-            <Button android:id="@+id/ble_begin"
-                    android:layout_width="0dp"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:text="@string/ble_begin_write"
-                    />
-            <Button android:id="@+id/ble_write"
-                    android:layout_width="0dp"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:text="@string/ble_write"
-                    />
-            <Button android:id="@+id/ble_execute"
-                    android:layout_width="0dp"
-                    android:layout_height="wrap_content"
-                    android:layout_weight="1"
-                    android:text="@string/ble_execute_write"
-                    />
-        </LinearLayout>
-    </LinearLayout>
-
-    <include android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            layout="@layout/pass_fail_buttons"
-            />
-</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/ble_scanner_hardware_scan_filter.xml b/apps/CtsVerifier/res/layout/ble_scanner_hardware_scan_filter.xml
index f356ded..dabd640 100644
--- a/apps/CtsVerifier/res/layout/ble_scanner_hardware_scan_filter.xml
+++ b/apps/CtsVerifier/res/layout/ble_scanner_hardware_scan_filter.xml
@@ -17,40 +17,41 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:orientation="vertical"
-        android:padding="10dip"
-        >
-    <TextView android:text="@string/ble_scanner_scan_filter_instruction"
+        android:padding="10dip">
+    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-    />
-    <LinearLayout android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_centerInParent="true"
-            >
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                >
-            <Button android:id="@+id/ble_scan_with_filter"
+            android:layout_height="wrap_content">
+        <LinearLayout android:orientation="vertical"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content">
+            <TextView android:text="@string/ble_scanner_scan_filter_instruction"
                     android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"/>
+            <LinearLayout android:orientation="vertical"
+                    android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:text="@string/ble_scan_with_filter"
-                    />
-            <Button android:id="@+id/ble_scan_without_filter"
-                    android:layout_width="wrap_content"
+                    android:layout_centerInParent="true">
+                <LinearLayout android:orientation="vertical"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content">
+                    <Button android:id="@+id/ble_scan_with_filter"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="@string/ble_scan_with_filter"/>
+                    <Button android:id="@+id/ble_scan_without_filter"
+                            android:layout_width="wrap_content"
+                            android:layout_height="wrap_content"
+                            android:text="@string/ble_scan_without_filter"/>
+                </LinearLayout>
+                <ListView android:id="@+id/ble_scan_result_list"
+                        android:layout_height="wrap_content"
+                        android:layout_width="match_parent">
+                </ListView>
+            </LinearLayout>
+            <include android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-                    android:text="@string/ble_scan_without_filter"
-                    />
+                    android:layout_alignParentBottom="true"
+                    layout="@layout/pass_fail_buttons"/>
         </LinearLayout>
-        <ListView android:id="@+id/ble_scan_result_list"
-                android:layout_height="wrap_content"
-                android:layout_width="match_parent">
-        </ListView>
-    </LinearLayout>
-    <include android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            layout="@layout/pass_fail_buttons"
-    />
+    </ScrollView>
 </RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_scanner_power_level.xml b/apps/CtsVerifier/res/layout/ble_scanner_power_level.xml
index b240db6..c24dbb4 100644
--- a/apps/CtsVerifier/res/layout/ble_scanner_power_level.xml
+++ b/apps/CtsVerifier/res/layout/ble_scanner_power_level.xml
@@ -19,154 +19,139 @@
         android:orientation="vertical"
         android:padding="10dip"
         >
-    <TextView android:text="@string/ble_scanner_power_level_instruction"
+    <ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
             android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:id="@+id/ble_scanner_power_level_instruction"
-    />
-    <LinearLayout android:orientation="vertical"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_below="@+id/ble_scanner_power_level_instruction"
-            android:layout_centerInParent="true"
-            android:padding="10dp"
-            >
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_centerInParent="true"
-                >
-            <TextView android:text="@string/ble_ultra_low"
-                  android:layout_width="100dp"
-                  android:layout_height="wrap_content"
-            />
-            <TextView android:id="@+id/ble_ultra_low_mac"
-                  android:layout_width="200dp"
-                  android:layout_height="wrap_content"
-            />
-            <TextView android:id="@+id/ble_ultra_low_rssi"
-                  android:layout_width="100dp"
-                  android:layout_height="wrap_content"
-            />
-        </LinearLayout>
-        <LinearLayout android:orientation="horizontal"
+            android:layout_height="wrap_content">
+        <LinearLayout android:orientation="vertical"
                 android:layout_width="match_parent"
                 android:layout_height="wrap_content">
-            <TextView android:layout_width="100dp"
-                  android:layout_height="wrap_content"/>
-            <TextView android:id="@+id/ble_ultra_low_count"
-                    android:layout_width="100dp"
+            <TextView android:text="@string/ble_scanner_power_level_instruction"
+                    android:layout_width="wrap_content"
                     android:layout_height="wrap_content"
+                    android:id="@+id/ble_scanner_power_level_instruction"
             />
-            <TextView android:id="@+id/ble_ultra_low_set_power"
-                    android:layout_width="100dp"
+            <HorizontalScrollView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content">
+                <LinearLayout android:orientation="vertical"
+                        android:layout_width="match_parent"
+                        android:layout_height="wrap_content"
+                        android:layout_below="@+id/ble_scanner_power_level_instruction"
+                        android:layout_centerInParent="true"
+                        android:padding="10dp">
+                    <LinearLayout android:orientation="horizontal"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_centerInParent="true">
+                        <TextView android:text="@string/ble_ultra_low"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_ultra_low_mac"
+                                android:layout_width="200dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_ultra_low_rssi"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                    </LinearLayout>
+                    <LinearLayout android:orientation="horizontal"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content">
+                        <TextView android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_ultra_low_count"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_ultra_low_set_power"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                    </LinearLayout>
+                    <LinearLayout android:orientation="horizontal"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_centerInParent="true">
+                        <TextView android:text="@string/ble_low"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_low_mac"
+                                android:layout_width="200dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_low_rssi"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                    </LinearLayout>
+                    <LinearLayout android:orientation="horizontal"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content">
+                        <TextView android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_low_count"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_low_set_power"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                    </LinearLayout>
+                    <LinearLayout android:orientation="horizontal"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_centerInParent="true">
+                        <TextView android:text="@string/ble_medium"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_medium_mac"
+                                android:layout_width="200dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_medium_rssi"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                    </LinearLayout>
+                    <LinearLayout android:orientation="horizontal"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content">
+                        <TextView android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_medium_count"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_medium_set_power"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                    </LinearLayout>
+                    <LinearLayout android:orientation="horizontal"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content"
+                            android:layout_centerInParent="true">
+                        <TextView android:text="@string/ble_high"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_high_mac"
+                                android:layout_width="200dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_high_rssi"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                    </LinearLayout>
+                    <LinearLayout android:orientation="horizontal"
+                            android:layout_width="match_parent"
+                            android:layout_height="wrap_content">
+                        <TextView android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_high_count"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                        <TextView android:id="@+id/ble_high_set_power"
+                                android:layout_width="100dp"
+                                android:layout_height="wrap_content"/>
+                    </LinearLayout>
+                </LinearLayout>
+            </HorizontalScrollView>
+            <TextView android:id="@+id/ble_timer"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+            <include android:layout_width="match_parent"
                     android:layout_height="wrap_content"
-            />
+                    android:layout_alignParentBottom="true"
+                    layout="@layout/pass_fail_buttons"/>
         </LinearLayout>
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_centerInParent="true"
-                >
-            <TextView android:text="@string/ble_low"
-                  android:layout_width="100dp"
-                  android:layout_height="wrap_content"
-            />
-            <TextView android:id="@+id/ble_low_mac"
-                  android:layout_width="200dp"
-                  android:layout_height="wrap_content"
-            />
-            <TextView android:id="@+id/ble_low_rssi"
-                  android:layout_width="100dp"
-                  android:layout_height="wrap_content"
-            />
-        </LinearLayout>
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-            <TextView android:layout_width="100dp"
-                  android:layout_height="wrap_content"/>
-            <TextView android:id="@+id/ble_low_count"
-                    android:layout_width="100dp"
-                    android:layout_height="wrap_content"
-            />
-            <TextView android:id="@+id/ble_low_set_power"
-                    android:layout_width="100dp"
-                    android:layout_height="wrap_content"
-            />
-        </LinearLayout>
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_centerInParent="true"
-                >
-            <TextView android:text="@string/ble_medium"
-                  android:layout_width="100dp"
-                  android:layout_height="wrap_content"
-            />
-            <TextView android:id="@+id/ble_medium_mac"
-                  android:layout_width="200dp"
-                  android:layout_height="wrap_content"
-            />
-            <TextView android:id="@+id/ble_medium_rssi"
-                  android:layout_width="100dp"
-                  android:layout_height="wrap_content"
-            />
-        </LinearLayout>
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-            <TextView android:layout_width="100dp"
-                  android:layout_height="wrap_content"/>
-            <TextView android:id="@+id/ble_medium_count"
-                    android:layout_width="100dp"
-                    android:layout_height="wrap_content"
-            />
-            <TextView android:id="@+id/ble_medium_set_power"
-                    android:layout_width="100dp"
-                    android:layout_height="wrap_content"
-            />
-        </LinearLayout>
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content"
-                android:layout_centerInParent="true"
-                >
-            <TextView android:text="@string/ble_high"
-                  android:layout_width="100dp"
-                  android:layout_height="wrap_content"
-            />
-            <TextView android:id="@+id/ble_high_mac"
-                  android:layout_width="200dp"
-                  android:layout_height="wrap_content"
-            />
-            <TextView android:id="@+id/ble_high_rssi"
-                  android:layout_width="100dp"
-                  android:layout_height="wrap_content"
-            />
-        </LinearLayout>
-        <LinearLayout android:orientation="horizontal"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-            <TextView android:layout_width="100dp"
-                  android:layout_height="wrap_content"/>
-            <TextView android:id="@+id/ble_high_count"
-                    android:layout_width="100dp"
-                    android:layout_height="wrap_content"
-            />
-            <TextView android:id="@+id/ble_high_set_power"
-                    android:layout_width="100dp"
-                    android:layout_height="wrap_content"
-            />
-        </LinearLayout>
-        <TextView android:id="@+id/ble_timer"
-                android:layout_width="fill_parent"
-                android:layout_height="wrap_content" />
-    </LinearLayout>
-
-    <include android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentBottom="true"
-            layout="@layout/pass_fail_buttons"
-            />
+    </ScrollView>
 </RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_server_start.xml b/apps/CtsVerifier/res/layout/ble_server_start.xml
index 9ce714d..c377ca1 100644
--- a/apps/CtsVerifier/res/layout/ble_server_start.xml
+++ b/apps/CtsVerifier/res/layout/ble_server_start.xml
@@ -19,7 +19,7 @@
         android:orientation="vertical"
         android:padding="10dip"
         >
-    <include android:id="@+id/pass_fail_buttons" 
+    <include android:id="@+id/pass_fail_buttons"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_alignParentBottom="true"
@@ -32,4 +32,4 @@
             android:layout_alignParentTop="true"
             android:padding="10dip"
             />
-</RelativeLayout>
\ No newline at end of file
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/ble_server_start_item.xml b/apps/CtsVerifier/res/layout/ble_test_item.xml
similarity index 100%
rename from apps/CtsVerifier/res/layout/ble_server_start_item.xml
rename to apps/CtsVerifier/res/layout/ble_test_item.xml
diff --git a/apps/CtsVerifier/res/layout/bt_device_picker.xml b/apps/CtsVerifier/res/layout/bt_device_picker.xml
index ecca0e5..48a4b43 100644
--- a/apps/CtsVerifier/res/layout/bt_device_picker.xml
+++ b/apps/CtsVerifier/res/layout/bt_device_picker.xml
@@ -19,6 +19,13 @@
         android:orientation="vertical"
         >
 
+    <ProgressBar android:id="@+id/bt_progress_bar"
+            android:indeterminate="true"
+            android:layout_height="4dp"
+            android:layout_width="match_parent"
+            style="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
+            />
+
     <TextView android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/bt_paired_devices"
diff --git a/apps/CtsVerifier/res/layout/bt_messages.xml b/apps/CtsVerifier/res/layout/bt_messages.xml
index cb46811..1504431 100644
--- a/apps/CtsVerifier/res/layout/bt_messages.xml
+++ b/apps/CtsVerifier/res/layout/bt_messages.xml
@@ -18,6 +18,14 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         >
+
+    <ProgressBar android:id="@+id/bt_progress_bar"
+        android:indeterminate="true"
+        android:layout_height="4dp"
+        android:layout_width="match_parent"
+        style="@android:style/Widget.DeviceDefault.ProgressBar.Horizontal"
+        />
+
     <TextView android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:text="@string/bt_sent_messages"
diff --git a/apps/CtsVerifier/res/layout/byod_custom_view.xml b/apps/CtsVerifier/res/layout/byod_custom_view.xml
new file mode 100644
index 0000000..00c9ad9
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/byod_custom_view.xml
@@ -0,0 +1,38 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:orientation="vertical">
+    <ScrollView android:id="@+id/scrollView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingTop="2dip"
+            android:paddingBottom="12dip"
+            android:paddingStart="14dip"
+            android:paddingEnd="10dip"
+            android:overScrollMode="ifContentScrolls">
+        <TextView android:id="@+id/message"
+                style="@style/InstructionsSmallFont"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content" />
+    </ScrollView>
+
+    <ImageView android:id="@+id/sample_icon"
+            android:layout_width="56dip"
+            android:layout_height="56dip"
+            android:layout_gravity="center_horizontal" />
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/ca_boot_notify.xml b/apps/CtsVerifier/res/layout/ca_boot_notify.xml
index e9309d4..0ceece1 100644
--- a/apps/CtsVerifier/res/layout/ca_boot_notify.xml
+++ b/apps/CtsVerifier/res/layout/ca_boot_notify.xml
@@ -14,56 +14,60 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-  android:orientation="vertical" android:layout_width="fill_parent"
-  android:layout_height="fill_parent">
-
-  <ScrollView
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:layout_alignParentTop="true" >
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
+      android:orientation="vertical" android:layout_width="fill_parent"
+      android:layout_height="fill_parent">
 
-    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-      android:orientation="vertical"
-      android:layout_width="fill_parent"
-      android:layout_height="wrap_content">
+      <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_alignParentTop="true" >
 
-      <TextView
-          android:id="@+id/check_cert_desc"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:text="@string/caboot_check_cert_installed"/>
+        <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+          android:orientation="vertical"
+          android:layout_width="fill_parent"
+          android:layout_height="wrap_content">
 
-      <Button android:id="@+id/check_creds"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:text="@string/caboot_check_creds" />
+          <TextView
+              android:id="@+id/check_cert_desc"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/caboot_check_cert_installed"/>
 
-      <TextView
-          android:id="@+id/need_to_install_cert"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:text="@string/caboot_if_not_installed"/>
+          <Button android:id="@+id/check_creds"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/caboot_check_creds" />
 
-      <Button android:id="@+id/install"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:text="@string/caboot_install_cert" />
+          <TextView
+              android:id="@+id/need_to_install_cert"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/caboot_if_not_installed"/>
 
-      <TextView
-          android:id="@+id/reboot"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:text="@string/caboot_reboot_desc"/>
+          <Button android:id="@+id/install"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/caboot_install_cert" />
 
-      <TextView
-          android:id="@+id/after_reboot"
-          android:layout_width="wrap_content"
-          android:layout_height="wrap_content"
-          android:text="@string/caboot_after_boot"/>
+          <TextView
+              android:id="@+id/reboot"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/caboot_reboot_desc"/>
 
-      <include layout="@layout/pass_fail_buttons" />
+          <TextView
+              android:id="@+id/after_reboot"
+              android:layout_width="wrap_content"
+              android:layout_height="wrap_content"
+              android:text="@string/caboot_after_boot"/>
+
+          <include layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+      </ScrollView>
     </LinearLayout>
-  </ScrollView>
-</LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/ca_main.xml b/apps/CtsVerifier/res/layout/ca_main.xml
index 467ed01..274430d 100644
--- a/apps/CtsVerifier/res/layout/ca_main.xml
+++ b/apps/CtsVerifier/res/layout/ca_main.xml
@@ -14,65 +14,68 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-  android:orientation="vertical" android:layout_width="fill_parent"
-  android:layout_height="fill_parent">
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
+      android:orientation="vertical" android:layout_width="fill_parent"
+      android:layout_height="fill_parent">
 
 
-  <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="horizontal" android:layout_width="fill_parent"
-    android:layout_height="wrap_content">
-    <!--Button android:id="@+id/focusmodesbutton" android:layout_width="0px"
-      android:layout_height="wrap_content" android:text="@string/ca_focus_modes_label"
-      android:layout_weight="1" /-->
-    <Button android:id="@+id/findcheckerboardbutton" android:layout_width="0px"
-      android:layout_height="wrap_content" android:text="@string/ca_find_checkerboard_label"
-      android:layout_weight="1" />
+      <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:orientation="horizontal" android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
+        <!--Button android:id="@+id/focusmodesbutton" android:layout_width="0px"
+          android:layout_height="wrap_content" android:text="@string/ca_focus_modes_label"
+          android:layout_weight="1" /-->
+        <Button android:id="@+id/findcheckerboardbutton" android:layout_width="0px"
+          android:layout_height="wrap_content" android:text="@string/ca_find_checkerboard_label"
+          android:layout_weight="1" />
 
-    <Button android:id="@+id/meteringbutton" android:layout_width="0px"
-      android:layout_height="wrap_content" android:text="@string/ca_metering_label"
-      android:layout_weight="1" />
+        <Button android:id="@+id/meteringbutton" android:layout_width="0px"
+          android:layout_height="wrap_content" android:text="@string/ca_metering_label"
+          android:layout_weight="1" />
 
-    <Button android:id="@+id/exposurecompensationbutton" android:layout_width="0px"
-      android:layout_height="wrap_content" android:text="@string/ca_exposure_test_label"
-      android:layout_weight="1"/>
+        <Button android:id="@+id/exposurecompensationbutton" android:layout_width="0px"
+          android:layout_height="wrap_content" android:text="@string/ca_exposure_test_label"
+          android:layout_weight="1"/>
 
-    <Button android:id="@+id/whitebalancebutton" android:layout_width="0px"
-      android:layout_height="wrap_content" android:text="@string/ca_wb_test_label"
-      android:layout_weight="1" />
+        <Button android:id="@+id/whitebalancebutton" android:layout_width="0px"
+          android:layout_height="wrap_content" android:text="@string/ca_wb_test_label"
+          android:layout_weight="1" />
 
-    <Button android:id="@+id/lockbutton" android:layout_width="0px"
-      android:layout_height="wrap_content" android:text="@string/ca_lock_test_label"
-      android:layout_weight="1" />
-  </LinearLayout>
+        <Button android:id="@+id/lockbutton" android:layout_width="0px"
+          android:layout_height="wrap_content" android:text="@string/ca_lock_test_label"
+          android:layout_weight="1" />
+      </LinearLayout>
 
-  <LinearLayout android:orientation="horizontal"
-    android:layout_width="fill_parent" android:layout_height="0px"
-    android:layout_weight="1">
+      <LinearLayout android:orientation="horizontal"
+        android:layout_width="fill_parent" android:layout_height="0px"
+        android:layout_weight="1">
 
-    <SurfaceView android:id="@+id/cameraview" android:layout_height="fill_parent"
-      android:layout_width="wrap_content"
-      android:layout_weight="0" />
+        <SurfaceView android:id="@+id/cameraview" android:layout_height="fill_parent"
+          android:layout_width="wrap_content"
+          android:layout_weight="0" />
 
-    <LinearLayout android:orientation="vertical"
-      android:layout_width="fill_parent" android:layout_height="match_parent"
-      android:layout_weight="1">
+        <LinearLayout android:orientation="vertical"
+          android:layout_width="fill_parent" android:layout_height="match_parent"
+          android:layout_weight="1">
 
-       <ListView android:id="@+id/ca_tests"
+           <ListView android:id="@+id/ca_tests"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_weight="1"
+                android:layout_marginLeft="10px"/>
+
+          <ImageView android:id="@+id/resultview" android:layout_height="wrap_content"
             android:layout_width="fill_parent"
-            android:layout_height="wrap_content"
-            android:layout_weight="1"
-            android:layout_marginLeft="10px"/>
+            android:layout_weight="1" />
+        </LinearLayout>
 
-      <ImageView android:id="@+id/resultview" android:layout_height="wrap_content"
-        android:layout_width="fill_parent"
-        android:layout_weight="1" />
+      </LinearLayout>
+
+      <include layout="@layout/pass_fail_buttons" />
+
     </LinearLayout>
-
-  </LinearLayout>
-
-  <include layout="@layout/pass_fail_buttons" />
-
-</LinearLayout>
-
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/cainstallnotify_main.xml b/apps/CtsVerifier/res/layout/cainstallnotify_main.xml
index 16882bd..6cb6160 100644
--- a/apps/CtsVerifier/res/layout/cainstallnotify_main.xml
+++ b/apps/CtsVerifier/res/layout/cainstallnotify_main.xml
@@ -14,32 +14,37 @@
      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.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:padding="10dip" >
-
-    <ScrollView
-        android:id="@+id/ca_notify_test_scroller"
+    android:layout_height="match_parent">
+    <LinearLayout xmlns:app="http://schemas.android.com/apk/res-auto"
         android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1"
+        android:layout_height="match_parent"
         android:orientation="vertical"
         android:padding="10dip" >
 
-        <LinearLayout
-            android:id="@+id/ca_notify_test_items"
+        <ScrollView
+            android:id="@+id/ca_notify_test_scroller"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:padding="10dip" >
+
+            <LinearLayout
+                android:id="@+id/ca_notify_test_items"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical" >
+            </LinearLayout>
+        </ScrollView>
+
+        <include
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:orientation="vertical" >
-        </LinearLayout>
-    </ScrollView>
+            android:layout_weight="0"
+            layout="@layout/pass_fail_buttons" />
 
-    <include
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_weight="0"
-        layout="@layout/pass_fail_buttons" />
-
-</LinearLayout>
\ No newline at end of file
+    </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/fs_info.xml b/apps/CtsVerifier/res/layout/fs_info.xml
index 3fe6815..ea02fee 100644
--- a/apps/CtsVerifier/res/layout/fs_info.xml
+++ b/apps/CtsVerifier/res/layout/fs_info.xml
@@ -13,62 +13,44 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="fill_parent" android:layout_height="wrap_content">
+<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent" android:layout_height="fill_parent">
+    <GridLayout
+        android:columnCount="2"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content">
 
-    <ImageView android:id="@+id/fs_legend_good_image"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:src="@drawable/fs_good"
-        android:layout_alignParentTop="true"
-        android:layout_alignParentLeft="true" />
-    <TextView android:id="@+id/fs_legend_good_text"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/fs_legend_good"
-        android:layout_toRightOf="@id/fs_legend_good_image"
-        android:layout_alignTop="@id/fs_legend_good_image"
-        android:layout_marginLeft="3dip" />
+        <ImageView android:id="@+id/fs_legend_good_image"
+            android:src="@drawable/fs_good"
+            android:layout_gravity="top|left" />
+        <TextView android:id="@+id/fs_legend_good_text"
+            android:text="@string/fs_legend_good"
+            android:layout_marginLeft="3dip"
+            android:layout_gravity="top|left" />
 
-    <ImageView android:id="@+id/fs_legend_indeterminate_image"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:src="@drawable/fs_indeterminate"
-        android:layout_alignParentLeft="true"
-        android:layout_below="@id/fs_legend_good_image" />
-    <TextView android:id="@+id/fs_legend_indeterminate_text"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/fs_legend_indeterminate"
-        android:layout_toRightOf="@id/fs_legend_indeterminate_image"
-        android:layout_alignTop="@id/fs_legend_indeterminate_image"
-        android:layout_marginLeft="3dip" />
+        <ImageView android:id="@+id/fs_legend_indeterminate_image"
+            android:src="@drawable/fs_indeterminate"
+            android:layout_gravity="top|left" />
+        <TextView android:id="@+id/fs_legend_indeterminate_text"
+            android:text="@string/fs_legend_indeterminate"
+            android:layout_marginLeft="3dip"
+            android:layout_gravity="top|left" />
 
-    <ImageView android:id="@+id/fs_legend_warning_image"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:src="@drawable/fs_warning"
-        android:layout_alignParentLeft="true"
-        android:layout_below="@id/fs_legend_indeterminate_image" />
-    <TextView android:id="@+id/fs_legend_warning_text"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/fs_legend_warning"
-        android:layout_toRightOf="@id/fs_legend_warning_image"
-        android:layout_alignTop="@id/fs_legend_warning_image"
-        android:layout_marginLeft="3dip" />
+        <ImageView android:id="@+id/fs_legend_warning_image"
+            android:src="@drawable/fs_warning"
+            android:layout_gravity="top|left" />
+        <TextView android:id="@+id/fs_legend_warning_text"
+            android:text="@string/fs_legend_warning"
+            android:layout_marginLeft="3dip"
+            android:layout_gravity="top|left" />
 
-    <ImageView android:id="@+id/fs_legend_error_image"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:src="@drawable/fs_error"
-        android:layout_alignParentLeft="true"
-        android:layout_below="@id/fs_legend_warning_image" />
-    <TextView android:id="@+id/fs_legend_error_text"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/fs_legend_error"
-        android:layout_toRightOf="@id/fs_legend_error_image"
-        android:layout_alignTop="@id/fs_legend_error_image"
-        android:layout_marginLeft="3dip" />
-</RelativeLayout>
+        <ImageView android:id="@+id/fs_legend_error_image"
+            android:src="@drawable/fs_error"
+            android:layout_gravity="top|left" />
+        <TextView android:id="@+id/fs_legend_error_text"
+            android:text="@string/fs_legend_error"
+            android:layout_marginLeft="3dip"
+            android:layout_gravity="top|left" />
+    </GridLayout>
+</ScrollView>
diff --git a/apps/CtsVerifier/res/layout/fs_main.xml b/apps/CtsVerifier/res/layout/fs_main.xml
index 7473f0f..8a78c81 100644
--- a/apps/CtsVerifier/res/layout/fs_main.xml
+++ b/apps/CtsVerifier/res/layout/fs_main.xml
@@ -13,29 +13,34 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-         android:orientation="vertical"
-         android:layout_width="match_parent"
-         android:layout_height="match_parent">
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
+             android:orientation="vertical"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent">
 
-     <TextView android:id="@+id/fs_warnings"
-               android:layout_width="wrap_content"
-               android:layout_height="wrap_content"
-               android:text="@string/empty"/>
+         <TextView android:id="@+id/fs_warnings"
+                   android:layout_width="wrap_content"
+                   android:layout_height="wrap_content"
+                   android:text="@string/empty"/>
 
-     <ListView android:id="@id/android:list"
-               android:layout_width="match_parent"
-               android:layout_height="match_parent"
-               android:background="#000000"
-               android:layout_weight="1"
-               android:drawSelectorOnTop="false"/>
+         <ListView android:id="@id/android:list"
+                   android:layout_width="match_parent"
+                   android:layout_height="match_parent"
+                   android:background="#000000"
+                   android:layout_weight="1"
+                   android:drawSelectorOnTop="false"/>
 
-     <TextView android:id="@id/android:empty"
-               android:layout_width="match_parent"
-               android:layout_height="match_parent"
-               android:background="#000000"
-               android:text="@string/fs_no_data"/>
+         <TextView android:id="@id/android:empty"
+                   android:layout_width="match_parent"
+                   android:layout_height="match_parent"
+                   android:background="#000000"
+                   android:text="@string/fs_no_data"/>
 
-    <include layout="@layout/pass_fail_buttons" />
+        <include layout="@layout/pass_fail_buttons" />
 
-</LinearLayout>
+    </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/intent_driven_test.xml b/apps/CtsVerifier/res/layout/intent_driven_test.xml
index 00c1cf6..bd9e4ca 100644
--- a/apps/CtsVerifier/res/layout/intent_driven_test.xml
+++ b/apps/CtsVerifier/res/layout/intent_driven_test.xml
@@ -1,30 +1,36 @@
 <?xml version="1.0" encoding="utf-8"?>
 
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical">
-
-  <ScrollView
-      android:layout_width="match_parent"
-      android:layout_height="0dp"
-      android:layout_weight="1">
-    <TextView android:id="@+id/info"
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:textSize="18sp"
-        android:padding="5dp"
-        android:text="@string/dc_start_alarm_test_info"/>
-  </ScrollView>
+        android:layout_height="match_parent"
+        android:orientation="vertical">
 
-  <LinearLayout android:id="@+id/buttons"
-      android:orientation="horizontal"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"/>
+        <ScrollView
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1">
+            <TextView android:id="@+id/info"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:textSize="18sp"
+                android:padding="5dp"
+                android:text="@string/dc_start_alarm_test_info"/>
+        </ScrollView>
 
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-      <include layout="@layout/pass_fail_buttons"/>
-  </LinearLayout>
-</LinearLayout>
+        <LinearLayout android:id="@+id/buttons"
+            android:orientation="horizontal"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"/>
+
+        <LinearLayout
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content">
+
+            <include layout="@layout/pass_fail_buttons"/>
+        </LinearLayout>
+    </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/tests/tests/uirendering/res/layout/draw_activity_view.xml b/apps/CtsVerifier/res/layout/its_main.xml
similarity index 69%
copy from tests/tests/uirendering/res/layout/draw_activity_view.xml
copy to apps/CtsVerifier/res/layout/its_main.xml
index 54a72e3..2f5eade 100644
--- a/tests/tests/uirendering/res/layout/draw_activity_view.xml
+++ b/apps/CtsVerifier/res/layout/its_main.xml
@@ -13,5 +13,12 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"
+    >
 
-<android.uirendering.cts.CanvasClientView xmlns:android="http://schemas.android.com/apk/res/android"/>
+    <include layout="@layout/pass_fail_buttons" />
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/js_charging.xml b/apps/CtsVerifier/res/layout/js_charging.xml
index 4c0e552..8d9ed1d 100644
--- a/apps/CtsVerifier/res/layout/js_charging.xml
+++ b/apps/CtsVerifier/res/layout/js_charging.xml
@@ -1,67 +1,76 @@
 <?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.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
     android:layout_height="match_parent">
-    <TextView
+    <ScrollView app:layout_box="all"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/js_test_description"
-        android:layout_margin="@dimen/js_padding"/>
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_margin="@dimen/js_padding"
-        android:text="@string/js_charging_description_1"
-        android:textStyle="bold"/>
-    <Button
-        android:id="@+id/js_charging_start_test_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:text="@string/js_start_test_text"
-        android:onClick="startTest"
-        android:enabled="false"/>
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/js_test_description"
+                android:layout_margin="@dimen/js_padding"/>
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_margin="@dimen/js_padding"
+                android:text="@string/js_charging_description_1"
+                android:textStyle="bold"/>
+            <Button
+                android:id="@+id/js_charging_start_test_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:text="@string/js_start_test_text"
+                android:onClick="startTest"
+                android:enabled="false"/>
 
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/js_padding"
-        android:layout_marginBottom="@dimen/js_padding">
-        <ImageView
-            android:id="@+id/charging_off_test_image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/fs_indeterminate"
-            android:layout_marginRight="@dimen/js_padding"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/js_charging_off_test"
-            android:textSize="16dp"/>
-    </LinearLayout>
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_margin="@dimen/js_padding"
-        android:text="@string/js_charging_description_2"
-        android:textStyle="bold"/>
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/js_padding"
-        android:layout_marginBottom="@dimen/js_padding">
-        <ImageView
-            android:id="@+id/charging_on_test_image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/fs_indeterminate"
-            android:layout_marginRight="@dimen/js_padding"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/js_charging_on_test"
-            android:textSize="16dp"/>
-    </LinearLayout>
-    <include layout="@layout/pass_fail_buttons" />
-</LinearLayout>
\ No newline at end of file
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/js_padding"
+                android:layout_marginBottom="@dimen/js_padding">
+                <ImageView
+                    android:id="@+id/charging_off_test_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/fs_indeterminate"
+                    android:layout_marginRight="@dimen/js_padding"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/js_charging_off_test"
+                    android:textSize="16dp"/>
+            </LinearLayout>
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:layout_margin="@dimen/js_padding"
+                android:text="@string/js_charging_description_2"
+                android:textStyle="bold"/>
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/js_padding"
+                android:layout_marginBottom="@dimen/js_padding">
+                <ImageView
+                    android:id="@+id/charging_on_test_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/fs_indeterminate"
+                    android:layout_marginRight="@dimen/js_padding"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/js_charging_on_test"
+                    android:textSize="16dp"/>
+            </LinearLayout>
+            <include layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+    </ScrollView>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/js_connectivity.xml b/apps/CtsVerifier/res/layout/js_connectivity.xml
index 5208c18..b0e2824 100644
--- a/apps/CtsVerifier/res/layout/js_connectivity.xml
+++ b/apps/CtsVerifier/res/layout/js_connectivity.xml
@@ -1,83 +1,91 @@
 <?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.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
     android:layout_height="match_parent">
-    <TextView
+    <ScrollView app:layout_box="all"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/js_test_description"
-        android:layout_margin="@dimen/js_padding"/>
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:orientation="vertical" android:layout_width="match_parent"
+            android:layout_height="match_parent">
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/js_test_description"
+                android:layout_margin="@dimen/js_padding"/>
 
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/js_connectivity_description_1"
-        android:layout_margin="@dimen/js_padding"
-        android:textStyle="bold"/>
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/js_connectivity_description_1"
+                android:layout_margin="@dimen/js_padding"
+                android:textStyle="bold"/>
 
-    <Button
-        android:id="@+id/js_connectivity_start_test_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:text="@string/js_start_test_text"
-        android:onClick="startTest"
-        android:enabled="false"/>
+            <Button
+                android:id="@+id/js_connectivity_start_test_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:text="@string/js_start_test_text"
+                android:onClick="startTest"
+                android:enabled="false"/>
 
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/js_padding"
-        android:layout_marginBottom="@dimen/js_padding">
-        <ImageView
-            android:id="@+id/connectivity_off_test_unmetered_image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/fs_indeterminate"
-            android:layout_marginRight="@dimen/js_padding"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/js_unmetered_connectivity_test"
-            android:textSize="16dp"/>
-    </LinearLayout>
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/js_padding"
+                android:layout_marginBottom="@dimen/js_padding">
+                <ImageView
+                    android:id="@+id/connectivity_off_test_unmetered_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/fs_indeterminate"
+                    android:layout_marginRight="@dimen/js_padding"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/js_unmetered_connectivity_test"
+                    android:textSize="16dp"/>
+            </LinearLayout>
 
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/js_padding"
-        android:layout_marginBottom="@dimen/js_padding">
-        <ImageView
-            android:id="@+id/connectivity_off_test_any_connectivity_image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/fs_indeterminate"
-            android:layout_marginRight="@dimen/js_padding"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/js_any_connectivity_test"
-            android:textSize="16dp"/>
-    </LinearLayout>
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/js_padding"
+                android:layout_marginBottom="@dimen/js_padding">
+                <ImageView
+                    android:id="@+id/connectivity_off_test_any_connectivity_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/fs_indeterminate"
+                    android:layout_marginRight="@dimen/js_padding"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/js_any_connectivity_test"
+                    android:textSize="16dp"/>
+            </LinearLayout>
 
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/js_padding"
-        android:layout_marginBottom="@dimen/js_padding">
-        <ImageView
-            android:id="@+id/connectivity_off_test_no_connectivity_image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/fs_indeterminate"
-            android:layout_marginRight="@dimen/js_padding"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/js_no_connectivity_test"
-            android:textSize="16dp"/>
-    </LinearLayout>
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/js_padding"
+                android:layout_marginBottom="@dimen/js_padding">
+                <ImageView
+                    android:id="@+id/connectivity_off_test_no_connectivity_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/fs_indeterminate"
+                    android:layout_marginRight="@dimen/js_padding"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/js_no_connectivity_test"
+                    android:textSize="16dp"/>
+            </LinearLayout>
 
-    <include layout="@layout/pass_fail_buttons" />
-</LinearLayout>
\ No newline at end of file
+            <include layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+    </ScrollView>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/js_idle.xml b/apps/CtsVerifier/res/layout/js_idle.xml
index 90e55ec..4277173 100644
--- a/apps/CtsVerifier/res/layout/js_idle.xml
+++ b/apps/CtsVerifier/res/layout/js_idle.xml
@@ -1,63 +1,71 @@
 <?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.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
     android:layout_height="match_parent">
-
-    <TextView
+    <ScrollView app:layout_box="all"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/js_test_description"
-        android:layout_margin="@dimen/js_padding"/>
-    <TextView
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/js_idle_description_1"
-        android:layout_margin="@dimen/js_padding"
-        android:textStyle="bold"/>
+        android:layout_height="match_parent">
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/js_test_description"
+                android:layout_margin="@dimen/js_padding"/>
+            <TextView
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/js_idle_description_1"
+                android:layout_margin="@dimen/js_padding"
+                android:textStyle="bold"/>
 
-    <Button
-        android:id="@+id/js_idle_start_test_button"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center"
-        android:text="@string/js_start_test_text"
-        android:onClick="startTest"
-        android:enabled="false"/>
+            <Button
+                android:id="@+id/js_idle_start_test_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_gravity="center"
+                android:text="@string/js_start_test_text"
+                android:onClick="startTest"
+                android:enabled="false"/>
 
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/js_padding"
-        android:layout_marginBottom="@dimen/js_padding">
-        <ImageView
-            android:id="@+id/idle_off_test_image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/fs_indeterminate"
-            android:layout_marginRight="@dimen/js_padding"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/js_idle_item_idle_off"
-            android:textSize="16dp"/>
-    </LinearLayout>
-    <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_marginTop="@dimen/js_padding"
-        android:layout_marginBottom="@dimen/js_padding">
-        <ImageView
-            android:id="@+id/idle_on_test_image"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:src="@drawable/fs_indeterminate"
-            android:layout_marginRight="@dimen/js_padding"/>
-        <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/js_idle_item_idle_on"
-            android:textSize="16dp"/>
-    </LinearLayout>
-    <include layout="@layout/pass_fail_buttons" />
-</LinearLayout>
\ No newline at end of file
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/js_padding"
+                android:layout_marginBottom="@dimen/js_padding">
+                <ImageView
+                    android:id="@+id/idle_off_test_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/fs_indeterminate"
+                    android:layout_marginRight="@dimen/js_padding"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/js_idle_item_idle_off"
+                    android:textSize="16dp"/>
+            </LinearLayout>
+            <LinearLayout
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_marginTop="@dimen/js_padding"
+                android:layout_marginBottom="@dimen/js_padding">
+                <ImageView
+                    android:id="@+id/idle_on_test_image"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:src="@drawable/fs_indeterminate"
+                    android:layout_marginRight="@dimen/js_padding"/>
+                <TextView
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:text="@string/js_idle_item_idle_on"
+                    android:textSize="16dp"/>
+            </LinearLayout>
+            <include layout="@layout/pass_fail_buttons" />
+        </LinearLayout>
+    </ScrollView>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/location_mode_main.xml b/apps/CtsVerifier/res/layout/location_mode_main.xml
index fde6aba..1768434 100644
--- a/apps/CtsVerifier/res/layout/location_mode_main.xml
+++ b/apps/CtsVerifier/res/layout/location_mode_main.xml
@@ -14,32 +14,37 @@
      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.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:orientation="vertical"
-    android:padding="10dip" >
-
-    <ScrollView
-        android:id="@+id/test_scroller"
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
         android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1"
+        android:layout_height="match_parent"
         android:orientation="vertical"
         android:padding="10dip" >
 
-        <LinearLayout
-            android:id="@+id/test_items"
+        <ScrollView
+            android:id="@+id/test_scroller"
+            android:layout_width="match_parent"
+            android:layout_height="0dp"
+            android:layout_weight="1"
+            android:orientation="vertical"
+            android:padding="10dip" >
+
+            <LinearLayout
+                android:id="@+id/test_items"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical" >
+            </LinearLayout>
+        </ScrollView>
+
+        <include
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:orientation="vertical" >
-        </LinearLayout>
-    </ScrollView>
+            android:layout_weight="0"
+            layout="@layout/pass_fail_buttons" />
 
-    <include
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:layout_weight="0"
-        layout="@layout/pass_fail_buttons" />
-
-</LinearLayout>
+    </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/pa_main.xml b/apps/CtsVerifier/res/layout/pa_main.xml
index 76cb7d4..832af71 100644
--- a/apps/CtsVerifier/res/layout/pa_main.xml
+++ b/apps/CtsVerifier/res/layout/pa_main.xml
@@ -13,19 +13,24 @@
      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.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <include
-        android:id="@+id/pass_fail_buttons"
-        android:layout_gravity="top"
-        layout="@layout/pass_fail_buttons" />
-
-    <TextureView
-        android:id="@+id/texture_view"
+    android:layout_height="match_parent">
+    <RelativeLayout app:layout_box="all"
         android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_below="@id/pass_fail_buttons" />
+        android:layout_height="match_parent" >
 
-</RelativeLayout>
+        <include
+            android:id="@+id/pass_fail_buttons"
+            android:layout_gravity="top"
+            layout="@layout/pass_fail_buttons" />
+
+        <TextureView
+            android:id="@+id/texture_view"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:layout_below="@id/pass_fail_buttons" />
+
+    </RelativeLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/pass_fail_buttons.xml b/apps/CtsVerifier/res/layout/pass_fail_buttons.xml
index 5eec539..b269dcd 100644
--- a/apps/CtsVerifier/res/layout/pass_fail_buttons.xml
+++ b/apps/CtsVerifier/res/layout/pass_fail_buttons.xml
@@ -13,28 +13,31 @@
      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"
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
         android:layout_width="match_parent"
-        android:layout_height="wrap_content">
+        android:layout_height="wrap_content"
+        android:orientation="horizontal">
 
     <ImageButton android:id="@+id/pass_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_weight="1"            
+            android:layout_weight="1"
+            android:contentDescription="@string/pass_button_text"
             android:src="@drawable/fs_good"/>
-            
+
     <ImageButton android:id="@+id/info_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_weight="1"
+            android:contentDescription="@string/info_button_text"
             android:src="@drawable/fs_indeterminate"
             android:visibility="gone"/>
 
     <ImageButton android:id="@+id/fail_button"
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
-            android:layout_weight="1"            
+            android:layout_weight="1"
+            android:contentDescription="@string/fail_button_text"
             android:src="@drawable/fs_error"/>
-            
+
 </LinearLayout>
diff --git a/apps/CtsVerifier/res/layout/pass_fail_list.xml b/apps/CtsVerifier/res/layout/pass_fail_list.xml
index 0b247f4..cdd40e1 100644
--- a/apps/CtsVerifier/res/layout/pass_fail_list.xml
+++ b/apps/CtsVerifier/res/layout/pass_fail_list.xml
@@ -13,24 +13,30 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:orientation="vertical"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        >
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
 
-    <ListView android:id="@id/android:list"
+    <LinearLayout app:layout_box="all"
+            android:orientation="vertical"
             android:layout_width="match_parent"
             android:layout_height="match_parent"
-            android:layout_weight="1"
-            />
+            >
 
-    <TextView android:id="@id/android:empty"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            />
+        <ListView android:id="@id/android:list"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                />
 
-    <include layout="@layout/pass_fail_buttons" />
+        <TextView android:id="@id/android:empty"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_weight="1"
+                />
 
-</LinearLayout>
+        <include layout="@layout/pass_fail_buttons" />
+
+    </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/poa_main.xml b/apps/CtsVerifier/res/layout/poa_main.xml
index 578a6a6..41bade0 100644
--- a/apps/CtsVerifier/res/layout/poa_main.xml
+++ b/apps/CtsVerifier/res/layout/poa_main.xml
@@ -13,17 +13,22 @@
      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="fill_parent"
-    android:layout_height="fill_parent"
-    android:orientation="vertical" >
+<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <LinearLayout app:layout_box="all"
+        android:layout_width="fill_parent"
+        android:layout_height="fill_parent"
+        android:orientation="vertical" >
 
-    <include layout="@layout/pass_fail_buttons" />
+        <include layout="@layout/pass_fail_buttons" />
 
-    <TextView
-        android:id="@+id/poa_status_text"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:textAppearance="@style/InstructionsFont" />
+        <TextView
+            android:id="@+id/poa_status_text"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent"
+            android:textAppearance="@style/InstructionsFont" />
 
-</LinearLayout>
\ No newline at end of file
+    </LinearLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/pwa_widgets.xml b/apps/CtsVerifier/res/layout/pwa_widgets.xml
index 4bfcec6..537fc32 100644
--- a/apps/CtsVerifier/res/layout/pwa_widgets.xml
+++ b/apps/CtsVerifier/res/layout/pwa_widgets.xml
@@ -13,16 +13,19 @@
      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.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent" >
+    android:layout_height="match_parent">
 
      <TextureView
+         app:layout_box="all"
          android:id="@+id/texture_view"
          android:layout_width="match_parent"
          android:layout_height="match_parent" />
 
      <LinearLayout
+         app:layout_box="all"
          android:layout_width="fill_parent"
          android:layout_height="wrap_content"
          android:orientation="vertical" >
@@ -70,4 +73,4 @@
          </LinearLayout>
      </LinearLayout>
 
-</FrameLayout>
+</android.support.wearable.view.BoxInsetLayout>
diff --git a/apps/CtsVerifier/res/layout/screen_pinning.xml b/apps/CtsVerifier/res/layout/screen_pinning.xml
new file mode 100644
index 0000000..f1d7b65
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/screen_pinning.xml
@@ -0,0 +1,62 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent" >
+
+    <TextView
+        android:id="@+id/error_text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+
+    <ScrollView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentTop="true" >
+
+        <RelativeLayout
+            android:id="@+id/instructions_group"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical" >
+
+            <LinearLayout
+                android:id="@+id/instructions_list"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:orientation="vertical" >
+
+            </LinearLayout>
+
+            <Button
+                android:id="@+id/next_button"
+                android:layout_width="wrap_content"
+                android:layout_height="wrap_content"
+                android:layout_alignParentEnd="true"
+                android:layout_below="@id/instructions_list"
+                android:text="@string/next_button_text" />
+
+        </RelativeLayout>
+    </ScrollView>
+
+    <include
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentBottom="true"
+        layout="@layout/pass_fail_buttons" />
+
+</RelativeLayout>
\ No newline at end of file
diff --git a/apps/CtsVerifier/res/layout/test_list_footer.xml b/apps/CtsVerifier/res/layout/test_list_footer.xml
new file mode 100644
index 0000000..fdb8e43
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/test_list_footer.xml
@@ -0,0 +1,38 @@
+<?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
+  -->
+<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content">
+
+    <Button
+        android:id="@+id/clear"
+        android:text="@string/clear"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+    <Button
+        android:id="@+id/view"
+        android:text="@string/view"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+    <Button
+        android:id="@+id/export"
+        android:text="@string/export"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+</GridLayout>
diff --git a/apps/CtsVerifier/res/layout/tv_item.xml b/apps/CtsVerifier/res/layout/tv_item.xml
new file mode 100644
index 0000000..e7311f9
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/tv_item.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content" >
+
+    <ImageView
+        android:id="@+id/status"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_alignParentLeft="true"
+        android:layout_alignParentTop="true"
+        android:layout_marginTop="10dip"
+        android:contentDescription="@string/pass_button_text"
+        android:padding="10dip"
+        android:src="@drawable/fs_indeterminate" />
+
+    <TextView
+        android:id="@+id/instructions"
+        style="@style/InstructionsSmallFont"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_alignParentTop="true"
+        android:layout_toRightOf="@id/status" />
+
+    <Button
+        android:id="@+id/user_action_button"
+        android:enabled="false"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_alignParentRight="true"
+        android:layout_below="@id/instructions"
+        android:layout_marginLeft="20dip"
+        android:layout_marginRight="20dip"
+        android:layout_toRightOf="@id/status"
+        android:visibility="gone" />
+
+</RelativeLayout>
diff --git a/apps/CtsVerifier/res/layout/tv_overlay.xml b/apps/CtsVerifier/res/layout/tv_overlay.xml
new file mode 100644
index 0000000..8cd5dd8
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/tv_overlay.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<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/overlay_view_text"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:textSize="20sp"
+            android:text="@string/overlay_view_text">
+    </TextView>
+
+</LinearLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index cb35c3d..b92bca9 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -36,6 +36,7 @@
     <string name="test_category_features">Features</string>
     <string name="test_category_deskclock">Clock</string>
     <string name="test_category_jobscheduler">Job Scheduler</string>
+    <string name="test_category_tv">TV</string>
     <string name="test_category_other">Other</string>
     <string name="clear">Clear</string>
     <string name="test_results_cleared">Test results cleared.</string>
@@ -67,7 +68,10 @@
         \n\nFollow the instructions below to check that the data backup and restore works:
         \n\n1. Make sure backup and automatic restore are enabled in settings. Depending on the
         backup transport supported by the device you may need to do additional steps. For instance
-        you may need to set a Google account as the backup account for the device.
+        you may need to set a Google account as the backup account for the device. If you cannot
+        find the corresponding setting options on your device, run \"adb shell bmgr enable true\"
+        to enable the backup manager. You can check its status by executing \"adb shell bmgr
+        enabled\".
         \n\n2. Run the backup manager: adb shell bmgr run
         \n\n3. Uninstall the program: adb uninstall com.android.cts.verifier
         \n\n4. Reinstall the CTS Verifier and verify that the values are still the same.
@@ -184,14 +188,16 @@
     <!-- BLE client side strings -->
     <string name="ble_client_service_name">Bluetooth LE GATT Client Handler Service</string>
     <string name="ble_client_test_name">BLE Client Test</string>
-    <string name="ble_client_connect_name">1. BLE Client Connect</string>
-    <string name="ble_discover_service_name">2. BLE Discover Service</string>
-    <string name="ble_client_characteristic_name">3. BLE Read/Write Characteristic</string>
-    <string name="ble_reliable_write_name">4. BLE Reliable Write</string>
-    <string name="ble_notify_characteristic_name">5. BLE Notify Characteristic</string>
-    <string name="ble_client_descriptor_name">6. BLE Read/Write Descriptor</string>
-    <string name="ble_read_rssi_name">7. BLE Read RSSI</string>
-    <string name="ble_client_disconnect_name">8. BLE Client Disconnect</string>
+    <string name="ble_client_connect_name">BLE Client Connect</string>
+    <string name="ble_discover_service_name">BLE Discover Service</string>
+    <string name="ble_read_characteristic_name">BLE Read Characteristic</string>
+    <string name="ble_write_characteristic_name">BLE Write Characteristic</string>
+    <string name="ble_reliable_write_name">BLE Reliable Write</string>
+    <string name="ble_notify_characteristic_name">BLE Notify Characteristic</string>
+    <string name="ble_read_descriptor_name">BLE Read Descriptor</string>
+    <string name="ble_write_descriptor_name">BLE Write Descriptor</string>
+    <string name="ble_read_rssi_name">BLE Read RSSI</string>
+    <string name="ble_client_disconnect_name">BLE Client Disconnect</string>
     <string name="ble_client_test_info">The BLE test must be done simultaneously on two devices. This device is the client. All tests listed here must be done in order.</string>
     <string name="ble_client_send_connect_info">Type in the Bluetooth address of the remote device to connect to, and verify that the devices are connected.</string>
     <string name="ble_discover_service_info">Verify that the service is discovered when you press the "Discover Service" button.</string>
@@ -214,6 +220,7 @@
     <string name="ble_waiting_notification">Waiting on notification</string>
     <string name="ble_read_rssi">Read RSSI</string>
     <string name="ble_disconnect">Disconnect</string>
+    <string name="ble_test_text">TEST</string>
 
     <!-- BLE server side strings -->
     <string name="ble_server_service_name">Bluetooth LE GATT Server Handler Service</string>
@@ -263,6 +270,8 @@
     <string name="ble_scanner_scan_filter_instruction">Scan filter is to scan data with service UUID = 0x6666 only. If you scan without scan filter, data with service UUID = 0x5555 and 0x6666 will show up on screen.\nFor monsoon test:\n\tClick scan with filter, lock the screen, connect to monsoon. It will not wake up when advertiser is advertising unscannable data packets, but will show a peak in power usage when advertiser is advertising scannable data.\nFor logcat test:\n\tClick scan with filter, logcat the scanner. No data will be received by GattService when advertiser is advertising unscannable data.</string>
     <string name="ble_scan_with_filter">Scan with filter</string>
     <string name="ble_scan_without_filter">Scan without filter</string>
+    <string name="ble_scan_start">Start scan</string>
+    <string name="ble_scan_stop">Stop scan</string>
 
     <!-- Strings for FeatureSummaryActivity -->
     <string name="feature_summary">Hardware/Software Feature Summary</string>
@@ -434,6 +443,10 @@
     <string name="nfc_hce_payment_dynamic_aids_reader">Dynamic payment AIDs (Reader)</string>
     <string name="nfc_hce_payment_dynamic_aids_help">This test tries to register dynamic AIDs for a payment service.</string>
 
+    <string name="nfc_hce_large_num_aids_emulator">Large number of AIDs (Emulator)</string>
+    <string name="nfc_hce_large_num_aids_reader">Large number of AIDs (Reader)</string>
+    <string name="nfc_hce_large_num_aids_help">This test tries to register a large number of different AIDs, to make sure there are no limitations on the maximum amount of HCE apps on the device. Note that this test may take a few seconds to complete; please be patient.</string>
+
     <string name="nfc_hce_payment_prefix_aids_emulator">Payment prefix AIDs (Emulator)</string>
     <string name="nfc_hce_payment_prefix_aids_reader">Payment prefix AIDs (Reader)</string>
     <string name="nfc_hce_payment_prefix_aids_help">This test statically registers prefix AIDs for a payment service.</string>
@@ -766,6 +779,30 @@
     <string name="usb_test_passed">Received all expected messages. Pass button enabled!</string>
     <string name="usb_file_descriptor_error">Could not open file descriptor for USB accessory... try reconnecting and restarting the accessory?</string>
 
+    <!-- Strings for the Camera ITS test activity -->
+    <string name="camera_its_test">Camera ITS Test</string>
+    <string name="camera_its_test_info">
+        1. Connect your Android device to a computer with adb installed via a USB cable.
+        \n\n2. Setup the CameraITS test environment by following the setup instructions in the
+        README file found in the CameraITS directory included in the CTS Verifier bundle
+        (cd CameraITS; source build/envsetup.sh;).
+        \n\n3. Setup the test scene described in the CameraITS README file, and aim the camera
+        at it.
+        \n\n4. Run the full ITS test suite on all possible camera Ids.
+        (cd CameraITS; python tools/run_all_tests.py camera=[cameraId]).  Once all
+        of the tests have been run, the \'PASS\' button will be enabled if all of the tests have
+        succeeded.  Please note that these tests can take 20+ minutes to run.
+    </string>
+    <string name="no_camera_manager">
+        No camera manager exists!  This test device is in a bad state.
+    </string>
+    <string name="all_legacy_devices">
+        All cameras on this device are LEGACY mode only - ITS tests will only be applied to LIMITED
+        or better devices.  \'PASS\' button enabled.
+    </string>
+    <string name="its_test_passed">All Camera ITS tests passed.  Pass button enabled!</string>
+    <string name="its_test_failed">Some Camera ITS tests failed.</string>
+
     <!-- Strings for StreamingVideoActivity -->
     <string name="streaming_video">Streaming Video Quality Verifier</string>
     <string name="streaming_video_info">This is a test for assessing the quality of streaming videos.  Play each stream and verify that the video is smooth and in sync with the audio, and that there are no quality problems.</string>
@@ -938,6 +975,15 @@
         itself according to the current rotation of the device.</string>
 
     <string name="test_category_notifications">Notifications</string>
+    <string name="package_priority_test">Notification Package Priority Test</string>
+    <string name="package_priority_info">This test checks that the NotificationManagerService respects
+        user preferences about relative package priorities.
+    </string>
+    <string name="package_priority_bot">Verifying that the CTS Robot helper package is installed.</string>
+    <string name="package_priority_high">Find \"%s\" under \"App notifications\" in the \"Sound &amp; notifications\" settings panel, and mark it as having notification priority.</string>
+    <string name="package_priority_default">Find \"%s\" under \"App notifications\" in the \"Sound &amp; notifications\" settings panel, and make sure it has default priority.</string>
+    <string name="package_priority_user_order">Check that ranker respects user priorities.</string>
+
     <string name="attention_test">Notification Attention Management Test</string>
     <string name="attention_info">This test checks that the NotificationManagerService is
         respecting user preferences about notification ranking and filtering.
@@ -1058,6 +1104,20 @@
     <string name="widget_pass">Pass</string>
     <string name="widget_fail">Fail</string>
 
+    <string name="provisioning_byod_nonmarket_allow">Enable non-market apps</string>
+    <string name="provisioning_byod_nonmarket_allow_info">
+        This test verifies that non-market apps can be installed if permitted.\n
+        1. A package installation UI should appear.\n
+        2. Accept the package and verify that it installs.
+    </string>
+
+    <string name="provisioning_byod_nonmarket_deny">Disable non-market apps</string>
+    <string name="provisioning_byod_nonmarket_deny_info">
+        This test verifies that non-market apps cannot be installed unless permitted.\n
+        1. A package installation UI should appear.\n
+        2. Verify that the installation of the package is refused.
+    </string>
+
     <!-- Strings for DeskClock -->
     <string name="deskclock_tests">Alarms and Timers Tests</string>
     <string name="deskclock_tests_info">
@@ -1195,6 +1255,7 @@
         Start by pressing the button on screen and follow instructions to finish the managed provisioning process.
         If your device has not been encrypted before, it will be encrypted and rebooted.
         After the provisioning process completes, return to this page and carry out further verifications.
+        Note: the device will remain encrypted after the test which can only be disabled by factory reset.
     </string>
     <string name="provisioning_byod_start">Start BYOD provisioning flow</string>
     <string name="provisioning_byod_instructions">
@@ -1203,7 +1264,10 @@
         After reboot follow instructions in the notification area to complete the provisioning.\n
         2. After successful provisioning, you should be automatically redirected back to this page.
         Please press through the following verification steps.
-        Allow a few seconds after returning from provisioning, as the profile owner test should automatically pass.
+        Allow a few seconds after returning from provisioning, as the profile owner test should automatically pass.\n
+        \n
+        If the device is being encrypted during step 1, it will remain encrypted After this test.
+        The only way to disable the encryption is to factory reset the device.
     </string>
     <string name="provisioning_byod_profileowner">Profile owner installed</string>
     <string name="provisioning_byod_diskencryption">Full disk encryption enabled</string>
@@ -1223,6 +1287,13 @@
         \n
         Verify that you are prompted with the above choices and both options work as intended. Then mark this test accordingly.
     </string>
+    <string name="provisioning_byod_work_notification">Work notification is badged</string>
+    <string name="provisioning_byod_work_notification_instruction">
+        Please press the Go button to trigger a notification.\n
+        \n
+        Verify that the notification is badged (see sample badge below). Then mark this test accordingly.
+    </string>
+    <string name="provisioning_byod_work_notification_title">This is a work notification</string>
     <string name="provisioning_byod_profile_visible_instruction">
         Please press the Go button to open the Settings page.
         Navigate to Accounts and confirm that:\n
@@ -1246,7 +1317,7 @@
         Go to All Apps screen and scroll through it to confirm that:\n
         \n
         - A new set of work apps including CTS Verifier appear in the list.\n
-        - Work badge overlay appears on work app\'s icon.\n
+        - Work badge overlay appears on work app\'s icon (see example icon below).\n
         \n
         Then navigate back to this screen using Recents button.
     </string>
@@ -1279,7 +1350,7 @@
 
     <string name="js_charging_test">Charging Constraints</string>
     <string name="js_charging_instructions">Verify the behaviour of the JobScheduler API for when the device is on power and unplugged from power. Simply follow the on-screen instructions.</string>
-    <string name="js_charging_description_1">Unplug the phone in order to begin.</string>
+    <string name="js_charging_description_1">Unplug the device in order to begin.</string>
     <string name="js_charging_off_test">Device not charging will not execute a job with a charging constraint.</string>
     <string name="js_charging_on_test">Device when charging will execute a job with a charging constraint.</string>
     <string name="js_charging_description_2">After the above test has passed, plug the device back in to continue. If the above failed, you can simply fail this test.</string>
@@ -1291,7 +1362,103 @@
     <string name="js_any_connectivity_test">Device with no connectivity will not execute a job with an unmetered connectivity constraint.</string>
     <string name="js_no_connectivity_test">Device with no connectivity will still execute a job with no connectivity constraints.</string>
 
+    <!-- String for Live Channels app Tests -->
+
+    <string name="tv_input_discover_test">3rd-party TV input app discoverability test</string>
+    <string name="tv_input_discover_test_info">
+    This test verifies that the pre-loaded Live Channels app is invoked via intents and issues
+    appropriate calls to framework APIs, so that TV input apps work properly with the pre-loaded
+    Live Channels app.
+    </string>
+    <string name="tv_input_discover_test_go_to_setup">
+    Press the \"Launch Live Channels\" button, and set up the newly installed TV input:
+    \"CTS Verifier\".
+    </string>
+    <string name="tv_input_discover_test_verify_setup">
+    Setup activity must have been started.
+    </string>
+    <string name="tv_input_discover_test_tune_to_channel">
+    Press the \"Launch Live Channels\" button, and tune to the channel named \"Dummy\" from
+    \"CTS Verifier\" input. If necessary, configure the channel to be visible.
+    </string>
+    <string name="tv_input_discover_test_verify_tune">
+    Tune command must be called.
+    </string>
+    <string name="tv_input_discover_test_verify_overlay_view">
+    Overlay view must be shown. Verify that there is a text view displaying \"Overlay View Dummy Text\"
+    when you tune to the \"Dummy\" channel.
+    </string>
+
+    <string name="tv_parental_control_test">Live Channels app parental control test</string>
+    <string name="tv_parental_control_test_info">
+    This test verifies that the default Live Channels app invokes proper parental control APIs in
+    the framework.
+    </string>
+    <string name="tv_parental_control_test_turn_on_parental_control">
+    Press the \"Launch Live Channels\" button, and turn on the parental control. If it\'s on
+    already, turn it off and on again.
+    </string>
+    <string name="tv_parental_control_test_verify_receive_broadcast1">
+    TV input service must have received ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED broadcast.
+    </string>
+    <string name="tv_parental_control_test_block_tv_ma">
+    Press the \"Launch Live Channels\" button, and block \"Fake\" rating for \"CtsVerifier\" rating
+    system in the parental control settings. You may have to enable the rating system if it is
+    disabled by default. If it\'s already blocked, unblock, save, and then block again.
+    </string>
+    <string name="tv_parental_control_test_verify_receive_broadcast2">
+    TV input service must have received ACTION_BLOCKED_RATINGS_CHANGED broadcast.
+    </string>
+    <string name="tv_parental_control_test_block_unblock">
+    Press the \"Launch Live Channels\" button; verify that the channel is blocked visually.
+    Try unblock the screen by entering PIN; verify that it\'s unblocked visually.
+    </string>
+
+    <string name="tv_launch_tv_app">Launch Live Channels</string>
+    <string name="tv_channel_not_found">
+    CtsVerifier channel is not set up. Please set up before proceeding.
+    </string>
+
+    <string name="tv_multiple_tracks_test">Live Channels app multiple tracks / subtitle test</string>
+    <string name="tv_multiple_tracks_test_info">
+    This test verifies that the default Live Channels app invokes proper mulitple tracks / subtitle
+    APIs in the framework.
+    </string>
+    <string name="tv_multiple_tracks_test_select_subtitle">
+    Press the \"Launch Live Channels\" button. Verify that the closed caption is off by default.
+    Set closed caption to English.
+    </string>
+    <string name="tv_multiple_tracks_test_verify_set_caption_enabled">
+    Caption should be enabled.
+    </string>
+    <string name="tv_multiple_tracks_test_verify_select_subtitle">
+    The English subtitle track should be selected.
+    </string>
+    <string name="tv_multiple_tracks_test_select_audio">
+    Press the \"Launch Live Channels\" button. Verify that the audio track is English by default.
+    Select Spanish audio track.
+    </string>
+    <string name="tv_multiple_tracks_test_verify_select_audio">
+    The Spanish audio track should be selected.
+    </string>
+
+    <string name="overlay_view_text">Overlay View Dummy Text</string>
+    <string name="fake_rating">Fake</string>
+
     <!-- A list of fully-qualified test classes that should not be run. -->
     <string-array name="disabled_tests" />
 
+    <!-- Strings for screen pinning test -->
+    <string name="screen_pinning_test">Screen Pinning Test</string>
+    <string name="screen_pin_instructions">Pressing next will prompt you to enter screen pinning, allow this app to enter screen pinning.</string>
+    <string name="screen_pin_check_pinned">Press Next to verify the app is pinned.</string>
+    <string name="screen_pin_no_exit">Try to leave the app without unpinning the screen. Press next once you have verified you cannot leave.</string>
+    <string name="screen_pin_exit">Use interactions defined by your device to unpin such as long pressing the back and overview button, then press next.</string>
+    <string name="screen_pinning_done">All tests completed successfully.</string>
+
+    <string name="error_screen_no_longer_pinned">The screen was no longer pinned.</string>
+    <string name="error_screen_already_pinned">Cannot start the test with the screen already pinned.</string>
+    <string name="error_screen_pinning_did_not_start">Screen was not pinned.</string>
+    <string name="error_screen_pinning_did_not_exit">Screen was not unpinned.</string>
+    <string name="error_screen_pinning_couldnt_exit">Could not exit screen pinning through API.</string>
 </resources>
diff --git a/apps/CtsVerifier/res/xml/mock_content_rating_systems.xml b/apps/CtsVerifier/res/xml/mock_content_rating_systems.xml
new file mode 100644
index 0000000..245d7f5
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/mock_content_rating_systems.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
+-->
+
+<rating-system-definitions xmlns:android="http://schemas.android.com/apk/res/android"
+    android:versionCode="1">
+    <rating-system-definition android:name="CTS_VERIFIER"
+        android:title="CtsVerifier"
+        android:description="@string/app_name">
+        <rating-definition android:name="MOCK_FAKE"
+            android:title="Fake"
+            android:contentAgeHint="0"
+            android:description="@string/fake_rating" />
+    </rating-system-definition>
+</rating-system-definitions>
diff --git a/tests/tests/uirendering/res/layout/draw_activity_view.xml b/apps/CtsVerifier/res/xml/mock_tv_input_service.xml
similarity index 62%
copy from tests/tests/uirendering/res/layout/draw_activity_view.xml
copy to apps/CtsVerifier/res/xml/mock_tv_input_service.xml
index 54a72e3..1a2cf86 100644
--- a/tests/tests/uirendering/res/layout/draw_activity_view.xml
+++ b/apps/CtsVerifier/res/xml/mock_tv_input_service.xml
@@ -1,5 +1,5 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- 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.
@@ -14,4 +14,6 @@
      limitations under the License.
 -->
 
-<android.uirendering.cts.CanvasClientView xmlns:android="http://schemas.android.com/apk/res/android"/>
+<tv-input xmlns:android="http://schemas.android.com/apk/res/android"
+    android:setupActivity="com.android.cts.verifier.tv.MockTvInputSetupActivity"
+    android:settingsActivity="com.android.cts.verifier.tv.MockTvInputSettingsActivity" />
diff --git a/apps/CtsVerifier/src/android/support/wearable/view/BoxInsetLayout.java b/apps/CtsVerifier/src/android/support/wearable/view/BoxInsetLayout.java
index 95bac11..77d6a21 100644
--- a/apps/CtsVerifier/src/android/support/wearable/view/BoxInsetLayout.java
+++ b/apps/CtsVerifier/src/android/support/wearable/view/BoxInsetLayout.java
@@ -18,6 +18,8 @@
 
 import com.android.cts.verifier.R;
 
+import android.annotation.TargetApi;
+import android.os.Build;
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Rect;
@@ -38,6 +40,7 @@
  * The {@code layout_box} attribute is ignored on a device with a rectangular
  * screen.
  */
+@TargetApi(Build.VERSION_CODES.KITKAT_WATCH)
 public class BoxInsetLayout extends FrameLayout {
 
     private static float FACTOR = 0.146467f; //(1 - sqrt(2)/2)/2
@@ -81,10 +84,10 @@
             requestLayout();
         }
         mInsets.set(
-                insets.getSystemWindowInsetLeft(),
-                insets.getSystemWindowInsetTop(),
-                insets.getSystemWindowInsetRight(),
-                insets.getSystemWindowInsetBottom());
+            insets.getSystemWindowInsetLeft(),
+            insets.getSystemWindowInsetTop(),
+            insets.getSystemWindowInsetRight(),
+            insets.getSystemWindowInsetBottom());
         return insets;
     }
 
@@ -187,9 +190,9 @@
             int totalMargin = 0;
             // BoxInset is a padding. Ignore margin when we want to do BoxInset.
             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
-                totalPadding = boxInset;
+                totalPadding += boxInset;
             } else {
-                totalMargin = plwf + lp.leftMargin;
+                totalMargin += plwf + lp.leftMargin;
             }
             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
                 totalPadding += boxInset;
@@ -206,10 +209,12 @@
             }
 
             // adjust height
+            totalPadding = 0;
+            totalMargin = 0;
             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
-                totalPadding = boxInset;
+                totalPadding += boxInset;
             } else {
-                totalMargin = ptwf + lp.topMargin;
+                totalMargin += ptwf + lp.topMargin;
             }
             if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
                 totalPadding += boxInset;
@@ -236,7 +241,7 @@
     }
 
     private void layoutBoxChildren(int left, int top, int right, int bottom,
-            boolean forceLeftGravity) {
+                                  boolean forceLeftGravity) {
         final int count = getChildCount();
         int boxInset = (int)(FACTOR * Math.max(right - left, bottom - top));
 
@@ -272,55 +277,80 @@
                 int paddingTop = child.getPaddingTop();
                 int paddingBottom = child.getPaddingBottom();
 
-                switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
-                    case Gravity.CENTER_HORIZONTAL:
-                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
-                                lp.leftMargin - lp.rightMargin;
-                        break;
-                    case Gravity.RIGHT:
-                        if (!forceLeftGravity) {
-                            if (mLastKnownRound
-                                    && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
-                                paddingRight = boxInset;
-                                childLeft = right - left - width;
-                            } else {
-                                childLeft = parentRight - width - lp.rightMargin;
-                            }
+                // If the child's width is match_parent, we ignore gravity and set boxInset padding
+                // on both sides, with a left position of parentLeft + the child's left margin.
+                if (lp.width == LayoutParams.MATCH_PARENT) {
+                    if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
+                        paddingLeft = boxInset;
+                    }
+                    if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
+                        paddingRight = boxInset;
+                    }
+                    childLeft = parentLeft + lp.leftMargin;
+                } else {
+                    switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+                        case Gravity.CENTER_HORIZONTAL:
+                            childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
+                                    lp.leftMargin - lp.rightMargin;
                             break;
-                        }
-                    case Gravity.LEFT:
-                    default:
-                        if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
-                            paddingLeft = boxInset;
-                            childLeft = 0;
-                        } else {
-                            childLeft = parentLeft + lp.leftMargin;
-                        }
+                        case Gravity.RIGHT:
+                            if (!forceLeftGravity) {
+                                if (mLastKnownRound
+                                        && ((lp.boxedEdges & LayoutParams.BOX_RIGHT) != 0)) {
+                                    paddingRight = boxInset;
+                                    childLeft = right - left - width;
+                                } else {
+                                    childLeft = parentRight - width - lp.rightMargin;
+                                }
+                                break;
+                            }
+                        case Gravity.LEFT:
+                        default:
+                            if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_LEFT) != 0)) {
+                                paddingLeft = boxInset;
+                                childLeft = 0;
+                            } else {
+                                childLeft = parentLeft + lp.leftMargin;
+                            }
+                    }
                 }
 
-                switch (verticalGravity) {
-                    case Gravity.TOP:
-                        if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
-                            paddingTop = boxInset;
-                            childTop = 0;
-                        } else {
+                // If the child's height is match_parent, we ignore gravity and set boxInset padding
+                // on both top and bottom, with a top position of parentTop + the child's top
+                // margin.
+                if (lp.height == LayoutParams.MATCH_PARENT) {
+                    if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
+                        paddingTop = boxInset;
+                    }
+                    if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
+                        paddingBottom = boxInset;
+                    }
+                    childTop = parentTop + lp.topMargin;
+                } else {
+                    switch (verticalGravity) {
+                        case Gravity.TOP:
+                            if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_TOP) != 0)) {
+                                paddingTop = boxInset;
+                                childTop = 0;
+                            } else {
+                                childTop = parentTop + lp.topMargin;
+                            }
+                            break;
+                        case Gravity.CENTER_VERTICAL:
+                            childTop = parentTop + (parentBottom - parentTop - height) / 2 +
+                                    lp.topMargin - lp.bottomMargin;
+                            break;
+                        case Gravity.BOTTOM:
+                            if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
+                                paddingBottom = boxInset;
+                                childTop = bottom - top - height;
+                            } else {
+                                childTop = parentBottom - height - lp.bottomMargin;
+                            }
+                            break;
+                        default:
                             childTop = parentTop + lp.topMargin;
-                        }
-                        break;
-                    case Gravity.CENTER_VERTICAL:
-                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
-                                lp.topMargin - lp.bottomMargin;
-                        break;
-                    case Gravity.BOTTOM:
-                        if (mLastKnownRound && ((lp.boxedEdges & LayoutParams.BOX_BOTTOM) != 0)) {
-                            paddingBottom = boxInset;
-                            childTop = bottom - top - height;
-                        } else {
-                            childTop = parentBottom - height - lp.bottomMargin;
-                        }
-                        break;
-                    default:
-                        childTop = parentTop + lp.topMargin;
+                    }
                 }
 
                 child.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
index 0b73642..6b9316f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ManifestTestListAdapter.java
@@ -105,18 +105,20 @@
 
     private String mTestParent;
 
-    public ManifestTestListAdapter(Context context, String testParent) {
+    public ManifestTestListAdapter(Context context, String testParent, String[] disabledTestArray) {
         super(context);
         mContext = context;
         mTestParent = testParent;
-
-        String[] disabledTestArray = context.getResources().getStringArray(R.array.disabled_tests);
         mDisabledTests = new HashSet<>(disabledTestArray.length);
         for (int i = 0; i < disabledTestArray.length; i++) {
             mDisabledTests.add(disabledTestArray[i]);
         }
     }
 
+    public ManifestTestListAdapter(Context context, String testParent) {
+        this(context, testParent, context.getResources().getStringArray(R.array.disabled_tests));
+    }
+
     @Override
     protected List<TestListItem> getRows() {
 
@@ -305,11 +307,8 @@
     List<TestListItem> filterTests(List<TestListItem> tests) {
         List<TestListItem> filteredTests = new ArrayList<TestListItem>();
         for (TestListItem test : tests) {
-            String[] excludedFeatures = test.excludedFeatures;
-            String[] requiredFeatures = test.requiredFeatures;
-            String[] applicableFeatures = test.applicableFeatures;
-            if (!hasAnyFeature(excludedFeatures) && hasAllFeatures(requiredFeatures)) {
-                if (hasAnyFeature(applicableFeatures) || hasAllFeatures(applicableFeatures)) {
+            if (!hasAnyFeature(test.excludedFeatures) && hasAllFeatures(test.requiredFeatures)) {
+                if (test.applicableFeatures == null || hasAnyFeature(test.applicableFeatures)) {
                     filteredTests.add(test);
                 }
             }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
index 444a250..5a08558 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/PassFailButtons.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier;
 
+import com.android.compatibility.common.util.ReportLog;
+
 import android.app.AlertDialog;
 import android.app.Dialog;
 import android.content.ContentResolver;
@@ -32,6 +34,7 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 import android.widget.ImageButton;
+import android.widget.Toast;
 
 /**
  * {@link Activity}s to handle clicks to the pass and fail buttons of the pass fail buttons layout.
@@ -93,10 +96,18 @@
          * @param passed Whether or not the test passed.
          */
         void setTestResultAndFinish(boolean passed);
+
+        /** @return A {@link ReportLog} that is used to record test metric data. */
+        ReportLog getReportLog();
     }
 
     public static class Activity extends android.app.Activity implements PassFailActivity {
         private WakeLock mWakeLock;
+        private final ReportLog reportLog;
+
+        public Activity() {
+           this.reportLog = new CtsVerifierReportLog();
+        }
 
         @Override
         protected void onResume() {
@@ -148,13 +159,22 @@
 
         @Override
         public void setTestResultAndFinish(boolean passed) {
-            PassFailButtons.setTestResultAndFinishHelper(this, getTestId(), getTestDetails(),
-                    passed);
+            PassFailButtons.setTestResultAndFinishHelper(
+                    this, getTestId(), getTestDetails(), passed, getReportLog());
         }
+
+        @Override
+        public ReportLog getReportLog() { return reportLog; }
     }
 
     public static class ListActivity extends android.app.ListActivity implements PassFailActivity {
 
+        private final ReportLog reportLog;
+
+        public ListActivity() {
+            this.reportLog = new CtsVerifierReportLog();
+        }
+
         @Override
         public void setPassFailButtonClickListeners() {
             setPassFailClickListeners(this);
@@ -187,14 +207,23 @@
 
         @Override
         public void setTestResultAndFinish(boolean passed) {
-            PassFailButtons.setTestResultAndFinishHelper(this, getTestId(), getTestDetails(),
-                    passed);
+            PassFailButtons.setTestResultAndFinishHelper(
+                    this, getTestId(), getTestDetails(), passed, getReportLog());
         }
+
+        @Override
+        public ReportLog getReportLog() { return reportLog; }
     }
 
     public static class TestListActivity extends AbstractTestListActivity
             implements PassFailActivity {
 
+        private final ReportLog reportLog;
+
+        public TestListActivity() {
+            this.reportLog = new CtsVerifierReportLog();
+        }
+
         @Override
         public void setPassFailButtonClickListeners() {
             setPassFailClickListeners(this);
@@ -227,9 +256,12 @@
 
         @Override
         public void setTestResultAndFinish(boolean passed) {
-            PassFailButtons.setTestResultAndFinishHelper(this, getTestId(), getTestDetails(),
-                    passed);
+            PassFailButtons.setTestResultAndFinishHelper(
+                    this, getTestId(), getTestDetails(), passed, getReportLog());
         }
+
+        @Override
+        public ReportLog getReportLog() { return reportLog; }
     }
 
     private static <T extends android.app.Activity & PassFailActivity>
@@ -238,12 +270,29 @@
             @Override
             public void onClick(View target) {
                 setTestResultAndFinish(activity, activity.getTestId(), activity.getTestDetails(),
-                        target);
+                        activity.getReportLog(), target);
             }
         };
 
-        activity.findViewById(R.id.pass_button).setOnClickListener(clickListener);
-        activity.findViewById(R.id.fail_button).setOnClickListener(clickListener);
+        View passButton = activity.findViewById(R.id.pass_button);
+        passButton.setOnClickListener(clickListener);
+        passButton.setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View view) {
+                Toast.makeText(activity, R.string.pass_button_text, Toast.LENGTH_SHORT).show();
+                return true;
+            }
+        });
+
+        View failButton = activity.findViewById(R.id.fail_button);
+        failButton.setOnClickListener(clickListener);
+        failButton.setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View view) {
+                Toast.makeText(activity, R.string.fail_button_text, Toast.LENGTH_SHORT).show();
+                return true;
+            }
+        });
     }
 
     private static void setInfo(final android.app.Activity activity, final int titleId,
@@ -257,6 +306,13 @@
                 showInfoDialog(activity, titleId, messageId, viewId);
             }
         });
+        infoButton.setOnLongClickListener(new View.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View view) {
+                Toast.makeText(activity, R.string.info_button_text, Toast.LENGTH_SHORT).show();
+                return true;
+            }
+        });
 
         // Show the info dialog if the user has never seen it before.
         if (!hasSeenInfoDialog(activity)) {
@@ -341,7 +397,7 @@
 
     /** Set the test result corresponding to the button clicked and finish the activity. */
     private static void setTestResultAndFinish(android.app.Activity activity, String testId,
-            String testDetails, View target) {
+            String testDetails, ReportLog reportLog, View target) {
         boolean passed;
         switch (target.getId()) {
             case R.id.pass_button:
@@ -353,16 +409,16 @@
             default:
                 throw new IllegalArgumentException("Unknown id: " + target.getId());
         }
-        setTestResultAndFinishHelper(activity, testId, testDetails, passed);
+        setTestResultAndFinishHelper(activity, testId, testDetails, passed, reportLog);
     }
 
     /** Set the test result and finish the activity. */
     private static void setTestResultAndFinishHelper(android.app.Activity activity, String testId,
-            String testDetails, boolean passed) {
+            String testDetails, boolean passed, ReportLog reportLog) {
         if (passed) {
-            TestResult.setPassedResult(activity, testId, testDetails);
+            TestResult.setPassedResult(activity, testId, testDetails, reportLog);
         } else {
-            TestResult.setFailedResult(activity, testId, testDetails);
+            TestResult.setFailedResult(activity, testId, testDetails, reportLog);
         }
 
         activity.finish();
@@ -371,4 +427,8 @@
     private static ImageButton getPassButtonView(android.app.Activity activity) {
         return (ImageButton) activity.findViewById(R.id.pass_button);
     }
+
+    public static class CtsVerifierReportLog extends ReportLog {
+
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
index 43d300a..8cfc6df 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListActivity.java
@@ -23,19 +23,42 @@
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
 import android.widget.Toast;
 
 import java.io.IOException;
 
 /** Top-level {@link ListActivity} for launching tests and managing results. */
-public class TestListActivity extends AbstractTestListActivity {
+public class TestListActivity extends AbstractTestListActivity implements View.OnClickListener {
 
     private static final String TAG = TestListActivity.class.getSimpleName();
 
     @Override
+    public void onClick (View v) {
+        handleMenuItemSelected(v.getId());
+    }
+
+    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+
+        if (!isTaskRoot()) {
+            finish();
+        }
+
         setTitle(getString(R.string.title_version, Version.getVersionName(this)));
+
+        if (!getWindow().hasFeature(Window.FEATURE_ACTION_BAR)) {
+            View footer = getLayoutInflater().inflate(R.layout.test_list_footer, null);
+
+            footer.findViewById(R.id.clear).setOnClickListener(this);
+            footer.findViewById(R.id.view).setOnClickListener(this);
+            footer.findViewById(R.id.export).setOnClickListener(this);
+
+            getListView().addFooterView(footer);
+        }
+
         setTestListAdapter(new ManifestTestListAdapter(this, null));
     }
 
@@ -48,22 +71,7 @@
 
     @Override
     public boolean onOptionsItemSelected(MenuItem item) {
-        switch (item.getItemId()) {
-            case R.id.clear:
-                handleClearItemSelected();
-                return true;
-
-            case R.id.view:
-                handleViewItemSelected();
-                return true;
-
-            case R.id.export:
-                handleExportItemSelected();
-                return true;
-
-            default:
-                return super.onOptionsItemSelected(item);
-        }
+        return handleMenuItemSelected(item.getItemId()) ? true : super.onOptionsItemSelected(item);
     }
 
     private void handleClearItemSelected() {
@@ -86,4 +94,23 @@
     private void handleExportItemSelected() {
         new ReportExporter(this, mAdapter).execute();
     }
+
+    private boolean handleMenuItemSelected(int id) {
+        switch (id) {
+            case R.id.clear:
+                handleClearItemSelected();
+                return true;
+
+            case R.id.view:
+                handleViewItemSelected();
+                return true;
+
+            case R.id.export:
+                handleExportItemSelected();
+                return true;
+
+            default:
+                return false;
+        }
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
index afe3a73..2160902 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestListAdapter.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier;
 
+import com.android.compatibility.common.util.ReportLog;
+
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
@@ -30,6 +32,9 @@
 import android.widget.ListView;
 import android.widget.TextView;
 
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -66,6 +71,9 @@
     /** Map from test name to test details. */
     private final Map<String, String> mTestDetails = new HashMap<String, String>();
 
+    /** Map from test name to {@link ReportLog}. */
+    private final Map<String, ReportLog> mReportLogs = new HashMap<String, ReportLog>();
+
     private final LayoutInflater mLayoutInflater;
 
     /** {@link ListView} row that is either a test category header or a test. */
@@ -168,7 +176,7 @@
 
     public void setTestResult(TestResult testResult) {
         new SetTestResultTask(testResult.getName(), testResult.getResult(),
-                testResult.getDetails()).execute();
+                testResult.getDetails(), testResult.getReportLog()).execute();
     }
 
     class RefreshTestResultsTask extends AsyncTask<Void, Void, RefreshResult> {
@@ -187,6 +195,8 @@
             mTestResults.putAll(result.mResults);
             mTestDetails.clear();
             mTestDetails.putAll(result.mDetails);
+            mReportLogs.clear();
+            mReportLogs.putAll(result.mReportLogs);
             notifyDataSetChanged();
         }
     }
@@ -195,12 +205,17 @@
         List<TestListItem> mItems;
         Map<String, Integer> mResults;
         Map<String, String> mDetails;
+        Map<String, ReportLog> mReportLogs;
 
-        RefreshResult(List<TestListItem> items, Map<String, Integer> results,
-                Map<String, String> details) {
+        RefreshResult(
+                List<TestListItem> items,
+                Map<String, Integer> results,
+                Map<String, String> details,
+                Map<String, ReportLog> reportLogs) {
             mItems = items;
             mResults = results;
             mDetails = details;
+            mReportLogs = reportLogs;
         }
     }
 
@@ -211,11 +226,13 @@
         TestResultsProvider.COLUMN_TEST_NAME,
         TestResultsProvider.COLUMN_TEST_RESULT,
         TestResultsProvider.COLUMN_TEST_DETAILS,
+        TestResultsProvider.COLUMN_TEST_METRICS,
     };
 
     RefreshResult getRefreshResults(List<TestListItem> items) {
         Map<String, Integer> results = new HashMap<String, Integer>();
         Map<String, String> details = new HashMap<String, String>();
+        Map<String, ReportLog> reportLogs = new HashMap<String, ReportLog>();
         ContentResolver resolver = mContext.getContentResolver();
         Cursor cursor = null;
         try {
@@ -226,8 +243,10 @@
                     String testName = cursor.getString(1);
                     int testResult = cursor.getInt(2);
                     String testDetails = cursor.getString(3);
+                    ReportLog reportLog = (ReportLog) deserialize(cursor.getBlob(4));
                     results.put(testName, testResult);
                     details.put(testName, testDetails);
+                    reportLogs.put(testName, reportLog);
                 } while (cursor.moveToNext());
             }
         } finally {
@@ -235,7 +254,7 @@
                 cursor.close();
             }
         }
-        return new RefreshResult(items, results, details);
+        return new RefreshResult(items, results, details, reportLogs);
     }
 
     class ClearTestResultsTask extends AsyncTask<Void, Void, Void> {
@@ -256,15 +275,22 @@
 
         private final String mDetails;
 
-        SetTestResultTask(String testName, int result, String details) {
+        private final ReportLog mReportLog;
+
+        SetTestResultTask(
+                String testName,
+                int result,
+                String details,
+                ReportLog reportLog) {
             mTestName = testName;
             mResult = result;
             mDetails = details;
+            mReportLog = reportLog;
         }
 
         @Override
         protected Void doInBackground(Void... params) {
-            TestResultsProvider.setTestResult(mContext, mTestName, mResult, mDetails);
+            TestResultsProvider.setTestResult(mContext, mTestName, mResult, mDetails, mReportLog);
             return null;
         }
     }
@@ -332,6 +358,13 @@
                 : null;
     }
 
+    public ReportLog getReportLog(int position) {
+        TestListItem item = getItem(position);
+        return mReportLogs.containsKey(item.testName)
+                ? mReportLogs.get(item.testName)
+                : null;
+    }
+
     public boolean allTestsPassed() {
         for (TestListItem item : mRows) {
             if (item.isTest() && (!mTestResults.containsKey(item.testName)
@@ -400,4 +433,29 @@
 
         }
     }
+
+    private static Object deserialize(byte[] bytes) {
+        if (bytes == null || bytes.length == 0) {
+            return null;
+        }
+        ByteArrayInputStream byteStream = new ByteArrayInputStream(bytes);
+        ObjectInputStream objectInput = null;
+        try {
+            objectInput = new ObjectInputStream(byteStream);
+            return objectInput.readObject();
+        } catch (IOException e) {
+            return null;
+        } catch (ClassNotFoundException e) {
+            return null;
+        } finally {
+            try {
+                if (objectInput != null) {
+                    objectInput.close();
+                }
+                byteStream.close();
+            } catch (IOException e) {
+                // Ignore close exception.
+            }
+        }
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
index 68513ac..d8a675c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResult.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier;
 
+import com.android.compatibility.common.util.ReportLog;
+
 import android.app.Activity;
 import android.content.Intent;
 
@@ -35,29 +37,44 @@
     private static final String TEST_NAME = "name";
     private static final String TEST_RESULT = "result";
     private static final String TEST_DETAILS = "details";
+    private static final String TEST_METRICS = "metrics";
 
     private final String mName;
     private final int mResult;
     private final String mDetails;
+    private final ReportLog mReportLog;
 
     /** Sets the test activity's result to pass. */
     public static void setPassedResult(Activity activity, String testId, String testDetails) {
+        setPassedResult(activity, testId, testDetails, null /*reportLog*/);
+    }
+
+    /** Sets the test activity's result to pass including a test report log result. */
+    public static void setPassedResult(Activity activity, String testId, String testDetails,
+            ReportLog reportLog) {
         activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_PASSED, testId,
-                testDetails));
+                testDetails, reportLog));
     }
 
     /** Sets the test activity's result to failed. */
     public static void setFailedResult(Activity activity, String testId, String testDetails) {
+        setFailedResult(activity, testId, testDetails, null /*reportLog*/);
+    }
+
+    /** Sets the test activity's result to failed including a test report log result. */
+    public static void setFailedResult(Activity activity, String testId, String testDetails,
+            ReportLog reportLog) {
         activity.setResult(Activity.RESULT_OK, createResult(activity, TEST_RESULT_FAILED, testId,
-                testDetails));
+                testDetails, reportLog));
     }
 
     private static Intent createResult(Activity activity, int testResult, String testName,
-            String testDetails) {
+            String testDetails, ReportLog reportLog) {
         Intent data = new Intent(activity, activity.getClass());
         data.putExtra(TEST_NAME, testName);
         data.putExtra(TEST_RESULT, testResult);
         data.putExtra(TEST_DETAILS, testDetails);
+        data.putExtra(TEST_METRICS, reportLog);
         return data;
     }
 
@@ -69,13 +86,16 @@
         String name = data.getStringExtra(TEST_NAME);
         int result = data.getIntExtra(TEST_RESULT, TEST_RESULT_NOT_EXECUTED);
         String details = data.getStringExtra(TEST_DETAILS);
-        return new TestResult(name, result, details);
+        ReportLog reportLog = (ReportLog) data.getSerializableExtra(TEST_METRICS);
+        return new TestResult(name, result, details, reportLog);
     }
 
-    private TestResult(String name, int result, String details) {
+    private TestResult(
+            String name, int result, String details, ReportLog reportLog) {
         this.mName = name;
         this.mResult = result;
         this.mDetails = details;
+        this.mReportLog = reportLog;
     }
 
     /** Return the name of the test like "com.android.cts.verifier.foo.FooTest" */
@@ -92,4 +112,9 @@
     public String getDetails() {
         return mDetails;
     }
+
+    /** @return the {@link ReportLog} or null if not set */
+    public ReportLog getReportLog() {
+        return mReportLog;
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java
index e4cd24a..45e528f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsBackupHelper.java
@@ -59,6 +59,7 @@
             int resultIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_RESULT);
             int infoSeenIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_INFO_SEEN);
             int detailsIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_DETAILS);
+            int metricsIndex = cursor.getColumnIndex(TestResultsProvider.COLUMN_TEST_METRICS);
 
             ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
             DataOutputStream dataOutput = new DataOutputStream(byteOutput);
@@ -69,11 +70,16 @@
                 int result = cursor.getInt(resultIndex);
                 int infoSeen = cursor.getInt(infoSeenIndex);
                 String details = cursor.getString(detailsIndex);
+                byte[] metricsData = cursor.getBlob(metricsIndex);
 
                 dataOutput.writeUTF(name);
                 dataOutput.writeInt(result);
                 dataOutput.writeInt(infoSeen);
                 dataOutput.writeUTF(details != null ? details : "");
+                dataOutput.writeInt(metricsData.length);
+                if (metricsData.length > 0) {
+                    dataOutput.write(metricsData);
+                }
             }
 
             byte[] rawBytes = byteOutput.toByteArray();
@@ -106,12 +112,19 @@
                     int result = dataInput.readInt();
                     int infoSeen = dataInput.readInt();
                     String details = dataInput.readUTF();
+                    int metricsDataSize = dataInput.readInt();
 
                     values[i] = new ContentValues();
                     values[i].put(TestResultsProvider.COLUMN_TEST_NAME, name);
                     values[i].put(TestResultsProvider.COLUMN_TEST_RESULT, result);
                     values[i].put(TestResultsProvider.COLUMN_TEST_INFO_SEEN, infoSeen);
                     values[i].put(TestResultsProvider.COLUMN_TEST_DETAILS, details);
+
+                    if (metricsDataSize > 0) {
+                        byte[] metrics = new byte[metricsDataSize];
+                        dataInput.readFully(metrics);
+                        values[i].put(TestResultsProvider.COLUMN_TEST_METRICS, metrics);
+                    }
                 }
 
                 ContentResolver resolver = mContext.getContentResolver();
@@ -127,7 +140,7 @@
 
     private void failBackupTest() {
         TestResultsProvider.setTestResult(mContext, BackupTestActivity.class.getName(),
-                TestResult.TEST_RESULT_FAILED, null);
+                TestResult.TEST_RESULT_FAILED, null /*testDetails*/, null /*testMetrics*/);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java
index df05519..a9f672e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsProvider.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier;
 
+import com.android.compatibility.common.util.ReportLog;
+
 import android.app.backup.BackupManager;
 import android.content.ContentProvider;
 import android.content.ContentResolver;
@@ -28,6 +30,10 @@
 import android.database.sqlite.SQLiteQueryBuilder;
 import android.net.Uri;
 
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+
 /** {@link ContentProvider} that provides read and write access to the test results. */
 public class TestResultsProvider extends ContentProvider {
 
@@ -56,6 +62,9 @@
     /** String containing the test's details. */
     static final String COLUMN_TEST_DETAILS = "testdetails";
 
+    /** ReportLog containing the test result metrics. */
+    static final String COLUMN_TEST_METRICS = "testmetrics";
+
     private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
     private static final int RESULTS_ALL = 1;
     private static final int RESULTS_ID = 2;
@@ -96,7 +105,8 @@
                     + COLUMN_TEST_NAME + " TEXT, "
                     + COLUMN_TEST_RESULT + " INTEGER,"
                     + COLUMN_TEST_INFO_SEEN + " INTEGER DEFAULT 0,"
-                    + COLUMN_TEST_DETAILS + " TEXT);");
+                    + COLUMN_TEST_DETAILS + " TEXT,"
+                    + COLUMN_TEST_METRICS + " BLOB);");
         }
 
         @Override
@@ -202,11 +212,12 @@
     }
 
     static void setTestResult(Context context, String testName, int testResult,
-            String testDetails) {
+            String testDetails, ReportLog reportLog) {
         ContentValues values = new ContentValues(2);
         values.put(TestResultsProvider.COLUMN_TEST_RESULT, testResult);
         values.put(TestResultsProvider.COLUMN_TEST_NAME, testName);
         values.put(TestResultsProvider.COLUMN_TEST_DETAILS, testDetails);
+        values.put(TestResultsProvider.COLUMN_TEST_METRICS, serialize(reportLog));
 
         ContentResolver resolver = context.getContentResolver();
         int numUpdated = resolver.update(TestResultsProvider.RESULTS_CONTENT_URI, values,
@@ -218,4 +229,24 @@
         }
     }
 
+    private static byte[] serialize(Object o) {
+        ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
+        ObjectOutputStream objectOutput = null;
+        try {
+            objectOutput = new ObjectOutputStream(byteStream);
+            objectOutput.writeObject(o);
+            return byteStream.toByteArray();
+        } catch (IOException e) {
+            return null;
+        } finally {
+            try {
+                if (objectOutput != null) {
+                    objectOutput.close();
+                }
+                byteStream.close();
+            } catch (IOException e) {
+                // Ignore close exception.
+            }
+        }
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
index e40b428..dc2502c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/TestResultsReport.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier;
 
+import com.android.compatibility.common.util.MetricsXmlSerializer;
+import com.android.compatibility.common.util.ReportLog;
 import com.android.cts.verifier.TestListAdapter.TestListItem;
 
 import org.xmlpull.v1.XmlSerializer;
@@ -128,6 +130,12 @@
                     xml.endTag(null, TEST_DETAILS_TAG);
                 }
 
+                ReportLog reportLog = mAdapter.getReportLog(i);
+                if (reportLog != null) {
+                    MetricsXmlSerializer metricsXmlSerializer = new MetricsXmlSerializer(xml);
+                    metricsXmlSerializer.serialize(reportLog);
+                }
+
                 xml.endTag(null, TEST_TAG);
             }
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientCharacteristicActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientCharacteristicActivity.java
deleted file mode 100644
index 1e1941f..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientCharacteristicActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.bluetooth;
-
-public class BleClientCharacteristicActivity extends BleReadWriteActivity {
-    public BleClientCharacteristicActivity() {
-        super(BleReadWriteActivity.CHARACTERISTIC);
-    }
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientConnectActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientConnectActivity.java
deleted file mode 100755
index fb351b1..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientConnectActivity.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.bluetooth;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.Toast;
-
-public class BleClientConnectActivity extends PassFailButtons.Activity {
-
-    private EditText mEditText;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.ble_client_connect);
-        setPassFailButtonClickListeners();
-        setInfoResources(R.string.ble_client_connect_name,
-                         R.string.ble_client_send_connect_info, -1);
-        getPassButton().setEnabled(false);
-
-        mEditText = (EditText) findViewById(R.id.ble_address);
-
-        ((Button) findViewById(R.id.ble_connect)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                String address = mEditText.getText().toString();
-                if (!BluetoothAdapter.checkBluetoothAddress(address)) {
-                    showMessage("Invalid bluetooth address.");
-                } else {
-                    Intent intent = new Intent(BleClientConnectActivity.this,
-                                               BleClientService.class);
-                    intent.putExtra(BleClientService.EXTRA_COMMAND,
-                                    BleClientService.COMMAND_CONNECT);
-                    intent.putExtra(BluetoothDevice.EXTRA_DEVICE, address);
-                    startService(intent);
-                }
-            }
-        });
-
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BleClientService.BLE_BLUETOOTH_CONNECTED);
-        registerReceiver(onBroadcast, filter);
-    }
-
-    @Override
-    protected void onDestroy(){
-        super.onDestroy();
-        unregisterReceiver(onBroadcast);
-    }
-
-    private void showMessage(String msg) {
-        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
-    }
-
-    private BroadcastReceiver onBroadcast = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            showMessage("Bluetooth LE connected");
-            getPassButton().setEnabled(true);
-        }
-    };
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientDisconnectActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientDisconnectActivity.java
deleted file mode 100644
index 083d327..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientDisconnectActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.bluetooth;
-
-public class BleClientDisconnectActivity extends BleButtonActivity {
-    public BleClientDisconnectActivity() {
-        super(BleButtonActivity.DISCONNECT);
-    }
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
index 556ad06..10f862d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientService.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.verifier.bluetooth;
 
+import java.util.Arrays;
 import java.util.UUID;
 import java.util.List;
 
@@ -29,10 +30,16 @@
 import android.bluetooth.BluetoothGattService;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.ParcelUuid;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -53,6 +60,8 @@
     public static final int COMMAND_BEGIN_WRITE = 9;
     public static final int COMMAND_EXECUTE_WRITE = 10;
     public static final int COMMAND_ABORT_RELIABLE = 11;
+    public static final int COMMAND_SCAN_START = 12;
+    public static final int COMMAND_SCAN_STOP = 13;
 
     public static final String BLE_BLUETOOTH_CONNECTED =
             "com.android.cts.verifier.bluetooth.BLE_BLUETOOTH_CONNECTED";
@@ -87,6 +96,8 @@
             "com.android.cts.verifier.bluetooth.EXTRA_DESCRIPTOR_VALUE";
     public static final String EXTRA_RSSI_VALUE =
             "com.android.cts.verifier.bluetooth.EXTRA_RSSI_VALUE";
+    public static final String EXTRA_ERROR_MESSAGE =
+            "com.android.cts.verifier.bluetooth.EXTRA_ERROR_MESSAGE";
 
     private static final UUID SERVICE_UUID =
             UUID.fromString("00009999-0000-1000-8000-00805f9b34fb");
@@ -97,11 +108,15 @@
     private static final UUID DESCRIPTOR_UUID =
             UUID.fromString("00009996-0000-1000-8000-00805f9b34fb");
 
+    private static final String WRITE_VALUE = "TEST";
+
     private BluetoothManager mBluetoothManager;
     private BluetoothAdapter mBluetoothAdapter;
     private BluetoothDevice mDevice;
     private BluetoothGatt mBluetoothGatt;
+    private BluetoothLeScanner mScanner;
     private Handler mHandler;
+    private Context mContext;
 
     @Override
     public void onCreate() {
@@ -109,12 +124,14 @@
 
         mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
         mBluetoothAdapter = mBluetoothManager.getAdapter();
+        mScanner = mBluetoothAdapter.getBluetoothLeScanner();
         mHandler = new Handler();
+        mContext = this;
+        startScan();
     }
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        if (intent != null) handleIntent(intent);
         return START_NOT_STICKY;
     }
 
@@ -128,59 +145,8 @@
         super.onDestroy();
         mBluetoothGatt.disconnect();
         mBluetoothGatt.close();
-    }
-
-    private void handleIntent(Intent intent) {
-        int command = intent.getIntExtra(EXTRA_COMMAND, -1);
-        String address = intent.getStringExtra(BluetoothDevice.EXTRA_DEVICE); // sometimes null
-        String writeValue = intent.getStringExtra(EXTRA_WRITE_VALUE); // sometimes null
-        boolean enable = intent.getBooleanExtra(EXTRA_BOOL, false);
-        BluetoothGattService service;
-        BluetoothGattCharacteristic characteristic;
-        BluetoothGattDescriptor descriptor;
-
-        switch (command) {
-            case COMMAND_CONNECT:
-                mDevice = mBluetoothAdapter.getRemoteDevice(address);
-                mBluetoothGatt = mDevice.connectGatt(this, false, mGattCallbacks);
-                break;
-            case COMMAND_DISCONNECT:
-                if (mBluetoothGatt != null) mBluetoothGatt.disconnect();
-                break;
-            case COMMAND_DISCOVER_SERVICE:
-                if (mBluetoothGatt != null) mBluetoothGatt.discoverServices();
-                break;
-            case COMMAND_READ_RSSI:
-                if (mBluetoothGatt != null) mBluetoothGatt.readRemoteRssi();
-                break;
-            case COMMAND_WRITE_CHARACTERISTIC:
-                writeCharacteristic(writeValue);
-                break;
-            case COMMAND_READ_CHARACTERISTIC:
-                readCharacteristic();
-                break;
-            case COMMAND_WRITE_DESCRIPTOR:
-                writeDescriptor(writeValue);
-                break;
-            case COMMAND_READ_DESCRIPTOR:
-                readDescriptor();
-                break;
-            case COMMAND_SET_NOTIFICATION:
-                setNotification(enable);
-                break;
-            case COMMAND_BEGIN_WRITE:
-                if (mBluetoothGatt != null) mBluetoothGatt.beginReliableWrite();
-                break;
-            case COMMAND_EXECUTE_WRITE:
-                if (mBluetoothGatt != null) mBluetoothGatt.executeReliableWrite();
-                break;
-            case COMMAND_ABORT_RELIABLE:
-                if (mBluetoothGatt != null) mBluetoothGatt.abortReliableWrite(mDevice);
-                break;
-            default:
-                showMessage("Unrecognized command: " + command);
-                break;
-        }
+        mBluetoothGatt = null;
+        stopScan();
     }
 
     private void writeCharacteristic(String writeValue) {
@@ -213,55 +179,69 @@
             mBluetoothGatt.setCharacteristicNotification(characteristic, enable);
     }
 
+    private void notifyError(String message) {
+        showMessage(message);
+    }
+
     private void notifyConnected() {
+        showMessage("BLE connected");
         Intent intent = new Intent(BLE_BLUETOOTH_CONNECTED);
         sendBroadcast(intent);
     }
 
     private void notifyDisconnected() {
+        showMessage("BLE disconnected");
         Intent intent = new Intent(BLE_BLUETOOTH_DISCONNECTED);
         sendBroadcast(intent);
     }
 
     private void notifyServicesDiscovered() {
+        showMessage("Service discovered");
         Intent intent = new Intent(BLE_SERVICES_DISCOVERED);
         sendBroadcast(intent);
     }
 
     private void notifyCharacteristicRead(String value) {
+        showMessage("Characteristic read: " + value);
         Intent intent = new Intent(BLE_CHARACTERISTIC_READ);
         intent.putExtra(EXTRA_CHARACTERISTIC_VALUE, value);
         sendBroadcast(intent);
     }
 
-    private void notifyCharacteristicWrite() {
+    private void notifyCharacteristicWrite(String value) {
+        showMessage("Characteristic write: " + value);
         Intent intent = new Intent(BLE_CHARACTERISTIC_WRITE);
         sendBroadcast(intent);
     }
 
     private void notifyCharacteristicChanged(String value) {
+        showMessage("Characteristic changed: " + value);
         Intent intent = new Intent(BLE_CHARACTERISTIC_CHANGED);
         intent.putExtra(EXTRA_CHARACTERISTIC_VALUE, value);
         sendBroadcast(intent);
     }
 
     private void notifyDescriptorRead(String value) {
+        showMessage("Descriptor read: " + value);
         Intent intent = new Intent(BLE_DESCRIPTOR_READ);
         intent.putExtra(EXTRA_DESCRIPTOR_VALUE, value);
         sendBroadcast(intent);
     }
 
-    private void notifyDescriptorWrite() {
+    private void notifyDescriptorWrite(String value) {
+        showMessage("Descriptor write: " + value);
         Intent intent = new Intent(BLE_DESCRIPTOR_WRITE);
         sendBroadcast(intent);
     }
 
     private void notifyReliableWriteCompleted() {
+        showMessage("Reliable write compelte");
         Intent intent = new Intent(BLE_RELIABLE_WRITE_COMPLETED);
         sendBroadcast(intent);
     }
 
     private void notifyReadRemoteRssi(int rssi) {
+        showMessage("Remote rssi read: " + rssi);
         Intent intent = new Intent(BLE_READ_REMOTE_RSSI);
         intent.putExtra(EXTRA_RSSI_VALUE, rssi);
         sendBroadcast(intent);
@@ -310,81 +290,173 @@
         });
     }
 
+    private void sleep(int millis) {
+        try {
+            Thread.sleep(millis);
+        } catch (InterruptedException e) {
+            Log.e(TAG, "Error in thread sleep", e);
+        }
+    }
+
+    private void reliableWrite() {
+        mBluetoothGatt.beginReliableWrite();
+        sleep(1000);
+        writeCharacteristic(WRITE_VALUE);
+        sleep(1000);
+        if (!mBluetoothGatt.executeReliableWrite()) {
+            Log.w(TAG, "reliable write failed");
+        }
+        sleep(1000);
+        mBluetoothGatt.abortReliableWrite();
+    }
+
     private final BluetoothGattCallback mGattCallbacks = new BluetoothGattCallback() {
         @Override
         public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
             if (DEBUG) Log.d(TAG, "onConnectionStateChange");
             if (status == BluetoothGatt.GATT_SUCCESS) {
-                if (newState == BluetoothProfile.STATE_CONNECTED) notifyConnected();
-                else if (status == BluetoothProfile.STATE_DISCONNECTED) {
+                if (newState == BluetoothProfile.STATE_CONNECTED) {
+                    notifyConnected();
+                    stopScan();
+                    sleep(1000);
+                    mBluetoothGatt.discoverServices();
+                } else if (status == BluetoothProfile.STATE_DISCONNECTED) {
                     notifyDisconnected();
-                    showMessage("Bluetooth LE disconnected");
                 }
+            } else {
+                showMessage("Failed to connect");
             }
         }
 
         @Override
         public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+            if (DEBUG) Log.d(TAG, "onServiceDiscovered");
             if ((status == BluetoothGatt.GATT_SUCCESS) &&
                 (mBluetoothGatt.getService(SERVICE_UUID) != null)) {
                 notifyServicesDiscovered();
-            }
-        }
-
-        @Override
-        public void onCharacteristicRead(BluetoothGatt gatt,
-                                         BluetoothGattCharacteristic characteristic, int status) {
-            if ((status == BluetoothGatt.GATT_SUCCESS) &&
-                (characteristic.getUuid().equals(CHARACTERISTIC_UUID))) {
-                notifyCharacteristicRead(characteristic.getStringValue(0));
+                sleep(1000);
+                writeCharacteristic(WRITE_VALUE);
             }
         }
 
         @Override
         public void onCharacteristicWrite(BluetoothGatt gatt,
                                           BluetoothGattCharacteristic characteristic, int status) {
-            if (DEBUG) Log.d(TAG, "onCharacteristicWrite: characteristic.val=" + characteristic.getStringValue(0)
-                                  + " status=" + status);
+            String value = characteristic.getStringValue(0);
+            if (DEBUG) Log.d(TAG, "onCharacteristicWrite: characteristic.val="
+                    + value + " status=" + status);
             BluetoothGattCharacteristic mCharacteristic = getCharacteristic(CHARACTERISTIC_UUID);
             if ((status == BluetoothGatt.GATT_SUCCESS) &&
-                (characteristic.getStringValue(0).equals(mCharacteristic.getStringValue(0)))) {
-                notifyCharacteristicWrite();
+                (value.equals(mCharacteristic.getStringValue(0)))) {
+                notifyCharacteristicWrite(value);
+                sleep(1000);
+                readCharacteristic();
+            } else {
+                notifyError("Failed to write characteristic: " + value);
             }
         }
 
         @Override
-        public void onCharacteristicChanged(BluetoothGatt gatt,
-                                            BluetoothGattCharacteristic characteristic) {
-            if (characteristic.getUuid().equals(UPDATE_CHARACTERISTIC_UUID))
-                notifyCharacteristicChanged(characteristic.getStringValue(0));
-        }
-
-        @Override
-        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
-                                     int status) {
+        public void onCharacteristicRead(BluetoothGatt gatt,
+                                         BluetoothGattCharacteristic characteristic, int status) {
+            if (DEBUG) Log.d(TAG, "onCharacteristicRead");
             if ((status == BluetoothGatt.GATT_SUCCESS) &&
-                (descriptor.getUuid().equals(DESCRIPTOR_UUID))) {
-                notifyDescriptorRead(new String(descriptor.getValue()));
+                (characteristic.getUuid().equals(CHARACTERISTIC_UUID))) {
+                notifyCharacteristicRead(characteristic.getStringValue(0));
+                sleep(1000);
+                writeDescriptor(WRITE_VALUE);
+            } else {
+                notifyError("Failed to read characteristic");
             }
         }
 
         @Override
         public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
                                       int status) {
+            if (DEBUG) Log.d(TAG, "onDescriptorWrite");
             if ((status == BluetoothGatt.GATT_SUCCESS) &&
                 (descriptor.getUuid().equals(DESCRIPTOR_UUID))) {
-                notifyDescriptorWrite();
+                notifyDescriptorWrite(new String(descriptor.getValue()));
+                sleep(1000);
+                readDescriptor();
+            } else {
+                notifyError("Failed to write descriptor");
+            }
+        }
+
+        @Override
+        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+                                     int status) {
+            if (DEBUG) Log.d(TAG, "onDescriptorRead");
+            if ((status == BluetoothGatt.GATT_SUCCESS) &&
+                (descriptor.getUuid() != null) &&
+                (descriptor.getUuid().equals(DESCRIPTOR_UUID))) {
+                notifyDescriptorRead(new String(descriptor.getValue()));
+                sleep(1000);
+                setNotification(true);
+            } else {
+                notifyError("Failed to read descriptor");
+            }
+        }
+
+        @Override
+        public void onCharacteristicChanged(BluetoothGatt gatt,
+                                            BluetoothGattCharacteristic characteristic) {
+            if (DEBUG) Log.d(TAG, "onCharacteristicChanged");
+            if ((characteristic.getUuid() != null) &&
+                (characteristic.getUuid().equals(UPDATE_CHARACTERISTIC_UUID))) {
+                notifyCharacteristicChanged(characteristic.getStringValue(0));
+                setNotification(false);
+                sleep(1000);
+                mBluetoothGatt.readRemoteRssi();
             }
         }
 
         @Override
         public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
-            if (status == BluetoothGatt.GATT_SUCCESS) notifyReliableWriteCompleted();
+            if (DEBUG) Log.d(TAG, "onReliableWriteComplete: " + status);
+            if (status == BluetoothGatt.GATT_SUCCESS) {
+                notifyReliableWriteCompleted();
+            } else {
+                notifyError("Failed to complete reliable write: " + status);
+            }
+            sleep(1000);
+            mBluetoothGatt.disconnect();
         }
 
         @Override
         public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
-            if (status == BluetoothGatt.GATT_SUCCESS) notifyReadRemoteRssi(rssi);
+            if (DEBUG) Log.d(TAG, "onReadRemoteRssi");
+            if (status == BluetoothGatt.GATT_SUCCESS) {
+                notifyReadRemoteRssi(rssi);
+            } else {
+                notifyError("Failed to read remote rssi");
+            }
+            sleep(1000);
+            reliableWrite();
         }
     };
-}
\ No newline at end of file
+
+    private final ScanCallback mScanCallback = new ScanCallback() {
+        @Override
+        public void onScanResult(int callbackType, ScanResult result) {
+            if (mBluetoothGatt == null) {
+                mBluetoothGatt = result.getDevice().connectGatt(mContext, false, mGattCallbacks);
+            }
+        }
+    };
+
+    private void startScan() {
+        if (DEBUG) Log.d(TAG, "startScan");
+        List<ScanFilter> filter = Arrays.asList(new ScanFilter.Builder().setServiceUuid(
+                new ParcelUuid(BleServerService.ADV_SERVICE_UUID)).build());
+        ScanSettings setting = new ScanSettings.Builder()
+                .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY).build();
+        mScanner.startScan(filter, setting, mScanCallback);
+    }
+
+    private void stopScan() {
+        if (DEBUG) Log.d(TAG, "stopScan");
+        mScanner.stopScan(mScanCallback);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientStartActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientStartActivity.java
new file mode 100644
index 0000000..5a4e327
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientStartActivity.java
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.bluetooth;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+public class BleClientStartActivity extends PassFailButtons.Activity {
+
+    private TestAdapter mTestAdapter;
+    private int mAllPassed;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.ble_server_start);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.ble_server_start_name,
+                         R.string.ble_server_start_info, -1);
+        getPassButton().setEnabled(false);
+
+        mTestAdapter = new TestAdapter(this, setupTestList());
+        ListView listView = (ListView) findViewById(R.id.ble_server_tests);
+        listView.setAdapter(mTestAdapter);
+
+        mAllPassed = 0;
+        startService(new Intent(this, BleClientService.class));
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(BleClientService.BLE_BLUETOOTH_CONNECTED);
+        filter.addAction(BleClientService.BLE_BLUETOOTH_DISCONNECTED);
+        filter.addAction(BleClientService.BLE_SERVICES_DISCOVERED);
+        filter.addAction(BleClientService.BLE_CHARACTERISTIC_READ);
+        filter.addAction(BleClientService.BLE_CHARACTERISTIC_WRITE);
+        filter.addAction(BleClientService.BLE_CHARACTERISTIC_CHANGED);
+        filter.addAction(BleClientService.BLE_DESCRIPTOR_READ);
+        filter.addAction(BleClientService.BLE_DESCRIPTOR_WRITE);
+        filter.addAction(BleClientService.BLE_RELIABLE_WRITE_COMPLETED);
+        filter.addAction(BleClientService.BLE_READ_REMOTE_RSSI);
+        registerReceiver(onBroadcast, filter);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        unregisterReceiver(onBroadcast);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        stopService(new Intent(this, BleClientService.class));
+    }
+
+    private List<Integer> setupTestList() {
+        ArrayList<Integer> testList = new ArrayList<Integer>();
+        testList.add(R.string.ble_client_connect_name);
+        testList.add(R.string.ble_discover_service_name);
+        testList.add(R.string.ble_read_characteristic_name);
+        testList.add(R.string.ble_write_characteristic_name);
+        testList.add(R.string.ble_reliable_write_name);
+        testList.add(R.string.ble_notify_characteristic_name);
+        testList.add(R.string.ble_read_descriptor_name);
+        testList.add(R.string.ble_write_descriptor_name);
+        testList.add(R.string.ble_read_rssi_name);
+        testList.add(R.string.ble_client_disconnect_name);
+        return testList;
+    }
+
+    private BroadcastReceiver onBroadcast = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action == BleClientService.BLE_BLUETOOTH_CONNECTED) {
+                mTestAdapter.setTestPass(0);
+                mAllPassed |= 0x01;
+            } else if (action == BleClientService.BLE_SERVICES_DISCOVERED) {
+                mTestAdapter.setTestPass(1);
+                mAllPassed |= 0x02;
+            } else if (action == BleClientService.BLE_CHARACTERISTIC_READ) {
+                mTestAdapter.setTestPass(2);
+                mAllPassed |= 0x04;
+            } else if (action == BleClientService.BLE_CHARACTERISTIC_WRITE) {
+                mTestAdapter.setTestPass(3);
+                mAllPassed |= 0x08;
+            } else if (action == BleClientService.BLE_RELIABLE_WRITE_COMPLETED) {
+                mTestAdapter.setTestPass(4);
+                mAllPassed |= 0x10;
+            } else if (action == BleClientService.BLE_CHARACTERISTIC_CHANGED) {
+                mTestAdapter.setTestPass(5);
+                mAllPassed |= 0x20;
+            } else if (action == BleClientService.BLE_DESCRIPTOR_READ) {
+                mTestAdapter.setTestPass(6);
+                mAllPassed |= 0x40;
+            } else if (action == BleClientService.BLE_DESCRIPTOR_WRITE) {
+                mTestAdapter.setTestPass(7);
+                mAllPassed |= 0x80;
+            } else if (action == BleClientService.BLE_READ_REMOTE_RSSI) {
+                mTestAdapter.setTestPass(8);
+                mAllPassed |= 0x100;
+            } else if (action == BleClientService.BLE_BLUETOOTH_DISCONNECTED) {
+                mTestAdapter.setTestPass(9);
+                mAllPassed |= 0x200;
+            }
+            mTestAdapter.notifyDataSetChanged();
+            if (mAllPassed == 0x3FF) getPassButton().setEnabled(true);
+        }
+    };
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestActivity.java
deleted file mode 100644
index a13d934..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientTestActivity.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2011 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.bluetooth;
-
-import com.android.cts.verifier.ManifestTestListAdapter;
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.os.Bundle;
-
-public class BleClientTestActivity extends PassFailButtons.TestListActivity {
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.pass_fail_list);
-        setPassFailButtonClickListeners();
-        setInfoResources(R.string.ble_client_test_name, R.string.ble_client_test_info, -1);
-
-        setTestListAdapter(new ManifestTestListAdapter(this, getClass().getName()));
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleDiscoverServiceActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleDiscoverServiceActivity.java
deleted file mode 100644
index 6896b04..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleDiscoverServiceActivity.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.bluetooth;
-
-public class BleDiscoverServiceActivity extends BleButtonActivity {
-    public BleDiscoverServiceActivity() {
-        super(BleButtonActivity.DISCOVER_SERVICE);
-    }
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleNotifyCharacteristicActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleNotifyCharacteristicActivity.java
deleted file mode 100644
index e0c79bf..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleNotifyCharacteristicActivity.java
+++ /dev/null
@@ -1,92 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.bluetooth;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.TextView;
-import android.widget.Toast;
-
-public class BleNotifyCharacteristicActivity extends PassFailButtons.Activity {
-
-    private boolean mEnable;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.ble_notify_characteristic);
-        setPassFailButtonClickListeners();
-        setInfoResources(R.string.ble_notify_characteristic_name,
-                         R.string.ble_notify_characteristic_info, -1);
-
-        mEnable = false;
-
-        ((Button) findViewById(R.id.ble_notify)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                mEnable = !mEnable;
-                if (mEnable) ((Button) v).setText(getString(R.string.ble_stop_notification));
-                else ((Button) v).setText(getString(R.string.ble_begin_notification));
-
-                Intent intent = new Intent(BleNotifyCharacteristicActivity.this,
-                                           BleClientService.class);
-                intent.putExtra(BleClientService.EXTRA_COMMAND,
-                                BleClientService.COMMAND_SET_NOTIFICATION);
-                intent.putExtra(BleClientService.EXTRA_BOOL, mEnable);
-                startService(intent);
-            }
-        });
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BleClientService.BLE_CHARACTERISTIC_CHANGED);
-        registerReceiver(onBroadcast, filter);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        unregisterReceiver(onBroadcast);
-        mEnable = false;
-        Intent intent = new Intent(BleNotifyCharacteristicActivity.this,
-                                   BleClientService.class);
-        intent.putExtra(BleClientService.EXTRA_COMMAND,
-                        BleClientService.COMMAND_SET_NOTIFICATION);
-        intent.putExtra(BleClientService.EXTRA_BOOL, mEnable);
-        startService(intent);
-    }
-
-    private BroadcastReceiver onBroadcast = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String value = intent.getStringExtra(BleClientService.EXTRA_CHARACTERISTIC_VALUE);
-            ((TextView) findViewById(R.id.ble_notify_text)).setText(value);
-        }
-    };
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadRssiActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadRssiActivity.java
deleted file mode 100644
index 800499c..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadRssiActivity.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.bluetooth;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-import android.widget.Toast;
-
-public class BleReadRssiActivity extends PassFailButtons.Activity {
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.ble_read_rssi);
-        setPassFailButtonClickListeners();
-        setInfoResources(R.string.ble_read_rssi_name,
-                         R.string.ble_read_rssi_info, -1);
-
-        ((Button) findViewById(R.id.ble_read_rssi)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(BleReadRssiActivity.this, BleClientService.class);
-                intent.putExtra(BleClientService.EXTRA_COMMAND,
-                                BleClientService.COMMAND_READ_RSSI);
-                startService(intent);
-            }
-        });
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BleClientService.BLE_READ_REMOTE_RSSI);
-        registerReceiver(onBroadcast, filter);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        unregisterReceiver(onBroadcast);
-    }
-
-    private BroadcastReceiver onBroadcast = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            int rssi = intent.getIntExtra(BleClientService.EXTRA_RSSI_VALUE, 128);
-            ((TextView) findViewById(R.id.ble_rssi_text)).setText(Integer.toString(rssi));
-        }
-    };
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadWriteActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadWriteActivity.java
deleted file mode 100644
index 22233ef..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReadWriteActivity.java
+++ /dev/null
@@ -1,127 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.bluetooth;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.TextView;
-import android.widget.Toast;
-
-class BleReadWriteActivity extends PassFailButtons.Activity {
-
-    static final int CHARACTERISTIC = 0;
-    static final int DESCRIPTOR = 1;
-
-    private int mWriteCommand;
-    private int mReadCommand;
-    private String mWriteFilter;
-    private String mReadFilter;
-    private String mExtraValue;
-    private int mName;
-    private EditText mEditText;
-
-    BleReadWriteActivity(int target) {
-        if (target == CHARACTERISTIC) {
-            mWriteCommand = BleClientService.COMMAND_WRITE_CHARACTERISTIC;
-            mReadCommand = BleClientService.COMMAND_READ_CHARACTERISTIC;
-            mWriteFilter = BleClientService.BLE_CHARACTERISTIC_WRITE;
-            mReadFilter = BleClientService.BLE_CHARACTERISTIC_READ;
-            mExtraValue = BleClientService.EXTRA_CHARACTERISTIC_VALUE;
-            mName = R.string.ble_client_characteristic_name;
-        } else if (target == DESCRIPTOR) {
-            mWriteCommand = BleClientService.COMMAND_WRITE_DESCRIPTOR;
-            mReadCommand = BleClientService.COMMAND_READ_DESCRIPTOR;
-            mWriteFilter = BleClientService.BLE_DESCRIPTOR_WRITE;
-            mReadFilter = BleClientService.BLE_DESCRIPTOR_READ;
-            mExtraValue = BleClientService.EXTRA_DESCRIPTOR_VALUE;
-            mName = R.string.ble_client_descriptor_name;
-        }
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.ble_client_read_write);
-        setPassFailButtonClickListeners();
-        setInfoResources(mName, R.string.ble_read_write_info, -1);
-
-        mEditText = (EditText) findViewById(R.id.write_text);
-
-        ((Button) findViewById(R.id.ble_write)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                String writeValue = mEditText.getText().toString();
-                Intent intent = new Intent(BleReadWriteActivity.this, BleClientService.class);
-                intent.putExtra(BleClientService.EXTRA_COMMAND, mWriteCommand);
-                intent.putExtra(BleClientService.EXTRA_WRITE_VALUE, writeValue);
-                startService(intent);
-                mEditText.setText("");
-            }
-        });
-
-        ((Button) findViewById(R.id.ble_read)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(BleReadWriteActivity.this, BleClientService.class);
-                intent.putExtra(BleClientService.EXTRA_COMMAND, mReadCommand);
-                startService(intent);
-            }
-        });
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(mReadFilter);
-        filter.addAction(mWriteFilter);
-        registerReceiver(onBroadcast, filter);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        unregisterReceiver(onBroadcast);
-    }
-
-    private void showMessage(String msg) {
-        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
-    }
-
-    private BroadcastReceiver onBroadcast = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action == mWriteFilter)
-                showMessage("Write successful callback");
-            else if (action == mReadFilter) {
-                String value = intent.getStringExtra(mExtraValue);
-                ((TextView) findViewById(R.id.read_text)).setText(value);
-            }
-        }
-    };
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReliableWriteActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReliableWriteActivity.java
deleted file mode 100644
index c7460b5..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleReliableWriteActivity.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.bluetooth;
-
-import com.android.cts.verifier.PassFailButtons;
-import com.android.cts.verifier.R;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.widget.Button;
-import android.widget.EditText;
-import android.widget.Toast;
-
-public class BleReliableWriteActivity extends PassFailButtons.Activity {
-
-    EditText mEditText;
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        setContentView(R.layout.ble_reliable_write);
-        setPassFailButtonClickListeners();
-        setInfoResources(R.string.ble_reliable_write_name, R.string.ble_reliable_write_info, -1);
-        getPassButton().setEnabled(false);
-        ((Button) findViewById(R.id.ble_execute)).setEnabled(false);
-
-        mEditText = (EditText) findViewById(R.id.write_text);
-
-        ((Button) findViewById(R.id.ble_begin)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(BleReliableWriteActivity.this, BleClientService.class);
-                intent.putExtra(BleClientService.EXTRA_COMMAND,
-                                BleClientService.COMMAND_BEGIN_WRITE);
-                startService(intent);
-            }
-        });
-
-        ((Button) findViewById(R.id.ble_write)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                String writeValue = mEditText.getText().toString();
-                Intent intent = new Intent(BleReliableWriteActivity.this, BleClientService.class);
-                intent.putExtra(BleClientService.EXTRA_COMMAND,
-                                BleClientService.COMMAND_WRITE_CHARACTERISTIC);
-                intent.putExtra(BleClientService.EXTRA_WRITE_VALUE, writeValue);
-                startService(intent);
-                mEditText.setText("");
-            }
-        });
-
-        ((Button) findViewById(R.id.ble_execute)).setOnClickListener(new OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                Intent intent = new Intent(BleReliableWriteActivity.this, BleClientService.class);
-                intent.putExtra(BleClientService.EXTRA_COMMAND,
-                                BleClientService.COMMAND_EXECUTE_WRITE);
-                startService(intent);
-            }
-        });
-    }
-
-    @Override
-    public void onResume() {
-        super.onResume();
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(BleClientService.BLE_CHARACTERISTIC_WRITE);
-        filter.addAction(BleClientService.BLE_RELIABLE_WRITE_COMPLETED);
-        registerReceiver(onBroadcast, filter);
-    }
-
-    @Override
-    public void onPause() {
-        super.onPause();
-        unregisterReceiver(onBroadcast);
-        Intent intent = new Intent(this, BleClientService.class);
-        intent.putExtra(BleClientService.EXTRA_COMMAND, BleClientService.COMMAND_ABORT_RELIABLE);
-        startService(intent);
-    }
-
-    private void showMessage(String msg) {
-        Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
-    }
-
-    private BroadcastReceiver onBroadcast = new BroadcastReceiver() {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
-            if (action == BleClientService.BLE_CHARACTERISTIC_WRITE) {
-                showMessage("Write value verified.");
-                ((Button) findViewById(R.id.ble_execute)).setEnabled(true);
-            } else if (action == BleClientService.BLE_RELIABLE_WRITE_COMPLETED) {
-                showMessage("Reliable write completed.");
-                getPassButton().setEnabled(true);
-            }
-        }
-    };
-}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java
index a6489c1..bf3484e 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleScannerPowerLevelActivity.java
@@ -59,6 +59,7 @@
         setPassFailButtonClickListeners();
         setInfoResources(R.string.ble_power_level_name,
                          R.string.ble_power_level_info, -1);
+        getPassButton().setEnabled(false);
 
         mTimerText = (TextView)findViewById(R.id.ble_timer);
         mTimer = new CountDownTimer(REFRESH_MAC_TIME, 1000) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
index 91b3a6c..8718f57 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleServerService.java
@@ -33,10 +33,15 @@
 import android.bluetooth.BluetoothGattService;
 import android.bluetooth.BluetoothManager;
 import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.BluetoothLeAdvertiser;
 import android.content.Context;
 import android.content.Intent;
 import android.os.Handler;
 import android.os.IBinder;
+import android.os.ParcelUuid;
 import android.util.Log;
 import android.widget.Toast;
 
@@ -76,6 +81,8 @@
             UUID.fromString("00009997-0000-1000-8000-00805f9b34fb");
     private static final UUID DESCRIPTOR_UUID =
             UUID.fromString("00009996-0000-1000-8000-00805f9b34fb");
+    public static final UUID ADV_SERVICE_UUID=
+            UUID.fromString("00003333-0000-1000-8000-00805f9b34fb");
 
     private BluetoothManager mBluetoothManager;
     private BluetoothGattServer mGattServer;
@@ -84,12 +91,14 @@
     private Timer mNotificationTimer;
     private Handler mHandler;
     private String mReliableWriteValue;
+    private BluetoothLeAdvertiser mAdvertiser;
 
     @Override
     public void onCreate() {
         super.onCreate();
 
         mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+        mAdvertiser = mBluetoothManager.getAdapter().getBluetoothLeAdvertiser();
         mGattServer = mBluetoothManager.openGattServer(this, mCallbacks);
         mService = createService();
         if (mGattServer != null) {
@@ -106,6 +115,7 @@
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
+        startAdvertise();
         return START_NOT_STICKY;
     }
 
@@ -117,6 +127,7 @@
     @Override
     public void onDestroy() {
         super.onDestroy();
+        stopAdvertise();
         if (mGattServer == null) {
            return;
         }
@@ -366,5 +377,26 @@
             }
         }
     };
+
+    private void startAdvertise() {
+        if (DEBUG) Log.d(TAG, "startAdvertise");
+        AdvertiseData data = new AdvertiseData.Builder()
+            .addServiceData(new ParcelUuid(ADV_SERVICE_UUID), new byte[]{1,2,3})
+            .addServiceUuid(new ParcelUuid(ADV_SERVICE_UUID))
+            .build();
+        AdvertiseSettings setting = new AdvertiseSettings.Builder()
+            .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
+            .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM)
+            .setConnectable(true)
+            .build();
+        mAdvertiser.startAdvertising(setting, data, mAdvertiseCallback);
+    }
+
+    private void stopAdvertise() {
+        if (DEBUG) Log.d(TAG, "stopAdvertise");
+        mAdvertiser.stopAdvertising(mAdvertiseCallback);
+    }
+
+    private final AdvertiseCallback mAdvertiseCallback = new AdvertiseCallback(){};
 }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
index 9895f02..56d73aa 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BluetoothTestActivity.java
@@ -20,7 +20,12 @@
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 
+import android.bluetooth.BluetoothAdapter;
 import android.os.Bundle;
+import android.widget.Toast;
+
+import java.util.ArrayList;
+import java.util.List;
 
 public class BluetoothTestActivity extends PassFailButtons.TestListActivity {
 
@@ -31,6 +36,32 @@
         setPassFailButtonClickListeners();
         setInfoResources(R.string.bluetooth_test, R.string.bluetooth_test_info, -1);
 
-        setTestListAdapter(new ManifestTestListAdapter(this, getClass().getName()));
+        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (bluetoothAdapter == null) {
+            Toast.makeText(this, "bluetooth not supported", Toast.LENGTH_SHORT);
+            return;
+        }
+
+        List<String> disabledTestArray = new ArrayList<String>();
+        for (String s : this.getResources().getStringArray(R.array.disabled_tests)) {
+            disabledTestArray.add(s);
+        }
+        if (!this.getPackageManager().hasSystemFeature("android.hardware.bluetooth_le")) {
+            disabledTestArray.add(
+                  "com.android.cts.verifier.bluetooth.BleAdvertiserTestActivity");
+            disabledTestArray.add(
+                  "com.android.cts.verifier.bluetooth.BleScannerTestActivity");
+            disabledTestArray.add(
+                  "com.android.cts.verifier.bluetooth.BleClientTestActivity");
+            disabledTestArray.add(
+                  "com.android.cts.verifier.bluetooth.BleServerStartActivity");
+        } else if (!bluetoothAdapter.isMultipleAdvertisementSupported()) {
+            disabledTestArray.add(
+                  "com.android.cts.verifier.bluetooth.BleAdvertiserTestActivity");
+            disabledTestArray.add(
+                  "com.android.cts.verifier.bluetooth.BleServerStartActivity");
+        }
+        setTestListAdapter(new ManifestTestListAdapter(this, getClass().getName(),
+                disabledTestArray.toArray(new String[disabledTestArray.size()])));
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/DevicePickerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/DevicePickerActivity.java
index be71f66..a5dea4b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/DevicePickerActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/DevicePickerActivity.java
@@ -29,12 +29,12 @@
 import android.content.IntentFilter;
 import android.os.Bundle;
 import android.view.View;
-import android.view.Window;
 import android.view.View.OnClickListener;
 import android.widget.AdapterView;
 import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.ListView;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.AdapterView.OnItemClickListener;
 
@@ -61,12 +61,15 @@
 
     private TextView mEmptyNewView;
 
+    private ProgressBar mProgressBar;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
         setContentView(R.layout.bt_device_picker);
 
+        mProgressBar = (ProgressBar) findViewById(R.id.bt_progress_bar);
+
         mPairedDevicesAdapter = new ArrayAdapter<Device>(this, R.layout.bt_device_name);
         ListView pairedDevicesListView = (ListView) findViewById(R.id.bt_paired_devices);
         pairedDevicesListView.setAdapter(mPairedDevicesAdapter);
@@ -182,10 +185,10 @@
         public void onReceive(Context context, Intent intent) {
             if (BluetoothAdapter.ACTION_DISCOVERY_STARTED.equals(intent.getAction())) {
                 mEmptyNewView.setText(R.string.bt_scanning);
-                setProgressBarIndeterminateVisibility(true);
+                mProgressBar.setVisibility(View.VISIBLE);
             } else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED.equals(intent.getAction())) {
                 mEmptyNewView.setText(R.string.bt_no_devices);
-                setProgressBarIndeterminateVisibility(false);
+                mProgressBar.setVisibility(View.INVISIBLE);
             } else if (BluetoothDevice.ACTION_FOUND.equals(intent.getAction())) {
                 BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
                 if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/MessageTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/MessageTestActivity.java
index 2c6324b..4e0b78f 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/MessageTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/MessageTestActivity.java
@@ -33,10 +33,10 @@
 import android.os.Message;
 import android.view.View;
 import android.view.View.OnClickListener;
-import android.view.Window;
 import android.widget.ArrayAdapter;
 import android.widget.Button;
 import android.widget.ListView;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.Toast;
 
@@ -71,6 +71,8 @@
 
     private AlertDialog mInstructionsDialog;
 
+    private ProgressBar mProgressBar;
+
     private String mDeviceAddress;
 
     private final boolean mSecure;
@@ -89,10 +91,11 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
         setContentView(R.layout.bt_messages);
         setPassFailButtonClickListeners();
 
+        mProgressBar = (ProgressBar) findViewById(R.id.bt_progress_bar);
+
         if (mServer) {
             setTitle(mSecure ? R.string.bt_secure_server : R.string.bt_insecure_server);
         } else {
@@ -217,18 +220,18 @@
         switch (state) {
             case BluetoothChatService.STATE_LISTEN:
                 setEmptyViewText(R.string.bt_waiting);
-                setProgressBarIndeterminateVisibility(true);
+                mProgressBar.setVisibility(View.VISIBLE);
                 showInstructionsDialog();
                 break;
 
             case BluetoothChatService.STATE_CONNECTING:
                 setEmptyViewText(R.string.bt_connecting);
-                setProgressBarIndeterminateVisibility(true);
+                mProgressBar.setVisibility(View.VISIBLE);
                 break;
 
             case BluetoothChatService.STATE_CONNECTED:
                 setEmptyViewText(R.string.bt_no_messages);
-                setProgressBarIndeterminateVisibility(false);
+                mProgressBar.setVisibility(View.INVISIBLE);
 
                 hideInstructionsDialog();
                 sendInitialMessageFromClient();
@@ -236,7 +239,7 @@
 
             case BluetoothChatService.STATE_NONE:
                 setEmptyViewText(R.string.bt_no_messages);
-                setProgressBarIndeterminateVisibility(false);
+                mProgressBar.setVisibility(View.INVISIBLE);
                 break;
         }
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/TestAdapter.java b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/TestAdapter.java
index 631fe36..8f4ed56 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/TestAdapter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/TestAdapter.java
@@ -80,7 +80,7 @@
         if (convertView != null) {
             vg = (ViewGroup) convertView;
         } else {
-            vg = (ViewGroup) inflater.inflate(R.layout.ble_server_start_item, null);
+            vg = (ViewGroup) inflater.inflate(R.layout.ble_test_item, null);
         }
 
         Test test = tests.get(position);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientDescriptorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsException.java
similarity index 62%
rename from apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientDescriptorActivity.java
rename to apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsException.java
index ab2229a..d390bb1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/bluetooth/BleClientDescriptorActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsException.java
@@ -14,10 +14,21 @@
  * limitations under the License.
  */
 
-package com.android.cts.verifier.bluetooth;
+package com.android.cts.verifier.camera.its;
 
-public class BleClientDescriptorActivity extends BleReadWriteActivity {
-    public BleClientDescriptorActivity() {
-        super(BleReadWriteActivity.DESCRIPTOR);
+/**
+ * All exceptions are converted to ItsExceptions.
+ */
+class ItsException extends Exception {
+    public ItsException(Throwable cause) {
+        super(cause);
     }
-}
\ No newline at end of file
+
+    public ItsException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public ItsException(String message) {
+        super(message);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsSerializer.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsSerializer.java
new file mode 100644
index 0000000..cf8365a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsSerializer.java
@@ -0,0 +1,714 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.camera.its;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.BlackLevelPattern;
+import android.hardware.camera2.params.ColorSpaceTransform;
+import android.hardware.camera2.params.Face;
+import android.hardware.camera2.params.LensShadingMap;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.RggbChannelVector;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.params.TonemapCurve;
+import android.location.Location;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Rational;
+import android.util.Size;
+import android.util.SizeF;
+import android.util.Range;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Class to deal with serializing and deserializing between JSON and Camera2 objects.
+ */
+public class ItsSerializer {
+    public static final String TAG = ItsSerializer.class.getSimpleName();
+
+    private static class MetadataEntry {
+        public MetadataEntry(String k, Object v) {
+            key = k;
+            value = v;
+        }
+        public String key;
+        public Object value;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeRational(Rational rat) throws org.json.JSONException {
+        JSONObject ratObj = new JSONObject();
+        ratObj.put("numerator", rat.getNumerator());
+        ratObj.put("denominator", rat.getDenominator());
+        return ratObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeSize(Size size) throws org.json.JSONException {
+        JSONObject sizeObj = new JSONObject();
+        sizeObj.put("width", size.getWidth());
+        sizeObj.put("height", size.getHeight());
+        return sizeObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeSizeF(SizeF size) throws org.json.JSONException {
+        JSONObject sizeObj = new JSONObject();
+        sizeObj.put("width", size.getWidth());
+        sizeObj.put("height", size.getHeight());
+        return sizeObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeRect(Rect rect) throws org.json.JSONException {
+        JSONObject rectObj = new JSONObject();
+        rectObj.put("left", rect.left);
+        rectObj.put("right", rect.right);
+        rectObj.put("top", rect.top);
+        rectObj.put("bottom", rect.bottom);
+        return rectObj;
+    }
+
+    private static Object serializePoint(Point point) throws org.json.JSONException {
+        JSONObject pointObj = new JSONObject();
+        pointObj.put("x", point.x);
+        pointObj.put("y", point.y);
+        return pointObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeFace(Face face)
+            throws org.json.JSONException {
+        JSONObject faceObj = new JSONObject();
+        faceObj.put("bounds", serializeRect(face.getBounds()));
+        faceObj.put("score", face.getScore());
+        faceObj.put("id", face.getId());
+        faceObj.put("leftEye", serializePoint(face.getLeftEyePosition()));
+        faceObj.put("rightEye", serializePoint(face.getRightEyePosition()));
+        faceObj.put("mouth", serializePoint(face.getMouthPosition()));
+        return faceObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeStreamConfigurationMap(
+            StreamConfigurationMap map)
+            throws org.json.JSONException {
+        // TODO: Serialize the rest of the StreamConfigurationMap fields.
+        JSONObject mapObj = new JSONObject();
+        JSONArray cfgArray = new JSONArray();
+        int fmts[] = map.getOutputFormats();
+        if (fmts != null) {
+            for (int fi = 0; fi < Array.getLength(fmts); fi++) {
+                Size sizes[] = map.getOutputSizes(fmts[fi]);
+                if (sizes != null) {
+                    for (int si = 0; si < Array.getLength(sizes); si++) {
+                        JSONObject obj = new JSONObject();
+                        obj.put("format", fmts[fi]);
+                        obj.put("width",sizes[si].getWidth());
+                        obj.put("height", sizes[si].getHeight());
+                        obj.put("input", false);
+                        obj.put("minFrameDuration",
+                                map.getOutputMinFrameDuration(fmts[fi],sizes[si]));
+                        cfgArray.put(obj);
+                    }
+                }
+            }
+        }
+        mapObj.put("availableStreamConfigurations", cfgArray);
+        return mapObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeMeteringRectangle(MeteringRectangle rect)
+            throws org.json.JSONException {
+        JSONObject rectObj = new JSONObject();
+        rectObj.put("x", rect.getX());
+        rectObj.put("y", rect.getY());
+        rectObj.put("width", rect.getWidth());
+        rectObj.put("height", rect.getHeight());
+        rectObj.put("weight", rect.getMeteringWeight());
+        return rectObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializePair(Pair pair)
+            throws org.json.JSONException {
+        JSONArray pairObj = new JSONArray();
+        pairObj.put(pair.first);
+        pairObj.put(pair.second);
+        return pairObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeRange(Range range)
+            throws org.json.JSONException {
+        JSONArray rangeObj = new JSONArray();
+        rangeObj.put(range.getLower());
+        rangeObj.put(range.getUpper());
+        return rangeObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeColorSpaceTransform(ColorSpaceTransform xform)
+            throws org.json.JSONException {
+        JSONArray xformObj = new JSONArray();
+        for (int row = 0; row < 3; row++) {
+            for (int col = 0; col < 3; col++) {
+                xformObj.put(serializeRational(xform.getElement(col,row)));
+            }
+        }
+        return xformObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeTonemapCurve(TonemapCurve curve)
+            throws org.json.JSONException {
+        JSONObject curveObj = new JSONObject();
+        String names[] = {"red", "green", "blue"};
+        for (int ch = 0; ch < 3; ch++) {
+            JSONArray curveArr = new JSONArray();
+            int len = curve.getPointCount(ch);
+            for (int i = 0; i < len; i++) {
+                curveArr.put(curve.getPoint(ch,i).x);
+                curveArr.put(curve.getPoint(ch,i).y);
+            }
+            curveObj.put(names[ch], curveArr);
+        }
+        return curveObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeRggbChannelVector(RggbChannelVector vec)
+            throws org.json.JSONException {
+        JSONArray vecObj = new JSONArray();
+        vecObj.put(vec.getRed());
+        vecObj.put(vec.getGreenEven());
+        vecObj.put(vec.getGreenOdd());
+        vecObj.put(vec.getBlue());
+        return vecObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeBlackLevelPattern(BlackLevelPattern pat)
+            throws org.json.JSONException {
+        int patVals[] = new int[4];
+        pat.copyTo(patVals, 0);
+        JSONArray patObj = new JSONArray();
+        patObj.put(patVals[0]);
+        patObj.put(patVals[1]);
+        patObj.put(patVals[2]);
+        patObj.put(patVals[3]);
+        return patObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeLocation(Location loc)
+            throws org.json.JSONException {
+        return loc.toString();
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeLensShadingMap(LensShadingMap map)
+            throws org.json.JSONException {
+        JSONArray mapObj = new JSONArray();
+        for (int row = 0; row < map.getRowCount(); row++) {
+            for (int col = 0; col < map.getColumnCount(); col++) {
+                for (int ch = 0; ch < 4; ch++) {
+                    mapObj.put(map.getGainFactor(ch, col, row));
+                }
+            }
+        }
+        return mapObj;
+    }
+
+    private static String getKeyName(Object keyObj) throws ItsException {
+        if (keyObj.getClass() == CaptureResult.Key.class
+                || keyObj.getClass() == TotalCaptureResult.class) {
+            return ((CaptureResult.Key)keyObj).getName();
+        } else if (keyObj.getClass() == CaptureRequest.Key.class) {
+            return ((CaptureRequest.Key)keyObj).getName();
+        } else if (keyObj.getClass() == CameraCharacteristics.Key.class) {
+            return ((CameraCharacteristics.Key)keyObj).getName();
+        }
+        throw new ItsException("Invalid key object");
+    }
+
+    private static Object getKeyValue(CameraMetadata md, Object keyObj) throws ItsException {
+        if (md.getClass() == CaptureResult.class || md.getClass() == TotalCaptureResult.class) {
+            return ((CaptureResult)md).get((CaptureResult.Key)keyObj);
+        } else if (md.getClass() == CaptureRequest.class) {
+            return ((CaptureRequest)md).get((CaptureRequest.Key)keyObj);
+        } else if (md.getClass() == CameraCharacteristics.class) {
+            return ((CameraCharacteristics)md).get((CameraCharacteristics.Key)keyObj);
+        }
+        throw new ItsException("Invalid key object");
+    }
+
+    @SuppressWarnings("unchecked")
+    private static MetadataEntry serializeEntry(Type keyType, Object keyObj, CameraMetadata md)
+            throws ItsException {
+        String keyName = getKeyName(keyObj);
+
+        try {
+            Object keyValue = getKeyValue(md, keyObj);
+            if (keyValue == null) {
+                return new MetadataEntry(keyName, JSONObject.NULL);
+            } else if (keyType == Float.class) {
+                // The JSON serializer doesn't handle floating point NaN or Inf.
+                if (((Float)keyValue).isInfinite() || ((Float)keyValue).isNaN()) {
+                    Logt.w(TAG, "Inf/NaN floating point value serialized: " + keyName);
+                    return null;
+                }
+                return new MetadataEntry(keyName, keyValue);
+            } else if (keyType == Integer.class || keyType == Long.class || keyType == Byte.class ||
+                       keyType == Boolean.class || keyType == String.class) {
+                return new MetadataEntry(keyName, keyValue);
+            } else if (keyType == Rational.class) {
+                return new MetadataEntry(keyName, serializeRational((Rational)keyValue));
+            } else if (keyType == Size.class) {
+                return new MetadataEntry(keyName, serializeSize((Size)keyValue));
+            } else if (keyType == SizeF.class) {
+                return new MetadataEntry(keyName, serializeSizeF((SizeF)keyValue));
+            } else if (keyType == Rect.class) {
+                return new MetadataEntry(keyName, serializeRect((Rect)keyValue));
+            } else if (keyType == Face.class) {
+                return new MetadataEntry(keyName, serializeFace((Face)keyValue));
+            } else if (keyType == StreamConfigurationMap.class) {
+                return new MetadataEntry(keyName,
+                        serializeStreamConfigurationMap((StreamConfigurationMap)keyValue));
+            } else if (keyType instanceof ParameterizedType &&
+                    ((ParameterizedType)keyType).getRawType() == Range.class) {
+                return new MetadataEntry(keyName, serializeRange((Range)keyValue));
+            } else if (keyType == ColorSpaceTransform.class) {
+                return new MetadataEntry(keyName,
+                        serializeColorSpaceTransform((ColorSpaceTransform)keyValue));
+            } else if (keyType == MeteringRectangle.class) {
+                return new MetadataEntry(keyName,
+                        serializeMeteringRectangle((MeteringRectangle)keyValue));
+            } else if (keyType == Location.class) {
+                return new MetadataEntry(keyName,
+                        serializeLocation((Location)keyValue));
+            } else if (keyType == RggbChannelVector.class) {
+                return new MetadataEntry(keyName,
+                        serializeRggbChannelVector((RggbChannelVector)keyValue));
+            } else if (keyType == BlackLevelPattern.class) {
+                return new MetadataEntry(keyName,
+                        serializeBlackLevelPattern((BlackLevelPattern)keyValue));
+            } else if (keyType == TonemapCurve.class) {
+                return new MetadataEntry(keyName,
+                        serializeTonemapCurve((TonemapCurve)keyValue));
+            } else if (keyType == Point.class) {
+                return new MetadataEntry(keyName,
+                        serializePoint((Point)keyValue));
+            } else if (keyType == LensShadingMap.class) {
+                return new MetadataEntry(keyName,
+                        serializeLensShadingMap((LensShadingMap)keyValue));
+            } else {
+                Logt.w(TAG, String.format("Serializing unsupported key type: " + keyType));
+                return null;
+            }
+        } catch (org.json.JSONException e) {
+            throw new ItsException("JSON error for key: " + keyName + ": ", e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static MetadataEntry serializeArrayEntry(Type keyType, Object keyObj, CameraMetadata md)
+            throws ItsException {
+        String keyName = getKeyName(keyObj);
+        try {
+            Object keyValue = getKeyValue(md, keyObj);
+            if (keyValue == null) {
+                return new MetadataEntry(keyName, JSONObject.NULL);
+            }
+            int arrayLen = Array.getLength(keyValue);
+            Type elmtType = ((GenericArrayType)keyType).getGenericComponentType();
+            if (elmtType == int.class  || elmtType == float.class || elmtType == byte.class ||
+                elmtType == long.class || elmtType == double.class || elmtType == boolean.class) {
+                return new MetadataEntry(keyName, new JSONArray(keyValue));
+            } else if (elmtType == Rational.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeRational((Rational)Array.get(keyValue,i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Size.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeSize((Size)Array.get(keyValue,i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Rect.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeRect((Rect)Array.get(keyValue,i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Face.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeFace((Face)Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == StreamConfigurationMap.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeStreamConfigurationMap(
+                            (StreamConfigurationMap)Array.get(keyValue,i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType instanceof ParameterizedType &&
+                    ((ParameterizedType)elmtType).getRawType() == Range.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeRange((Range)Array.get(keyValue,i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType instanceof ParameterizedType &&
+                    ((ParameterizedType)elmtType).getRawType() == Pair.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializePair((Pair)Array.get(keyValue,i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == MeteringRectangle.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeMeteringRectangle(
+                            (MeteringRectangle)Array.get(keyValue,i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Location.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeLocation((Location)Array.get(keyValue,i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == RggbChannelVector.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeRggbChannelVector(
+                            (RggbChannelVector)Array.get(keyValue,i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == BlackLevelPattern.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeBlackLevelPattern(
+                            (BlackLevelPattern)Array.get(keyValue,i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Point.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializePoint((Point)Array.get(keyValue,i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else {
+                Logt.w(TAG, String.format("Serializing unsupported array type: " + elmtType));
+                return null;
+            }
+        } catch (org.json.JSONException e) {
+            throw new ItsException("JSON error for key: " + keyName + ": ", e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public static JSONObject serialize(CameraMetadata md)
+            throws ItsException {
+        JSONObject jsonObj = new JSONObject();
+        Field[] allFields = md.getClass().getDeclaredFields();
+        if (md.getClass() == TotalCaptureResult.class) {
+            allFields = CaptureResult.class.getDeclaredFields();
+        }
+        for (Field field : allFields) {
+            if (Modifier.isPublic(field.getModifiers()) &&
+                    Modifier.isStatic(field.getModifiers()) &&
+                    (field.getType() == CaptureRequest.Key.class
+                      || field.getType() == CaptureResult.Key.class
+                      || field.getType() == TotalCaptureResult.Key.class
+                      || field.getType() == CameraCharacteristics.Key.class) &&
+                    field.getGenericType() instanceof ParameterizedType) {
+                ParameterizedType paramType = (ParameterizedType)field.getGenericType();
+                Type[] argTypes = paramType.getActualTypeArguments();
+                if (argTypes.length > 0) {
+                    try {
+                        Type keyType = argTypes[0];
+                        Object keyObj = field.get(md);
+                        MetadataEntry entry;
+                        if (keyType instanceof GenericArrayType) {
+                            entry = serializeArrayEntry(keyType, keyObj, md);
+                        } else {
+                            entry = serializeEntry(keyType, keyObj, md);
+                        }
+
+                        // TODO: Figure this weird case out.
+                        // There is a weird case where the entry is non-null but the toString
+                        // of the entry is null, and if this happens, the null-ness spreads like
+                        // a virus and makes the whole JSON object null from the top level down.
+                        // Not sure if it's a bug in the library or I'm just not using it right.
+                        // Workaround by checking for this case explicitly and not adding the
+                        // value to the jsonObj when it is detected.
+                        if (entry != null && entry.key != null && entry.value != null
+                                          && entry.value.toString() == null) {
+                            Logt.w(TAG, "Error encountered serializing value for key: " + entry.key);
+                        } else if (entry != null) {
+                            jsonObj.put(entry.key, entry.value);
+                        } else {
+                            // Ignore.
+                        }
+                    } catch (IllegalAccessException e) {
+                        throw new ItsException(
+                                "Access error for field: " + field + ": ", e);
+                    } catch (org.json.JSONException e) {
+                        throw new ItsException(
+                                "JSON error for field: " + field + ": ", e);
+                    }
+                }
+            }
+        }
+        return jsonObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    public static CaptureRequest.Builder deserialize(CaptureRequest.Builder mdDefault,
+            JSONObject jsonReq) throws ItsException {
+        try {
+            Logt.i(TAG, "Parsing JSON capture request ...");
+
+            // Iterate over the CaptureRequest reflected fields.
+            CaptureRequest.Builder md = mdDefault;
+            Field[] allFields = CaptureRequest.class.getDeclaredFields();
+            for (Field field : allFields) {
+                if (Modifier.isPublic(field.getModifiers()) &&
+                        Modifier.isStatic(field.getModifiers()) &&
+                        field.getType() == CaptureRequest.Key.class &&
+                        field.getGenericType() instanceof ParameterizedType) {
+                    ParameterizedType paramType = (ParameterizedType)field.getGenericType();
+                    Type[] argTypes = paramType.getActualTypeArguments();
+                    if (argTypes.length > 0) {
+                        CaptureRequest.Key key = (CaptureRequest.Key)field.get(md);
+                        String keyName = key.getName();
+                        Type keyType = argTypes[0];
+
+                        // For each reflected CaptureRequest entry, look inside the JSON object
+                        // to see if it is being set. If it is found, remove the key from the
+                        // JSON object. After this process, there should be no keys left in the
+                        // JSON (otherwise an invalid key was specified).
+
+                        if (jsonReq.has(keyName) && !jsonReq.isNull(keyName)) {
+                            if (keyType instanceof GenericArrayType) {
+                                Type elmtType =
+                                        ((GenericArrayType)keyType).getGenericComponentType();
+                                JSONArray ja = jsonReq.getJSONArray(keyName);
+                                Object val[] = new Object[ja.length()];
+                                for (int i = 0; i < ja.length(); i++) {
+                                    if (elmtType == int.class) {
+                                        Array.set(val, i, ja.getInt(i));
+                                    } else if (elmtType == byte.class) {
+                                        Array.set(val, i, (byte)ja.getInt(i));
+                                    } else if (elmtType == float.class) {
+                                        Array.set(val, i, (float)ja.getDouble(i));
+                                    } else if (elmtType == long.class) {
+                                        Array.set(val, i, ja.getLong(i));
+                                    } else if (elmtType == double.class) {
+                                        Array.set(val, i, ja.getDouble(i));
+                                    } else if (elmtType == boolean.class) {
+                                        Array.set(val, i, ja.getBoolean(i));
+                                    } else if (elmtType == String.class) {
+                                        Array.set(val, i, ja.getString(i));
+                                    } else if (elmtType == Size.class){
+                                        JSONObject obj = ja.getJSONObject(i);
+                                        Array.set(val, i, new Size(
+                                                obj.getInt("width"), obj.getInt("height")));
+                                    } else if (elmtType == Rect.class) {
+                                        JSONObject obj = ja.getJSONObject(i);
+                                        Array.set(val, i, new Rect(
+                                                obj.getInt("left"), obj.getInt("top"),
+                                                obj.getInt("bottom"), obj.getInt("right")));
+                                    } else if (elmtType == Rational.class) {
+                                        JSONObject obj = ja.getJSONObject(i);
+                                        Array.set(val, i, new Rational(
+                                                obj.getInt("numerator"),
+                                                obj.getInt("denominator")));
+                                    } else if (elmtType == RggbChannelVector.class) {
+                                        JSONArray arr = ja.getJSONArray(i);
+                                        Array.set(val, i, new RggbChannelVector(
+                                                (float)arr.getDouble(0),
+                                                (float)arr.getDouble(1),
+                                                (float)arr.getDouble(2),
+                                                (float)arr.getDouble(3)));
+                                    } else if (elmtType == ColorSpaceTransform.class) {
+                                        JSONArray arr = ja.getJSONArray(i);
+                                        Rational xform[] = new Rational[9];
+                                        for (int j = 0; j < 9; j++) {
+                                            xform[j] = new Rational(
+                                                    arr.getJSONObject(j).getInt("numerator"),
+                                                    arr.getJSONObject(j).getInt("denominator"));
+                                        }
+                                        Array.set(val, i, new ColorSpaceTransform(xform));
+                                    } else if (elmtType == MeteringRectangle.class) {
+                                        JSONObject obj = ja.getJSONObject(i);
+                                        Array.set(val, i, new MeteringRectangle(
+                                                obj.getInt("x"),
+                                                obj.getInt("y"),
+                                                obj.getInt("width"),
+                                                obj.getInt("height"),
+                                                obj.getInt("weight")));
+                                    } else {
+                                        throw new ItsException(
+                                                "Failed to parse key from JSON: " + keyName);
+                                    }
+                                }
+                                if (val != null) {
+                                    Logt.i(TAG, "Set: "+keyName+" -> "+Arrays.toString(val));
+                                    md.set(key, val);
+                                    jsonReq.remove(keyName);
+                                }
+                            } else {
+                                Object val = null;
+                                if (keyType == Integer.class) {
+                                    val = jsonReq.getInt(keyName);
+                                } else if (keyType == Byte.class) {
+                                    val = (byte)jsonReq.getInt(keyName);
+                                } else if (keyType == Double.class) {
+                                    val = jsonReq.getDouble(keyName);
+                                } else if (keyType == Long.class) {
+                                    val = jsonReq.getLong(keyName);
+                                } else if (keyType == Float.class) {
+                                    val = (float)jsonReq.getDouble(keyName);
+                                } else if (keyType == Boolean.class) {
+                                    val = jsonReq.getBoolean(keyName);
+                                } else if (keyType == String.class) {
+                                    val = jsonReq.getString(keyName);
+                                } else if (keyType == Size.class) {
+                                    JSONObject obj = jsonReq.getJSONObject(keyName);
+                                    val = new Size(
+                                            obj.getInt("width"), obj.getInt("height"));
+                                } else if (keyType == Rect.class) {
+                                    JSONObject obj = jsonReq.getJSONObject(keyName);
+                                    val = new Rect(
+                                            obj.getInt("left"), obj.getInt("top"),
+                                            obj.getInt("right"), obj.getInt("bottom"));
+                                } else if (keyType == Rational.class) {
+                                    JSONObject obj = jsonReq.getJSONObject(keyName);
+                                    val = new Rational(obj.getInt("numerator"),
+                                                       obj.getInt("denominator"));
+                                } else if (keyType == RggbChannelVector.class) {
+                                    JSONObject obj = jsonReq.optJSONObject(keyName);
+                                    JSONArray arr = jsonReq.optJSONArray(keyName);
+                                    if (arr != null) {
+                                        val = new RggbChannelVector(
+                                                (float)arr.getDouble(0),
+                                                (float)arr.getDouble(1),
+                                                (float)arr.getDouble(2),
+                                                (float)arr.getDouble(3));
+                                    } else if (obj != null) {
+                                        val = new RggbChannelVector(
+                                                (float)obj.getDouble("red"),
+                                                (float)obj.getDouble("greenEven"),
+                                                (float)obj.getDouble("greenOdd"),
+                                                (float)obj.getDouble("blue"));
+                                    } else {
+                                        throw new ItsException("Invalid RggbChannelVector object");
+                                    }
+                                } else if (keyType == ColorSpaceTransform.class) {
+                                    JSONArray arr = jsonReq.getJSONArray(keyName);
+                                    Rational a[] = new Rational[9];
+                                    for (int i = 0; i < 9; i++) {
+                                        a[i] = new Rational(
+                                                arr.getJSONObject(i).getInt("numerator"),
+                                                arr.getJSONObject(i).getInt("denominator"));
+                                    }
+                                    val = new ColorSpaceTransform(a);
+                                } else if (keyType instanceof ParameterizedType &&
+                                        ((ParameterizedType)keyType).getRawType() == Range.class &&
+                                        ((ParameterizedType)keyType).getActualTypeArguments().length == 1 &&
+                                        ((ParameterizedType)keyType).getActualTypeArguments()[0] == Integer.class) {
+                                    JSONArray arr = jsonReq.getJSONArray(keyName);
+                                    val = new Range<Integer>(arr.getInt(0), arr.getInt(1));
+                                } else {
+                                    throw new ItsException(
+                                            "Failed to parse key from JSON: " +
+                                            keyName + ", " + keyType);
+                                }
+                                if (val != null) {
+                                    Logt.i(TAG, "Set: " + keyName + " -> " + val);
+                                    md.set(key ,val);
+                                    jsonReq.remove(keyName);
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+
+            // Ensure that there were no invalid keys in the JSON request object.
+            if (jsonReq.length() != 0) {
+                throw new ItsException("Invalid JSON key(s): " + jsonReq.toString());
+            }
+
+            Logt.i(TAG, "Parsing JSON capture request completed");
+            return md;
+        } catch (java.lang.IllegalAccessException e) {
+            throw new ItsException("Access error: ", e);
+        } catch (org.json.JSONException e) {
+            throw new ItsException("JSON error: ", e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public static List<CaptureRequest.Builder> deserializeRequestList(
+            CameraDevice device, JSONObject jsonObjTop)
+            throws ItsException {
+        try {
+            List<CaptureRequest.Builder> requests = null;
+            JSONArray jsonReqs = jsonObjTop.getJSONArray("captureRequests");
+            requests = new LinkedList<CaptureRequest.Builder>();
+            for (int i = 0; i < jsonReqs.length(); i++) {
+                CaptureRequest.Builder templateReq = device.createCaptureRequest(
+                        CameraDevice.TEMPLATE_STILL_CAPTURE);
+                requests.add(
+                    deserialize(templateReq, jsonReqs.getJSONObject(i)));
+            }
+            return requests;
+        } catch (org.json.JSONException e) {
+            throw new ItsException("JSON error: ", e);
+        } catch (android.hardware.camera2.CameraAccessException e) {
+            throw new ItsException("Access error: ", e);
+        }
+    }
+}
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
new file mode 100644
index 0000000..a305cd2
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
@@ -0,0 +1,1334 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.camera.its;
+
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CaptureFailure;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.DngCreator;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.media.Image;
+import android.media.ImageReader;
+import android.net.Uri;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Vibrator;
+import android.util.Log;
+import android.util.Rational;
+import android.util.Size;
+import android.view.Surface;
+
+import com.android.ex.camera2.blocking.BlockingCameraManager;
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
+import com.android.ex.camera2.blocking.BlockingSessionCallback;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.BufferedWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.math.BigInteger;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.security.MessageDigest;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ItsService extends Service implements SensorEventListener {
+    public static final String TAG = ItsService.class.getSimpleName();
+
+    // Timeouts, in seconds.
+    public static final int TIMEOUT_CALLBACK = 3;
+    public static final int TIMEOUT_3A = 10;
+
+    // State transition timeouts, in ms.
+    private static final long TIMEOUT_IDLE_MS = 2000;
+    private static final long TIMEOUT_STATE_MS = 500;
+
+    // Timeout to wait for a capture result after the capture buffer has arrived, in ms.
+    private static final long TIMEOUT_CAP_RES = 2000;
+
+    private static final int MAX_CONCURRENT_READER_BUFFERS = 10;
+
+    // Supports at most RAW+YUV+JPEG, one surface each.
+    private static final int MAX_NUM_OUTPUT_SURFACES = 3;
+
+    public static final int SERVERPORT = 6000;
+
+    public static final String REGION_KEY = "regions";
+    public static final String REGION_AE_KEY = "ae";
+    public static final String REGION_AWB_KEY = "awb";
+    public static final String REGION_AF_KEY = "af";
+    public static final String LOCK_AE_KEY = "aeLock";
+    public static final String LOCK_AWB_KEY = "awbLock";
+    public static final String TRIGGER_KEY = "triggers";
+    public static final String TRIGGER_AE_KEY = "ae";
+    public static final String TRIGGER_AF_KEY = "af";
+    public static final String VIB_PATTERN_KEY = "pattern";
+    public static final String EVCOMP_KEY = "evComp";
+
+    private CameraManager mCameraManager = null;
+    private HandlerThread mCameraThread = null;
+    private Handler mCameraHandler = null;
+    private BlockingCameraManager mBlockingCameraManager = null;
+    private BlockingStateCallback mCameraListener = null;
+    private CameraDevice mCamera = null;
+    private CameraCaptureSession mSession = null;
+    private ImageReader[] mCaptureReaders = null;
+    private CameraCharacteristics mCameraCharacteristics = null;
+
+    private Vibrator mVibrator = null;
+
+    private HandlerThread mSaveThreads[] = new HandlerThread[MAX_NUM_OUTPUT_SURFACES];
+    private Handler mSaveHandlers[] = new Handler[MAX_NUM_OUTPUT_SURFACES];
+    private HandlerThread mResultThread = null;
+    private Handler mResultHandler = null;
+
+    private volatile boolean mThreadExitFlag = false;
+
+    private volatile ServerSocket mSocket = null;
+    private volatile SocketRunnable mSocketRunnableObj = null;
+    private volatile BlockingQueue<ByteBuffer> mSocketWriteQueue =
+            new LinkedBlockingDeque<ByteBuffer>();
+    private final Object mSocketWriteEnqueueLock = new Object();
+    private final Object mSocketWriteDrainLock = new Object();
+
+    private volatile BlockingQueue<Object[]> mSerializerQueue =
+            new LinkedBlockingDeque<Object[]>();
+
+    private AtomicInteger mCountCallbacksRemaining = new AtomicInteger();
+    private AtomicInteger mCountRawOrDng = new AtomicInteger();
+    private AtomicInteger mCountRaw10 = new AtomicInteger();
+    private AtomicInteger mCountJpg = new AtomicInteger();
+    private AtomicInteger mCountYuv = new AtomicInteger();
+    private AtomicInteger mCountCapRes = new AtomicInteger();
+    private boolean mCaptureRawIsDng;
+    private CaptureResult mCaptureResults[] = null;
+
+    private volatile ConditionVariable mInterlock3A = new ConditionVariable(true);
+    private volatile boolean mIssuedRequest3A = false;
+    private volatile boolean mConvergedAE = false;
+    private volatile boolean mConvergedAF = false;
+    private volatile boolean mConvergedAWB = false;
+    private volatile boolean mLockedAE = false;
+    private volatile boolean mLockedAWB = false;
+    private volatile boolean mNeedsLockedAE = false;
+    private volatile boolean mNeedsLockedAWB = false;
+
+    class MySensorEvent {
+        public Sensor sensor;
+        public int accuracy;
+        public long timestamp;
+        public float values[];
+    }
+
+    // For capturing motion sensor traces.
+    private SensorManager mSensorManager = null;
+    private Sensor mAccelSensor = null;
+    private Sensor mMagSensor = null;
+    private Sensor mGyroSensor = null;
+    private volatile LinkedList<MySensorEvent> mEvents = null;
+    private volatile Object mEventLock = new Object();
+    private volatile boolean mEventsEnabled = false;
+
+    public interface CaptureCallback {
+        void onCaptureAvailable(Image capture);
+    }
+
+    public abstract class CaptureResultListener extends CameraCaptureSession.CaptureCallback {}
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    public void onCreate() {
+        try {
+            mThreadExitFlag = false;
+
+            // Get handle to camera manager.
+            mCameraManager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
+            if (mCameraManager == null) {
+                throw new ItsException("Failed to connect to camera manager");
+            }
+            mBlockingCameraManager = new BlockingCameraManager(mCameraManager);
+            mCameraListener = new BlockingStateCallback();
+
+            // Register for motion events.
+            mEvents = new LinkedList<MySensorEvent>();
+            mSensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
+            mAccelSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+            mMagSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+            mGyroSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);
+            mSensorManager.registerListener(this, mAccelSensor, SensorManager.SENSOR_DELAY_FASTEST);
+            mSensorManager.registerListener(this, mMagSensor, SensorManager.SENSOR_DELAY_FASTEST);
+            mSensorManager.registerListener(this, mGyroSensor, SensorManager.SENSOR_DELAY_FASTEST);
+
+            // Get a handle to the system vibrator.
+            mVibrator = (Vibrator)getSystemService(Context.VIBRATOR_SERVICE);
+
+            // Create threads to receive images and save them.
+            for (int i = 0; i < MAX_NUM_OUTPUT_SURFACES; i++) {
+                mSaveThreads[i] = new HandlerThread("SaveThread" + i);
+                mSaveThreads[i].start();
+                mSaveHandlers[i] = new Handler(mSaveThreads[i].getLooper());
+            }
+
+            // Create a thread to handle object serialization.
+            (new Thread(new SerializerRunnable())).start();;
+
+            // Create a thread to receive capture results and process them.
+            mResultThread = new HandlerThread("ResultThread");
+            mResultThread.start();
+            mResultHandler = new Handler(mResultThread.getLooper());
+
+            // Create a thread for the camera device.
+            mCameraThread = new HandlerThread("ItsCameraThread");
+            mCameraThread.start();
+            mCameraHandler = new Handler(mCameraThread.getLooper());
+
+            // Create a thread to process commands, listening on a TCP socket.
+            mSocketRunnableObj = new SocketRunnable();
+            (new Thread(mSocketRunnableObj)).start();
+        } catch (ItsException e) {
+            Logt.e(TAG, "Service failed to start: ", e);
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        try {
+            // Just log a message indicating that the service is running and is able to accept
+            // socket connections.
+            while (!mThreadExitFlag && mSocket==null) {
+                Thread.sleep(1);
+            }
+            if (!mThreadExitFlag){
+                Logt.i(TAG, "ItsService ready");
+            } else {
+                Logt.e(TAG, "Starting ItsService in bad state");
+            }
+        } catch (java.lang.InterruptedException e) {
+            Logt.e(TAG, "Error starting ItsService (interrupted)", e);
+        }
+        return START_STICKY;
+    }
+
+    @Override
+    public void onDestroy() {
+        mThreadExitFlag = true;
+        for (int i = 0; i < MAX_NUM_OUTPUT_SURFACES; i++) {
+            if (mSaveThreads[i] != null) {
+                mSaveThreads[i].quit();
+                mSaveThreads[i] = null;
+            }
+        }
+        if (mResultThread != null) {
+            mResultThread.quitSafely();
+            mResultThread = null;
+        }
+        if (mCameraThread != null) {
+            mCameraThread.quitSafely();
+            mCameraThread = null;
+        }
+    }
+
+    public void openCameraDevice(int cameraId) throws ItsException {
+        Logt.i(TAG, String.format("Opening camera %d", cameraId));
+
+        String[] devices;
+        try {
+            devices = mCameraManager.getCameraIdList();
+            if (devices == null || devices.length == 0) {
+                throw new ItsException("No camera devices");
+            }
+        } catch (CameraAccessException e) {
+            throw new ItsException("Failed to get device ID list", e);
+        }
+
+        try {
+            mCamera = mBlockingCameraManager.openCamera(devices[cameraId],
+                    mCameraListener, mCameraHandler);
+            mCameraCharacteristics = mCameraManager.getCameraCharacteristics(
+                    devices[cameraId]);
+        } catch (CameraAccessException e) {
+            throw new ItsException("Failed to open camera", e);
+        } catch (BlockingOpenException e) {
+            throw new ItsException("Failed to open camera (after blocking)", e);
+        }
+        mSocketRunnableObj.sendResponse("cameraOpened", "");
+    }
+
+    public void closeCameraDevice() throws ItsException {
+        try {
+            if (mCamera != null) {
+                Logt.i(TAG, "Closing camera");
+                mCamera.close();
+                mCamera = null;
+            }
+        } catch (Exception e) {
+            throw new ItsException("Failed to close device");
+        }
+        mSocketRunnableObj.sendResponse("cameraClosed", "");
+    }
+
+    class SerializerRunnable implements Runnable {
+        // Use a separate thread to perform JSON serialization (since this can be slow due to
+        // the reflection).
+        @Override
+        public void run() {
+            Logt.i(TAG, "Serializer thread starting");
+            while (! mThreadExitFlag) {
+                try {
+                    Object objs[] = mSerializerQueue.take();
+                    JSONObject jsonObj = new JSONObject();
+                    String tag = null;
+                    for (int i = 0; i < objs.length; i++) {
+                        Object obj = objs[i];
+                        if (obj instanceof String) {
+                            if (tag != null) {
+                                throw new ItsException("Multiple tags for socket response");
+                            }
+                            tag = (String)obj;
+                        } else if (obj instanceof CameraCharacteristics) {
+                            jsonObj.put("cameraProperties", ItsSerializer.serialize(
+                                    (CameraCharacteristics)obj));
+                        } else if (obj instanceof CaptureRequest) {
+                            jsonObj.put("captureRequest", ItsSerializer.serialize(
+                                    (CaptureRequest)obj));
+                        } else if (obj instanceof CaptureResult) {
+                            jsonObj.put("captureResult", ItsSerializer.serialize(
+                                    (CaptureResult)obj));
+                        } else if (obj instanceof JSONArray) {
+                            jsonObj.put("outputs", (JSONArray)obj);
+                        } else {
+                            throw new ItsException("Invalid object received for serialiation");
+                        }
+                    }
+                    if (tag == null) {
+                        throw new ItsException("No tag provided for socket response");
+                    }
+                    mSocketRunnableObj.sendResponse(tag, null, jsonObj, null);
+                    Logt.i(TAG, String.format("Serialized %s", tag));
+                } catch (org.json.JSONException e) {
+                    Logt.e(TAG, "Error serializing object", e);
+                    break;
+                } catch (ItsException e) {
+                    Logt.e(TAG, "Error serializing object", e);
+                    break;
+                } catch (java.lang.InterruptedException e) {
+                    Logt.e(TAG, "Error serializing object (interrupted)", e);
+                    break;
+                }
+            }
+            Logt.i(TAG, "Serializer thread terminated");
+        }
+    }
+
+    class SocketWriteRunnable implements Runnable {
+
+        // Use a separate thread to service a queue of objects to be written to the socket,
+        // writing each sequentially in order. This is needed since different handler functions
+        // (called on different threads) will need to send data back to the host script.
+
+        public Socket mOpenSocket = null;
+
+        public SocketWriteRunnable(Socket openSocket) {
+            mOpenSocket = openSocket;
+        }
+
+        public void setOpenSocket(Socket openSocket) {
+            mOpenSocket = openSocket;
+        }
+
+        @Override
+        public void run() {
+            Logt.i(TAG, "Socket writer thread starting");
+            while (true) {
+                try {
+                    ByteBuffer b = mSocketWriteQueue.take();
+                    synchronized(mSocketWriteDrainLock) {
+                        if (mOpenSocket == null) {
+                            continue;
+                        }
+                        if (b.hasArray()) {
+                            mOpenSocket.getOutputStream().write(b.array());
+                        } else {
+                            byte[] barray = new byte[b.capacity()];
+                            b.get(barray);
+                            mOpenSocket.getOutputStream().write(barray);
+                        }
+                        mOpenSocket.getOutputStream().flush();
+                        Logt.i(TAG, String.format("Wrote to socket: %d bytes", b.capacity()));
+                    }
+                } catch (IOException e) {
+                    Logt.e(TAG, "Error writing to socket", e);
+                    break;
+                } catch (java.lang.InterruptedException e) {
+                    Logt.e(TAG, "Error writing to socket (interrupted)", e);
+                    break;
+                }
+            }
+            Logt.i(TAG, "Socket writer thread terminated");
+        }
+    }
+
+    class SocketRunnable implements Runnable {
+
+        // Format of sent messages (over the socket):
+        // * Serialized JSON object on a single line (newline-terminated)
+        // * For byte buffers, the binary data then follows
+        //
+        // Format of received messages (from the socket):
+        // * Serialized JSON object on a single line (newline-terminated)
+
+        private Socket mOpenSocket = null;
+        private SocketWriteRunnable mSocketWriteRunnable = null;
+
+        @Override
+        public void run() {
+            Logt.i(TAG, "Socket thread starting");
+            try {
+                mSocket = new ServerSocket(SERVERPORT);
+            } catch (IOException e) {
+                Logt.e(TAG, "Failed to create socket", e);
+            }
+
+            // Create a new thread to handle writes to this socket.
+            mSocketWriteRunnable = new SocketWriteRunnable(null);
+            (new Thread(mSocketWriteRunnable)).start();
+
+            while (!mThreadExitFlag) {
+                // Receive the socket-open request from the host.
+                try {
+                    Logt.i(TAG, "Waiting for client to connect to socket");
+                    mOpenSocket = mSocket.accept();
+                    if (mOpenSocket == null) {
+                        Logt.e(TAG, "Socket connection error");
+                        break;
+                    }
+                    mSocketWriteQueue.clear();
+                    mSocketWriteRunnable.setOpenSocket(mOpenSocket);
+                    Logt.i(TAG, "Socket connected");
+                } catch (IOException e) {
+                    Logt.e(TAG, "Socket open error: ", e);
+                    break;
+                }
+
+                // Process commands over the open socket.
+                while (!mThreadExitFlag) {
+                    try {
+                        BufferedReader input = new BufferedReader(
+                                new InputStreamReader(mOpenSocket.getInputStream()));
+                        if (input == null) {
+                            Logt.e(TAG, "Failed to get socket input stream");
+                            break;
+                        }
+                        String line = input.readLine();
+                        if (line == null) {
+                            Logt.i(TAG, "Socket readline retuned null (host disconnected)");
+                            break;
+                        }
+                        processSocketCommand(line);
+                    } catch (IOException e) {
+                        Logt.e(TAG, "Socket read error: ", e);
+                        break;
+                    } catch (ItsException e) {
+                        Logt.e(TAG, "Script error: ", e);
+                        break;
+                    }
+                }
+
+                // Close socket and go back to waiting for a new connection.
+                try {
+                    synchronized(mSocketWriteDrainLock) {
+                        mSocketWriteQueue.clear();
+                        mOpenSocket.close();
+                        mOpenSocket = null;
+                        Logt.i(TAG, "Socket disconnected");
+                    }
+                } catch (java.io.IOException e) {
+                    Logt.e(TAG, "Exception closing socket");
+                }
+            }
+
+            // It's an overall error state if the code gets here; no recevery.
+            // Try to do some cleanup, but the service probably needs to be restarted.
+            Logt.i(TAG, "Socket server loop exited");
+            mThreadExitFlag = true;
+            try {
+                if (mOpenSocket != null) {
+                    mOpenSocket.close();
+                    mOpenSocket = null;
+                }
+            } catch (java.io.IOException e) {
+                Logt.w(TAG, "Exception closing socket");
+            }
+            try {
+                if (mSocket != null) {
+                    mSocket.close();
+                    mSocket = null;
+                }
+            } catch (java.io.IOException e) {
+                Logt.w(TAG, "Exception closing socket");
+            }
+        }
+
+        public void processSocketCommand(String cmd)
+                throws ItsException {
+            // Each command is a serialized JSON object.
+            try {
+                JSONObject cmdObj = new JSONObject(cmd);
+                if ("open".equals(cmdObj.getString("cmdName"))) {
+                    int cameraId = cmdObj.getInt("cameraId");
+                    openCameraDevice(cameraId);
+                } else if ("close".equals(cmdObj.getString("cmdName"))) {
+                    closeCameraDevice();
+                } else if ("getCameraProperties".equals(cmdObj.getString("cmdName"))) {
+                    doGetProps();
+                } else if ("startSensorEvents".equals(cmdObj.getString("cmdName"))) {
+                    doStartSensorEvents();
+                } else if ("getSensorEvents".equals(cmdObj.getString("cmdName"))) {
+                    doGetSensorEvents();
+                } else if ("do3A".equals(cmdObj.getString("cmdName"))) {
+                    do3A(cmdObj);
+                } else if ("doCapture".equals(cmdObj.getString("cmdName"))) {
+                    doCapture(cmdObj);
+                } else if ("doVibrate".equals(cmdObj.getString("cmdName"))) {
+                    doVibrate(cmdObj);
+                } else {
+                    throw new ItsException("Unknown command: " + cmd);
+                }
+            } catch (org.json.JSONException e) {
+                Logt.e(TAG, "Invalid command: ", e);
+            }
+        }
+
+        public void sendResponse(String tag, String str, JSONObject obj, ByteBuffer bbuf)
+                throws ItsException {
+            try {
+                JSONObject jsonObj = new JSONObject();
+                jsonObj.put("tag", tag);
+                if (str != null) {
+                    jsonObj.put("strValue", str);
+                }
+                if (obj != null) {
+                    jsonObj.put("objValue", obj);
+                }
+                if (bbuf != null) {
+                    jsonObj.put("bufValueSize", bbuf.capacity());
+                }
+                ByteBuffer bstr = ByteBuffer.wrap(
+                        (jsonObj.toString()+"\n").getBytes(Charset.defaultCharset()));
+                synchronized(mSocketWriteEnqueueLock) {
+                    if (bstr != null) {
+                        mSocketWriteQueue.put(bstr);
+                    }
+                    if (bbuf != null) {
+                        mSocketWriteQueue.put(bbuf);
+                    }
+                }
+            } catch (org.json.JSONException e) {
+                throw new ItsException("JSON error: ", e);
+            } catch (java.lang.InterruptedException e) {
+                throw new ItsException("Socket error: ", e);
+            }
+        }
+
+        public void sendResponse(String tag, String str)
+                throws ItsException {
+            sendResponse(tag, str, null, null);
+        }
+
+        public void sendResponse(String tag, JSONObject obj)
+                throws ItsException {
+            sendResponse(tag, null, obj, null);
+        }
+
+        public void sendResponseCaptureBuffer(String tag, ByteBuffer bbuf)
+                throws ItsException {
+            sendResponse(tag, null, null, bbuf);
+        }
+
+        public void sendResponse(LinkedList<MySensorEvent> events)
+                throws ItsException {
+            try {
+                JSONArray accels = new JSONArray();
+                JSONArray mags = new JSONArray();
+                JSONArray gyros = new JSONArray();
+                for (MySensorEvent event : events) {
+                    JSONObject obj = new JSONObject();
+                    obj.put("time", event.timestamp);
+                    obj.put("x", event.values[0]);
+                    obj.put("y", event.values[1]);
+                    obj.put("z", event.values[2]);
+                    if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
+                        accels.put(obj);
+                    } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
+                        mags.put(obj);
+                    } else if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {
+                        gyros.put(obj);
+                    }
+                }
+                JSONObject obj = new JSONObject();
+                obj.put("accel", accels);
+                obj.put("mag", mags);
+                obj.put("gyro", gyros);
+                sendResponse("sensorEvents", null, obj, null);
+            } catch (org.json.JSONException e) {
+                throw new ItsException("JSON error: ", e);
+            }
+        }
+
+        public void sendResponse(CameraCharacteristics props)
+                throws ItsException {
+            try {
+                Object objs[] = new Object[2];
+                objs[0] = "cameraProperties";
+                objs[1] = props;
+                mSerializerQueue.put(objs);
+            } catch (InterruptedException e) {
+                throw new ItsException("Interrupted: ", e);
+            }
+        }
+
+        public void sendResponseCaptureResult(CameraCharacteristics props,
+                                              CaptureRequest request,
+                                              CaptureResult result,
+                                              ImageReader[] readers)
+                throws ItsException {
+            try {
+                JSONArray jsonSurfaces = new JSONArray();
+                for (int i = 0; i < readers.length; i++) {
+                    JSONObject jsonSurface = new JSONObject();
+                    jsonSurface.put("width", readers[i].getWidth());
+                    jsonSurface.put("height", readers[i].getHeight());
+                    int format = readers[i].getImageFormat();
+                    if (format == ImageFormat.RAW_SENSOR) {
+                        jsonSurface.put("format", "raw");
+                    } else if (format == ImageFormat.RAW10) {
+                        jsonSurface.put("format", "raw10");
+                    } else if (format == ImageFormat.JPEG) {
+                        jsonSurface.put("format", "jpeg");
+                    } else if (format == ImageFormat.YUV_420_888) {
+                        jsonSurface.put("format", "yuv");
+                    } else {
+                        throw new ItsException("Invalid format");
+                    }
+                    jsonSurfaces.put(jsonSurface);
+                }
+
+                Object objs[] = new Object[5];
+                objs[0] = "captureResults";
+                objs[1] = props;
+                objs[2] = request;
+                objs[3] = result;
+                objs[4] = jsonSurfaces;
+                mSerializerQueue.put(objs);
+            } catch (org.json.JSONException e) {
+                throw new ItsException("JSON error: ", e);
+            } catch (InterruptedException e) {
+                throw new ItsException("Interrupted: ", e);
+            }
+        }
+    }
+
+    public ImageReader.OnImageAvailableListener
+            createAvailableListener(final CaptureCallback listener) {
+        return new ImageReader.OnImageAvailableListener() {
+            @Override
+            public void onImageAvailable(ImageReader reader) {
+                Image i = null;
+                try {
+                    i = reader.acquireNextImage();
+                    listener.onCaptureAvailable(i);
+                } finally {
+                    if (i != null) {
+                        i.close();
+                    }
+                }
+            }
+        };
+    }
+
+    private ImageReader.OnImageAvailableListener
+            createAvailableListenerDropper(final CaptureCallback listener) {
+        return new ImageReader.OnImageAvailableListener() {
+            @Override
+            public void onImageAvailable(ImageReader reader) {
+                Image i = reader.acquireNextImage();
+                i.close();
+            }
+        };
+    }
+
+    private void doStartSensorEvents() throws ItsException {
+        synchronized(mEventLock) {
+            mEventsEnabled = true;
+        }
+        mSocketRunnableObj.sendResponse("sensorEventsStarted", "");
+    }
+
+    private void doGetSensorEvents() throws ItsException {
+        synchronized(mEventLock) {
+            mSocketRunnableObj.sendResponse(mEvents);
+            mEvents.clear();
+            mEventsEnabled = false;
+        }
+    }
+
+    private void doGetProps() throws ItsException {
+        mSocketRunnableObj.sendResponse(mCameraCharacteristics);
+    }
+
+    private void prepareCaptureReader(int[] widths, int[] heights, int formats[], int numSurfaces) {
+        if (mCaptureReaders != null) {
+            for (int i = 0; i < mCaptureReaders.length; i++) {
+                if (mCaptureReaders[i] != null) {
+                    mCaptureReaders[i].close();
+                }
+            }
+        }
+        mCaptureReaders = new ImageReader[numSurfaces];
+        for (int i = 0; i < numSurfaces; i++) {
+            mCaptureReaders[i] = ImageReader.newInstance(widths[i], heights[i], formats[i],
+                    MAX_CONCURRENT_READER_BUFFERS);
+        }
+    }
+
+    private void do3A(JSONObject params) throws ItsException {
+        try {
+            // Start a 3A action, and wait for it to converge.
+            // Get the converged values for each "A", and package into JSON result for caller.
+
+            // 3A happens on full-res frames.
+            Size sizes[] = ItsUtils.getYuvOutputSizes(mCameraCharacteristics);
+            int widths[] = new int[1];
+            int heights[] = new int[1];
+            int formats[] = new int[1];
+            widths[0] = sizes[0].getWidth();
+            heights[0] = sizes[0].getHeight();
+            formats[0] = ImageFormat.YUV_420_888;
+            int width = widths[0];
+            int height = heights[0];
+
+            prepareCaptureReader(widths, heights, formats, 1);
+            List<Surface> outputSurfaces = new ArrayList<Surface>(1);
+            outputSurfaces.add(mCaptureReaders[0].getSurface());
+            BlockingSessionCallback sessionListener = new BlockingSessionCallback();
+            mCamera.createCaptureSession(outputSurfaces, sessionListener, mCameraHandler);
+            mSession = sessionListener.waitAndGetSession(TIMEOUT_IDLE_MS);
+
+            // Add a listener that just recycles buffers; they aren't saved anywhere.
+            ImageReader.OnImageAvailableListener readerListener =
+                    createAvailableListenerDropper(mCaptureCallback);
+            mCaptureReaders[0].setOnImageAvailableListener(readerListener, mSaveHandlers[0]);
+
+            // Get the user-specified regions for AE, AWB, AF.
+            // Note that the user specifies normalized [x,y,w,h], which is converted below
+            // to an [x0,y0,x1,y1] region in sensor coords. The capture request region
+            // also has a fifth "weight" element: [x0,y0,x1,y1,w].
+            MeteringRectangle[] regionAE = new MeteringRectangle[]{
+                    new MeteringRectangle(0,0,width,height,1)};
+            MeteringRectangle[] regionAF = new MeteringRectangle[]{
+                    new MeteringRectangle(0,0,width,height,1)};
+            MeteringRectangle[] regionAWB = new MeteringRectangle[]{
+                    new MeteringRectangle(0,0,width,height,1)};
+            if (params.has(REGION_KEY)) {
+                JSONObject regions = params.getJSONObject(REGION_KEY);
+                if (regions.has(REGION_AE_KEY)) {
+                    regionAE = ItsUtils.getJsonWeightedRectsFromArray(
+                            regions.getJSONArray(REGION_AE_KEY), true, width, height);
+                }
+                if (regions.has(REGION_AF_KEY)) {
+                    regionAF = ItsUtils.getJsonWeightedRectsFromArray(
+                            regions.getJSONArray(REGION_AF_KEY), true, width, height);
+                }
+                if (regions.has(REGION_AWB_KEY)) {
+                    regionAWB = ItsUtils.getJsonWeightedRectsFromArray(
+                            regions.getJSONArray(REGION_AWB_KEY), true, width, height);
+                }
+            }
+
+            // If AE or AWB lock is specified, then the 3A will converge first and then lock these
+            // values, waiting until the HAL has reported that the lock was successful.
+            mNeedsLockedAE = params.optBoolean(LOCK_AE_KEY, false);
+            mNeedsLockedAWB = params.optBoolean(LOCK_AWB_KEY, false);
+
+            // An EV compensation can be specified as part of AE convergence.
+            int evComp = params.optInt(EVCOMP_KEY, 0);
+            if (evComp != 0) {
+                Logt.i(TAG, String.format("Running 3A with AE exposure compensation value: %d", evComp));
+            }
+
+            // By default, AE and AF both get triggered, but the user can optionally override this.
+            // Also, AF won't get triggered if the lens is fixed-focus.
+            boolean doAE = true;
+            boolean doAF = true;
+            if (params.has(TRIGGER_KEY)) {
+                JSONObject triggers = params.getJSONObject(TRIGGER_KEY);
+                if (triggers.has(TRIGGER_AE_KEY)) {
+                    doAE = triggers.getBoolean(TRIGGER_AE_KEY);
+                }
+                if (triggers.has(TRIGGER_AF_KEY)) {
+                    doAF = triggers.getBoolean(TRIGGER_AF_KEY);
+                }
+            }
+            if (doAF && mCameraCharacteristics.get(
+                            CameraCharacteristics.LENS_INFO_MINIMUM_FOCUS_DISTANCE) == 0) {
+                // Send a dummy result back for the code that is waiting for this message to see
+                // that AF has converged.
+                Logt.i(TAG, "Ignoring request for AF on fixed-focus camera");
+                mSocketRunnableObj.sendResponse("afResult", "0.0");
+                doAF = false;
+            }
+
+            mInterlock3A.open();
+            mIssuedRequest3A = false;
+            mConvergedAE = false;
+            mConvergedAWB = false;
+            mConvergedAF = false;
+            mLockedAE = false;
+            mLockedAWB = false;
+            long tstart = System.currentTimeMillis();
+            boolean triggeredAE = false;
+            boolean triggeredAF = false;
+
+            Logt.i(TAG, String.format("Initiating 3A: AE:%d, AF:%d, AWB:1, AELOCK:%d, AWBLOCK:%d",
+                    doAE?1:0, doAF?1:0, mNeedsLockedAE?1:0, mNeedsLockedAWB?1:0));
+
+            // Keep issuing capture requests until 3A has converged.
+            while (true) {
+
+                // Block until can take the next 3A frame. Only want one outstanding frame
+                // at a time, to simplify the logic here.
+                if (!mInterlock3A.block(TIMEOUT_3A * 1000) ||
+                        System.currentTimeMillis() - tstart > TIMEOUT_3A * 1000) {
+                    throw new ItsException(
+                            "3A failed to converge after " + TIMEOUT_3A + " seconds.\n" +
+                            "AE converge state: " + mConvergedAE + ", \n" +
+                            "AF convergence state: " + mConvergedAF + ", \n" +
+                            "AWB convergence state: " + mConvergedAWB + ".");
+                }
+                mInterlock3A.close();
+
+                // If not converged yet, issue another capture request.
+                if (       (doAE && (!triggeredAE || !mConvergedAE))
+                        || !mConvergedAWB
+                        || (doAF && (!triggeredAF || !mConvergedAF))
+                        || (doAE && mNeedsLockedAE && !mLockedAE)
+                        || (mNeedsLockedAWB && !mLockedAWB)) {
+
+                    // Baseline capture request for 3A.
+                    CaptureRequest.Builder req = mCamera.createCaptureRequest(
+                            CameraDevice.TEMPLATE_PREVIEW);
+                    req.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
+                    req.set(CaptureRequest.CONTROL_MODE, CaptureRequest.CONTROL_MODE_AUTO);
+                    req.set(CaptureRequest.CONTROL_CAPTURE_INTENT,
+                            CaptureRequest.CONTROL_CAPTURE_INTENT_PREVIEW);
+                    req.set(CaptureRequest.CONTROL_AE_MODE,
+                            CaptureRequest.CONTROL_AE_MODE_ON);
+                    req.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, 0);
+                    req.set(CaptureRequest.CONTROL_AE_LOCK, false);
+                    req.set(CaptureRequest.CONTROL_AE_REGIONS, regionAE);
+                    req.set(CaptureRequest.CONTROL_AF_MODE,
+                            CaptureRequest.CONTROL_AF_MODE_AUTO);
+                    req.set(CaptureRequest.CONTROL_AF_REGIONS, regionAF);
+                    req.set(CaptureRequest.CONTROL_AWB_MODE,
+                            CaptureRequest.CONTROL_AWB_MODE_AUTO);
+                    req.set(CaptureRequest.CONTROL_AWB_LOCK, false);
+                    req.set(CaptureRequest.CONTROL_AWB_REGIONS, regionAWB);
+
+                    if (evComp != 0) {
+                        req.set(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, evComp);
+                    }
+
+                    if (mConvergedAE && mNeedsLockedAE) {
+                        req.set(CaptureRequest.CONTROL_AE_LOCK, true);
+                    }
+                    if (mConvergedAWB && mNeedsLockedAWB) {
+                        req.set(CaptureRequest.CONTROL_AWB_LOCK, true);
+                    }
+
+                    // Trigger AE first.
+                    if (doAE && !triggeredAE) {
+                        Logt.i(TAG, "Triggering AE");
+                        req.set(CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER,
+                                CaptureRequest.CONTROL_AE_PRECAPTURE_TRIGGER_START);
+                        triggeredAE = true;
+                    }
+
+                    // After AE has converged, trigger AF.
+                    if (doAF && !triggeredAF && (!doAE || (triggeredAE && mConvergedAE))) {
+                        Logt.i(TAG, "Triggering AF");
+                        req.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                                CaptureRequest.CONTROL_AF_TRIGGER_START);
+                        triggeredAF = true;
+                    }
+
+                    req.addTarget(mCaptureReaders[0].getSurface());
+
+                    mIssuedRequest3A = true;
+                    mSession.capture(req.build(), mCaptureResultListener, mResultHandler);
+                } else {
+                    mSocketRunnableObj.sendResponse("3aConverged", "");
+                    Logt.i(TAG, "3A converged");
+                    break;
+                }
+            }
+        } catch (android.hardware.camera2.CameraAccessException e) {
+            throw new ItsException("Access error: ", e);
+        } catch (org.json.JSONException e) {
+            throw new ItsException("JSON error: ", e);
+        } finally {
+            mSocketRunnableObj.sendResponse("3aDone", "");
+        }
+    }
+
+    private void doVibrate(JSONObject params) throws ItsException {
+        try {
+            if (mVibrator == null) {
+                throw new ItsException("Unable to start vibrator");
+            }
+            JSONArray patternArray = params.getJSONArray(VIB_PATTERN_KEY);
+            int len = patternArray.length();
+            long pattern[] = new long[len];
+            for (int i = 0; i < len; i++) {
+                pattern[i] = patternArray.getLong(i);
+            }
+            Logt.i(TAG, String.format("Starting vibrator, pattern length %d",len));
+            mVibrator.vibrate(pattern, -1);
+            mSocketRunnableObj.sendResponse("vibrationStarted", "");
+        } catch (org.json.JSONException e) {
+            throw new ItsException("JSON error: ", e);
+        }
+    }
+
+    private void doCapture(JSONObject params) throws ItsException {
+        try {
+            // Parse the JSON to get the list of capture requests.
+            List<CaptureRequest.Builder> requests = ItsSerializer.deserializeRequestList(
+                    mCamera, params);
+
+            // Set the output surface(s) and listeners.
+            int widths[] = new int[MAX_NUM_OUTPUT_SURFACES];
+            int heights[] = new int[MAX_NUM_OUTPUT_SURFACES];
+            int formats[] = new int[MAX_NUM_OUTPUT_SURFACES];
+            int numSurfaces = 0;
+            try {
+                mCountRawOrDng.set(0);
+                mCountJpg.set(0);
+                mCountYuv.set(0);
+                mCountRaw10.set(0);
+                mCountCapRes.set(0);
+                mCaptureRawIsDng = false;
+                mCaptureResults = new CaptureResult[requests.size()];
+
+                JSONArray jsonOutputSpecs = ItsUtils.getOutputSpecs(params);
+                if (jsonOutputSpecs != null) {
+                    numSurfaces = jsonOutputSpecs.length();
+                    if (numSurfaces > MAX_NUM_OUTPUT_SURFACES) {
+                        throw new ItsException("Too many output surfaces");
+                    }
+                    for (int i = 0; i < numSurfaces; i++) {
+                        // Get the specified surface.
+                        JSONObject surfaceObj = jsonOutputSpecs.getJSONObject(i);
+                        String sformat = surfaceObj.optString("format");
+                        Size sizes[];
+                        if ("yuv".equals(sformat) || "".equals(sformat)) {
+                            // Default to YUV if no format is specified.
+                            formats[i] = ImageFormat.YUV_420_888;
+                            sizes = ItsUtils.getYuvOutputSizes(mCameraCharacteristics);
+                        } else if ("jpg".equals(sformat) || "jpeg".equals(sformat)) {
+                            formats[i] = ImageFormat.JPEG;
+                            sizes = ItsUtils.getJpegOutputSizes(mCameraCharacteristics);
+                        } else if ("raw".equals(sformat)) {
+                            formats[i] = ImageFormat.RAW_SENSOR;
+                            sizes = ItsUtils.getRawOutputSizes(mCameraCharacteristics);
+                        } else if ("raw10".equals(sformat)) {
+                            formats[i] = ImageFormat.RAW10;
+                            sizes = ItsUtils.getRawOutputSizes(mCameraCharacteristics);
+                        } else if ("dng".equals(sformat)) {
+                            formats[i] = ImageFormat.RAW_SENSOR;
+                            sizes = ItsUtils.getRawOutputSizes(mCameraCharacteristics);
+                            mCaptureRawIsDng = true;
+                        } else {
+                            throw new ItsException("Unsupported format: " + sformat);
+                        }
+                        // If the size is omitted, then default to the largest allowed size for the
+                        // format.
+                        widths[i] = surfaceObj.optInt("width");
+                        heights[i] = surfaceObj.optInt("height");
+                        if (widths[i] <= 0) {
+                            if (sizes == null || sizes.length == 0) {
+                                throw new ItsException(String.format(
+                                        "Zero stream configs available for requested format: %s",
+                                        sformat));
+                            }
+                            widths[i] = sizes[0].getWidth();
+                        }
+                        if (heights[i] <= 0) {
+                            heights[i] = sizes[0].getHeight();
+                        }
+                    }
+                } else {
+                    // No surface(s) specified at all.
+                    // Default: a single output surface which is full-res YUV.
+                    Size sizes[] =
+                            ItsUtils.getYuvOutputSizes(mCameraCharacteristics);
+                    numSurfaces = 1;
+                    widths[0] = sizes[0].getWidth();
+                    heights[0] = sizes[0].getHeight();
+                    formats[0] = ImageFormat.YUV_420_888;
+                }
+
+                prepareCaptureReader(widths, heights, formats, numSurfaces);
+                List<Surface> outputSurfaces = new ArrayList<Surface>(numSurfaces);
+                for (int i = 0; i < numSurfaces; i++) {
+                    outputSurfaces.add(mCaptureReaders[i].getSurface());
+                }
+                BlockingSessionCallback sessionListener = new BlockingSessionCallback();
+                mCamera.createCaptureSession(outputSurfaces, sessionListener, mCameraHandler);
+                mSession = sessionListener.waitAndGetSession(TIMEOUT_IDLE_MS);
+
+                for (int i = 0; i < numSurfaces; i++) {
+                    ImageReader.OnImageAvailableListener readerListener =
+                            createAvailableListener(mCaptureCallback);
+                    mCaptureReaders[i].setOnImageAvailableListener(readerListener,mSaveHandlers[i]);
+                }
+
+                // Plan for how many callbacks need to be received throughout the duration of this
+                // sequence of capture requests. There is one callback per image surface, and one
+                // callback for the CaptureResult, for each capture.
+                int numCaptures = requests.size();
+                mCountCallbacksRemaining.set(numCaptures * (numSurfaces + 1));
+
+            } catch (CameraAccessException e) {
+                throw new ItsException("Error configuring outputs", e);
+            } catch (org.json.JSONException e) {
+                throw new ItsException("JSON error", e);
+            }
+
+            // Initiate the captures.
+            for (int i = 0; i < requests.size(); i++) {
+                // For DNG captures, need the LSC map to be available.
+                if (mCaptureRawIsDng) {
+                    requests.get(i).set(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE, 1);
+                }
+
+                CaptureRequest.Builder req = requests.get(i);
+                for (int j = 0; j < numSurfaces; j++) {
+                    req.addTarget(mCaptureReaders[j].getSurface());
+                }
+                mSession.capture(req.build(), mCaptureResultListener, mResultHandler);
+            }
+
+            // Make sure all callbacks have been hit (wait until captures are done).
+            // If no timeouts are received after a timeout, then fail.
+            int currentCount = mCountCallbacksRemaining.get();
+            while (currentCount > 0) {
+                try {
+                    Thread.sleep(TIMEOUT_CALLBACK*1000);
+                } catch (InterruptedException e) {
+                    throw new ItsException("Timeout failure", e);
+                }
+                int newCount = mCountCallbacksRemaining.get();
+                if (newCount == currentCount) {
+                    throw new ItsException(
+                            "No callback received within timeout");
+                }
+                currentCount = newCount;
+            }
+        } catch (android.hardware.camera2.CameraAccessException e) {
+            throw new ItsException("Access error: ", e);
+        }
+    }
+
+    @Override
+    public final void onSensorChanged(SensorEvent event) {
+        synchronized(mEventLock) {
+            if (mEventsEnabled) {
+                MySensorEvent ev2 = new MySensorEvent();
+                ev2.sensor = event.sensor;
+                ev2.accuracy = event.accuracy;
+                ev2.timestamp = event.timestamp;
+                ev2.values = new float[event.values.length];
+                System.arraycopy(event.values, 0, ev2.values, 0, event.values.length);
+                mEvents.add(ev2);
+            }
+        }
+    }
+
+    @Override
+    public final void onAccuracyChanged(Sensor sensor, int accuracy) {
+    }
+
+    private final CaptureCallback mCaptureCallback = new CaptureCallback() {
+        @Override
+        public void onCaptureAvailable(Image capture) {
+            try {
+                int format = capture.getFormat();
+                if (format == ImageFormat.JPEG) {
+                    Logt.i(TAG, "Received JPEG capture");
+                    byte[] img = ItsUtils.getDataFromImage(capture);
+                    ByteBuffer buf = ByteBuffer.wrap(img);
+                    int count = mCountJpg.getAndIncrement();
+                    mSocketRunnableObj.sendResponseCaptureBuffer("jpegImage", buf);
+                } else if (format == ImageFormat.YUV_420_888) {
+                    Logt.i(TAG, "Received YUV capture");
+                    byte[] img = ItsUtils.getDataFromImage(capture);
+                    ByteBuffer buf = ByteBuffer.wrap(img);
+                    int count = mCountYuv.getAndIncrement();
+                    mSocketRunnableObj.sendResponseCaptureBuffer("yuvImage", buf);
+                } else if (format == ImageFormat.RAW10) {
+                    Logt.i(TAG, "Received RAW10 capture");
+                    byte[] img = ItsUtils.getDataFromImage(capture);
+                    ByteBuffer buf = ByteBuffer.wrap(img);
+                    int count = mCountRaw10.getAndIncrement();
+                    mSocketRunnableObj.sendResponseCaptureBuffer("raw10Image", buf);
+                } else if (format == ImageFormat.RAW_SENSOR) {
+                    Logt.i(TAG, "Received RAW16 capture");
+                    int count = mCountRawOrDng.getAndIncrement();
+                    if (! mCaptureRawIsDng) {
+                        byte[] img = ItsUtils.getDataFromImage(capture);
+                        ByteBuffer buf = ByteBuffer.wrap(img);
+                        mSocketRunnableObj.sendResponseCaptureBuffer("rawImage", buf);
+                    } else {
+                        // Wait until the corresponding capture result is ready, up to a timeout.
+                        long t0 = android.os.SystemClock.elapsedRealtime();
+                        while (! mThreadExitFlag
+                                && android.os.SystemClock.elapsedRealtime()-t0 < TIMEOUT_CAP_RES) {
+                            if (mCaptureResults[count] != null) {
+                                Logt.i(TAG, "Writing capture as DNG");
+                                DngCreator dngCreator = new DngCreator(
+                                        mCameraCharacteristics, mCaptureResults[count]);
+                                ByteArrayOutputStream dngStream = new ByteArrayOutputStream();
+                                dngCreator.writeImage(dngStream, capture);
+                                byte[] dngArray = dngStream.toByteArray();
+                                ByteBuffer dngBuf = ByteBuffer.wrap(dngArray);
+                                mSocketRunnableObj.sendResponseCaptureBuffer("dngImage", dngBuf);
+                                break;
+                            } else {
+                                Thread.sleep(1);
+                            }
+                        }
+                    }
+                } else {
+                    throw new ItsException("Unsupported image format: " + format);
+                }
+                mCountCallbacksRemaining.decrementAndGet();
+            } catch (IOException e) {
+                Logt.e(TAG, "Script error: ", e);
+            } catch (InterruptedException e) {
+                Logt.e(TAG, "Script error: ", e);
+            } catch (ItsException e) {
+                Logt.e(TAG, "Script error: ", e);
+            }
+        }
+    };
+
+    private static float r2f(Rational r) {
+        return (float)r.getNumerator() / (float)r.getDenominator();
+    }
+
+    private final CaptureResultListener mCaptureResultListener = new CaptureResultListener() {
+        @Override
+        public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
+                long timestamp, long frameNumber) {
+        }
+
+        @Override
+        public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
+                TotalCaptureResult result) {
+            try {
+                // Currently result has all 0 values.
+                if (request == null || result == null) {
+                    throw new ItsException("Request/result is invalid");
+                }
+
+                StringBuilder logMsg = new StringBuilder();
+                logMsg.append(String.format(
+                        "Capt result: AE=%d, AF=%d, AWB=%d, sens=%d, exp=%.1fms, dur=%.1fms, ",
+                        result.get(CaptureResult.CONTROL_AE_STATE),
+                        result.get(CaptureResult.CONTROL_AF_STATE),
+                        result.get(CaptureResult.CONTROL_AWB_STATE),
+                        result.get(CaptureResult.SENSOR_SENSITIVITY),
+                        result.get(CaptureResult.SENSOR_EXPOSURE_TIME).intValue() / 1000000.0f,
+                        result.get(CaptureResult.SENSOR_FRAME_DURATION).intValue() / 1000000.0f));
+                if (result.get(CaptureResult.COLOR_CORRECTION_GAINS) != null) {
+                    logMsg.append(String.format(
+                            "gains=[%.1f, %.1f, %.1f, %.1f], ",
+                            result.get(CaptureResult.COLOR_CORRECTION_GAINS).getRed(),
+                            result.get(CaptureResult.COLOR_CORRECTION_GAINS).getGreenEven(),
+                            result.get(CaptureResult.COLOR_CORRECTION_GAINS).getGreenOdd(),
+                            result.get(CaptureResult.COLOR_CORRECTION_GAINS).getBlue()));
+                } else {
+                    logMsg.append("gains=[], ");
+                }
+                if (result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM) != null) {
+                    logMsg.append(String.format(
+                            "xform=[%.1f, %.1f, %.1f, %.1f, %.1f, %.1f, %.1f, %.1f, %.1f], ",
+                            r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(0,0)),
+                            r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(1,0)),
+                            r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(2,0)),
+                            r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(0,1)),
+                            r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(1,1)),
+                            r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(2,1)),
+                            r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(0,2)),
+                            r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(1,2)),
+                            r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(2,2))));
+                } else {
+                    logMsg.append("xform=[], ");
+                }
+                logMsg.append(String.format(
+                        "foc=%.1f",
+                        result.get(CaptureResult.LENS_FOCUS_DISTANCE)));
+                Logt.i(TAG, logMsg.toString());
+
+                if (result.get(CaptureResult.CONTROL_AE_STATE) != null) {
+                    mConvergedAE = result.get(CaptureResult.CONTROL_AE_STATE) ==
+                                              CaptureResult.CONTROL_AE_STATE_CONVERGED ||
+                                   result.get(CaptureResult.CONTROL_AE_STATE) ==
+                                              CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED ||
+                                   result.get(CaptureResult.CONTROL_AE_STATE) ==
+                                              CaptureResult.CONTROL_AE_STATE_LOCKED;
+                    mLockedAE = result.get(CaptureResult.CONTROL_AE_STATE) ==
+                                           CaptureResult.CONTROL_AE_STATE_LOCKED;
+                }
+                if (result.get(CaptureResult.CONTROL_AF_STATE) != null) {
+                    mConvergedAF = result.get(CaptureResult.CONTROL_AF_STATE) ==
+                                              CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED;
+                }
+                if (result.get(CaptureResult.CONTROL_AWB_STATE) != null) {
+                    mConvergedAWB = result.get(CaptureResult.CONTROL_AWB_STATE) ==
+                                               CaptureResult.CONTROL_AWB_STATE_CONVERGED ||
+                                    result.get(CaptureResult.CONTROL_AWB_STATE) ==
+                                               CaptureResult.CONTROL_AWB_STATE_LOCKED;
+                    mLockedAWB = result.get(CaptureResult.CONTROL_AWB_STATE) ==
+                                            CaptureResult.CONTROL_AWB_STATE_LOCKED;
+                }
+
+                if (mConvergedAE && (!mNeedsLockedAE || mLockedAE)) {
+                    if (result.get(CaptureResult.SENSOR_SENSITIVITY) != null
+                            && result.get(CaptureResult.SENSOR_EXPOSURE_TIME) != null) {
+                        mSocketRunnableObj.sendResponse("aeResult", String.format("%d %d",
+                                result.get(CaptureResult.SENSOR_SENSITIVITY).intValue(),
+                                result.get(CaptureResult.SENSOR_EXPOSURE_TIME).intValue()
+                                ));
+                    } else {
+                        Logt.i(TAG, String.format(
+                                "AE converged but NULL exposure values, sensitivity:%b, expTime:%b",
+                                result.get(CaptureResult.SENSOR_SENSITIVITY) == null,
+                                result.get(CaptureResult.SENSOR_EXPOSURE_TIME) == null));
+                    }
+                }
+
+                if (mConvergedAF) {
+                    if (result.get(CaptureResult.LENS_FOCUS_DISTANCE) != null) {
+                        mSocketRunnableObj.sendResponse("afResult", String.format("%f",
+                                result.get(CaptureResult.LENS_FOCUS_DISTANCE)
+                                ));
+                    } else {
+                        Logt.i(TAG, "AF converged but NULL focus distance values");
+                    }
+                }
+
+                if (mConvergedAWB && (!mNeedsLockedAWB || mLockedAWB)) {
+                    if (result.get(CaptureResult.COLOR_CORRECTION_GAINS) != null
+                            && result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM) != null) {
+                        mSocketRunnableObj.sendResponse("awbResult", String.format(
+                                "%f %f %f %f %f %f %f %f %f %f %f %f %f",
+                                result.get(CaptureResult.COLOR_CORRECTION_GAINS).getRed(),
+                                result.get(CaptureResult.COLOR_CORRECTION_GAINS).getGreenEven(),
+                                result.get(CaptureResult.COLOR_CORRECTION_GAINS).getGreenOdd(),
+                                result.get(CaptureResult.COLOR_CORRECTION_GAINS).getBlue(),
+                                r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(0,0)),
+                                r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(1,0)),
+                                r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(2,0)),
+                                r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(0,1)),
+                                r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(1,1)),
+                                r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(2,1)),
+                                r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(0,2)),
+                                r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(1,2)),
+                                r2f(result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM).getElement(2,2))
+                                ));
+                    } else {
+                        Logt.i(TAG, String.format(
+                                "AWB converged but NULL color correction values, gains:%b, ccm:%b",
+                                result.get(CaptureResult.COLOR_CORRECTION_GAINS) == null,
+                                result.get(CaptureResult.COLOR_CORRECTION_TRANSFORM) == null));
+                    }
+                }
+
+                if (mIssuedRequest3A) {
+                    mIssuedRequest3A = false;
+                    mInterlock3A.open();
+                } else {
+                    int count = mCountCapRes.getAndIncrement();
+                    mCaptureResults[count] = result;
+                    mSocketRunnableObj.sendResponseCaptureResult(mCameraCharacteristics,
+                            request, result, mCaptureReaders);
+                    mCountCallbacksRemaining.decrementAndGet();
+                }
+            } catch (ItsException e) {
+                Logt.e(TAG, "Script error: ", e);
+            } catch (Exception e) {
+                Logt.e(TAG, "Script error: ", e);
+            }
+        }
+
+        @Override
+        public void onCaptureFailed(CameraCaptureSession session, CaptureRequest request,
+                CaptureFailure failure) {
+            Logt.e(TAG, "Script error: capture failed");
+        }
+    };
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
new file mode 100644
index 0000000..12b9bfc
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.camera.its;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Toast;
+import java.util.HashSet;
+import java.util.Arrays;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+
+/**
+ * Test for Camera features that require that the camera be aimed at a specific test scene.
+ * This test activity requires a USB connection to a computer, and a corresponding host-side run of
+ * the python scripts found in the CameraITS directory.
+ */
+public class ItsTestActivity extends PassFailButtons.Activity {
+    private static final String TAG = "ItsTestActivity";
+    private static final String EXTRA_SUCCESS = "camera.its.extra.SUCCESS";
+    private static final String ACTION_ITS_RESULT =
+            "com.android.cts.verifier.camera.its.ACTION_ITS_RESULT";
+
+    class SuccessReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.i(TAG, "Received result for Camera ITS tests");
+            if (ACTION_ITS_RESULT.equals(intent.getAction())) {
+                String result = intent.getStringExtra(EXTRA_SUCCESS);
+                String[] parts = result.split("=");
+                if (parts.length != 2) {
+                    Toast.makeText(ItsTestActivity.this,
+                            "Received unknown ITS result string: " + result,
+                            Toast.LENGTH_SHORT).show();
+                }
+                String cameraId = parts[0];
+                boolean pass = parts[1].equals("True");
+                if(pass) {
+                    Log.i(TAG, "Received Camera " + cameraId + " ITS SUCCESS from host.");
+                    mITSPassedCameraIds.add(cameraId);
+                    if (mCameraIds != null &&
+                            mITSPassedCameraIds.containsAll(Arrays.asList(mCameraIds))) {
+                        ItsTestActivity.this.showToast(R.string.its_test_passed);
+                        ItsTestActivity.this.getPassButton().setEnabled(true);
+                    }
+                } else {
+                    Log.i(TAG, "Received Camera " + cameraId + " ITS FAILURE from host.");
+                    ItsTestActivity.this.showToast(R.string.its_test_failed);
+                }
+            }
+        }
+    }
+
+    private final SuccessReceiver mSuccessReceiver = new SuccessReceiver();
+    private final HashSet<String> mITSPassedCameraIds = new HashSet<>();
+    private String[] mCameraIds = null;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.its_main);
+        setInfoResources(R.string.camera_its_test, R.string.camera_its_test_info, -1);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        CameraManager manager = (CameraManager) this.getSystemService(Context.CAMERA_SERVICE);
+        if (manager == null) {
+            showToast(R.string.no_camera_manager);
+        } else {
+            try {
+                mCameraIds = manager.getCameraIdList();
+                boolean allCamerasAreLegacy = true;
+                for (String id : mCameraIds) {
+                    CameraCharacteristics characteristics = manager.getCameraCharacteristics(id);
+                    if (characteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
+                            != CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
+                        allCamerasAreLegacy = false;
+                        break;
+                    }
+                }
+                if (allCamerasAreLegacy) {
+                    showToast(R.string.all_legacy_devices);
+                    getPassButton().setEnabled(false);
+                }
+            } catch (CameraAccessException e) {
+                Toast.makeText(ItsTestActivity.this,
+                        "Received error from camera service while checking device capabilities: "
+                                + e, Toast.LENGTH_SHORT).show();
+            }
+            IntentFilter filter = new IntentFilter(ACTION_ITS_RESULT);
+            registerReceiver(mSuccessReceiver, filter);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        unregisterReceiver(mSuccessReceiver);
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        setContentView(R.layout.its_main);
+        setInfoResources(R.string.camera_its_test, R.string.camera_its_test_info, -1);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+    }
+
+    private void showToast(int messageId) {
+        Toast.makeText(ItsTestActivity.this, messageId, Toast.LENGTH_SHORT).show();
+    }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
new file mode 100644
index 0000000..2011314
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
@@ -0,0 +1,223 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.camera.its;
+
+import android.content.Context;
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.Image;
+import android.media.Image.Plane;
+import android.net.Uri;
+import android.os.Environment;
+import android.util.Log;
+import android.util.Size;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ItsUtils {
+    public static final String TAG = ItsUtils.class.getSimpleName();
+
+    public static ByteBuffer jsonToByteBuffer(JSONObject jsonObj) {
+        return ByteBuffer.wrap(jsonObj.toString().getBytes(Charset.defaultCharset()));
+    }
+
+    public static MeteringRectangle[] getJsonWeightedRectsFromArray(
+            JSONArray a, boolean normalized, int width, int height)
+            throws ItsException {
+        try {
+            // Returns [x0,y0,x1,y1,wgt,  x0,y0,x1,y1,wgt,  x0,y0,x1,y1,wgt,  ...]
+            assert(a.length() % 5 == 0);
+            MeteringRectangle[] ma = new MeteringRectangle[a.length() / 5];
+            for (int i = 0; i < a.length(); i += 5) {
+                int x,y,w,h;
+                if (normalized) {
+                    x = (int)Math.floor(a.getDouble(i+0) * width + 0.5f);
+                    y = (int)Math.floor(a.getDouble(i+1) * height + 0.5f);
+                    w = (int)Math.floor(a.getDouble(i+2) * width + 0.5f);
+                    h = (int)Math.floor(a.getDouble(i+3) * height + 0.5f);
+                } else {
+                    x = a.getInt(i+0);
+                    y = a.getInt(i+1);
+                    w = a.getInt(i+2);
+                    h = a.getInt(i+3);
+                }
+                x = Math.max(x, 0);
+                y = Math.max(y, 0);
+                w = Math.min(w, width-x);
+                h = Math.min(h, height-y);
+                int wgt = a.getInt(i+4);
+                ma[i/5] = new MeteringRectangle(x,y,w,h,wgt);
+            }
+            return ma;
+        } catch (org.json.JSONException e) {
+            throw new ItsException("JSON error: ", e);
+        }
+    }
+
+    public static JSONArray getOutputSpecs(JSONObject jsonObjTop)
+            throws ItsException {
+        try {
+            if (jsonObjTop.has("outputSurfaces")) {
+                return jsonObjTop.getJSONArray("outputSurfaces");
+            }
+            return null;
+        } catch (org.json.JSONException e) {
+            throw new ItsException("JSON error: ", e);
+        }
+    }
+
+    public static Size[] getRawOutputSizes(CameraCharacteristics ccs)
+            throws ItsException {
+        return getOutputSizes(ccs, ImageFormat.RAW_SENSOR);
+    }
+
+    public static Size[] getJpegOutputSizes(CameraCharacteristics ccs)
+            throws ItsException {
+        return getOutputSizes(ccs, ImageFormat.JPEG);
+    }
+
+    public static Size[] getYuvOutputSizes(CameraCharacteristics ccs)
+            throws ItsException {
+        return getOutputSizes(ccs, ImageFormat.YUV_420_888);
+    }
+
+    private static Size[] getOutputSizes(CameraCharacteristics ccs, int format)
+            throws ItsException {
+        StreamConfigurationMap configMap = ccs.get(
+                CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        if (configMap == null) {
+            throw new ItsException("Failed to get stream config");
+        }
+        return configMap.getOutputSizes(format);
+    }
+
+    public static byte[] getDataFromImage(Image image)
+            throws ItsException {
+        int format = image.getFormat();
+        int width = image.getWidth();
+        int height = image.getHeight();
+        byte[] data = null;
+
+        // Read image data
+        Plane[] planes = image.getPlanes();
+
+        // Check image validity
+        if (!checkAndroidImageFormat(image)) {
+            throw new ItsException(
+                    "Invalid image format passed to getDataFromImage: " + image.getFormat());
+        }
+
+        if (format == ImageFormat.JPEG) {
+            // JPEG doesn't have pixelstride and rowstride, treat it as 1D buffer.
+            ByteBuffer buffer = planes[0].getBuffer();
+            data = new byte[buffer.capacity()];
+            buffer.get(data);
+            return data;
+        } else if (format == ImageFormat.YUV_420_888 || format == ImageFormat.RAW_SENSOR
+                || format == ImageFormat.RAW10) {
+            int offset = 0;
+            data = new byte[width * height * ImageFormat.getBitsPerPixel(format) / 8];
+            int maxRowSize = planes[0].getRowStride();
+            for (int i = 0; i < planes.length; i++) {
+                if (maxRowSize < planes[i].getRowStride()) {
+                    maxRowSize = planes[i].getRowStride();
+                }
+            }
+            byte[] rowData = new byte[maxRowSize];
+            for (int i = 0; i < planes.length; i++) {
+                ByteBuffer buffer = planes[i].getBuffer();
+                int rowStride = planes[i].getRowStride();
+                int pixelStride = planes[i].getPixelStride();
+                int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8;
+                Logt.i(TAG, String.format(
+                        "Reading image: fmt %d, plane %d, w %d, h %d, rowStride %d, pixStride %d",
+                        format, i, width, height, rowStride, pixelStride));
+                // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling.
+                int w = (i == 0) ? width : width / 2;
+                int h = (i == 0) ? height : height / 2;
+                for (int row = 0; row < h; row++) {
+                    if (pixelStride == bytesPerPixel) {
+                        // Special case: optimized read of the entire row
+                        int length = w * bytesPerPixel;
+                        buffer.get(data, offset, length);
+                        // Advance buffer the remainder of the row stride
+                        if (row < h - 1) {
+                            buffer.position(buffer.position() + rowStride - length);
+                        }
+                        offset += length;
+                    } else {
+                        // Generic case: should work for any pixelStride but slower.
+                        // Use intermediate buffer to avoid read byte-by-byte from
+                        // DirectByteBuffer, which is very bad for performance.
+                        // Also need avoid access out of bound by only reading the available
+                        // bytes in the bytebuffer.
+                        int readSize = rowStride;
+                        if (buffer.remaining() < readSize) {
+                            readSize = buffer.remaining();
+                        }
+                        buffer.get(rowData, 0, readSize);
+                        if (pixelStride >= 1) {
+                            for (int col = 0; col < w; col++) {
+                                data[offset++] = rowData[col * pixelStride];
+                            }
+                        } else {
+                            // PixelStride of 0 can mean pixel isn't a multiple of 8 bits, for
+                            // example with RAW10. Just copy the buffer, dropping any padding at
+                            // the end of the row.
+                            int length = (w * ImageFormat.getBitsPerPixel(format)) / 8;
+                            System.arraycopy(rowData,0,data,offset,length);
+                            offset += length;
+                        }
+                    }
+                }
+            }
+            Logt.i(TAG, String.format("Done reading image, format %d", format));
+            return data;
+        } else {
+            throw new ItsException("Unsupported image format: " + format);
+        }
+    }
+
+    private static boolean checkAndroidImageFormat(Image image) {
+        int format = image.getFormat();
+        Plane[] planes = image.getPlanes();
+        switch (format) {
+            case ImageFormat.YUV_420_888:
+            case ImageFormat.NV21:
+            case ImageFormat.YV12:
+                return 3 == planes.length;
+            case ImageFormat.RAW_SENSOR:
+            case ImageFormat.RAW10:
+            case ImageFormat.JPEG:
+                return 1 == planes.length;
+            default:
+                return false;
+        }
+    }
+}
+
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/Logt.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/Logt.java
new file mode 100644
index 0000000..852a1ce
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/Logt.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.camera.its;
+
+import android.util.Log;
+
+public class Logt {
+    public static void i(String tag, String msg) {
+        long t = android.os.SystemClock.elapsedRealtime();
+        Log.i(tag, String.format("[%d] %s", t, msg));
+    }
+    public static void e(String tag, String msg) {
+        long t = android.os.SystemClock.elapsedRealtime();
+        Log.e(tag, String.format("[%d] %s", t, msg));
+    }
+    public static void w(String tag, String msg) {
+        long t = android.os.SystemClock.elapsedRealtime();
+        Log.w(tag, String.format("[%d] %s", t, msg));
+    }
+    public static void e(String tag, String msg, Throwable tr) {
+        long t = android.os.SystemClock.elapsedRealtime();
+        Log.e(tag, String.format("[%d] %s", t, msg), tr);
+    }
+}
+
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
index 74a5317..36acf42 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/features/FeatureSummaryActivity.java
@@ -220,6 +220,9 @@
             new Feature(PackageManager.FEATURE_SENSOR_RELATIVE_HUMIDITY, false),
             new Feature(PackageManager.FEATURE_VERIFIED_BOOT, false),
 
+            // Features explicitly made optional in L
+            new Feature(PackageManager.FEATURE_LOCATION_NETWORK, false),
+
             // New hidden features in L
             new Feature("android.hardware.ethernet", false),
             new Feature("android.hardware.hdmi.cec", false),
@@ -238,6 +241,7 @@
         // features
         boolean hasWifi = false;
         boolean hasTelephony = false;
+        boolean hasBluetooth = false;
         boolean hasIllegalFeature = false;
 
         // get list of all features device thinks it has, & store in a HashMap
@@ -304,6 +308,7 @@
                 // device reports it -- yay! set the happy icon
                 hasWifi = hasWifi || PackageManager.FEATURE_WIFI.equals(f.name);
                 hasTelephony = hasTelephony || PackageManager.FEATURE_TELEPHONY.equals(f.name);
+                hasBluetooth = hasBluetooth || PackageManager.FEATURE_BLUETOOTH.equals(f.name);
                 statusIcon = R.drawable.fs_good;
                 actualFeatures.remove(f.name);
             } else if (!present && f.required) {
@@ -388,9 +393,11 @@
         if (hasIllegalFeature) {
             sb.append(getResources().getString(R.string.fs_disallowed)).append("\n");
         }
-        if (!hasWifi && !hasTelephony) {
+
+        if (!hasWifi && !hasTelephony && !hasBluetooth) {
             sb.append(getResources().getString(R.string.fs_missing_wifi_telephony)).append("\n");
         }
+
         String warnings = sb.toString().trim();
         if (warnings == null || "".equals(warnings)) {
             ((TextView) (findViewById(R.id.fs_warnings))).setVisibility(View.GONE);
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 da823e8..057d00d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodFlowTestActivity.java
@@ -29,10 +29,12 @@
 import android.os.Bundle;
 import android.provider.Settings;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.View.OnClickListener;
 import android.widget.ArrayAdapter;
+import android.widget.ImageView;
 import android.widget.ListView;
 import android.widget.TextView;
 import android.widget.Toast;
@@ -69,11 +71,13 @@
     protected DevicePolicyManager mDevicePolicyManager;
 
     private TestItem mProfileOwnerInstalled;
-    private TestItem mDiskEncryptionTest;
     private TestItem mProfileVisibleTest;
     private TestItem mDeviceAdminVisibleTest;
     private TestItem mWorkAppVisibleTest;
     private TestItem mCrossProfileIntentFiltersTest;
+    private TestItem mDisableNonMarketTest;
+    private TestItem mEnableNonMarketTest;
+    private TestItem mWorkNotificationBadgedTest;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -155,13 +159,6 @@
             }
         };
 
-        mDiskEncryptionTest = new TestItem(this, R.string.provisioning_byod_diskencryption) {
-            @Override
-            public TestResult getPassFailState() {
-                return isDeviceEncrypted() ? TestResult.Passed : TestResult.Failed;
-            }
-        };
-
         mProfileVisibleTest = new TestItem(this, R.string.provisioning_byod_profile_visible,
                 R.string.provisioning_byod_profile_visible_instruction,
                 new Intent(Settings.ACTION_SETTINGS));
@@ -170,9 +167,31 @@
                 R.string.provisioning_byod_admin_visible_instruction,
                 new Intent(Settings.ACTION_SECURITY_SETTINGS));
 
-        mWorkAppVisibleTest = new TestItem(this, R.string.provisioning_byod_workapps_visible,
-                R.string.provisioning_byod_workapps_visible_instruction,
-                new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME));
+        /*
+         * To keep the image in this test up to date, use the instructions in
+         * {@link ByodIconSamplerActivity}.
+         */
+        mWorkAppVisibleTest = new TestItemWithIcon(this,
+                R.string.provisioning_byod_workapps_visible,
+                R.string.provisioning_byod_profile_visible_instruction,
+                new Intent(Intent.ACTION_MAIN).addCategory(Intent.CATEGORY_HOME),
+                R.drawable.badged_icon);
+
+        mWorkNotificationBadgedTest = new TestItemWithIcon(this,
+                R.string.provisioning_byod_work_notification,
+                R.string.provisioning_byod_work_notification_instruction,
+                new Intent(WorkNotificationTestActivity.ACTION_WORK_NOTIFICATION),
+                R.drawable.ic_corp_icon);
+
+        mDisableNonMarketTest = new TestItem(this, R.string.provisioning_byod_nonmarket_deny,
+                R.string.provisioning_byod_nonmarket_deny_info,
+                new Intent(ByodHelperActivity.ACTION_INSTALL_APK)
+                        .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS, false));
+
+        mEnableNonMarketTest = new TestItem(this, R.string.provisioning_byod_nonmarket_allow,
+                R.string.provisioning_byod_nonmarket_allow_info,
+                new Intent(ByodHelperActivity.ACTION_INSTALL_APK)
+                        .putExtra(ByodHelperActivity.EXTRA_ALLOW_NON_MARKET_APPS, true));
 
         Intent intent = new Intent(CrossProfileTestActivity.ACTION_CROSS_PROFILE);
         Intent chooser = Intent.createChooser(intent, getResources().getString(R.string.provisioning_cross_profile_chooser));
@@ -181,12 +200,14 @@
                 R.string.provisioning_byod_cross_profile_instruction,
                 chooser);
 
-        mTests.add(mDiskEncryptionTest);
         mTests.add(mProfileOwnerInstalled);
         mTests.add(mProfileVisibleTest);
         mTests.add(mDeviceAdminVisibleTest);
         mTests.add(mWorkAppVisibleTest);
+        mTests.add(mWorkNotificationBadgedTest);
         mTests.add(mCrossProfileIntentFiltersTest);
+        mTests.add(mDisableNonMarketTest);
+        mTests.add(mEnableNonMarketTest);
     }
 
     @Override
@@ -197,25 +218,33 @@
     }
 
     private void showManualTestDialog(final TestItem test) {
-        AlertDialog dialog = new AlertDialog.Builder(this)
+        AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(this)
                 .setIcon(android.R.drawable.ic_dialog_info)
-                .setMessage(test.getManualTestInstruction())
+                .setTitle(R.string.provisioning_byod)
                 .setNeutralButton(R.string.provisioning_byod_go, null)
-                .setPositiveButton(R.string.pass_button_text, new DialogInterface.OnClickListener() {
+                .setPositiveButton(R.string.pass_button_text, new AlertDialog.OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
+                        clearRemainingState(test);
                         setTestResult(test, TestResult.Passed);
                     }
                 })
-                .setNegativeButton(R.string.fail_button_text, new DialogInterface.OnClickListener() {
+                .setNegativeButton(R.string.fail_button_text, new AlertDialog.OnClickListener() {
                     @Override
                     public void onClick(DialogInterface dialog, int which) {
+                        clearRemainingState(test);
                         setTestResult(test, TestResult.Failed);
                     }
-                })
-                .create();
-        dialog.show();
-
+                });
+        View customView = test.getCustomView();
+        if (customView != null) {
+            dialogBuilder.setView(customView);
+        } else {
+            dialogBuilder.setMessage(test.getManualTestInstruction());
+        }
+        AlertDialog dialog = dialogBuilder.show();
+        // Note: Setting the OnClickListener on the Dialog rather than the Builder, prevents the
+        // dialog being dismissed on onClick.
         dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener(new OnClickListener() {
             @Override
             public void onClick(View v) {
@@ -224,6 +253,14 @@
         });
     }
 
+    private void clearRemainingState(final TestItem test) {
+        if (WorkNotificationTestActivity.ACTION_WORK_NOTIFICATION.equals(
+                test.getManualTestIntent().getAction())) {
+            ByodFlowTestActivity.this.startActivity(new Intent(
+                    WorkNotificationTestActivity.ACTION_CLEAR_WORK_NOTIFICATION));
+        }
+    }
+
     private void setTestResult(TestItem test, TestResult result) {
         test.setPassFailState(result);
 
@@ -282,11 +319,10 @@
                 this, ByodHelperActivity.class),
                 PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
                 PackageManager.DONT_KILL_APP);
-    }
-
-    private boolean isDeviceEncrypted() {
-        return mDevicePolicyManager.getStorageEncryptionStatus()
-                == DevicePolicyManager.ENCRYPTION_STATUS_ACTIVE;
+        getPackageManager().setComponentEnabledSetting(new ComponentName(
+                this, WorkNotificationTestActivity.class),
+                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                PackageManager.DONT_KILL_APP);
     }
 
     private void showToast(int messageId) {
@@ -350,6 +386,33 @@
         public Intent getManualTestIntent() {
             return mManualIntent;
         }
+
+        public View getCustomView() {
+            return null;
+        }
+    }
+
+    static class TestItemWithIcon extends TestItem {
+
+        private int mImageResId;
+        private Context mContext;
+
+        public TestItemWithIcon(Context context, int nameResId, int testInstructionResId,
+                Intent testIntent, int imageResId) {
+            super(context, nameResId, testInstructionResId, testIntent);
+            mContext = context;
+            mImageResId = imageResId;
+        }
+
+        @Override
+        public View getCustomView() {
+            LayoutInflater layoutInflater = LayoutInflater.from(mContext);
+            View view = layoutInflater.inflate(R.layout.byod_custom_view,
+                    null /* root */);
+            ((ImageView) view.findViewById(R.id.sample_icon)).setImageResource(mImageResId);
+            ((TextView) view.findViewById(R.id.message)).setText(getManualTestInstruction());
+            return view;
+        }
     }
 
     static class TestAdapter extends ArrayAdapter<TestItem> {
@@ -380,5 +443,4 @@
             return view;
         }
     }
-
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
index 5dac4bd..277324c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodHelperActivity.java
@@ -24,10 +24,14 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
+import android.net.Uri;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.util.Log;
 import android.widget.Toast;
 
+import static android.provider.Settings.Secure.INSTALL_NON_MARKET_APPS;
+
 import com.android.cts.verifier.R;
 import com.android.cts.verifier.managedprovisioning.ByodFlowTestActivity.TestResult;
 
@@ -53,17 +57,33 @@
 
     public static final String EXTRA_PROVISIONED = "extra_provisioned";
 
-    private ComponentName mAdminReceiverComponent;
+    // Primary -> managed intent: set unknown sources restriction and install package
+    public static final String ACTION_INSTALL_APK = "com.android.cts.verifier.managedprovisioning.BYOD_INSTALL_APK";
+    public static final String EXTRA_ALLOW_NON_MARKET_APPS = INSTALL_NON_MARKET_APPS;
 
+    private static final int REQUEST_INSTALL_PACKAGE = 1;
+
+    private static final String ORIGINAL_SETTINGS_NAME = "original settings";
+    private Bundle mOriginalSettings;
+
+    private ComponentName mAdminReceiverComponent;
     private DevicePolicyManager mDevicePolicyManager;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) {
+            Log.w(TAG, "Restored state");
+            mOriginalSettings = savedInstanceState.getBundle(ORIGINAL_SETTINGS_NAME);
+        } else {
+            mOriginalSettings = new Bundle();
+        }
+
         mAdminReceiverComponent = new ComponentName(this, DeviceAdminTestReceiver.class.getName());
         mDevicePolicyManager = (DevicePolicyManager) getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
-        String action = getIntent().getAction();
+        Intent intent = getIntent();
+        String action = intent.getAction();
         Log.d(TAG, "ByodHelperActivity.onCreate: " + action);
 
         // we are explicitly started by {@link DeviceAdminTestReceiver} after a successful provisioning.
@@ -83,16 +103,70 @@
                 mDevicePolicyManager.wipeData(0);
                 showToast(R.string.provisioning_byod_profile_deleted);
             }
+        } else if (action.equals(ACTION_INSTALL_APK)) {
+            boolean allowNonMarket = intent.getBooleanExtra(EXTRA_ALLOW_NON_MARKET_APPS, false);
+            boolean wasAllowed = getAllowNonMarket();
+
+            // Update permission to install non-market apps
+            setAllowNonMarket(allowNonMarket);
+            mOriginalSettings.putBoolean(INSTALL_NON_MARKET_APPS, wasAllowed);
+
+            // Request to install a non-market application- easiest way is to reinstall ourself
+            final Intent installIntent = new Intent(Intent.ACTION_INSTALL_PACKAGE)
+                    .setData(Uri.parse("package:" + getPackageName()))
+                    .putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true)
+                    .putExtra(Intent.EXTRA_RETURN_RESULT, true);
+            startActivityForResult(installIntent, REQUEST_INSTALL_PACKAGE);
+
+            // Not yet ready to finish- wait until the result comes back
+            return;
         }
         // This activity has no UI and is only used to respond to CtsVerifier in the primary side.
         finish();
     }
 
+    @Override
+    protected void onSaveInstanceState(final Bundle savedState) {
+        super.onSaveInstanceState(savedState);
+
+        savedState.putBundle(ORIGINAL_SETTINGS_NAME, mOriginalSettings);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch (requestCode) {
+            case REQUEST_INSTALL_PACKAGE: {
+                Log.w(TAG, "Received REQUEST_INSTALL_PACKAGE, resultCode = " + resultCode);
+                if (mOriginalSettings.containsKey(INSTALL_NON_MARKET_APPS)) {
+                    // Restore original setting
+                    setAllowNonMarket(mOriginalSettings.getBoolean(INSTALL_NON_MARKET_APPS));
+                    mOriginalSettings.remove(INSTALL_NON_MARKET_APPS);
+                }
+                finish();
+                break;
+            }
+            default: {
+                Log.wtf(TAG, "Unknown requestCode " + requestCode + "; data = " + data);
+                break;
+            }
+        }
+    }
+
     private boolean isProfileOwner() {
         return mDevicePolicyManager.isAdminActive(mAdminReceiverComponent) &&
                 mDevicePolicyManager.isProfileOwnerApp(mAdminReceiverComponent.getPackageName());
     }
 
+    private boolean getAllowNonMarket() {
+        String value = Settings.Secure.getString(getContentResolver(), INSTALL_NON_MARKET_APPS);
+        return "1".equals(value);
+    }
+
+    private void setAllowNonMarket(boolean allow) {
+        mDevicePolicyManager.setSecureSetting(mAdminReceiverComponent, INSTALL_NON_MARKET_APPS,
+                (allow ? "1" : "0"));
+    }
+
     private void startActivityInPrimary(Intent intent) {
         // Disable app components in the current profile, so only the counterpart in the other
         // profile can respond (via cross-profile intent filter)
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodIconSamplerActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodIconSamplerActivity.java
new file mode 100644
index 0000000..c0579d0
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/ByodIconSamplerActivity.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.managedprovisioning;
+
+import android.app.Activity;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.UserHandle;
+import android.os.Process;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+
+/**
+ * Activity used to generate sample image for {@link ByodFlowTestActivity} on a reference build.
+ *
+ * <p>Instructions: After Profile owner installed test has passed, run:
+ *  adb shell pm list users
+ *  adb shell am start -a com.android.cts.verifier.managedprovisioning.BYOD_SAMPLE_ICON \
+ *      --user <MANAGED_USER_ID>
+ * The icon can then be copied from /mnt/shell/emulated/<MANAGED_USER_ID>/badged_icon.png.
+ */
+public class ByodIconSamplerActivity  extends Activity {
+    static final String TAG = "ByodIconSamplerActivity";
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        sampleImage();
+        // This activity has no UI
+        finish();
+    }
+    /**
+     * Writes a badged option of the CTS tests app icon on the sdcard.
+     * For test development only: this should be used to regenerate the asset every time we have
+     * a new badge.
+     */
+    private void sampleImage() {
+        UserHandle userHandle = Process.myUserHandle();
+        Log.d(TAG, "Sampling image for: " + userHandle);
+        Drawable drawable = getPackageManager().getUserBadgedIcon(getAppIcon(), userHandle);
+        Bitmap bitmap = convertToBitmap(drawable);
+        String fileName = Environment.getExternalStorageDirectory().getPath() + "/badged_icon.png";
+        FileOutputStream file = null;
+        try {
+            file = new FileOutputStream(fileName);
+            bitmap.compress(Bitmap.CompressFormat.PNG, 100, file);
+        } catch (FileNotFoundException e) {
+            Log.d(TAG, "sampleImage: FileNotFoundException ", e);
+        } finally {
+            try {
+                if (file != null) {
+                    file.close();
+                    Log.d(TAG, "Wrote badged icon to file: " + fileName);
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    private Drawable getAppIcon() {
+        try {
+            PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(),
+                    0 /* flags */);
+            if (packageInfo.applicationInfo != null) {
+                return getResources().getDrawable(packageInfo.applicationInfo.icon);
+            }
+        } catch (NameNotFoundException e) {
+            // Should not happen
+            Log.d(TAG, "getAppIcon: NameNotFoundException", e);
+        }
+        return null;
+    }
+
+    private static Bitmap convertToBitmap(Drawable icon) {
+        if (icon == null) {
+            return null;
+        }
+        Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(), icon.getIntrinsicHeight(),
+                Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+        icon.draw(canvas);
+        return bitmap;
+    }
+}
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 8dccac3..fa7bc4c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceAdminTestReceiver.java
@@ -26,8 +26,6 @@
 import android.util.Log;
 import android.widget.Toast;
 
-import com.android.cts.verifier.managedprovisioning.ByodHelperActivity;
-
 /**
  * Profile owner receiver for BYOD flow test.
  * Setup cross-profile intent filter after successful provisioning.
@@ -50,7 +48,10 @@
             IntentFilter filter = new IntentFilter();
             filter.addAction(ByodHelperActivity.ACTION_QUERY_PROFILE_OWNER);
             filter.addAction(ByodHelperActivity.ACTION_REMOVE_PROFILE_OWNER);
+            filter.addAction(ByodHelperActivity.ACTION_INSTALL_APK);
             filter.addAction(CrossProfileTestActivity.ACTION_CROSS_PROFILE);
+            filter.addAction(WorkNotificationTestActivity.ACTION_WORK_NOTIFICATION);
+            filter.addAction(WorkNotificationTestActivity.ACTION_CLEAR_WORK_NOTIFICATION);
             dpm.addCrossProfileIntentFilter(getWho(context), filter,
                     DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT);
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/WorkNotificationTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/WorkNotificationTestActivity.java
new file mode 100644
index 0000000..c85ccf5
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/WorkNotificationTestActivity.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.managedprovisioning;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.Context;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+
+/**
+ * Test activity used to generate a notification.
+ */
+public class WorkNotificationTestActivity extends Activity {
+    public static final String ACTION_WORK_NOTIFICATION =
+            "com.android.cts.verifier.managedprovisioning.WORK_NOTIFICATION";
+    public static final String ACTION_CLEAR_WORK_NOTIFICATION =
+            "com.android.cts.verifier.managedprovisioning.CLEAR_WORK_NOTIFICATION";
+    private static final int NOTIFICATION_ID = 7;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final String action = getIntent().getAction();
+        final NotificationManager notificationManager = (NotificationManager)
+                getSystemService(Context.NOTIFICATION_SERVICE);
+        if (ACTION_WORK_NOTIFICATION.equals(action)) {
+            final Notification notification = new Notification.Builder(this)
+                .setSmallIcon(R.drawable.icon)
+                .setContentTitle(getString(R.string.provisioning_byod_work_notification_title))
+                .setVisibility(Notification.VISIBILITY_PUBLIC)
+                .setAutoCancel(true)
+                .build();
+            notificationManager.notify(NOTIFICATION_ID, notification);
+        } else if (ACTION_CLEAR_WORK_NOTIFICATION.equals(action)) {
+            notificationManager.cancel(NOTIFICATION_ID);
+        }
+        finish();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java
index 8aa82b5..5f3a10a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/BaseEmulatorActivity.java
@@ -45,7 +45,8 @@
             PrefixPaymentService2.COMPONENT,
             PrefixTransportService1.COMPONENT,
             PrefixTransportService2.COMPONENT,
-            PrefixAccessService.COMPONENT)
+            PrefixAccessService.COMPONENT,
+            LargeNumAidsService.COMPONENT)
     );
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java
index 87fa3d1..b30bb73 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceEmulatorTestActivity.java
@@ -104,6 +104,10 @@
                     new Intent(this, OnAndOffHostEmulatorActivity.class), null));
 
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                adapter.add(TestListItem.newTest(this, R.string.nfc_hce_large_num_aids_emulator,
+                        LargeNumAidsEmulatorActivity.class.getName(),
+                        new Intent(this, LargeNumAidsEmulatorActivity.class), null));
+
                 adapter.add(TestListItem.newTest(this, R.string.nfc_hce_payment_dynamic_aids_emulator,
                         DynamicAidEmulatorActivity.class.getName(),
                         new Intent(this, DynamicAidEmulatorActivity.class), null));
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
index f628fb7..035ce86 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceReaderTestActivity.java
@@ -16,6 +16,8 @@
 
 package com.android.cts.verifier.nfc.hce;
 
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.CardEmulation;
 import com.android.cts.verifier.ArrayTestListAdapter;
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
@@ -101,21 +103,29 @@
                     SimpleReaderActivity.class.getName(),
                     DynamicAidEmulatorActivity.buildReaderIntent(this), null));
 
-            adapter.add(TestListItem.newTest(this, R.string.nfc_hce_payment_prefix_aids_reader,
+            adapter.add(TestListItem.newTest(this, R.string.nfc_hce_large_num_aids_reader,
                     SimpleReaderActivity.class.getName(),
-                    PrefixPaymentEmulatorActivity.buildReaderIntent(this), null));
+                    LargeNumAidsEmulatorActivity.buildReaderIntent(this), null));
 
-            adapter.add(TestListItem.newTest(this, R.string.nfc_hce_payment_prefix_aids_reader_2,
-                    SimpleReaderActivity.class.getName(),
-                    PrefixPaymentEmulator2Activity.buildReaderIntent(this), null));
+            NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this);
+            CardEmulation cardEmulation = CardEmulation.getInstance(nfcAdapter);
+            if (cardEmulation.supportsAidPrefixRegistration()) {
+                adapter.add(TestListItem.newTest(this, R.string.nfc_hce_payment_prefix_aids_reader,
+                        SimpleReaderActivity.class.getName(),
+                        PrefixPaymentEmulatorActivity.buildReaderIntent(this), null));
 
-            adapter.add(TestListItem.newTest(this, R.string.nfc_hce_other_prefix_aids_reader,
-                    SimpleReaderActivity.class.getName(),
-                    DualNonPaymentPrefixEmulatorActivity.buildReaderIntent(this), null));
+                adapter.add(TestListItem.newTest(this, R.string.nfc_hce_payment_prefix_aids_reader_2,
+                        SimpleReaderActivity.class.getName(),
+                        PrefixPaymentEmulator2Activity.buildReaderIntent(this), null));
 
-            adapter.add(TestListItem.newTest(this, R.string.nfc_hce_other_conflicting_prefix_aids_reader,
-                    SimpleReaderActivity.class.getName(),
-                    ConflictingNonPaymentPrefixEmulatorActivity.buildReaderIntent(this), null));
+                adapter.add(TestListItem.newTest(this, R.string.nfc_hce_other_prefix_aids_reader,
+                        SimpleReaderActivity.class.getName(),
+                        DualNonPaymentPrefixEmulatorActivity.buildReaderIntent(this), null));
+
+                adapter.add(TestListItem.newTest(this, R.string.nfc_hce_other_conflicting_prefix_aids_reader,
+                        SimpleReaderActivity.class.getName(),
+                        ConflictingNonPaymentPrefixEmulatorActivity.buildReaderIntent(this), null));
+            }
         }
 
         setTestListAdapter(adapter);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java
index c67169a..698948b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/HceUtils.java
@@ -22,6 +22,9 @@
     public static final String TRANSPORT_PREFIX_AID = "F001020304";
     public static final String ACCESS_PREFIX_AID = "F005060708";
 
+    public static final String LARGE_NUM_AIDS_PREFIX = "F00102030414";
+    public static final String LARGE_NUM_AIDS_POSTFIX ="81";
+
     public static void enableComponent(PackageManager pm, ComponentName component) {
         pm.setComponentEnabledSetting(
                 component,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/LargeNumAidsEmulatorActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/LargeNumAidsEmulatorActivity.java
new file mode 100644
index 0000000..db58252
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/LargeNumAidsEmulatorActivity.java
@@ -0,0 +1,59 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.nfc.cardemulation.CardEmulation;
+import android.os.Bundle;
+
+import com.android.cts.verifier.R;
+import com.android.cts.verifier.nfc.NfcDialogs;
+
+import java.util.ArrayList;
+
+public class LargeNumAidsEmulatorActivity extends BaseEmulatorActivity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+       super.onCreate(savedInstanceState);
+       setContentView(R.layout.pass_fail_text);
+       setPassFailButtonClickListeners();
+       getPassButton().setEnabled(false);
+       setupServices(this, LargeNumAidsService.COMPONENT);
+    }
+
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+    }
+
+
+    @Override
+    void onServicesSetup(boolean result) {
+        ArrayList<String> aids = new ArrayList<String>();
+        for (int i = 0; i < 256; i++) {
+            aids.add(HceUtils.LARGE_NUM_AIDS_PREFIX + String.format("%02X", i) + HceUtils.LARGE_NUM_AIDS_POSTFIX);
+        }
+        mCardEmulation.registerAidsForService(LargeNumAidsService.COMPONENT,
+                CardEmulation.CATEGORY_OTHER, aids);
+    }
+
+    @Override
+    void onApduSequenceComplete(ComponentName component, long duration) {
+        if (component.equals(LargeNumAidsService.COMPONENT)) {
+            getPassButton().setEnabled(true);
+        }
+    }
+
+    public static Intent buildReaderIntent(Context context) {
+        Intent readerIntent = new Intent(context, SimpleReaderActivity.class);
+        readerIntent.putExtra(SimpleReaderActivity.EXTRA_APDUS,
+                LargeNumAidsService.getCommandSequence());
+        readerIntent.putExtra(SimpleReaderActivity.EXTRA_RESPONSES,
+                LargeNumAidsService.getResponseSequence());
+        readerIntent.putExtra(SimpleReaderActivity.EXTRA_LABEL,
+                context.getString(R.string.nfc_hce_large_num_aids_reader));
+        return readerIntent;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/LargeNumAidsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/LargeNumAidsService.java
new file mode 100644
index 0000000..5883543
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/nfc/hce/LargeNumAidsService.java
@@ -0,0 +1,37 @@
+package com.android.cts.verifier.nfc.hce;
+
+import android.content.ComponentName;
+
+public class LargeNumAidsService extends HceService {
+    static final String TAG = "LargeNumAidsService";
+
+    static final ComponentName COMPONENT =
+            new ComponentName("com.android.cts.verifier",
+            LargeNumAidsService.class.getName());
+
+    public static final CommandApdu[] getCommandSequence() {
+        CommandApdu[] commands = new CommandApdu[256];
+        for (int i = 0; i < 256; i++) {
+            commands[i] = HceUtils.buildSelectApdu(HceUtils.LARGE_NUM_AIDS_PREFIX + String.format("%02X", i) +
+                    HceUtils.LARGE_NUM_AIDS_POSTFIX, true);
+        }
+        return commands;
+    }
+
+    public static final String[] getResponseSequence() {
+        String[] responses = new String[256];
+        for (int i = 0; i < 256; i++) {
+            responses[i] = "9000" + String.format("%02X", i);
+        }
+        return responses;
+    }
+
+    public LargeNumAidsService() {
+        initialize(getCommandSequence(), getResponseSequence());
+    }
+
+    @Override
+    public ComponentName getComponent() {
+        return COMPONENT;
+    }
+}
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
new file mode 100644
index 0000000..d8f196a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/AttentionManagementVerifierActivity.java
@@ -0,0 +1,931 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.notifications;
+
+import static com.android.cts.verifier.notifications.MockListener.JSON_AMBIENT;
+import static com.android.cts.verifier.notifications.MockListener.JSON_MATCHES_ZEN_FILTER;
+import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
+
+import android.app.Notification;
+import android.content.ContentProviderOperation;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.service.notification.NotificationListenerService;
+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;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+public class AttentionManagementVerifierActivity
+        extends InteractiveVerifierActivity {
+    private static final String TAG = "NoListenerAttentionVerifier";
+
+    private static final String ALICE = "Alice";
+    private static final String ALICE_PHONE = "+16175551212";
+    private static final String ALICE_EMAIL = "alice@_foo._bar";
+    private static final String BOB = "Bob";
+    private static final String BOB_PHONE = "+16505551212";;
+    private static final String BOB_EMAIL = "bob@_foo._bar";
+    private static final String CHARLIE = "Charlie";
+    private static final String CHARLIE_PHONE = "+13305551212";
+    private static final String CHARLIE_EMAIL = "charlie@_foo._bar";
+    private static final int MODE_NONE = 0;
+    private static final int MODE_URI = 1;
+    private static final int MODE_PHONE = 2;
+    private static final int MODE_EMAIL = 3;
+
+    private Uri mAliceUri;
+    private Uri mBobUri;
+    private Uri mCharlieUri;
+
+    @Override
+    int getTitleResource() {
+        return R.string.attention_test;
+    }
+
+    @Override
+    int getInstructionsResource() {
+        return R.string.attention_info;
+    }
+
+    // Test Setup
+
+    @Override
+    protected List<InteractiveTestCase> createTestItems() {
+        List<InteractiveTestCase> tests = new ArrayList<>(17);
+        tests.add(new IsEnabledTest());
+        tests.add(new ServiceStartedTest());
+        tests.add(new InsertContactsTest());
+        tests.add(new SetModeNoneTest());
+        tests.add(new NoneInterceptsAllTest());
+        tests.add(new SetModePriorityTest());
+        tests.add(new PriorityInterceptsSomeTest());
+        tests.add(new SetModeAllTest());
+        tests.add(new AllInterceptsNothingTest());
+        tests.add(new DefaultOrderTest());
+        tests.add(new PrioritytOrderTest());
+        tests.add(new InterruptionOrderTest());
+        tests.add(new AmbientBitsTest());
+        tests.add(new LookupUriOrderTest());
+        tests.add(new EmailOrderTest());
+        tests.add(new PhoneOrderTest());
+        tests.add(new DeleteContactsTest());
+        return tests;
+    }
+
+    // Tests
+
+    protected class InsertContactsTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_create_contacts);
+        }
+
+        @Override
+        void setUp() {
+            insertSingleContact(ALICE, ALICE_PHONE, ALICE_EMAIL, true);
+            insertSingleContact(BOB, BOB_PHONE, BOB_EMAIL, false);
+            // charlie is not in contacts
+            status = READY;
+            // wait for insertions to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            mAliceUri = lookupContact(ALICE_PHONE);
+            mBobUri = lookupContact(BOB_PHONE);
+            mCharlieUri = lookupContact(CHARLIE_PHONE);
+
+            status = PASS;
+            if (mAliceUri == null) { status = FAIL; }
+            if (mBobUri == null) { status = FAIL; }
+            if (mCharlieUri != null) { status = FAIL; }
+            next();
+        }
+    }
+
+    protected class DeleteContactsTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_delete_contacts);
+        }
+
+        @Override
+        void test() {
+            final ArrayList<ContentProviderOperation> operationList = new ArrayList<>();
+            operationList.add(ContentProviderOperation.newDelete(mAliceUri).build());
+            operationList.add(ContentProviderOperation.newDelete(mBobUri).build());
+            try {
+                mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
+                status = READY;
+            } catch (RemoteException e) {
+                Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+                status = FAIL;
+            } catch (OperationApplicationException e) {
+                Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+                status = FAIL;
+            }
+            status = PASS;
+            next();
+        }
+    }
+
+    protected class SetModeNoneTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createRetryItem(parent, R.string.attention_filter_none);
+        }
+
+        @Override
+        void test() {
+            MockListener.probeFilter(mContext,
+                    new MockListener.IntegerResultCatcher() {
+                        @Override
+                        public void accept(int mode) {
+                            if (mode == NotificationListenerService.INTERRUPTION_FILTER_NONE) {
+                                status = PASS;
+                                next();
+                            } else {
+                                Log.i("SetModeNoneTest", "waiting, current mode is: " + mode);
+                                status = WAIT_FOR_USER;
+                            }
+                        }
+                    });
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    protected class NoneInterceptsAllTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_all_are_filtered);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_URI, false, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerPayloads(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> result) {
+                            Set<String> found = new HashSet<String>();
+                            if (result == null || result.size() == 0) {
+                                status = FAIL;
+                                next();
+                                return;
+                            }
+                            boolean pass = true;
+                            for (String payloadData : result) {
+                                try {
+                                    JSONObject payload = new JSONObject(payloadData);
+                                    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;
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+
+    }
+
+    protected class SetModeAllTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createRetryItem(parent, R.string.attention_filter_all);
+        }
+
+        @Override
+        void test() {
+            MockListener.probeFilter(mContext,
+                    new MockListener.IntegerResultCatcher() {
+                        @Override
+                        public void accept(int mode) {
+                            if (mode == NotificationListenerService.INTERRUPTION_FILTER_ALL) {
+                                status = PASS;
+                                next();
+                            } else {
+                                Log.i("SetModeAllTest", "waiting, current mode is: " + mode);
+                                status = WAIT_FOR_USER;
+                            }
+                        }
+                    });
+        }
+    }
+
+    protected class AllInterceptsNothingTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_none_are_filtered);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_URI, false, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerPayloads(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> result) {
+                            Set<String> found = new HashSet<String>();
+                            if (result == null || result.size() == 0) {
+                                status = FAIL;
+                                return;
+                            }
+                            boolean pass = true;
+                            for (String payloadData : result) {
+                                try {
+                                    JSONObject payload = new JSONObject(payloadData);
+                                    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;
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    protected class SetModePriorityTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createRetryItem(parent, R.string.attention_filter_priority);
+        }
+
+        @Override
+        void test() {
+            MockListener.probeFilter(mContext,
+                    new MockListener.IntegerResultCatcher() {
+                        @Override
+                        public void accept(int mode) {
+                            if (mode == NotificationListenerService.INTERRUPTION_FILTER_PRIORITY) {
+                                status = PASS;
+                                next();
+                            } else {
+                                Log.i("SetModePriorityTest", "waiting, current mode is: " + mode);
+                                status = WAIT_FOR_USER;
+                            }
+                        }
+                    });
+        }
+    }
+
+    protected class PriorityInterceptsSomeTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_some_are_filtered);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_URI, false, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerPayloads(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> result) {
+                            Set<String> found = new HashSet<String>();
+                            if (result == null || result.size() == 0) {
+                                status = FAIL;
+                                return;
+                            }
+                            boolean pass = true;
+                            for (String payloadData : result) {
+                                try {
+                                    JSONObject payload = new JSONObject(payloadData);
+                                    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;
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // ordered by time: C, B, A
+    protected class DefaultOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_default_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_NONE, false, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = findTagInKeys(ALICE, orderedKeys);
+                            int rankB = findTagInKeys(BOB, orderedKeys);
+                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
+                            if (rankC < rankB && rankB < rankA) {
+                                status = PASS;
+                            } else {
+                                logFail(rankA + ", " + rankB + ", " + rankC);
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // ordered by priority: B, C, A
+    protected class PrioritytOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_priority_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_NONE, true, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = findTagInKeys(ALICE, orderedKeys);
+                            int rankB = findTagInKeys(BOB, orderedKeys);
+                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
+                            if (rankB < rankC && rankC < rankA) {
+                                status = PASS;
+                            } else {
+                                logFail(rankA + ", " + rankB + ", " + rankC);
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // A starts at the top then falls to the bottom
+    protected class InterruptionOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_interruption_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_NONE, false, true);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            if (status == READY) {
+                MockListener.probeListenerOrder(mContext,
+                        new MockListener.StringListResultCatcher() {
+                            @Override
+                            public void accept(List<String> orderedKeys) {
+                                int rankA = findTagInKeys(ALICE, orderedKeys);
+                                int rankB = findTagInKeys(BOB, orderedKeys);
+                                int rankC = findTagInKeys(CHARLIE, orderedKeys);
+                                if (rankA < rankB && rankA < rankC) {
+                                    status = RETEST;
+                                    delay(12000);
+                                } else {
+                                    logFail("noisy notification did not sort to top.");
+                                    status = FAIL;
+                                    next();
+                                }
+                            }
+                        });
+                delay();  // in case the catcher never returns
+            } else {
+                MockListener.probeListenerOrder(mContext,
+                        new MockListener.StringListResultCatcher() {
+                            @Override
+                            public void accept(List<String> orderedKeys) {
+                                int rankA = findTagInKeys(ALICE, orderedKeys);
+                                int rankB = findTagInKeys(BOB, orderedKeys);
+                                int rankC = findTagInKeys(CHARLIE, orderedKeys);
+                                if (rankA > rankB && rankA > rankC) {
+                                    status = PASS;
+                                } else {
+                                    logFail("noisy notification did not fade back into the list.");
+                                    status = FAIL;
+                                }
+                                next();
+                            }
+                        });
+                delay();  // in case the catcher never returns
+            }
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // B & C above the fold, A below
+    protected class AmbientBitsTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_ambient_bit);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_NONE, true, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerPayloads(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> result) {
+                            Set<String> found = new HashSet<String>();
+                            if (result == null || result.size() == 0) {
+                                status = FAIL;
+                                return;
+                            }
+                            boolean pass = true;
+                            for (String payloadData : result) {
+                                try {
+                                    JSONObject payload = new JSONObject(payloadData);
+                                    String tag = payload.getString(JSON_TAG);
+                                    boolean ambient = payload.getBoolean(JSON_AMBIENT);
+                                    Log.e(TAG, tag + (ambient ? " is" : " isn't") + " ambient");
+                                    if (found.contains(tag)) {
+                                        // multiple entries for same notification!
+                                        pass = false;
+                                    } else if (ALICE.equals(tag)) {
+                                        found.add(ALICE);
+                                        pass &= ambient;
+                                    } else if (BOB.equals(tag)) {
+                                        found.add(BOB);
+                                        pass &= !ambient;
+                                    } else if (CHARLIE.equals(tag)) {
+                                        found.add(CHARLIE);
+                                        pass &= !ambient;
+                                    }
+                                } catch (JSONException e) {
+                                    pass = false;
+                                    Log.e(TAG, "failed to unpack data from mocklistener", e);
+                                }
+                            }
+                            pass &= found.size() == 3;
+                            status = pass ? PASS : FAIL;
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // ordered by contact affinity: A, B, C
+    protected class LookupUriOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_lookup_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_URI, false, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = findTagInKeys(ALICE, orderedKeys);
+                            int rankB = findTagInKeys(BOB, orderedKeys);
+                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
+                            if (rankA < rankB && rankB < rankC) {
+                                status = PASS;
+                            } else {
+                                logFail(rankA + ", " + rankB + ", " + rankC);
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // ordered by contact affinity: A, B, C
+    protected class EmailOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_email_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_EMAIL, false, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = findTagInKeys(ALICE, orderedKeys);
+                            int rankB = findTagInKeys(BOB, orderedKeys);
+                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
+                            if (rankA < rankB && rankB < rankC) {
+                                status = PASS;
+                            } else {
+                                logFail(rankA + ", " + rankB + ", " + rankC);
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // ordered by contact affinity: A, B, C
+    protected class PhoneOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_phone_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications(MODE_PHONE, false, false);
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = findTagInKeys(ALICE, orderedKeys);
+                            int rankB = findTagInKeys(BOB, orderedKeys);
+                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
+                            if (rankA < rankB && rankB < rankC) {
+                                status = PASS;
+                            } else {
+                                logFail(rankA + ", " + rankB + ", " + rankC);
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    // Utilities
+
+    // usePriorities true: B, C, A
+    // usePriorities false:
+    //   MODE_NONE: C, B, A
+    //   otherwise: A, B ,C
+    private void sendNotifications(int annotationMode, boolean usePriorities, boolean noisy) {
+        // TODO(cwren) Fixes flakey tests due to bug 17644321. Remove this line when it is fixed.
+        int baseId = NOTIFICATION_ID + (noisy ? 3 : 0);
+
+        // C, B, A when sorted by time.  Times must be in the past.
+        long whenA = System.currentTimeMillis() - 4000000L;
+        long whenB = System.currentTimeMillis() - 2000000L;
+        long whenC = System.currentTimeMillis() - 1000000L;
+
+        // B, C, A when sorted by priorities
+        int priorityA = usePriorities ? Notification.PRIORITY_MIN : Notification.PRIORITY_DEFAULT;
+        int priorityB = usePriorities ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
+        int priorityC = usePriorities ? Notification.PRIORITY_LOW : Notification.PRIORITY_DEFAULT;
+
+        Notification.Builder alice = new Notification.Builder(mContext)
+                .setContentTitle(ALICE)
+                .setContentText(ALICE)
+                .setSmallIcon(R.drawable.ic_stat_alice)
+                .setPriority(priorityA)
+                .setCategory(Notification.CATEGORY_MESSAGE)
+                .setWhen(whenA);
+        alice.setDefaults(noisy ? Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE : 0);
+        addPerson(annotationMode, alice, mAliceUri, ALICE_PHONE, ALICE_EMAIL);
+        mNm.notify(ALICE, baseId + 1, alice.build());
+
+        Notification.Builder bob = new Notification.Builder(mContext)
+                .setContentTitle(BOB)
+                .setContentText(BOB)
+                .setSmallIcon(R.drawable.ic_stat_bob)
+                .setPriority(priorityB)
+                .setCategory(Notification.CATEGORY_MESSAGE)
+                .setWhen(whenB);
+        addPerson(annotationMode, bob, mBobUri, BOB_PHONE, BOB_EMAIL);
+        mNm.notify(BOB, baseId + 2, bob.build());
+
+        Notification.Builder charlie = new Notification.Builder(mContext)
+                .setContentTitle(CHARLIE)
+                .setContentText(CHARLIE)
+                .setSmallIcon(R.drawable.ic_stat_charlie)
+                .setPriority(priorityC)
+                .setCategory(Notification.CATEGORY_MESSAGE)
+                .setWhen(whenC);
+        addPerson(annotationMode, charlie, mCharlieUri, CHARLIE_PHONE, CHARLIE_EMAIL);
+        mNm.notify(CHARLIE, baseId + 3, charlie.build());
+    }
+
+    private void addPerson(int mode, Notification.Builder note,
+            Uri uri, String phone, String email) {
+        if (mode == MODE_URI && uri != null) {
+            note.addPerson(uri.toString());
+        } else if (mode == MODE_PHONE) {
+            note.addPerson(Uri.fromParts("tel", phone, null).toString());
+        } else if (mode == MODE_EMAIL) {
+            note.addPerson(Uri.fromParts("mailto", email, null).toString());
+        }
+    }
+
+    private void insertSingleContact(String name, String phone, String email, boolean starred) {
+        final ArrayList<ContentProviderOperation> operationList =
+                new ArrayList<ContentProviderOperation>();
+        ContentProviderOperation.Builder builder =
+                ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI);
+        builder.withValue(ContactsContract.RawContacts.STARRED, starred ? 1 : 0);
+        operationList.add(builder.build());
+
+        builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
+        builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
+        builder.withValue(ContactsContract.Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+        builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
+        operationList.add(builder.build());
+
+        if (phone != null) {
+            builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
+            builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
+            builder.withValue(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+            builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);
+            builder.withValue(Phone.NUMBER, phone);
+            builder.withValue(ContactsContract.Data.IS_PRIMARY, 1);
+            operationList.add(builder.build());
+        }
+        if (email != null) {
+            builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
+            builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
+            builder.withValue(ContactsContract.Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+            builder.withValue(Email.TYPE, Email.TYPE_HOME);
+            builder.withValue(Email.DATA, email);
+            operationList.add(builder.build());
+        }
+
+        try {
+            mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
+        } catch (RemoteException e) {
+            Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+        } catch (OperationApplicationException e) {
+            Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+        }
+    }
+
+    private Uri lookupContact(String phone) {
+        Cursor c = null;
+        try {
+            Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
+                    Uri.encode(phone));
+            String[] projection = new String[] { ContactsContract.Contacts._ID,
+                    ContactsContract.Contacts.LOOKUP_KEY };
+            c = mContext.getContentResolver().query(phoneUri, projection, null, null, null);
+            if (c != null && c.getCount() > 0) {
+                c.moveToFirst();
+                int lookupIdx = c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
+                int idIdx = c.getColumnIndex(ContactsContract.Contacts._ID);
+                String lookupKey = c.getString(lookupIdx);
+                long contactId = c.getLong(idIdx);
+                return ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
+            }
+        } catch (Throwable t) {
+            Log.w(TAG, "Problem getting content resolver or performing contacts query.", t);
+        } finally {
+            if (c != null) {
+                c.close();
+            }
+        }
+        return null;
+    }
+
+    /** Search a list of notification keys for a givcen tag. */
+    private int findTagInKeys(String tag, List<String> orderedKeys) {
+        for (int i = 0; i < orderedKeys.size(); i++) {
+            if (orderedKeys.get(i).contains(tag)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
new file mode 100644
index 0000000..b658e4f
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/InteractiveVerifierActivity.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.notifications;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.provider.Settings.Secure;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
+
+public abstract class InteractiveVerifierActivity extends PassFailButtons.Activity
+        implements Runnable {
+    private static final String TAG = "InteractiveVerifier";
+    private static final String STATE = "state";
+    private static final String STATUS = "status";
+    private static LinkedBlockingQueue<String> sDeletedQueue = new LinkedBlockingQueue<String>();
+    protected static final String LISTENER_PATH = "com.android.cts.verifier/" +
+            "com.android.cts.verifier.notifications.MockListener";
+    protected static final int SETUP = 0;
+    protected static final int READY = 1;
+    protected static final int RETEST = 2;
+    protected static final int PASS = 3;
+    protected static final int FAIL = 4;
+    protected static final int WAIT_FOR_USER = 5;
+
+    protected static final int NOTIFICATION_ID = 1001;
+
+    // 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;
+    protected NotificationManager mNm;
+    protected Context mContext;
+    protected Runnable mRunner;
+    protected View mHandler;
+    protected String mPackageString;
+
+    private LayoutInflater mInflater;
+    private ViewGroup mItemList;
+    private List<InteractiveTestCase> mTestList;
+    private Iterator<InteractiveTestCase> mTestOrder;
+
+    public static class DismissService extends Service {
+        @Override
+        public IBinder onBind(Intent intent) {
+            return null;
+        }
+
+        @Override
+        public void onStart(Intent intent, int startId) {
+            if(intent != null) { sDeletedQueue.offer(intent.getAction()); }
+        }
+    }
+
+    protected abstract class InteractiveTestCase {
+        int status;
+        private View view;
+
+        abstract View inflate(ViewGroup parent);
+        View getView(ViewGroup parent) {
+            if (view == null) {
+                view = inflate(parent);
+            }
+            return view;
+        }
+
+        /** @return true if the test should re-run when the test activity starts. */
+        boolean autoStart() {
+            return false;
+        }
+
+        /** Set status to {@link #READY} to proceed, or {@link #SETUP} to try again. */
+        void setUp() { status = READY; next(); };
+
+        /** Set status to {@link #PASS} or @{link #FAIL} to proceed, or {@link #READY} to retry. */
+        void test() { status = FAIL; next(); };
+
+        /** Do not modify status. */
+        void tearDown() { next(); };
+
+        protected void logFail() {
+            logFail(null);
+        }
+
+        protected void logFail(String message) {
+            logWithStack("failed " + this.getClass().getSimpleName() +
+                    ((message == null) ? "" : ": " + message));
+        }
+    }
+
+    abstract int getTitleResource();
+    abstract int getInstructionsResource();
+
+    protected void onCreate(Bundle savedState) {
+        super.onCreate(savedState);
+        int savedStateIndex = (savedState == null) ? 0 : savedState.getInt(STATE, 0);
+        int savedStatus = (savedState == null) ? SETUP : savedState.getInt(STATUS, SETUP);
+        Log.i(TAG, "restored state(" + savedStateIndex + "}, status(" + savedStatus + ")");
+        mContext = this;
+        mRunner = this;
+        mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+        mPackageManager = getPackageManager();
+        mInflater = getLayoutInflater();
+        View view = mInflater.inflate(R.layout.nls_main, null);
+        mItemList = (ViewGroup) view.findViewById(R.id.nls_test_items);
+        mHandler = mItemList;
+        mTestList = new ArrayList<>();
+        mTestList.addAll(createTestItems());
+        for (InteractiveTestCase test: mTestList) {
+            mItemList.addView(test.getView(mItemList));
+        }
+        mTestOrder = mTestList.iterator();
+        for (int i = 0; i < savedStateIndex; i++) {
+            mCurrentTest = mTestOrder.next();
+            mCurrentTest.status = PASS;
+        }
+        mCurrentTest = mTestOrder.next();
+        mCurrentTest.status = savedStatus;
+
+        setContentView(view);
+        setPassFailButtonClickListeners();
+        getPassButton().setEnabled(false);
+
+        setInfoResources(getTitleResource(), getInstructionsResource(), -1);
+    }
+
+    @Override
+    protected void onSaveInstanceState (Bundle outState) {
+        final int stateIndex = mTestList.indexOf(mCurrentTest);
+        outState.putInt(STATE, stateIndex);
+        outState.putInt(STATUS, mCurrentTest.status);
+        Log.i(TAG, "saved state(" + stateIndex + "}, status(" + (mCurrentTest.status) + ")");
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        if (mCurrentTest.autoStart()) {
+            mCurrentTest.status = READY;
+        }
+        next();
+    }
+
+    // Interface Utilities
+
+    protected void markItem(InteractiveTestCase test) {
+        if (test == null) { return; }
+        View item = test.view;
+        ImageView status = (ImageView) item.findViewById(R.id.nls_status);
+        View button = item.findViewById(R.id.nls_action_button);
+        switch (test.status) {
+            case WAIT_FOR_USER:
+                status.setImageResource(R.drawable.fs_warning);
+                break;
+
+            case SETUP:
+            case READY:
+            case RETEST:
+                status.setImageResource(R.drawable.fs_clock);
+                break;
+
+            case FAIL:
+                status.setImageResource(R.drawable.fs_error);
+                button.setClickable(false);
+                button.setEnabled(false);
+                break;
+
+            case PASS:
+                status.setImageResource(R.drawable.fs_good);
+                button.setClickable(false);
+                button.setEnabled(false);
+                break;
+
+        }
+        status.invalidate();
+    }
+
+    protected View createNlsSettingsItem(ViewGroup parent, int messageId) {
+        return createUserItem(parent, R.string.nls_start_settings, messageId);
+    }
+
+    protected View createRetryItem(ViewGroup parent, int messageId, Object... messageFormatArgs) {
+        return createUserItem(parent, R.string.attention_ready, messageId, messageFormatArgs);
+    }
+
+    protected View createUserItem(ViewGroup parent, int actionId, int messageId,
+            Object... messageFormatArgs) {
+        View item = mInflater.inflate(R.layout.nls_item, parent, false);
+        TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
+        instructions.setText(getString(messageId, messageFormatArgs));
+        Button button = (Button) item.findViewById(R.id.nls_action_button);
+        button.setText(actionId);
+        button.setTag(actionId);
+        return item;
+    }
+
+    protected View  createAutoItem(ViewGroup parent, int stringId) {
+        View item = mInflater.inflate(R.layout.nls_item, parent, false);
+        TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
+        instructions.setText(stringId);
+        View button = item.findViewById(R.id.nls_action_button);
+        button.setVisibility(View.GONE);
+        return item;
+    }
+
+    // Test management
+
+    abstract protected List<InteractiveTestCase> createTestItems();
+
+    public void run() {
+        if (mCurrentTest == null) { return; }
+        markItem(mCurrentTest);
+        switch (mCurrentTest.status) {
+            case SETUP:
+                Log.i(TAG, "running setup for: " + mCurrentTest.getClass().getSimpleName());
+                mCurrentTest.setUp();
+                break;
+
+            case WAIT_FOR_USER:
+                Log.i(TAG, "waiting for user: " + mCurrentTest.getClass().getSimpleName());
+                break;
+
+            case READY:
+            case RETEST:
+                Log.i(TAG, "running test for: " + mCurrentTest.getClass().getSimpleName());
+                mCurrentTest.test();
+                break;
+
+            case FAIL:
+                Log.i(TAG, "FAIL: " + mCurrentTest.getClass().getSimpleName());
+                mCurrentTest = null;
+                break;
+
+            case PASS:
+                Log.i(TAG, "pass for: " + mCurrentTest.getClass().getSimpleName());
+                mCurrentTest.tearDown();
+                if (mTestOrder.hasNext()) {
+                    mCurrentTest = mTestOrder.next();
+                    Log.i(TAG, "next test is: " + mCurrentTest.getClass().getSimpleName());
+                } else {
+                    Log.i(TAG, "no more tests");
+                    mCurrentTest = null;
+                    getPassButton().setEnabled(true);
+                    mNm.cancelAll();
+                }
+                break;
+        }
+        markItem(mCurrentTest);
+    }
+
+    /**
+     * Return to the state machine to progress through the tests.
+     */
+    protected void next() {
+        mHandler.removeCallbacks(mRunner);
+        mHandler.post(mRunner);
+    }
+
+    /**
+     * Wait for things to settle before returning to the state machine.
+     */
+    protected void delay() {
+        delay(3000);
+    }
+
+    /**
+     * Wait for some time.
+     */
+    protected void delay(long waitTime) {
+        mHandler.removeCallbacks(mRunner);
+        mHandler.postDelayed(mRunner, waitTime);
+    }
+
+    // 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();
+            } else if (id == R.string.attention_ready) {
+                mCurrentTest.status = READY;
+                next();
+            }
+        }
+    }
+
+    // Utilities
+
+    protected PendingIntent makeIntent(int code, String tag) {
+        Intent intent = new Intent(tag);
+        intent.setComponent(new ComponentName(mContext, DismissService.class));
+        PendingIntent pi = PendingIntent.getService(mContext, code, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+        return pi;
+    }
+
+    protected boolean checkEquals(long expected, long actual, String message) {
+        if (expected == actual) {
+            return true;
+        }
+        logWithStack(String.format(message, expected, actual));
+        return false;
+    }
+
+    protected boolean checkEquals(String expected, String actual, String message) {
+        if (expected.equals(actual)) {
+            return true;
+        }
+        logWithStack(String.format(message, expected, actual));
+        return false;
+    }
+
+    protected boolean checkFlagSet(int expected, int actual, String message) {
+        if ((expected & actual) != 0) {
+            return true;
+        }
+        logWithStack(String.format(message, expected, actual));
+        return false;
+    };
+
+    protected void logWithStack(String message) {
+        Throwable stackTrace = new Throwable();
+        stackTrace.fillInStackTrace();
+        Log.e(TAG, message, stackTrace);
+    }
+
+    // Common Tests: useful for the side-effects they generate
+
+    protected class IsEnabledTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createNlsSettingsItem(parent, R.string.nls_enable_service);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        void test() {
+            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 = PASS;
+                } else {
+                    status = WAIT_FOR_USER;
+                }
+                next();
+            }
+        }
+
+        void tearDown() {
+            // wait for the service to start
+            delay();
+        }
+    }
+
+    protected class ServiceStartedTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_service_started);
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerStatus(mContext,
+                    new MockListener.StatusCatcher() {
+                        @Override
+                        public void accept(int result) {
+                            if (result == Activity.RESULT_OK) {
+                                status = PASS;
+                                next();
+                            } else {
+                                logFail();
+                                status = RETEST;
+                                delay();
+                            }
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+}
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 b4863fa..c80f371 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/MockListener.java
@@ -82,6 +82,7 @@
         Log.d(TAG, "created");
 
         mTestPackages.add("com.android.cts.verifier");
+        mTestPackages.add("com.android.cts.robot");
 
         mPosted = new ArrayList<String>();
         mRemoved = new ArrayList<String>();
@@ -238,8 +239,8 @@
         Log.d(TAG, "removed: " + sbn.getTag());
         mRemoved.add(sbn.getTag());
         mNotifications.remove(sbn.getKey());
-        onNotificationRankingUpdate(rankingMap);
         mNotificationKeys.remove(sbn.getTag());
+        onNotificationRankingUpdate(rankingMap);
     }
 
     public static void resetListenerData(Context context) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAttentionManagementVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAttentionManagementVerifierActivity.java
deleted file mode 100644
index b4e348f..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationAttentionManagementVerifierActivity.java
+++ /dev/null
@@ -1,883 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.verifier.notifications;
-
-import static com.android.cts.verifier.notifications.MockListener.JSON_AMBIENT;
-import static com.android.cts.verifier.notifications.MockListener.JSON_MATCHES_ZEN_FILTER;
-import static com.android.cts.verifier.notifications.MockListener.JSON_TAG;
-
-import android.app.Activity;
-import android.app.Notification;
-import android.content.ContentProviderOperation;
-import android.content.Intent;
-import android.content.OperationApplicationException;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.RemoteException;
-import android.provider.ContactsContract;
-import android.provider.ContactsContract.CommonDataKinds.Email;
-import android.provider.ContactsContract.CommonDataKinds.Phone;
-import android.provider.ContactsContract.CommonDataKinds.StructuredName;
-import android.provider.Settings.Secure;
-import android.service.notification.NotificationListenerService;
-import android.util.Log;
-import com.android.cts.verifier.R;
-import com.android.cts.verifier.nfc.TagVerifierActivity;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-public class NotificationAttentionManagementVerifierActivity
-        extends NotificationListenerVerifierActivity {
-    private static final String TAG = TagVerifierActivity.class.getSimpleName();
-    private static final String ALICE = "Alice";
-    private static final String ALICE_PHONE = "+16175551212";
-    private static final String ALICE_EMAIL = "alice@_foo._bar";
-    private static final String BOB = "Bob";
-    private static final String BOB_PHONE = "+16505551212";;
-    private static final String BOB_EMAIL = "bob@_foo._bar";
-    private static final String CHARLIE = "Charlie";
-    private static final String CHARLIE_PHONE = "+13305551212";
-    private static final String CHARLIE_EMAIL = "charlie@_foo._bar";
-    private static final int MODE_NONE = 0;
-    private static final int MODE_URI = 1;
-    private static final int MODE_PHONE = 2;
-    private static final int MODE_EMAIL = 3;
-    private static final int DELAYED_SETUP = CLEARED;
-
-    private Uri mAliceUri;
-    private Uri mBobUri;
-    private Uri mCharlieUri;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState, R.layout.nls_main);
-        setInfoResources(R.string.attention_test, R.string.attention_info, -1);
-    }
-
-    // Test Setup
-
-    @Override
-    protected void createTestItems() {
-        createNlsSettingsItem(R.string.nls_enable_service);
-        createAutoItem(R.string.nls_service_started);
-        createAutoItem(R.string.attention_create_contacts);
-        createRetryItem(R.string.attention_filter_none);
-        createAutoItem(R.string.attention_all_are_filtered);
-        createRetryItem(R.string.attention_filter_all);
-        createAutoItem(R.string.attention_none_are_filtered);
-        createAutoItem(R.string.attention_default_order);
-        createAutoItem(R.string.attention_interruption_order);
-        createAutoItem(R.string.attention_priority_order);
-        createAutoItem(R.string.attention_ambient_bit);
-        createAutoItem(R.string.attention_lookup_order);
-        createAutoItem(R.string.attention_email_order);
-        createAutoItem(R.string.attention_phone_order);
-        createRetryItem(R.string.attention_filter_priority);
-        createAutoItem(R.string.attention_some_are_filtered);
-        createAutoItem(R.string.attention_delete_contacts);
-    }
-
-    // Test management
-
-    @Override
-    protected void updateStateMachine() {
-        switch (mState) {
-            case 0:
-                testIsEnabled(mState);
-                break;
-            case 1:
-                testIsStarted(mState);
-                break;
-            case 2:
-                testInsertContacts(mState);
-                break;
-            case 3:
-                testModeNone(mState);
-                break;
-            case 4:
-                testNoneInterceptsAll(mState);
-                break;
-            case 5:
-                testModeAll(mState);
-                break;
-            case 6:
-                testAllInterceptsNothing(mState);
-                break;
-            case 7:
-                testDefaultOrder(mState);
-                break;
-            case 8:
-                testInterruptionOrder(mState);
-                break;
-            case 9:
-                testPrioritytOrder(mState);
-                break;
-            case 10:
-                testAmbientBits(mState);
-                break;
-            case 11:
-                testLookupUriOrder(mState);
-                break;
-            case 12:
-                testEmailOrder(mState);
-                break;
-            case 13:
-                testPhoneOrder(mState);
-                break;
-            case 14:
-                testModePriority(mState);
-                break;
-            case 15:
-                testPriorityInterceptsSome(mState);
-                break;
-            case 16:
-                testDeleteContacts(mState);
-                break;
-            case 17:
-                getPassButton().setEnabled(true);
-                mNm.cancelAll();
-                break;
-        }
-    }
-
-    // usePriorities true: B, C, A
-    // usePriorities false:
-    //   MODE_NONE: C, B, A
-    //   otherwise: A, B ,C
-    private void sendNotifications(int annotationMode, boolean usePriorities, boolean noisy) {
-        // TODO(cwren) Fixes flakey tests due to bug 17644321. Remove this line when it is fixed.
-        int baseId = NOTIFICATION_ID + (noisy ? 3 : 0);
-
-        // C, B, A when sorted by time.  Times must be in the past.
-        long whenA = System.currentTimeMillis() - 4000000L;
-        long whenB = System.currentTimeMillis() - 2000000L;
-        long whenC = System.currentTimeMillis() - 1000000L;
-
-        // B, C, A when sorted by priorities
-        int priorityA = usePriorities ? Notification.PRIORITY_MIN : Notification.PRIORITY_DEFAULT;
-        int priorityB = usePriorities ? Notification.PRIORITY_MAX : Notification.PRIORITY_DEFAULT;
-        int priorityC = usePriorities ? Notification.PRIORITY_LOW : Notification.PRIORITY_DEFAULT;
-
-        Notification.Builder alice = new Notification.Builder(mContext)
-                .setContentTitle(ALICE)
-                .setContentText(ALICE)
-                .setSmallIcon(R.drawable.fs_good)
-                .setPriority(priorityA)
-                .setCategory(Notification.CATEGORY_MESSAGE)
-                .setWhen(whenA);
-        alice.setDefaults(noisy ? Notification.DEFAULT_SOUND | Notification.DEFAULT_VIBRATE : 0);
-        addPerson(annotationMode, alice, mAliceUri, ALICE_PHONE, ALICE_EMAIL);
-        mNm.notify(ALICE, baseId + 1, alice.build());
-
-        Notification.Builder bob = new Notification.Builder(mContext)
-                .setContentTitle(BOB)
-                .setContentText(BOB)
-                .setSmallIcon(R.drawable.fs_warning)
-                .setPriority(priorityB)
-                .setCategory(Notification.CATEGORY_MESSAGE)
-                .setWhen(whenB);
-        addPerson(annotationMode, bob, mBobUri, BOB_PHONE, BOB_EMAIL);
-        mNm.notify(BOB, baseId + 2, bob.build());
-
-        Notification.Builder charlie = new Notification.Builder(mContext)
-                .setContentTitle(CHARLIE)
-                .setContentText(CHARLIE)
-                .setSmallIcon(R.drawable.fs_error)
-                .setPriority(priorityC)
-                .setCategory(Notification.CATEGORY_MESSAGE)
-                .setWhen(whenC);
-        addPerson(annotationMode, charlie, mCharlieUri, CHARLIE_PHONE, CHARLIE_EMAIL);
-        mNm.notify(CHARLIE, baseId + 3, charlie.build());
-    }
-
-    private void addPerson(int mode, Notification.Builder note,
-            Uri uri, String phone, String email) {
-        if (mode == MODE_URI && uri != null) {
-            note.addPerson(uri.toString());
-        } else if (mode == MODE_PHONE) {
-            note.addPerson(Uri.fromParts("tel", phone, null).toString());
-        } else if (mode == MODE_EMAIL) {
-            note.addPerson(Uri.fromParts("mailto", email, null).toString());
-        }
-    }
-
-    // Tests
-
-    private void testIsEnabled(int i) {
-        // no setup required
-        Intent settings = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
-        if (settings.resolveActivity(mPackageManager) == null) {
-            logWithStack("failed testIsEnabled: no settings activity");
-            mStatus[i] = FAIL;
-        } else {
-            // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden
-            String listeners = Secure.getString(getContentResolver(),
-                    "enabled_notification_listeners");
-            if (listeners != null && listeners.contains(LISTENER_PATH)) {
-                mStatus[i] = PASS;
-            } else {
-                mStatus[i] = WAIT_FOR_USER;
-            }
-        }
-        next();
-    }
-
-    private void testIsStarted(final int i) {
-        if (mStatus[i] == SETUP) {
-            mStatus[i] = READY;
-            // wait for the service to start
-            delay();
-        } else {
-            MockListener.probeListenerStatus(mContext,
-                    new MockListener.StatusCatcher() {
-                        @Override
-                        public void accept(int result) {
-                            if (result == Activity.RESULT_OK) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("failed testIsStarted: " + result);
-                                mStatus[i] = FAIL;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    private void testModeAll(final int i) {
-        if (mStatus[i] == READY || mStatus[i] == SETUP) {
-            MockListener.probeFilter(mContext,
-                    new MockListener.IntegerResultCatcher() {
-                        @Override
-                        public void accept(int mode) {
-                            if (mode == NotificationListenerService.INTERRUPTION_FILTER_ALL) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("waiting testModeAll: " + mode);
-                                mStatus[i] = WAIT_FOR_USER;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    private void testModePriority(final int i) {
-        if (mStatus[i] == READY || mStatus[i] == SETUP) {
-            MockListener.probeFilter(mContext,
-                    new MockListener.IntegerResultCatcher() {
-                        @Override
-                        public void accept(int mode) {
-                            if (mode == NotificationListenerService.INTERRUPTION_FILTER_PRIORITY) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("waiting testModePriority: " + mode);
-                                mStatus[i] = WAIT_FOR_USER;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    private void testModeNone(final int i) {
-        if (mStatus[i] == READY || mStatus[i] == SETUP) {
-            MockListener.probeFilter(mContext,
-                    new MockListener.IntegerResultCatcher() {
-                        @Override
-                        public void accept(int mode) {
-                            if (mode == NotificationListenerService.INTERRUPTION_FILTER_NONE) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("waiting testModeNone: " + mode);
-                                mStatus[i] = WAIT_FOR_USER;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-
-    private void insertSingleContact(String name, String phone, String email, boolean starred) {
-        final ArrayList<ContentProviderOperation> operationList =
-                new ArrayList<ContentProviderOperation>();
-        ContentProviderOperation.Builder builder =
-                ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI);
-        builder.withValue(ContactsContract.RawContacts.STARRED, starred ? 1 : 0);
-        operationList.add(builder.build());
-
-        builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
-        builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
-        builder.withValue(ContactsContract.Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
-        builder.withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name);
-        operationList.add(builder.build());
-
-        if (phone != null) {
-            builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
-            builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
-            builder.withValue(ContactsContract.Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
-            builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);
-            builder.withValue(Phone.NUMBER, phone);
-            builder.withValue(ContactsContract.Data.IS_PRIMARY, 1);
-            operationList.add(builder.build());
-        }
-        if (email != null) {
-            builder = ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI);
-            builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
-            builder.withValue(ContactsContract.Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
-            builder.withValue(Email.TYPE, Email.TYPE_HOME);
-            builder.withValue(Email.DATA, email);
-            operationList.add(builder.build());
-        }
-
-        try {
-            mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
-        } catch (RemoteException e) {
-            Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
-        } catch (OperationApplicationException e) {
-            Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
-        }
-    }
-
-    private Uri lookupContact(String phone) {
-        Cursor c = null;
-        try {
-            Uri phoneUri = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI,
-                    Uri.encode(phone));
-            String[] projection = new String[] { ContactsContract.Contacts._ID,
-                    ContactsContract.Contacts.LOOKUP_KEY };
-            c = mContext.getContentResolver().query(phoneUri, projection, null, null, null);
-            if (c != null && c.getCount() > 0) {
-                c.moveToFirst();
-                int lookupIdx = c.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY);
-                int idIdx = c.getColumnIndex(ContactsContract.Contacts._ID);
-                String lookupKey = c.getString(lookupIdx);
-                long contactId = c.getLong(idIdx);
-                return ContactsContract.Contacts.getLookupUri(contactId, lookupKey);
-            }
-        } catch (Throwable t) {
-            Log.w(TAG, "Problem getting content resolver or performing contacts query.", t);
-        } finally {
-            if (c != null) {
-                c.close();
-            }
-        }
-        return null;
-    }
-
-    private void testInsertContacts(final int i) {
-        if (mStatus[i] == SETUP) {
-            insertSingleContact(ALICE, ALICE_PHONE, ALICE_EMAIL, true);
-            insertSingleContact(BOB, BOB_PHONE, BOB_EMAIL, false);
-            // charlie is not in contacts
-            mStatus[i] = READY;
-            // wait for insertions to move through the system
-            delay();
-        } else {
-            mAliceUri = lookupContact(ALICE_PHONE);
-            mBobUri = lookupContact(BOB_PHONE);
-            mCharlieUri = lookupContact(CHARLIE_PHONE);
-
-            mStatus[i] = PASS;
-            if (mAliceUri == null) { mStatus[i] = FAIL; }
-            if (mBobUri == null) { mStatus[i] = FAIL; }
-            if (mCharlieUri != null) { mStatus[i] = FAIL; }
-            next();
-        }
-    }
-
-    // ordered by time: C, B, A
-    private void testDefaultOrder(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_NONE, false, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerOrder(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> orderedKeys) {
-                            int rankA = findTagInKeys(ALICE, orderedKeys);
-                            int rankB = findTagInKeys(BOB, orderedKeys);
-                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
-                            if (rankC < rankB && rankB < rankA) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("failed testDefaultOrder : "
-                                        + rankA + ", " + rankB + ", " + rankC);
-                                mStatus[i] = FAIL;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // ordered by priority: B, C, A
-    private void testPrioritytOrder(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_PHONE, true, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerOrder(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> orderedKeys) {
-                            int rankA = findTagInKeys(ALICE, orderedKeys);
-                            int rankB = findTagInKeys(BOB, orderedKeys);
-                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
-                            if (rankB < rankC && rankC < rankA) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("failed testPrioritytOrder : "
-                                        + rankA + ", " + rankB + ", " + rankC);
-                                mStatus[i] = FAIL;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // B & C above the fold, A below
-    private void testAmbientBits(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_PHONE, true, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerPayloads(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> result) {
-                            boolean pass = false;
-                            Set<String> found = new HashSet<String>();
-                            if (result != null && result.size() > 0) {
-                                pass = true;
-                                for (String payloadData : result) {
-                                    try {
-                                        JSONObject payload = new JSONObject(payloadData);
-                                        String tag = payload.getString(JSON_TAG);
-                                        if (found.contains(tag)) {
-                                            // multiple entries for same notification!
-                                            pass = false;
-                                        } else if (ALICE.equals(tag)) {
-                                            found.add(ALICE);
-                                            pass &= payload.getBoolean(JSON_AMBIENT);
-                                        } else if (BOB.equals(tag)) {
-                                            found.add(BOB);
-                                            pass &= !payload.getBoolean(JSON_AMBIENT);
-                                        } else if (CHARLIE.equals(tag)) {
-                                            found.add(CHARLIE);
-                                            pass &= !payload.getBoolean(JSON_AMBIENT);
-                                        }
-                                    } catch (JSONException e) {
-                                        pass = false;
-                                        Log.e(TAG, "failed to unpack data from mocklistener", e);
-                                    }
-                                }
-                            }
-                            pass &= found.size() == 3;
-                            mStatus[i] = pass ? PASS : FAIL;
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // ordered by contact affinity: A, B, C
-    private void testLookupUriOrder(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_URI, false, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerOrder(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> orderedKeys) {
-                            int rankA = findTagInKeys(ALICE, orderedKeys);
-                            int rankB = findTagInKeys(BOB, orderedKeys);
-                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
-                            if (rankA < rankB && rankB < rankC) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("failed testLookupUriOrder : "
-                                        + rankA + ", " + rankB + ", " + rankC);
-                                mStatus[i] = FAIL;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // ordered by contact affinity: A, B, C
-    private void testEmailOrder(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = DELAYED_SETUP;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == DELAYED_SETUP) {
-            sendNotifications(MODE_EMAIL, false, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerOrder(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> orderedKeys) {
-                            int rankA = findTagInKeys(ALICE, orderedKeys);
-                            int rankB = findTagInKeys(BOB, orderedKeys);
-                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
-                            if (rankA < rankB && rankB < rankC) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("failed testEmailOrder : "
-                                        + rankA + ", " + rankB + ", " + rankC);
-                                mStatus[i] = FAIL;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // ordered by contact affinity: A, B, C
-    private void testPhoneOrder(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_PHONE, false, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerOrder(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> orderedKeys) {
-                            int rankA = findTagInKeys(ALICE, orderedKeys);
-                            int rankB = findTagInKeys(BOB, orderedKeys);
-                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
-                            if (rankA < rankB && rankB < rankC) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("failed testPhoneOrder : "
-                                        + rankA + ", " + rankB + ", " + rankC);
-                                mStatus[i] = FAIL;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // A starts at the top then falls to the bottom
-    private void testInterruptionOrder(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_NONE, false, true);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else if (mStatus[i] == READY) {
-            MockListener.probeListenerOrder(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> orderedKeys) {
-                            int rankA = findTagInKeys(ALICE, orderedKeys);
-                            int rankB = findTagInKeys(BOB, orderedKeys);
-                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
-                            if (rankA < rankB && rankA < rankC) {
-                                mStatus[i] = RETRY;
-                                delay(12000);
-                            } else {
-                                logWithStack("noisy notification did not sort to top.");
-                                mStatus[i] = FAIL;
-                                next();
-                            }
-                        }
-                    });
-        } else if (mStatus[i] == RETRY) {
-            MockListener.probeListenerOrder(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> orderedKeys) {
-                            int rankA = findTagInKeys(ALICE, orderedKeys);
-                            int rankB = findTagInKeys(BOB, orderedKeys);
-                            int rankC = findTagInKeys(CHARLIE, orderedKeys);
-                            if (rankA > rankB && rankA > rankC) {
-                                mStatus[i] = PASS;
-                            } else {
-                                logWithStack("noisy notification did not fade back into the list.");
-                                mStatus[i] = FAIL;
-                            }
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // Nothing should be filtered when mode is ALL
-    private void testAllInterceptsNothing(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_URI, false, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerPayloads(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> result) {
-                            boolean pass = false;
-                            Set<String> found = new HashSet<String>();
-                            if (result != null && result.size() > 0) {
-                                pass = true;
-                                for (String payloadData : result) {
-                                    try {
-                                        JSONObject payload = new JSONObject(payloadData);
-                                        String tag = payload.getString(JSON_TAG);
-                                        if (found.contains(tag)) {
-                                            // multiple entries for same notification!
-                                            pass = false;
-                                        } else if (ALICE.equals(tag)) {
-                                            found.add(ALICE);
-                                            pass &= payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        } else if (BOB.equals(tag)) {
-                                            found.add(BOB);
-                                            pass &= payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        } else if (CHARLIE.equals(tag)) {
-                                            found.add(CHARLIE);
-                                            pass &= payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        }
-                                    } catch (JSONException e) {
-                                        pass = false;
-                                        Log.e(TAG, "failed to unpack data from mocklistener", e);
-                                    }
-                                }
-                            }
-                            pass &= found.size() == 3;
-                            mStatus[i] = pass ? PASS : FAIL;
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // A should be filtered when mode is Priority/Starred.
-    private void testPriorityInterceptsSome(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_URI, false, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerPayloads(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> result) {
-                            boolean pass = false;
-                            Set<String> found = new HashSet<String>();
-                            if (result != null && result.size() > 0) {
-                                pass = true;
-                                for (String payloadData : result) {
-                                    try {
-                                        JSONObject payload = new JSONObject(payloadData);
-                                        String tag = payload.getString(JSON_TAG);
-                                        if (found.contains(tag)) {
-                                            // multiple entries for same notification!
-                                            pass = false;
-                                        } else if (ALICE.equals(tag)) {
-                                            found.add(ALICE);
-                                            pass &= payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        } else if (BOB.equals(tag)) {
-                                            found.add(BOB);
-                                            pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        } else if (CHARLIE.equals(tag)) {
-                                            found.add(CHARLIE);
-                                            pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        }
-                                    } catch (JSONException e) {
-                                        pass = false;
-                                        Log.e(TAG, "failed to unpack data from mocklistener", e);
-                                    }
-                                }
-                            }
-                            pass &= found.size() == 3;
-                            mStatus[i] = pass ? PASS : FAIL;
-                            next();
-                        }
-                    });
-        }
-    }
-
-    // Nothing should get through when mode is None.
-    private void testNoneInterceptsAll(final int i) {
-        if (mStatus[i] == SETUP) {
-            mNm.cancelAll();
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            sendNotifications(MODE_URI, false, false);
-            mStatus[i] = READY;
-            // wait for notifications to move through the system
-            delay();
-        } else {
-            MockListener.probeListenerPayloads(mContext,
-                    new MockListener.StringListResultCatcher() {
-                        @Override
-                        public void accept(List<String> result) {
-                            boolean pass = false;
-                            Set<String> found = new HashSet<String>();
-                            if (result != null && result.size() > 0) {
-                                pass = true;
-                                for (String payloadData : result) {
-                                    try {
-                                        JSONObject payload = new JSONObject(payloadData);
-                                        String tag = payload.getString(JSON_TAG);
-                                        if (found.contains(tag)) {
-                                            // multiple entries for same notification!
-                                            pass = false;
-                                        } else if (ALICE.equals(tag)) {
-                                            found.add(ALICE);
-                                            pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        } else if (BOB.equals(tag)) {
-                                            found.add(BOB);
-                                            pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        } else if (CHARLIE.equals(tag)) {
-                                            found.add(CHARLIE);
-                                            pass &= !payload.getBoolean(JSON_MATCHES_ZEN_FILTER);
-                                        }
-                                    } catch (JSONException e) {
-                                        pass = false;
-                                        Log.e(TAG, "failed to unpack data from mocklistener", e);
-                                    }
-                                }
-                            }
-                            pass &= found.size() == 3;
-                            mStatus[i] = pass ? PASS : FAIL;
-                            next();
-                        }
-                    });
-        }
-    }
-
-    /** Search a list of notification keys for a givcen tag. */
-    private int findTagInKeys(String tag, List<String> orderedKeys) {
-        for (int i = 0; i < orderedKeys.size(); i++) {
-            if (orderedKeys.get(i).contains(tag)) {
-                return i;
-            }
-        }
-        return -1;
-    }
-
-    private void testDeleteContacts(final int i) {
-        if (mStatus[i] == SETUP) {
-            final ArrayList<ContentProviderOperation> operationList =
-                    new ArrayList<ContentProviderOperation>();
-            operationList.add(ContentProviderOperation.newDelete(mAliceUri).build());
-            operationList.add(ContentProviderOperation.newDelete(mBobUri).build());
-            try {
-                mContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operationList);
-                mStatus[i] = READY;
-            } catch (RemoteException e) {
-                Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
-                mStatus[i] = FAIL;
-            } catch (OperationApplicationException e) {
-                Log.e(TAG, String.format("%s: %s", e.toString(), e.getMessage()));
-                mStatus[i] = FAIL;
-            }
-            // wait for deletions to move through the system
-            delay(3000);
-        } else if (mStatus[i] == READY) {
-            mAliceUri = lookupContact(ALICE_PHONE);
-            mBobUri = lookupContact(BOB_PHONE);
-            mCharlieUri = lookupContact(CHARLIE_PHONE);
-
-            mStatus[i] = PASS;
-            if (mAliceUri != null) { mStatus[i] = FAIL; }
-            if (mBobUri != null) { mStatus[i] = FAIL; }
-            if (mCharlieUri != null) { mStatus[i] = FAIL; }
-            next();
-        }
-    }
-}
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 0ef595b..ace194c 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/NotificationListenerVerifierActivity.java
@@ -16,76 +16,30 @@
 
 package com.android.cts.verifier.notifications;
 
-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_TAG;
-import static com.android.cts.verifier.notifications.MockListener.JSON_WHEN;
-
 import android.annotation.SuppressLint;
 import android.app.Activity;
 import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.os.Bundle;
-import android.os.IBinder;
 import android.provider.Settings.Secure;
 import android.util.Log;
-import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
-import android.widget.Button;
-import android.widget.ImageView;
-import android.widget.TextView;
 
-import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
-import com.android.cts.verifier.nfc.TagVerifierActivity;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.UUID;
-import java.util.concurrent.LinkedBlockingQueue;
 
-public class NotificationListenerVerifierActivity extends PassFailButtons.Activity
-implements Runnable {
-    private static final String TAG = TagVerifierActivity.class.getSimpleName();
-    private static final String STATE = "state";
-    private static LinkedBlockingQueue<String> sDeletedQueue = new LinkedBlockingQueue<String>();
+import static com.android.cts.verifier.notifications.MockListener.*;
 
-    protected static final String LISTENER_PATH = "com.android.cts.verifier/" +
-            "com.android.cts.verifier.notifications.MockListener";
-    protected static final int SETUP = 0;
-    protected static final int PASS = 1;
-    protected static final int FAIL = 2;
-    protected static final int WAIT_FOR_USER = 3;
-    protected static final int CLEARED = 4;
-    protected static final int READY = 5;
-    protected static final int RETRY = 6;
-
-    protected static final int NOTIFICATION_ID = 1001;
-
-    protected int mState;
-    protected int[] mStatus;
-    protected PackageManager mPackageManager;
-    protected NotificationManager mNm;
-    protected Context mContext;
-    protected Runnable mRunner;
-    protected View mHandler;
-    protected String mPackageString;
-
-    private LayoutInflater mInflater;
-    private ViewGroup mItemList;
+public class NotificationListenerVerifierActivity extends InteractiveVerifierActivity
+        implements Runnable {
+    private static final String TAG = "NoListenerVerifier";
 
     private String mTag1;
     private String mTag2;
@@ -103,199 +57,31 @@
     private int mFlag2;
     private int mFlag3;
 
-    public static class DismissService extends Service {
-        @Override
-        public IBinder onBind(Intent intent) {
-            return null;
-        }
-
-        @Override
-        public void onStart(Intent intent, int startId) {
-            sDeletedQueue.offer(intent.getAction());
-        }
+    @Override
+    int getTitleResource() {
+        return R.string.nls_test;
     }
 
     @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        onCreate(savedInstanceState, R.layout.nls_main);
-        setInfoResources(R.string.nls_test, R.string.nls_info, -1);
+    int getInstructionsResource() {
+        return R.string.nls_info;
     }
 
-    protected void onCreate(Bundle savedInstanceState, int layoutId) {
-        super.onCreate(savedInstanceState);
-
-        if (savedInstanceState != null) {
-            mState = savedInstanceState.getInt(STATE, 0);
-        }
-        mContext = this;
-        mRunner = this;
-        mNm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
-        mPackageManager = getPackageManager();
-        mInflater = getLayoutInflater();
-        View view = mInflater.inflate(layoutId, null);
-        mItemList = (ViewGroup) view.findViewById(R.id.nls_test_items);
-        mHandler = mItemList;
-        createTestItems();
-        mStatus = new int[mItemList.getChildCount()];
-        setContentView(view);
-
-        setPassFailButtonClickListeners();
-        getPassButton().setEnabled(false);
-    }
+    // Test Setup
 
     @Override
-    protected void onSaveInstanceState (Bundle outState) {
-        outState.putInt(STATE, mState);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        next();
-    }
-
-    // Interface Utilities
-
-    protected void createTestItems() {
-        createNlsSettingsItem(R.string.nls_enable_service);
-        createAutoItem(R.string.nls_service_started);
-        createAutoItem(R.string.nls_note_received);
-        createAutoItem(R.string.nls_payload_intact);
-        createAutoItem(R.string.nls_clear_one);
-        createAutoItem(R.string.nls_clear_all);
-        createNlsSettingsItem(R.string.nls_disable_service);
-        createAutoItem(R.string.nls_service_stopped);
-        createAutoItem(R.string.nls_note_missed);
-    }
-
-    protected void setItemState(int index, boolean passed) {
-        ViewGroup item = (ViewGroup) mItemList.getChildAt(index);
-        ImageView status = (ImageView) item.findViewById(R.id.nls_status);
-        status.setImageResource(passed ? R.drawable.fs_good : R.drawable.fs_error);
-        View button = item.findViewById(R.id.nls_action_button);
-        button.setClickable(false);
-        button.setEnabled(false);
-        status.invalidate();
-    }
-
-    protected void markItemWaiting(int index) {
-        ViewGroup item = (ViewGroup) mItemList.getChildAt(index);
-        ImageView status = (ImageView) item.findViewById(R.id.nls_status);
-        status.setImageResource(R.drawable.fs_warning);
-        status.invalidate();
-    }
-
-    protected View createNlsSettingsItem(int messageId) {
-        return createUserItem(messageId, R.string.nls_start_settings);
-    }
-
-    protected View createRetryItem(int messageId) {
-        return createUserItem(messageId, R.string.attention_ready);
-    }
-
-    protected View createUserItem(int messageId, int actionId) {
-        View item = mInflater.inflate(R.layout.nls_item, mItemList, false);
-        TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
-        instructions.setText(messageId);
-        Button button = (Button) item.findViewById(R.id.nls_action_button);
-        button.setText(actionId);
-        mItemList.addView(item);
-        button.setTag(actionId);
-        return item;
-    }
-
-    protected View createAutoItem(int stringId) {
-        View item = mInflater.inflate(R.layout.nls_item, mItemList, false);
-        TextView instructions = (TextView) item.findViewById(R.id.nls_instructions);
-        instructions.setText(stringId);
-        View button = item.findViewById(R.id.nls_action_button);
-        button.setVisibility(View.GONE);
-        mItemList.addView(item);
-        return item;
-    }
-
-    // Test management
-
-    public void run() {
-        while (mState < mStatus.length && mStatus[mState] != WAIT_FOR_USER) {
-            if (mStatus[mState] == PASS) {
-                setItemState(mState, true);
-                mState++;
-            } else if (mStatus[mState] == FAIL) {
-                setItemState(mState, false);
-                return;
-            } else {
-                break;
-            }
-        }
-
-        if (mState < mStatus.length && mStatus[mState] == WAIT_FOR_USER) {
-            markItemWaiting(mState);
-        }
-
-        updateStateMachine();
-    }
-
-    protected void updateStateMachine() {
-        switch (mState) {
-            case 0:
-                testIsEnabled(mState);
-                break;
-            case 1:
-                testIsStarted(mState);
-                break;
-            case 2:
-                testNotificationRecieved(mState);
-                break;
-            case 3:
-                testDataIntact(mState);
-                break;
-            case 4:
-                testDismissOne(mState);
-                break;
-            case 5:
-                testDismissAll(mState);
-                break;
-            case 6:
-                testIsDisabled(mState);
-                break;
-            case 7:
-                testIsStopped(mState);
-                break;
-            case 8:
-                testNotificationNotRecieved(mState);
-                break;
-            case 9:
-                getPassButton().setEnabled(true);
-                mNm.cancelAll();
-                break;
-        }
-    }
-
-    public void launchSettings() {
-        startActivity(
-                new Intent("android.settings.ACTION_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();
-            } else if (id == R.string.attention_ready) {
-                mStatus[mState] = READY;
-                next();
-            }
-        }
-    }
-
-    protected PendingIntent makeIntent(int code, String tag) {
-        Intent intent = new Intent(tag);
-        intent.setComponent(new ComponentName(mContext, DismissService.class));
-        PendingIntent pi = PendingIntent.getService(mContext, code, intent,
-                PendingIntent.FLAG_UPDATE_CURRENT);
-        return pi;
+    protected List<InteractiveTestCase> createTestItems() {
+        List<InteractiveTestCase> tests = new ArrayList<>(9);
+        tests.add(new IsEnabledTest());
+        tests.add(new ServiceStartedTest());
+        tests.add(new NotificationRecievedTest());
+        tests.add(new DataIntactTest());
+        tests.add(new DismissOneTest());
+        tests.add(new DismissAllTest());
+        tests.add(new IsDisabledTest());
+        tests.add(new ServiceStoppedTest());
+        tests.add(new NotificationNotReceivedTest());
+        return tests;
     }
 
     @SuppressLint("NewApi")
@@ -310,9 +96,9 @@
         mWhen2 = System.currentTimeMillis() + 2;
         mWhen3 = System.currentTimeMillis() + 3;
 
-        mIcon1 = R.drawable.fs_good;
-        mIcon2 = R.drawable.fs_error;
-        mIcon3 = R.drawable.fs_warning;
+        mIcon1 = R.drawable.ic_stat_alice;
+        mIcon2 = R.drawable.ic_stat_bob;
+        mIcon3 = R.drawable.ic_stat_charlie;
 
         mId1 = NOTIFICATION_ID + 1;
         mId2 = NOTIFICATION_ID + 2;
@@ -321,356 +107,352 @@
         mPackageString = "com.android.cts.verifier";
 
         Notification n1 = new Notification.Builder(mContext)
-        .setContentTitle("ClearTest 1")
-        .setContentText(mTag1.toString())
-        .setPriority(Notification.PRIORITY_LOW)
-        .setSmallIcon(mIcon1)
-        .setWhen(mWhen1)
-        .setDeleteIntent(makeIntent(1, mTag1))
-        .setOnlyAlertOnce(true)
-        .build();
+                .setContentTitle("ClearTest 1")
+                .setContentText(mTag1.toString())
+                .setPriority(Notification.PRIORITY_LOW)
+                .setSmallIcon(mIcon1)
+                .setWhen(mWhen1)
+                .setDeleteIntent(makeIntent(1, mTag1))
+                .setOnlyAlertOnce(true)
+                .build();
         mNm.notify(mTag1, mId1, n1);
         mFlag1 = Notification.FLAG_ONLY_ALERT_ONCE;
 
         Notification n2 = new Notification.Builder(mContext)
-        .setContentTitle("ClearTest 2")
-        .setContentText(mTag2.toString())
-        .setPriority(Notification.PRIORITY_HIGH)
-        .setSmallIcon(mIcon2)
-        .setWhen(mWhen2)
-        .setDeleteIntent(makeIntent(2, mTag2))
-        .setAutoCancel(true)
-        .build();
+                .setContentTitle("ClearTest 2")
+                .setContentText(mTag2.toString())
+                .setPriority(Notification.PRIORITY_HIGH)
+                .setSmallIcon(mIcon2)
+                .setWhen(mWhen2)
+                .setDeleteIntent(makeIntent(2, mTag2))
+                .setAutoCancel(true)
+                .build();
         mNm.notify(mTag2, mId2, n2);
         mFlag2 = Notification.FLAG_AUTO_CANCEL;
 
         Notification n3 = new Notification.Builder(mContext)
-        .setContentTitle("ClearTest 3")
-        .setContentText(mTag3.toString())
-        .setPriority(Notification.PRIORITY_LOW)
-        .setSmallIcon(mIcon3)
-        .setWhen(mWhen3)
-        .setDeleteIntent(makeIntent(3, mTag3))
-        .setAutoCancel(true)
-        .setOnlyAlertOnce(true)
-        .build();
+                .setContentTitle("ClearTest 3")
+                .setContentText(mTag3.toString())
+                .setPriority(Notification.PRIORITY_LOW)
+                .setSmallIcon(mIcon3)
+                .setWhen(mWhen3)
+                .setDeleteIntent(makeIntent(3, mTag3))
+                .setAutoCancel(true)
+                .setOnlyAlertOnce(true)
+                .build();
         mNm.notify(mTag3, mId3, n3);
         mFlag3 = Notification.FLAG_ONLY_ALERT_ONCE | Notification.FLAG_AUTO_CANCEL;
     }
 
-    /**
-     * Return to the state machine to progress through the tests.
-     */
-    protected void next() {
-        mHandler.removeCallbacks(mRunner);
-        mHandler.post(mRunner);
-    }
-
-    /**
-     * Wait for things to settle before returning to the state machine.
-     */
-    protected void delay() {
-        delay(2000);
-    }
-
-    /**
-     * Wait for some time.
-     */
-    protected void delay(long waitTime) {
-        mHandler.removeCallbacks(mRunner);
-        mHandler.postDelayed(mRunner, waitTime);
-    }
-
-    protected boolean checkEquals(long expected, long actual, String message) {
-        if (expected == actual) {
-            return true;
-        }
-        logWithStack(String.format(message, expected, actual));
-        return false;
-    }
-
-    protected boolean checkEquals(String expected, String actual, String message) {
-        if (expected.equals(actual)) {
-            return true;
-        }
-        logWithStack(String.format(message, expected, actual));
-        return false;
-    }
-
-    protected boolean checkFlagSet(int expected, int actual, String message) {
-        if ((expected & actual) != 0) {
-            return true;
-        }
-        logWithStack(String.format(message, expected, actual));
-        return false;
-    };
-
-    protected void logWithStack(String message) {
-        Throwable stackTrace = new Throwable();
-        stackTrace.fillInStackTrace();
-        Log.e(TAG, message, stackTrace);
-    }
-
     // Tests
 
-    private void testIsEnabled(int i) {
-        // no setup required
-        Intent settings = new Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS");
-        if (settings.resolveActivity(mPackageManager) == null) {
-            logWithStack("failed testIsEnabled: no settings activity");
-            mStatus[i] = FAIL;
-        } else {
-            // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden
-            String listeners = Secure.getString(getContentResolver(),
-                    "enabled_notification_listeners");
-            if (listeners != null && listeners.contains(LISTENER_PATH)) {
-                mStatus[i] = PASS;
-            } else {
-                mStatus[i] = WAIT_FOR_USER;
-            }
-        }
-        next();
-    }
+    private class NotificationRecievedTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_note_received);
 
-    private void testIsStarted(final int i) {
-        if (mStatus[i] == SETUP) {
-            mStatus[i] = READY;
-            // wait for the service to start
-            delay();
-        } else {
-            MockListener.probeListenerStatus(mContext,
-                    new MockListener.StatusCatcher() {
-                @Override
-                public void accept(int result) {
-                    if (result == Activity.RESULT_OK) {
-                        mStatus[i] = PASS;
-                    } else {
-                        logWithStack("failed testIsStarted: " + result);
-                        mStatus[i] = FAIL;
-                    }
-                    next();
-                }
-            });
         }
-    }
 
-    private void testNotificationRecieved(final int i) {
-        if (mStatus[i] == SETUP) {
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
+        @Override
+        void setUp() {
             sendNotifications();
-            mStatus[i] = READY;
+            status = READY;
             // wait for notifications to move through the system
             delay();
-        } else {
+        }
+
+        @Override
+        void test() {
             MockListener.probeListenerPosted(mContext,
                     new MockListener.StringListResultCatcher() {
-                @Override
-                public void accept(List<String> result) {
-                    if (result != null && result.size() > 0 && result.contains(mTag1)) {
-                        mStatus[i] = PASS;
-                    } else {
-                        logWithStack("failed testNotificationRecieved");
-                        mStatus[i] = FAIL;
-                    }
-                    next();
-                }});
-        }
-    }
-
-    private void testDataIntact(final int i) {
-        // no setup required
-        MockListener.probeListenerPayloads(mContext,
-                new MockListener.StringListResultCatcher() {
-            @Override
-            public void accept(List<String> result) {
-                boolean pass = false;
-                Set<String> found = new HashSet<String>();
-                if (result != null && result.size() > 0) {
-                    pass = true;
-                    for(String payloadData : result) {
-                        try {
-                            JSONObject payload = new JSONObject(payloadData);
-                            pass &= checkEquals(mPackageString, payload.getString(JSON_PACKAGE),
-                                    "data integrity test fail: notification package (%s, %s)");
-                            String tag = payload.getString(JSON_TAG);
-                            if (mTag1.equals(tag)) {
-                                found.add(mTag1);
-                                pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON),
-                                        "data integrity test fail: notification icon (%d, %d)");
-                                pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS),
-                                        "data integrity test fail: notification flags (%d, %d)");
-                                pass &= checkEquals(mId1, payload.getInt(JSON_ID),
-                                        "data integrity test fail: notification ID (%d, %d)");
-                                pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN),
-                                        "data integrity test fail: notification when (%d, %d)");
-                            } else if (mTag2.equals(tag)) {
-                                found.add(mTag2);
-                                pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON),
-                                        "data integrity test fail: notification icon (%d, %d)");
-                                pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS),
-                                        "data integrity test fail: notification flags (%d, %d)");
-                                pass &= checkEquals(mId2, payload.getInt(JSON_ID),
-                                        "data integrity test fail: notification ID (%d, %d)");
-                                pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN),
-                                        "data integrity test fail: notification when (%d, %d)");
-                            } else if (mTag3.equals(tag)) {
-                                found.add(mTag3);
-                                pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON),
-                                        "data integrity test fail: notification icon (%d, %d)");
-                                pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS),
-                                        "data integrity test fail: notification flags (%d, %d)");
-                                pass &= checkEquals(mId3, payload.getInt(JSON_ID),
-                                        "data integrity test fail: notification ID (%d, %d)");
-                                pass &= checkEquals(mWhen3, payload.getLong(JSON_WHEN),
-                                        "data integrity test fail: notification when (%d, %d)");
+                        @Override
+                        public void accept(List<String> result) {
+                            if (result != null && result.size() > 0 && result.contains(mTag1)) {
+                                status = PASS;
                             } else {
-                                pass = false;
-                                logWithStack("failed on unexpected notification tag: " + tag);
+                                logFail();
+                                status = FAIL;
                             }
-                        } catch (JSONException e) {
-                            pass = false;
-                            Log.e(TAG, "failed to unpack data from mocklistener", e);
-                        }
-                    }
-                }
-                pass &= found.size() == 3;
-                mStatus[i] = pass ? PASS : FAIL;
-                next();
-            }});
-    }
-
-    private void testDismissOne(final int i) {
-        if (mStatus[i] == SETUP) {
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            MockListener.clearOne(mContext, mTag1, NOTIFICATION_ID + 1);
-            mStatus[i] = READY;
-            delay();
-        } else {
-            MockListener.probeListenerRemoved(mContext,
-                    new MockListener.StringListResultCatcher() {
-                @Override
-                public void accept(List<String> result) {
-                    if (result != null && result.size() > 0 && result.contains(mTag1)) {
-                        mStatus[i] = PASS;
-                        next();
-                    } else {
-                        if (mStatus[i] == RETRY) {
-                            logWithStack("failed testDismissOne");
-                            mStatus[i] = FAIL;
                             next();
-                        } else {
-                            logWithStack("failed testDismissOne, once: retrying");
-                            mStatus[i] = RETRY;
-                            delay();
                         }
-                    }
-                }});
+                    });
+            delay();  // in case the catcher never returns
         }
     }
 
-    private void testDismissAll(final int i) {
-        if (mStatus[i] == SETUP) {
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            MockListener.clearAll(mContext);
-            mStatus[i] = READY;
-            delay();
-        } else {
-            MockListener.probeListenerRemoved(mContext,
+    private class DataIntactTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_payload_intact);
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerPayloads(mContext,
                     new MockListener.StringListResultCatcher() {
-                @Override
-                public void accept(List<String> result) {
-                    if (result != null && result.size() == 2
-                            && result.contains(mTag2) && result.contains(mTag3)) {
-                        mStatus[i] = PASS;
-                        next();
-                    } else {
-                        if (mStatus[i] == RETRY) {
-                            logWithStack("failed testDismissAll");
-                            mStatus[i] = FAIL;
+                        @Override
+                        public void accept(List<String> result) {
+                            Set<String> found = new HashSet<String>();
+                            if (result == null || result.size() == 0) {
+                                status = FAIL;
+                                return;
+                            }
+                            boolean pass = true;
+                            for (String payloadData : result) {
+                                try {
+                                    JSONObject payload = new JSONObject(payloadData);
+                                    pass &= checkEquals(mPackageString,
+                                            payload.getString(JSON_PACKAGE),
+                                            "data integrity test: notification package (%s, %s)");
+                                    String tag = payload.getString(JSON_TAG);
+                                    if (mTag1.equals(tag)) {
+                                        found.add(mTag1);
+                                        pass &= checkEquals(mIcon1, payload.getInt(JSON_ICON),
+                                                "data integrity test: notification icon (%d, %d)");
+                                        pass &= checkFlagSet(mFlag1, payload.getInt(JSON_FLAGS),
+                                                "data integrity test: notification flags (%d, %d)");
+                                        pass &= checkEquals(mId1, payload.getInt(JSON_ID),
+                                                "data integrity test: notification ID (%d, %d)");
+                                        pass &= checkEquals(mWhen1, payload.getLong(JSON_WHEN),
+                                                "data integrity test: notification when (%d, %d)");
+                                    } else if (mTag2.equals(tag)) {
+                                        found.add(mTag2);
+                                        pass &= checkEquals(mIcon2, payload.getInt(JSON_ICON),
+                                                "data integrity test: notification icon (%d, %d)");
+                                        pass &= checkFlagSet(mFlag2, payload.getInt(JSON_FLAGS),
+                                                "data integrity test: notification flags (%d, %d)");
+                                        pass &= checkEquals(mId2, payload.getInt(JSON_ID),
+                                                "data integrity test: notification ID (%d, %d)");
+                                        pass &= checkEquals(mWhen2, payload.getLong(JSON_WHEN),
+                                                "data integrity test: notification when (%d, %d)");
+                                    } else if (mTag3.equals(tag)) {
+                                        found.add(mTag3);
+                                        pass &= checkEquals(mIcon3, payload.getInt(JSON_ICON),
+                                                "data integrity test: notification icon (%d, %d)");
+                                        pass &= checkFlagSet(mFlag3, payload.getInt(JSON_FLAGS),
+                                                "data integrity test: notification flags (%d, %d)");
+                                        pass &= checkEquals(mId3, payload.getInt(JSON_ID),
+                                                "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;
+                                    Log.e(TAG, "failed to unpack data from mocklistener", e);
+                                }
+                            }
+
+                            pass &= found.size() == 3;
+                            status = pass ? PASS : FAIL;
                             next();
-                        } else {
-                            logWithStack("failed testDismissAll, once: retrying");
-                            mStatus[i] = RETRY;
-                            delay();
                         }
-                    }
-                }
-            });
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
         }
     }
 
-    private void testIsDisabled(int i) {
-        // no setup required
-        // TODO: find out why Secure.ENABLED_NOTIFICATION_LISTENERS is hidden
-        String listeners = Secure.getString(getContentResolver(),
-                "enabled_notification_listeners");
-        if (listeners == null || !listeners.contains(LISTENER_PATH)) {
-            mStatus[i] = PASS;
+    private class DismissOneTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_clear_one);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications();
+            status = READY;
+            delay();
+        }
+
+        @Override
+        void test() {
+            if (status == READY) {
+                MockListener.clearOne(mContext, mTag1, mId1);
+                status = RETEST;
+            } else {
+                MockListener.probeListenerRemoved(mContext,
+                        new MockListener.StringListResultCatcher() {
+                            @Override
+                            public void accept(List<String> result) {
+                                if (result != null && result.size() != 0
+                                        && result.contains(mTag1)
+                                        && !result.contains(mTag2)
+                                        && !result.contains(mTag3)) {
+                                    status = PASS;
+                                } else {
+                                    logFail();
+                                    status = FAIL;
+                                }
+                                next();
+                            }
+                        });
+            }
+            delay();
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    private class DismissAllTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_clear_all);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications();
+            status = READY;
+            delay();
+        }
+
+        @Override
+        void test() {
+            if (status == READY) {
+                MockListener.clearAll(mContext);
+                status = RETEST;
+            } else {
+                MockListener.probeListenerRemoved(mContext,
+                        new MockListener.StringListResultCatcher() {
+                            @Override
+                            public void accept(List<String> result) {
+                                if (result != null && result.size() != 0
+                                        && result.contains(mTag1)
+                                        && result.contains(mTag2)
+                                        && result.contains(mTag3)) {
+                                    status = PASS;
+                                } else {
+                                    logFail();
+                                    status = FAIL;
+                                }
+                                next();
+                            }
+                        });
+            }
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    private class IsDisabledTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createNlsSettingsItem(parent, R.string.nls_disable_service);
+        }
+
+        @Override
+        boolean autoStart() {
+            return true;
+        }
+
+        @Override
+        void test() {
+            String listeners = Secure.getString(getContentResolver(),
+                    ENABLED_NOTIFICATION_LISTENERS);
+            if (listeners == null || !listeners.contains(LISTENER_PATH)) {
+                status = PASS;
+            } else {
+                status = WAIT_FOR_USER;
+            }
             next();
-        } else {
-            mStatus[i] = WAIT_FOR_USER;
+        }
+
+        @Override
+        void tearDown() {
+            MockListener.resetListenerData(mContext);
             delay();
         }
     }
 
-    private void testIsStopped(final int i) {
-        if (mStatus[i] == SETUP) {
-            mStatus[i] = READY;
-            // wait for the service to start
-            delay();
-        } else {
+    private class ServiceStoppedTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_service_stopped);
+        }
+
+        @Override
+        void test() {
             MockListener.probeListenerStatus(mContext,
                     new MockListener.StatusCatcher() {
-                @Override
-                public void accept(int result) {
-                    if (result == Activity.RESULT_OK) {
-                        logWithStack("failed testIsStopped");
-                        mStatus[i] = FAIL;
-                    } else {
-                        mStatus[i] = PASS;
-                    }
-                    next();
-                }
-            });
+                        @Override
+                        public void accept(int result) {
+                            if (result == Activity.RESULT_OK) {
+                                logFail();
+                                status = FAIL;
+                            } else {
+                                status = PASS;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            // wait for intent to move through the system
+            delay();
         }
     }
 
-    private void testNotificationNotRecieved(final int i) {
-        if (mStatus[i] == SETUP) {
-            MockListener.resetListenerData(this);
-            mStatus[i] = CLEARED;
-            // wait for intent to move through the system
-            delay();
-        } else if (mStatus[i] == CLEARED) {
-            // setup for testNotificationRecieved
+    private class NotificationNotReceivedTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.nls_note_missed);
+
+        }
+
+        @Override
+        void setUp() {
             sendNotifications();
-            mStatus[i] = READY;
+            status = READY;
             delay();
-        } else {
+        }
+
+        @Override
+        void test() {
             MockListener.probeListenerPosted(mContext,
                     new MockListener.StringListResultCatcher() {
-                @Override
-                public void accept(List<String> result) {
-                    if (result == null || result.size() == 0) {
-                        mStatus[i] = PASS;
-                    } else {
-                        logWithStack("failed testNotificationNotRecieved");
-                        mStatus[i] = FAIL;
-                    }
-                    next();
-                }});
+                        @Override
+                        public void accept(List<String> result) {
+                            if (result == null || result.size() == 0) {
+                                status = PASS;
+                            } else {
+                                logFail();
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            mNm.cancelAll();
+            MockListener.resetListenerData(mContext);
+            delay();
         }
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
new file mode 100644
index 0000000..5870981
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/PackagePriorityVerifierActivity.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.notifications;
+
+import android.app.Notification;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.cts.verifier.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Tests that the notification ranker honors user preferences about package priority.
+ * Users can, in Settings, specify a package as being high priority. This should
+ * result in the notificaitons from that package being ranked higher than those from
+ * other packages.
+ */
+public class PackagePriorityVerifierActivity
+        extends InteractiveVerifierActivity {
+    private static final String ACTION_POST = "com.android.cts.robot.ACTION_POST";
+    private static final String ACTION_CANCEL = "com.android.cts.robot.ACTION_CANCEL";
+    private static final String EXTRA_ID = "ID";
+    private static final String EXTRA_NOTIFICATION = "NOTIFICATION";
+    private static final String NOTIFICATION_BOT_PACKAGE = "com.android.cts.robot";
+    private CharSequence mAppLabel;
+
+    @Override
+    int getTitleResource() {
+        return R.string.package_priority_test;
+    }
+
+    @Override
+    int getInstructionsResource() {
+        return R.string.package_priority_info;
+    }
+
+    // Test Setup
+
+    @Override
+    protected List<InteractiveTestCase> createTestItems() {
+        mAppLabel = getString(R.string.app_name);
+        List<InteractiveTestCase> tests = new ArrayList<>(17);
+        tests.add(new CheckForBot());
+        tests.add(new IsEnabledTest());
+        tests.add(new ServiceStartedTest());
+        tests.add(new WaitForSetPriorityDefault());
+        tests.add(new DefaultOrderTest());
+        tests.add(new WaitForSetPriorityHigh());
+        tests.add(new PackagePriorityOrderTest());
+        return tests;
+    }
+
+    // Tests
+
+    /** Make sure the helper package is installed. */
+    protected class CheckForBot extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.package_priority_bot);
+        }
+
+        @Override
+        void test() {
+            PackageManager pm = mContext.getPackageManager();
+            try {
+                pm.getPackageInfo(NOTIFICATION_BOT_PACKAGE, 0);
+                status = PASS;
+            } catch (PackageManager.NameNotFoundException e) {
+                status = FAIL;
+                logFail("You must install the CTS Robot helper, aka " + NOTIFICATION_BOT_PACKAGE);
+            }
+            next();
+        }
+    }
+
+    /** Wait for the user to set the target package priority to default. */
+    protected class WaitForSetPriorityDefault extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createRetryItem(parent, R.string.package_priority_default, mAppLabel);
+        }
+
+        @Override
+        void setUp() {
+            Log.i("WaitForSetPriorityDefault", "waiting for user");
+            status = WAIT_FOR_USER;
+        }
+
+        @Override
+        void test() {
+            status = PASS;
+            next();
+        }
+
+        @Override
+        void tearDown() {
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    /** Wait for the user to set the target package priority to high. */
+    protected class WaitForSetPriorityHigh extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createRetryItem(parent, R.string.package_priority_high, mAppLabel);
+        }
+
+        @Override
+        void setUp() {
+            Log.i("WaitForSetPriorityHigh", "waiting for user");
+            status = WAIT_FOR_USER;
+        }
+
+        @Override
+        void test() {
+            status = PASS;
+            next();
+        }
+
+        @Override
+        void tearDown() {
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    /**
+     * With default priority, the notifcations should be reverse-ordered by time.
+     * A is before B, and therefor should B should rank before A.
+     */
+    protected class DefaultOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.attention_default_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications();
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = indexOfPackageInKeys(orderedKeys, getPackageName());
+                            int rankB = indexOfPackageInKeys(orderedKeys, NOTIFICATION_BOT_PACKAGE);
+                            if (rankB != -1 && rankB < rankA) {
+                                status = PASS;
+                            } else {
+                                logFail("expected rankA (" + rankA + ") > rankB (" + rankB + ")");
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            cancelNotifications();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+
+    /**
+     * With higher package priority, A should rank above B.
+     */
+    protected class PackagePriorityOrderTest extends InteractiveTestCase {
+        @Override
+        View inflate(ViewGroup parent) {
+            return createAutoItem(parent, R.string.package_priority_user_order);
+        }
+
+        @Override
+        void setUp() {
+            sendNotifications();
+            status = READY;
+            // wait for notifications to move through the system
+            delay();
+        }
+
+        @Override
+        void test() {
+            MockListener.probeListenerOrder(mContext,
+                    new MockListener.StringListResultCatcher() {
+                        @Override
+                        public void accept(List<String> orderedKeys) {
+                            int rankA = indexOfPackageInKeys(orderedKeys, getPackageName());
+                            int rankB = indexOfPackageInKeys(orderedKeys, NOTIFICATION_BOT_PACKAGE);
+                            if (rankA != -1 && rankA < rankB) {
+                                status = PASS;
+                            } else {
+                                logFail("expected rankA (" + rankA + ") < rankB (" + rankB + ")");
+                                status = FAIL;
+                            }
+                            next();
+                        }
+                    });
+            delay();  // in case the catcher never returns
+        }
+
+        @Override
+        void tearDown() {
+            cancelNotifications();
+            MockListener.resetListenerData(mContext);
+            delay();
+        }
+    }
+    // Utilities
+
+    private void sendNotifications() {
+        // post ours first, with an explicit time in the past to avoid any races.
+        Notification.Builder alice = new Notification.Builder(mContext)
+                .setContentTitle("alice title")
+                .setContentText("alice content")
+                .setSmallIcon(R.drawable.ic_stat_alice)
+                .setWhen(System.currentTimeMillis() - 10000L)
+                .setPriority(Notification.PRIORITY_DEFAULT);
+        mNm.notify(0, alice.build());
+
+        // then post theirs, so it should be higher by default due to recency
+        Notification.Builder bob = new Notification.Builder(mContext)
+                .setContentTitle("bob title")
+                .setContentText("bob content")
+                .setSmallIcon(android.R.drawable.stat_notify_error) // must be global resource
+                .setWhen(System.currentTimeMillis())
+                .setPriority(Notification.PRIORITY_DEFAULT);
+        Intent postIntent = new Intent(ACTION_POST);
+        postIntent.setPackage(NOTIFICATION_BOT_PACKAGE);
+        postIntent.putExtra(EXTRA_ID, 0);
+        postIntent.putExtra(EXTRA_NOTIFICATION, bob.build());
+        postIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+        sendBroadcast(postIntent);
+    }
+
+    private void cancelNotifications() {
+        //cancel ours
+        mNm.cancelAll();
+        //cancel theirs
+        Intent cancelIntent = new Intent(ACTION_CANCEL);
+        cancelIntent.setPackage(NOTIFICATION_BOT_PACKAGE);
+        cancelIntent.putExtra(EXTRA_ID, 0);
+        cancelIntent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+        sendBroadcast(cancelIntent);
+    }
+
+    /** Search a list of notification keys for a given packageName. */
+    private int indexOfPackageInKeys(List<String> orderedKeys, String packageName) {
+        for (int i = 0; i < orderedKeys.size(); i++) {
+            if (orderedKeys.get(i).contains(packageName)) {
+                return i;
+            }
+        }
+        return -1;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
new file mode 100644
index 0000000..be78556
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/os/TimeoutResetActivity.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.verifier.os;
+
+import android.app.Activity;
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.lang.reflect.Field;
+
+/**
+ * Activity resets the screen timeout to its original timeout. Used devices without Device Admin.
+ */
+public class TimeoutResetActivity extends Activity {
+    public static final String EXTRA_OLD_TIMEOUT = "com.android.cts.verifier.extra.OLD_TIMEOUT";
+    /** Set the timeout to the default to reset the activity to if not specified. */
+    public static final long FALLBACK_TIMEOUT = -1L;
+    /**
+     * Empirically determined buffer time in milliseconds between setting short timeout time and
+     * resetting the timeout.
+     */
+    public static final long RESET_BUFFER_TIME = 2000L;
+    /** Short timeout to trigger screen off. */
+    public static final long SCREEN_OFF_TIMEOUT = 0L;
+    public static final String TAG = TimeoutResetActivity.class.getSimpleName();
+
+    private static long getUserActivityTimeout(WindowManager.LayoutParams params) {
+        try {
+            return getUserActivityTimeoutField(params).getLong(params);
+        } catch (Exception e) {
+            Log.e(TAG, "error loading the userActivityTimeout field", e);
+            return -1;
+        }
+    }
+
+    private static Field getUserActivityTimeoutField(WindowManager.LayoutParams params)
+            throws NoSuchFieldException {
+        return params.getClass().getField("userActivityTimeout");
+    }
+
+    private static void setUserActivityTimeout(WindowManager.LayoutParams params, long timeout) {
+        try {
+            getUserActivityTimeoutField(params).setLong(params, timeout);
+            Log.d(TAG, "UserActivityTimeout set to " + timeout);
+        } catch (Exception e) {
+            Log.e(TAG, "error setting the userActivityTimeout field", e);
+            throw new RuntimeException(e);
+        }
+    }
+
+    public static void turnOffScreen(final Activity activity) {
+        activity.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                WindowManager.LayoutParams params = activity.getWindow().getAttributes();
+
+                // to restore timeout after shutoff
+                final long oldTimeout = getUserActivityTimeout(params);
+
+                final long timeout = SCREEN_OFF_TIMEOUT;
+                setUserActivityTimeout(params, timeout);
+
+                // upon setting this, timeout will be reduced
+                activity.getWindow().setAttributes(params);
+
+                ((AlarmManager) activity.getSystemService(ALARM_SERVICE)).setExact(
+                        AlarmManager.RTC,
+                        System.currentTimeMillis() + RESET_BUFFER_TIME,
+                        PendingIntent.getActivity(
+                                activity.getApplicationContext(),
+                                0,
+                                new Intent(activity, TimeoutResetActivity.class)
+                                        .putExtra(EXTRA_OLD_TIMEOUT, oldTimeout),
+                                0));
+            }
+        });
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        long timeout = getIntent().getLongExtra(EXTRA_OLD_TIMEOUT, FALLBACK_TIMEOUT);
+        if (timeout < 1000) { // in case the old timeout was super low by accident
+            timeout = FALLBACK_TIMEOUT;
+        }
+
+        WindowManager.LayoutParams params = getWindow().getAttributes();
+        setUserActivityTimeout(params, timeout);
+        getWindow().setAttributes(params);
+
+        finish();
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionActivity.java
index 18d9d43..77de71d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/ProjectionActivity.java
@@ -150,6 +150,8 @@
     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
         Log.i(TAG, "onSurfaceTextureSizeChanged " + surface.toString() + "w: " + width + " h: "
                 + height);
+        mWidth = width;
+        mHeight = height;
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/list/ListPresentation.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/list/ListPresentation.java
index 5dddf5c..46abaaa 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/projection/list/ListPresentation.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/list/ListPresentation.java
@@ -50,7 +50,7 @@
         setContentView(view);
 
         for (int i = 0; i < NUM_ITEMS; ++i) {
-            mItemList.add("Item #" + 1 + i);
+            mItemList.add("Item #" + (1 + i));
         }
 
         ListView listView = (ListView) view.findViewById(R.id.pla_list);
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/projection/offscreen/ProjectionOffscreenActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/projection/offscreen/ProjectionOffscreenActivity.java
index 510a03b..cfa097b 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/projection/offscreen/ProjectionOffscreenActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/projection/offscreen/ProjectionOffscreenActivity.java
@@ -35,6 +35,7 @@
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.SystemClock;
+import android.os.Vibrator;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.KeyEvent;
@@ -75,7 +76,7 @@
         public void run() {
             try {
                 mService.onKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_DOWN));
-                mTimeKeyEventSent = SystemClock.uptimeMillis();
+                mTimeKeyEventSent = SystemClock.elapsedRealtime();
             } catch (RemoteException e) {
                 Log.e(TAG, "Error running onKeyEvent", e);
             }
@@ -89,6 +90,7 @@
             Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
             Ringtone r = RingtoneManager.getRingtone(getApplicationContext(), notification);
             r.play();
+            ((Vibrator) getSystemService(VIBRATOR_SERVICE)).vibrate(1000);
         }
     };
 
@@ -101,11 +103,11 @@
             handler.postDelayed(
                     sendKeyEventRunnable, DELAYED_RUNNABLE_TIME);
             mStatusView.setText("Running test...");
-            mTimeScreenTurnedOff = SystemClock.uptimeMillis();
+            mTimeScreenTurnedOff = SystemClock.elapsedRealtime();
             // Notify user its safe to turn screen back on after 5s + fudge factor
             handler.postDelayed(playNotificationRunnable, TIME_SCREEN_OFF + 500);
         } else if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
-            if (SystemClock.uptimeMillis() - mTimeScreenTurnedOff < TIME_SCREEN_OFF) {
+            if (SystemClock.elapsedRealtime() - mTimeScreenTurnedOff < TIME_SCREEN_OFF) {
                 mStatusView.setText("ERROR: Turned on screen too early");
                 getPassButton().setEnabled(false);
                 mTestStatus = TestStatus.FAILED;
@@ -196,7 +198,7 @@
 
         if (mTimeKeyEventSent != 0
                 && mTestStatus == TestStatus.RUNNING
-                && mTimeKeyEventSent + RENDERER_DELAY_THRESHOLD < SystemClock.uptimeMillis()) {
+                && mTimeKeyEventSent + RENDERER_DELAY_THRESHOLD < SystemClock.elapsedRealtime()) {
             mTestStatus = TestStatus.FAILED;
             mStatusView.setText("Failed: took too long to render");
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java
index 25f90d9..41bc303 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sample/SampleTestActivity.java
@@ -16,8 +16,12 @@
 
 package com.android.cts.verifier.sample;
 
+import com.android.compatibility.common.util.ReportLog;
+import com.android.compatibility.common.util.ResultType;
+import com.android.compatibility.common.util.ResultUnit;
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
+import com.android.cts.verifier.TestResult;
 
 import android.content.Intent;
 import android.net.Uri;
@@ -61,6 +65,7 @@
             public void onClick(View v) {
                 try {
                     createFileAndShare();
+                    recordMetricsExample();
                 } catch (Exception e) {
                     e.printStackTrace();
                 }
@@ -68,6 +73,21 @@
         });
     }
 
+    private void recordMetricsExample() {
+        double[] metricValues = new double[] {1, 11, 21, 1211, 111221};
+
+        // Record metric results
+        getReportLog().setSummary(
+                "Sample Summary", 1.0, ResultType.HIGHER_BETTER, ResultUnit.BYTE);
+        getReportLog().addValues("Sample Values", metricValues, ResultType.NEUTRAL, ResultUnit.FPS);
+
+        // Alternatively, activities can invoke TestResult directly to record metrics
+        ReportLog reportLog = new PassFailButtons.CtsVerifierReportLog();
+        reportLog.setSummary("Sample Summary", 1.0, ResultType.HIGHER_BETTER, ResultUnit.BYTE);
+        getReportLog().addValues("Sample Values", metricValues, ResultType.NEUTRAL, ResultUnit.FPS);
+        TestResult.setPassedResult(this, "manualSample", "manualDetails", reportLog);
+    }
+
     /**
      * Creates a temporary file containing the test string and then issues the intent to share it.
      *
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
new file mode 100644
index 0000000..200865e
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/screenpinning/ScreenPinningTestActivity.java
@@ -0,0 +1,242 @@
+/*
+ * 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.screenpinning;
+
+import android.app.ActivityManager;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+public class ScreenPinningTestActivity extends PassFailButtons.Activity {
+
+    private static final String TAG = "ScreenPinningTestActivity";
+    private static final String KEY_CURRENT_TEST = "keyCurrentTest";
+
+    private Test[] mTests;
+    private int mTestIndex;
+
+    private ActivityManager mActivityManager;
+    private Button mNextButton;
+    private LinearLayout mInstructions;
+
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.screen_pinning);
+        setPassFailButtonClickListeners();
+
+        mTests = new Test[] {
+            // Verify not already pinned.
+            mCheckStartedUnpinned,
+            // Enter pinning, verify pinned, try leaving and have the user exit.
+            mCheckStartPinning,
+            mCheckIsPinned,
+            mCheckTryLeave,
+            mCheckUnpin,
+            // Enter pinning, verify pinned, and use APIs to exit.
+            mCheckStartPinning,
+            mCheckIsPinned,
+            mCheckUnpinFromCode,
+            // All done.
+            mDone,
+        };
+
+        mActivityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
+        mInstructions = (LinearLayout) findViewById(R.id.instructions_list);
+
+        mNextButton = (Button) findViewById(R.id.next_button);
+        mNextButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                if ((mTestIndex >= 0) && (mTestIndex < mTests.length)) {
+                    mTests[mTestIndex].onNextClick();
+                }
+            }
+        });
+
+        // Don't allow pass until all tests complete.
+        findViewById(R.id.pass_button).setVisibility(View.GONE);
+
+        // Figure out if we are in a test or starting from the beginning.
+        if (savedInstanceState != null && savedInstanceState.containsKey(KEY_CURRENT_TEST)) {
+            mTestIndex = savedInstanceState.getInt(KEY_CURRENT_TEST);
+        } else {
+            mTestIndex = 0;
+        }
+        // Display any pre-existing text.
+        for (int i = 0; i < mTestIndex; i++) {
+            mTests[i].showText();
+        }
+        mTests[mTestIndex].run();
+    };
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        outState.putInt(KEY_CURRENT_TEST, mTestIndex);
+    }
+
+    @Override
+    public void onBackPressed() {
+        // Block back button so we can test screen pinning exit functionality.
+        // Users can still leave by pressing fail (or when done the pass) button.
+    }
+
+    private void show(int id) {
+        TextView tv = new TextView(this);
+        tv.setPadding(10, 10, 10, 30);
+        tv.setText(id);
+        mInstructions.addView(tv);
+    }
+
+    private void succeed() {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mTestIndex++;
+                if (mTestIndex < mTests.length) {
+                    mTests[mTestIndex].run();
+                } else {
+                    mNextButton.setVisibility(View.GONE);
+                    findViewById(R.id.pass_button).setVisibility(View.VISIBLE);
+                }
+            }
+        });
+    }
+
+    private void error(int errorId) {
+        error(errorId, new Throwable());
+    }
+
+    private void error(final int errorId, final Throwable cause) {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                String error = getString(errorId);
+                Log.d(TAG, error, cause);
+                // No more instructions needed.
+                findViewById(R.id.instructions_group).setVisibility(View.GONE);
+
+                ((TextView) findViewById(R.id.error_text)).setText(error);
+            }
+        });
+    }
+
+    // Verify we don't start in screen pinning.
+    private final Test mCheckStartedUnpinned = new Test(0) {
+        public void run() {
+            if (mActivityManager.isInLockTaskMode()) {
+                error(R.string.error_screen_already_pinned);
+            } else {
+                succeed();
+            }
+        }
+    };
+
+    // Start screen pinning by having the user click next then confirm it for us.
+    private final Test mCheckStartPinning = new Test(R.string.screen_pin_instructions) {
+        protected void onNextClick() {
+            startLockTask();
+            succeed();
+        }
+    };
+
+    // Click next and check that we got pinned.
+    // Wait for the user to click next to verify that they got back from the prompt
+    // successfully.
+    private final Test mCheckIsPinned = new Test(R.string.screen_pin_check_pinned) {
+        protected void onNextClick() {
+            if (mActivityManager.isInLockTaskMode()) {
+                succeed();
+            } else {
+                error(R.string.error_screen_pinning_did_not_start);
+            }
+        }
+    };
+
+    // Tell user to try to leave.
+    private final Test mCheckTryLeave = new Test(R.string.screen_pin_no_exit) {
+        protected void onNextClick() {
+            if (mActivityManager.isInLockTaskMode()) {
+                succeed();
+            } else {
+                error(R.string.error_screen_no_longer_pinned);
+            }
+        }
+    };
+
+    // Verify that the user unpinned and it worked.
+    private final Test mCheckUnpin = new Test(R.string.screen_pin_exit) {
+        protected void onNextClick() {
+            if (!mActivityManager.isInLockTaskMode()) {
+                succeed();
+            } else {
+                error(R.string.error_screen_pinning_did_not_exit);
+            }
+        }
+    };
+
+    // Unpin from code and check that it worked.
+    private final Test mCheckUnpinFromCode = new Test(0) {
+        protected void run() {
+            if (!mActivityManager.isInLockTaskMode()) {
+                error(R.string.error_screen_pinning_did_not_start);
+                return;
+            }
+            stopLockTask();
+            if (!mActivityManager.isInLockTaskMode()) {
+                succeed();
+            } else {
+                error(R.string.error_screen_pinning_couldnt_exit);
+            }
+        };
+    };
+
+    private final Test mDone = new Test(R.string.screen_pinning_done) {
+        protected void run() {
+            showText();
+            succeed();
+        };
+    };
+
+    private abstract class Test {
+        private final int mResId;
+
+        public Test(int showId) {
+            mResId = showId;
+        }
+
+        protected void run() {
+            showText();
+        }
+
+        public void showText() {
+            if (mResId == 0) {
+                return;
+            }
+            show(mResId);
+        }
+
+        protected void onNextClick() {
+        }
+    }
+
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
index dfcf120..52b3dee 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/AccelerometerMeasurementTestActivity.java
@@ -97,11 +97,11 @@
                 Sensor.TYPE_ACCELEROMETER,
                 SensorManager.SENSOR_DELAY_FASTEST);
         TestSensorOperation verifyMeasurements =
-                new TestSensorOperation(environment, 100 /* event count */);
+                TestSensorOperation.createOperation(environment, 100 /* event count */);
         verifyMeasurements.addVerification(new MeanVerification(
                 expectations,
                 new float[]{1.95f, 1.95f, 1.95f} /* m / s^2 */));
-        verifyMeasurements.execute();
+        verifyMeasurements.execute(getCurrentTestNode());
         return null;
     }
 
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 6f0a7aa..7ef63d7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BatchingTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/BatchingTestActivity.java
@@ -22,9 +22,7 @@
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
 import android.hardware.cts.helpers.TestSensorEnvironment;
-import android.hardware.cts.helpers.sensoroperations.TestSensorFlushOperation;
 import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
-import android.hardware.cts.helpers.sensoroperations.VerifiableSensorOperation;
 
 import java.util.concurrent.TimeUnit;
 
@@ -128,7 +126,7 @@
 
         int testDurationSec = maxBatchReportLatencySec + BATCHING_PADDING_TIME_S;
         TestSensorOperation operation =
-                new TestSensorOperation(environment, testDurationSec,TimeUnit.SECONDS);
+                TestSensorOperation.createOperation(environment, testDurationSec,TimeUnit.SECONDS);
         return executeTest(operation);
     }
 
@@ -145,15 +143,14 @@
                 maxBatchReportLatencyUs);
 
         int flushDurationSec = maxBatchReportLatencySec / 2;
-        TestSensorFlushOperation operation =
-                new TestSensorFlushOperation(environment, flushDurationSec, TimeUnit.SECONDS);
+        TestSensorOperation operation = TestSensorOperation
+                .createFlushOperation(environment, flushDurationSec, TimeUnit.SECONDS);
         return executeTest(operation);
     }
 
-    private String executeTest(VerifiableSensorOperation operation) throws InterruptedException {
+    private String executeTest(TestSensorOperation operation) throws InterruptedException {
         operation.addDefaultVerifications();
-        operation.setLogEvents(true);
-        operation.execute();
+        operation.execute(getCurrentTestNode());
         return null;
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java
index 4b2a7f4..7be0fb1 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/GyroscopeMeasurementTestActivity.java
@@ -156,8 +156,8 @@
                 getApplicationContext(),
                 Sensor.TYPE_GYROSCOPE,
                 SensorManager.SENSOR_DELAY_FASTEST);
-        TestSensorOperation sensorOperation =
-                new TestSensorOperation(environment, ROTATION_COLLECTION_SEC, TimeUnit.SECONDS);
+        TestSensorOperation sensorOperation = TestSensorOperation
+                .createOperation(environment, ROTATION_COLLECTION_SEC, TimeUnit.SECONDS);
 
         int gyroscopeAxes = environment.getSensorAxesCount();
         int[] expectationsDeg = getExpectationsDeg(gyroscopeAxes, rotationAxis, expectationDeg);
@@ -167,7 +167,7 @@
         sensorOperation.addVerification(integrationVerification);
 
         try {
-            sensorOperation.execute();
+            sensorOperation.execute(getCurrentTestNode());
         } finally {
             playSound();
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
index 553147b..229a9dc 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/MagneticFieldMeasurementTestActivity.java
@@ -21,7 +21,6 @@
 
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener2;
 import android.hardware.SensorManager;
 import android.hardware.cts.helpers.SensorCalibratedUncalibratedVerifier;
 import android.hardware.cts.helpers.SensorCtsHelper;
@@ -79,7 +78,7 @@
                 Sensor.TYPE_MAGNETIC_FIELD,
                 SensorManager.SENSOR_DELAY_FASTEST);
         TestSensorOperation verifyNorm =
-                new TestSensorOperation(environment, 100 /* event count */);
+                TestSensorOperation.createOperation(environment, 100 /* event count */);
 
         float expectedMagneticFieldEarth =
                 (SensorManager.MAGNETIC_FIELD_EARTH_MAX + SensorManager.MAGNETIC_FIELD_EARTH_MIN) / 2;
@@ -88,7 +87,7 @@
         verifyNorm.addVerification(new MagnitudeVerification(
                 expectedMagneticFieldEarth,
                 magneticFieldEarthThreshold));
-        verifyNorm.execute();
+        verifyNorm.execute(getCurrentTestNode());
         return null;
     }
 
@@ -124,11 +123,11 @@
                 Sensor.TYPE_MAGNETIC_FIELD,
                 SensorManager.SENSOR_DELAY_FASTEST);
         TestSensorOperation verifyStdDev =
-                new TestSensorOperation(environment, 100 /* event count */);
+                TestSensorOperation.createOperation(environment, 100 /* event count */);
 
         verifyStdDev.addVerification(new StandardDeviationVerification(
                 new float[]{2f, 2f, 2f} /* uT */));
-        verifyStdDev.execute();
+        verifyStdDev.execute(getCurrentTestNode());
         return null;
     }
 
@@ -166,7 +165,12 @@
      * A routine to help operators calibrate the magnetometer.
      */
     private void calibrateMagnetometer() throws InterruptedException {
-        SensorEventListener2 listener = new SensorEventListener2() {
+        TestSensorEnvironment environment = new TestSensorEnvironment(
+                getApplicationContext(),
+                Sensor.TYPE_MAGNETIC_FIELD,
+                SensorManager.SENSOR_DELAY_NORMAL);
+
+        TestSensorEventListener listener = new TestSensorEventListener(environment) {
             @Override
             public void onSensorChanged(SensorEvent event) {
                 clearText();
@@ -184,21 +188,11 @@
                 // TODO: automate finding out when the magnetometer is calibrated
                 logger.logInstructions(R.string.snsr_mag_calibration_complete);
             }
-
-            @Override
-            public void onAccuracyChanged(Sensor sensor, int accuracy) {}
-
-            @Override
-            public void onFlushCompleted(Sensor sensor) {}
         };
 
-        TestSensorEnvironment environment = new TestSensorEnvironment(
-                getApplicationContext(),
-                Sensor.TYPE_MAGNETIC_FIELD,
-                SensorManager.SENSOR_DELAY_NORMAL);
         TestSensorManager magnetometer = new TestSensorManager(environment);
         try {
-            magnetometer.registerListener(new TestSensorEventListener(listener));
+            magnetometer.registerListener(listener);
             waitForUserToContinue();
         } finally {
             magnetometer.unregisterListener();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java
index 8370d3e..74d51e4 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SensorPowerTestActivity.java
@@ -63,7 +63,7 @@
 
     @Override
     protected void activitySetUp() throws InterruptedException {
-        mScreenManipulator = new SensorTestScreenManipulator(getApplicationContext());
+        mScreenManipulator = new SensorTestScreenManipulator(this);
         mScreenManipulator.initialize(this);
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestActivity.java
index 16c5fcd..6512fd3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestActivity.java
@@ -69,7 +69,7 @@
     protected void activitySetUp() throws InterruptedException {
         PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
         mWakeLock =  powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "SensorCtsTests");
-        mScreenManipulator = new SensorTestScreenManipulator(getApplicationContext());
+        mScreenManipulator = new SensorTestScreenManipulator(this);
         mScreenManipulator.initialize(this);
 
         SensorTestLogger logger = getTestLogger();
@@ -80,6 +80,7 @@
         // automated CTS tests run with the USB connected, so the AP doesn't go to sleep
         // here we are not connected to USB, so we need to hold a wake-lock to avoid going to sleep
         mWakeLock.acquire();
+
         mScreenManipulator.turnScreenOff();
     }
 
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestResult.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestResult.java
index 5bbaaf7..380b282 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestResult.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsTestResult.java
@@ -26,6 +26,8 @@
 
 import android.content.Context;
 import android.hardware.cts.SensorTestCase;
+import android.hardware.cts.helpers.SensorTestPlatformException;
+import android.hardware.cts.helpers.reporting.ISensorTestNode;
 
 import java.util.Enumeration;
 
@@ -138,10 +140,24 @@
             SensorTestCase sensorTestCase = (SensorTestCase) testCase;
             sensorTestCase.setContext(mContext);
             sensorTestCase.setEmulateSensorUnderLoad(false);
+            sensorTestCase.setCurrentTestNode(new TestNode(testCase));
             // TODO: set delayed assertion provider
         } else {
             throw new IllegalStateException("TestCase must be an instance of SensorTestCase.");
         }
         super.run(testCase);
     }
+
+    private class TestNode implements ISensorTestNode {
+        private final TestCase mTestCase;
+
+        public TestNode(TestCase testCase) {
+            mTestCase = testCase;
+        }
+
+        @Override
+        public String getName() throws SensorTestPlatformException {
+            return mTestCase.getClass().getSimpleName() + "_" + mTestCase.getName();
+        }
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsVerifierTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsVerifierTestActivity.java
index a88abd0..8cf287a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsVerifierTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/base/SensorCtsVerifierTestActivity.java
@@ -19,6 +19,8 @@
 
 import com.android.cts.verifier.sensors.reporting.SensorTestDetails;
 
+import android.hardware.cts.helpers.reporting.ISensorTestNode;
+
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -35,6 +37,7 @@
     private volatile int mTestPassedCounter;
     private volatile int mTestSkippedCounter;
     private volatile int mTestFailedCounter;
+    private volatile ISensorTestNode mCurrentTestNode;
 
     /**
      * {@inheritDoc}
@@ -63,8 +66,12 @@
                 mTestFailedCounter);
     }
 
+    protected ISensorTestNode getCurrentTestNode() {
+        return mCurrentTestNode;
+    }
+
     private List<Method> findTestMethods() {
-        ArrayList<Method> testMethods = new ArrayList<Method>();
+        ArrayList<Method> testMethods = new ArrayList<>();
         for (Method method : mTestClass.getDeclaredMethods()) {
             if (Modifier.isPublic(method.getModifiers())
                     && method.getParameterTypes().length == 0
@@ -79,6 +86,7 @@
     private SensorTestDetails executeTest(Method testMethod) throws InterruptedException {
         String testMethodName = testMethod.getName();
         String testName = String.format("%s#%s", getTestClassName(), testMethodName);
+        mCurrentTestNode = new TestNode(testMethod);
 
         SensorTestDetails testDetails;
         try {
@@ -112,4 +120,17 @@
 
         return testDetails;
     }
+
+    private class TestNode implements ISensorTestNode {
+        private final Method mTestMethod;
+
+        public TestNode(Method testMethod) {
+            mTestMethod = testMethod;
+        }
+
+        @Override
+        public String getName() {
+            return mTestClass.getSimpleName() + "_" + mTestMethod.getName();
+        }
+    }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorTestScreenManipulator.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorTestScreenManipulator.java
index 835ff56..2956ed7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorTestScreenManipulator.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/helpers/SensorTestScreenManipulator.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.verifier.sensors.helpers;
 
+import com.android.cts.verifier.os.TimeoutResetActivity;
 import com.android.cts.verifier.sensors.base.BaseSensorTestActivity;
 import com.android.cts.verifier.sensors.base.ISensorTestStateContainer;
 
@@ -27,8 +28,12 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.os.PowerManager;
 import android.text.TextUtils;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
 
 /**
  * A class that provides functionality to manipulate the state of the device's screen.
@@ -52,8 +57,9 @@
  * - in a single-threaded environment
  */
 public class SensorTestScreenManipulator {
+    private static final String TAG = SensorTestScreenManipulator.class.getSimpleName();
 
-    private final Context mContext;
+    private final Activity mActivity;
     private final DevicePolicyManager mDevicePolicyManager;
     private final ComponentName mComponentName;
     private final PowerManager.WakeLock mWakeUpScreenWakeLock;
@@ -62,16 +68,17 @@
     private InternalBroadcastReceiver mBroadcastReceiver;
     private boolean mTurnOffScreenOnPowerDisconnected;
 
-    public SensorTestScreenManipulator(Context context) {
-        mContext = context;
-        mComponentName = SensorDeviceAdminReceiver.getComponentName(context);
+
+    public SensorTestScreenManipulator(Activity activity) {
+        mActivity = activity;
+        mComponentName = SensorDeviceAdminReceiver.getComponentName(activity);
         mDevicePolicyManager =
-                (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
+                (DevicePolicyManager) activity.getSystemService(Context.DEVICE_POLICY_SERVICE);
 
         int levelAndFlags = PowerManager.FULL_WAKE_LOCK
                 | PowerManager.ON_AFTER_RELEASE
                 | PowerManager.ACQUIRE_CAUSES_WAKEUP;
-        PowerManager powerManager = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
+        PowerManager powerManager = (PowerManager) activity.getSystemService(Context.POWER_SERVICE);
         mWakeUpScreenWakeLock = powerManager.newWakeLock(levelAndFlags, "SensorTestWakeUpScreen");
         mWakeUpScreenWakeLock.setReferenceCounted(false);
         mKeepScreenOnWakeLock = powerManager.newWakeLock(levelAndFlags, "SensorTestKeepScreenOn");
@@ -87,7 +94,7 @@
      */
     public synchronized void initialize(ISensorTestStateContainer stateContainer)
             throws InterruptedException {
-        if (!isDeviceAdminInitialized()) {
+        if (hasDeviceAdminFeature() && !isDeviceAdminInitialized()) {
             Intent intent = new Intent(DevicePolicyManager.ACTION_ADD_DEVICE_ADMIN);
             intent.putExtra(DevicePolicyManager.EXTRA_DEVICE_ADMIN, mComponentName);
             int resultCode = stateContainer.executeActivity(intent);
@@ -101,7 +108,7 @@
             mBroadcastReceiver = new InternalBroadcastReceiver();
             IntentFilter intentFilter = new IntentFilter();
             intentFilter.addAction(Intent.ACTION_POWER_DISCONNECTED);
-            mContext.registerReceiver(mBroadcastReceiver, intentFilter);
+            mActivity.registerReceiver(mBroadcastReceiver, intentFilter);
         }
     }
 
@@ -111,7 +118,7 @@
      */
     public synchronized  void close() {
         if (mBroadcastReceiver != null) {
-            mContext.unregisterReceiver(mBroadcastReceiver);
+            mActivity.unregisterReceiver(mBroadcastReceiver);
             mBroadcastReceiver = null;
         }
     }
@@ -121,8 +128,30 @@
      */
     public synchronized void turnScreenOff() {
         ensureDeviceAdminInitialized();
+
+        final CountDownLatch screenOffSignal = new CountDownLatch(1);
+        BroadcastReceiver screenOffBroadcastReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                mActivity.unregisterReceiver(this);
+                screenOffSignal.countDown();
+            }
+        };
+        mActivity.registerReceiver(
+                screenOffBroadcastReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
+
         releaseScreenOn();
-        mDevicePolicyManager.lockNow();
+        if (hasDeviceAdminFeature()) {
+            mDevicePolicyManager.lockNow();
+        } else {
+            TimeoutResetActivity.turnOffScreen(mActivity);
+        }
+
+        try {
+            screenOffSignal.await();
+        } catch (InterruptedException e) {
+            Log.wtf(TAG, "error waiting for screen off signal", e);
+        }
     }
 
     /**
@@ -175,7 +204,7 @@
     }
 
     private void ensureDeviceAdminInitialized() throws IllegalStateException {
-        if (!isDeviceAdminInitialized()) {
+        if (hasDeviceAdminFeature() && !isDeviceAdminInitialized()) {
             throw new IllegalStateException("Component must be initialized before it can be used.");
         }
     }
@@ -188,6 +217,10 @@
                 .hasGrantedPolicy(mComponentName, DeviceAdminInfo.USES_POLICY_FORCE_LOCK);
     }
 
+    private boolean hasDeviceAdminFeature() {
+        return mActivity.getPackageManager().hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN);
+    }
+
     private class InternalBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/streamquality/StreamingVideoActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/streamquality/StreamingVideoActivity.java
index fa233e8..9684d97 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/streamquality/StreamingVideoActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/streamquality/StreamingVideoActivity.java
@@ -119,30 +119,30 @@
         new Stream("H263 Video, AMR Audio", "http_h263_amr",
                 "http://redirector.c.play.google.com/"
                 + "videoplayback?id=271de9756065677e"
-                + "&itag=13&ip=0.0.0.0&ipbits=0&expire=999999999999999999"
-                + "&sparams=ip,ipbits,expire,ip,ipbits,expire,id,itag"
-                + "&signature=372FA4C532AA49D14EAF049BCDA66460EEE161E9"
-                + ".6D8BF096B73B7A68A7032CA8685053CFB498D30A"
+                + "&itag=13&ip=0.0.0.0&ipbits=0&expire=19000000000"
+                + "&sparams=ip,ipbits,expire,id,itag"
+                + "&signature=073A731E2BDF1E05206AC7B9B895C922ABCBA01D"
+                + ".1DDA3F999541D2136E6755F16FC44CA972767169"
                 + "&source=youtube"
-                + "&key=test_key1&user=android-device-test"),
+                + "&key=ik0&user=android-device-test"),
         new Stream("MPEG4 SP Video, AAC Audio", "http_mpeg4_aac",
                 "http://redirector.c.play.google.com/"
                 + "videoplayback?id=271de9756065677e"
-                + "&itag=17&ip=0.0.0.0&ipbits=0&expire=999999999999999999"
-                + "&sparams=ip,ipbits,expire,ip,ipbits,expire,id,itag"
-                + "&signature=3DCD3F79E045F95B6AF661765F046FB0440FF016"
-                + ".06A42661B3AF6BAF046F012549CC9BA34EBC80A9"
+                + "&itag=17&ip=0.0.0.0&ipbits=0&expire=19000000000"
+                + "&sparams=ip,ipbits,expire,id,itag"
+                + "&signature=6B0F8B8A6A7FD9E4CDF123349C2E061ED2020D74"
+                + ".3460FC81D6C8894BA2D241597D2E1D059845F5F0"
                 + "&source=youtube"
-                + "&key=test_key1&user=android-device-test"),
+                + "&key=ik0&user=android-device-test"),
         new Stream("H264 Base Video, AAC Audio", "http_h264_aac",
                 "http://redirector.c.play.google.com/"
                 + "videoplayback?id=271de9756065677e"
-                + "&itag=18&ip=0.0.0.0&ipbits=0&expire=999999999999999999"
-                + "&sparams=ip,ipbits,expire,ip,ipbits,expire,id,itag"
-                + "&signature=1219C2B07AF0638C27916307A6093C0E43CB894E"
-                + ".126B6B916BD57157782738AA7C03E59F21DBC168"
+                + "&itag=18&ip=0.0.0.0&ipbits=0&expire=19000000000"
+                + "&sparams=ip,ipbits,expire,id,itag"
+                + "&signature=75627CD4CEA73D7868CBDE3CE5C4011955164107"
+                + ".1DCFB0EF1372B48DDCFBE69645FE137AC02AF561"
                 + "&source=youtube"
-                + "&key=test_key1&user=android-device-test"),
+                + "&key=ik0&user=android-device-test"),
     };
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputService.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputService.java
new file mode 100644
index 0000000..f4460de
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputService.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.tv;
+
+import com.android.cts.verifier.R;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvInputService;
+import android.media.tv.TvTrackInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.view.Surface;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.TextView;
+
+import com.android.cts.verifier.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MockTvInputService extends TvInputService {
+    private static final String TAG = "MockTvInputService";
+
+    private static final String BROADCAST_ACTION = "action";
+    private static final String SELECT_TRACK_TYPE = "type";
+    private static final String SELECT_TRACK_ID = "id";
+    private static final String CAPTION_ENABLED = "enabled";
+
+    private static Object sLock = new Object();
+    private static Callback sTuneCallback = null;
+    private static Callback sOverlayViewCallback = null;
+    private static Callback sBroadcastCallback = null;
+    private static Callback sUnblockContentCallback = null;
+    private static Callback sSelectTrackCallback = null;
+    private static Callback sSetCaptionEnabledCallback = null;
+    private static TvContentRating sRating = null;
+
+    static final TvTrackInfo sEngAudioTrack =
+            new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "audio_eng")
+            .setAudioChannelCount(2)
+            .setAudioSampleRate(48000)
+            .setLanguage("eng")
+            .build();
+    static final TvTrackInfo sSpaAudioTrack =
+            new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "audio_spa")
+            .setAudioChannelCount(2)
+            .setAudioSampleRate(48000)
+            .setLanguage("spa")
+            .build();
+    static final TvTrackInfo sEngSubtitleTrack =
+            new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, "subtitle_eng")
+            .setLanguage("eng")
+            .build();
+    static final TvTrackInfo sSpaSubtitleTrack =
+            new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, "subtitle_spa")
+            .setLanguage("spa")
+            .build();
+
+    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            synchronized (sLock) {
+                if (sBroadcastCallback != null) {
+                    String expectedAction =
+                            sBroadcastCallback.getBundle().getString(BROADCAST_ACTION);
+                    if (intent.getAction().equals(expectedAction)) {
+                        sBroadcastCallback.post();
+                        sBroadcastCallback = null;
+                    }
+                }
+            }
+        }
+    };
+
+    static void expectTune(View postTarget, Runnable successCallback) {
+        synchronized (sLock) {
+            sTuneCallback = new Callback(postTarget, successCallback);
+        }
+    }
+
+    static void expectBroadcast(View postTarget, String action, Runnable successCallback) {
+        synchronized (sLock) {
+            sBroadcastCallback = new Callback(postTarget, successCallback);
+            sBroadcastCallback.getBundle().putString(BROADCAST_ACTION, action);
+        }
+    }
+
+    static void expectUnblockContent(View postTarget, Runnable successCallback) {
+        synchronized (sLock) {
+            sUnblockContentCallback = new Callback(postTarget, successCallback);
+        }
+    }
+
+    static void setBlockRating(TvContentRating rating) {
+        synchronized (sLock) {
+            sRating = rating;
+        }
+    }
+
+    static void expectOverlayView(View postTarget, Runnable successCallback) {
+        synchronized (sLock) {
+            sOverlayViewCallback = new Callback(postTarget, successCallback);
+        }
+    }
+
+    static void expectSelectTrack(int type, String id, View postTarget, Runnable successCallback) {
+        synchronized (sLock) {
+            sSelectTrackCallback = new Callback(postTarget, successCallback);
+            sSelectTrackCallback.getBundle().putInt(SELECT_TRACK_TYPE, type);
+            sSelectTrackCallback.getBundle().putString(SELECT_TRACK_ID, id);
+        }
+    }
+
+    static void expectSetCaptionEnabled(boolean enabled, View postTarget,
+            Runnable successCallback) {
+        synchronized (sLock) {
+            sSetCaptionEnabledCallback = new Callback(postTarget, successCallback);
+            sSetCaptionEnabledCallback.getBundle().putBoolean(CAPTION_ENABLED, enabled);
+        }
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED);
+        intentFilter.addAction(TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED);
+        registerReceiver(mBroadcastReceiver, intentFilter);
+    }
+
+    @Override
+    public void onDestroy() {
+        unregisterReceiver(mBroadcastReceiver);
+        super.onDestroy();
+    }
+
+    @Override
+    public Session onCreateSession(String inputId) {
+        Session session = new MockSessionImpl(this);
+        session.setOverlayViewEnabled(true);
+        return session;
+    }
+
+    private static class MockSessionImpl extends Session {
+        private final Context mContext;
+        private Surface mSurface = null;
+        private List<TvTrackInfo> mTracks = new ArrayList<>();
+
+        private MockSessionImpl(Context context) {
+            super(context);
+            mContext = context;
+            mTracks.add(sEngAudioTrack);
+            mTracks.add(sSpaAudioTrack);
+            mTracks.add(sEngSubtitleTrack);
+            mTracks.add(sSpaSubtitleTrack);
+        }
+
+        @Override
+        public void onRelease() {
+        }
+
+        private void draw() {
+            Surface surface = mSurface;
+            if (surface == null) return;
+            if (!surface.isValid()) return;
+
+            Canvas c = surface.lockCanvas(null);
+            if (c == null) return;
+            try {
+                Bitmap b = BitmapFactory.decodeResource(
+                        mContext.getResources(), R.drawable.icon);
+                int srcWidth = b.getWidth();
+                int srcHeight = b.getHeight();
+                int dstWidth = c.getWidth();
+                int dstHeight = c.getHeight();
+                c.drawColor(Color.BLACK);
+                c.drawBitmap(b, new Rect(0, 0, srcWidth, srcHeight),
+                        new Rect(10, 10, dstWidth - 10, dstHeight - 10), null);
+            } finally {
+                surface.unlockCanvasAndPost(c);
+            }
+        }
+
+        @Override
+        public View onCreateOverlayView() {
+            LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(
+                    LAYOUT_INFLATER_SERVICE);
+            View view = inflater.inflate(R.layout.tv_overlay, null);
+            TextView textView = (TextView) view.findViewById(R.id.overlay_view_text);
+            textView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+                @Override
+                public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
+                    Callback overlayViewCallback = null;
+                    synchronized (sLock) {
+                        overlayViewCallback = sOverlayViewCallback;
+                        sOverlayViewCallback = null;
+                    }
+                    if (overlayViewCallback != null) {
+                        overlayViewCallback.post();
+                    }
+                }
+            });
+            return view;
+        }
+
+        @Override
+        public boolean onSetSurface(Surface surface) {
+            mSurface = surface;
+            draw();
+            return true;
+        }
+
+        @Override
+        public void onSetStreamVolume(float volume) {
+        }
+
+        @Override
+        public boolean onTune(Uri channelUri) {
+            synchronized (sLock) {
+                if (sRating != null) {
+                    notifyContentBlocked(sRating);
+                }
+                if (sTuneCallback != null) {
+                    sTuneCallback.post();
+                    sTuneCallback = null;
+                }
+                if (sRating == null) {
+                    notifyContentAllowed();
+                }
+            }
+            notifyVideoAvailable();
+            notifyTracksChanged(mTracks);
+            notifyTrackSelected(TvTrackInfo.TYPE_AUDIO, sEngAudioTrack.getId());
+            notifyTrackSelected(TvTrackInfo.TYPE_SUBTITLE, null);
+            return true;
+        }
+
+        @Override
+        public boolean onSelectTrack(int type, String trackId) {
+            synchronized (sLock) {
+                if (sSelectTrackCallback != null) {
+                    Bundle bundle = sSelectTrackCallback.getBundle();
+                    if (bundle.getInt(SELECT_TRACK_TYPE) == type
+                            && bundle.getString(SELECT_TRACK_ID).equals(trackId)) {
+                        sSelectTrackCallback.post();
+                        sSelectTrackCallback = null;
+                    }
+                }
+            }
+            notifyTrackSelected(type, trackId);
+            return true;
+        }
+
+        @Override
+        public void onSetCaptionEnabled(boolean enabled) {
+            synchronized (sLock) {
+                if (sSetCaptionEnabledCallback != null) {
+                    Bundle bundle = sSetCaptionEnabledCallback.getBundle();
+                    if (bundle.getBoolean(CAPTION_ENABLED) == enabled) {
+                        sSetCaptionEnabledCallback.post();
+                        sSetCaptionEnabledCallback = null;
+                    }
+                }
+            }
+        }
+
+        @Override
+        public void onUnblockContent(TvContentRating unblockedRating) {
+            synchronized (sLock) {
+                if (sRating != null && sRating.equals(unblockedRating)) {
+                    sUnblockContentCallback.post();
+                    sRating = null;
+                    notifyContentAllowed();
+                }
+            }
+        }
+    }
+
+    private static class Callback {
+        private final View mPostTarget;
+        private final Runnable mAction;
+        private final Bundle mBundle = new Bundle();
+
+        Callback(View postTarget, Runnable action) {
+            mPostTarget = postTarget;
+            mAction = action;
+        }
+
+        public void post() {
+            mPostTarget.post(mAction);
+        }
+
+        public Bundle getBundle() {
+            return mBundle;
+        }
+    }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSettingsActivity.java
similarity index 75%
rename from common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
rename to apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSettingsActivity.java
index a376373..4231db7 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSettingsActivity.java
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.compatibility.common.util;
+package com.android.cts.verifier.tv;
 
-import junit.framework.TestCase;
+import android.preference.PreferenceActivity;
 
-public class CommonUtilTest extends TestCase {
-
-    // TODO(stuartscott): Add tests when there is something to test.
+public class MockTvInputSettingsActivity extends PreferenceActivity {
 
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSetupActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSetupActivity.java
new file mode 100644
index 0000000..81a8edc
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MockTvInputSetupActivity.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.tv;
+
+import android.app.Activity;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputInfo;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Pair;
+import android.view.View;
+
+public class MockTvInputSetupActivity extends Activity {
+    private static final String TAG = "MockTvInputSetupActivity";
+
+    private static final String CHANNEL_NUMBER = "999-0";
+    private static final String CHANNEL_NAME = "Dummy";
+
+    private static Object sLock = new Object();
+    private static Pair<View, Runnable> sLaunchCallback = null;
+
+    static void expectLaunch(View postTarget, Runnable successCallback) {
+        synchronized (sLock) {
+            sLaunchCallback = Pair.create(postTarget, successCallback);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        try {
+            super.onCreate(savedInstanceState);
+            final String inputId = getIntent().getStringExtra(TvInputInfo.EXTRA_INPUT_ID);
+            final Uri uri = TvContract.buildChannelsUriForInput(inputId);
+            final String[] projection = { TvContract.Channels._ID };
+            try (Cursor cursor = getContentResolver().query(uri, projection, null, null, null)) {
+                // If we already have channels, just finish without doing anything.
+                if (cursor != null && cursor.getCount() > 0) {
+                    return;
+                }
+            }
+            ContentValues values = new ContentValues();
+            values.put(TvContract.Channels.COLUMN_INPUT_ID, inputId);
+            values.put(TvContract.Channels.COLUMN_DISPLAY_NUMBER, CHANNEL_NUMBER);
+            values.put(TvContract.Channels.COLUMN_DISPLAY_NAME, CHANNEL_NAME);
+            Uri channelUri = getContentResolver().insert(uri, values);
+            // If the channel's ID happens to be zero, we add another and delete the one.
+            if (ContentUris.parseId(channelUri) == 0) {
+                getContentResolver().insert(uri, values);
+                getContentResolver().delete(channelUri, null, null);
+            }
+        } finally {
+            Pair<View, Runnable> launchCallback = null;
+            synchronized (sLock) {
+                launchCallback = sLaunchCallback;
+                sLaunchCallback = null;
+            }
+            if (launchCallback != null) {
+                launchCallback.first.post(launchCallback.second);
+            }
+
+            setResult(Activity.RESULT_OK);
+            finish();
+        }
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/MultipleTracksTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MultipleTracksTestActivity.java
new file mode 100644
index 0000000..66af4c6
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/MultipleTracksTestActivity.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.tv;
+
+import com.android.cts.verifier.R;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvTrackInfo;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * Tests for verifying TV app behavior on multiple tracks and subtitle.
+ */
+public class MultipleTracksTestActivity extends TvAppVerifierActivity
+        implements View.OnClickListener {
+    private static final String TAG = "MultipleTracksTestActivity";
+
+    private static final long TIMEOUT_MS = 5l * 60l * 1000l;  // 5 mins.
+
+    private View mSelectSubtitleItem;
+    private View mVerifySetCaptionEnabledItem;
+    private View mVerifySelectSubtitleItem;
+    private View mSelectAudioItem;
+    private View mVerifySelectAudioItem;
+
+    private Intent mTvAppIntent = null;
+
+    @Override
+    public void onClick(View v) {
+        final View postTarget = getPostTarget();
+
+        if (containsButton(mSelectSubtitleItem, v)) {
+            final Runnable failCallback = new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mVerifySetCaptionEnabledItem, false);
+                    setPassState(mVerifySelectSubtitleItem, false);
+                }
+            };
+            postTarget.postDelayed(failCallback, TIMEOUT_MS);
+            MockTvInputService.expectSetCaptionEnabled(true, postTarget, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mSelectSubtitleItem, true);
+                    setPassState(mVerifySetCaptionEnabledItem, true);
+                    Integer tag = (Integer) mSelectAudioItem.getTag();
+                    if (tag == 0) {
+                        mSelectAudioItem.setTag(Integer.valueOf(1));
+                    } else if (tag == 1) {
+                        setButtonEnabled(mSelectAudioItem, true);
+                    }
+                }
+            });
+            MockTvInputService.expectSelectTrack(TvTrackInfo.TYPE_SUBTITLE,
+                    MockTvInputService.sEngSubtitleTrack.getId(), postTarget, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mSelectSubtitleItem, true);
+                    setPassState(mVerifySelectSubtitleItem, true);
+                    Integer tag = (Integer) mSelectAudioItem.getTag();
+                    if (tag == 0) {
+                        mSelectAudioItem.setTag(Integer.valueOf(1));
+                    } else if (tag == 1) {
+                        setButtonEnabled(mSelectAudioItem, true);
+                    }
+                }
+            });
+        } else if (containsButton(mSelectAudioItem, v)) {
+            final Runnable failCallback = new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mVerifySelectAudioItem, false);
+                }
+            };
+            postTarget.postDelayed(failCallback, TIMEOUT_MS);
+            MockTvInputService.expectSelectTrack(TvTrackInfo.TYPE_AUDIO,
+                    MockTvInputService.sSpaAudioTrack.getId(), postTarget, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mSelectAudioItem, true);
+                    setPassState(mVerifySelectAudioItem, true);
+                    getPassButton().setEnabled(true);
+                }
+            });
+        }
+        if (mTvAppIntent == null) {
+            String[] projection = { TvContract.Channels._ID };
+            try (Cursor cursor = getContentResolver().query(TvContract.Channels.CONTENT_URI,
+                    projection, null, null, null)) {
+                if (cursor != null && cursor.moveToNext()) {
+                    mTvAppIntent = new Intent(Intent.ACTION_VIEW,
+                            TvContract.buildChannelUri(cursor.getLong(0)));
+                }
+            }
+            if (mTvAppIntent == null) {
+                Toast.makeText(this, R.string.tv_channel_not_found, Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+        startActivity(mTvAppIntent);
+    }
+
+    @Override
+    protected void createTestItems() {
+        mSelectSubtitleItem = createUserItem(
+                R.string.tv_multiple_tracks_test_select_subtitle,
+                R.string.tv_launch_tv_app, this);
+        setButtonEnabled(mSelectSubtitleItem, true);
+        mVerifySetCaptionEnabledItem = createAutoItem(
+                R.string.tv_multiple_tracks_test_verify_set_caption_enabled);
+        mVerifySelectSubtitleItem = createAutoItem(
+                R.string.tv_multiple_tracks_test_verify_select_subtitle);
+        mSelectAudioItem = createUserItem(
+                R.string.tv_multiple_tracks_test_select_audio,
+                R.string.tv_launch_tv_app, this);
+        mSelectAudioItem.setTag(Integer.valueOf(0));
+        mVerifySelectAudioItem = createAutoItem(
+                R.string.tv_multiple_tracks_test_verify_select_audio);
+    }
+
+    @Override
+    protected void setInfoResources() {
+        setInfoResources(R.string.tv_multiple_tracks_test,
+                R.string.tv_multiple_tracks_test_info, -1);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/ParentalControlTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/ParentalControlTestActivity.java
new file mode 100644
index 0000000..284b485
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/ParentalControlTestActivity.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.tv;
+
+import com.android.cts.verifier.R;
+
+import android.content.Intent;
+import android.database.Cursor;
+import android.media.tv.TvContentRating;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputManager;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * Tests for verifying TV app behavior on parental control.
+ */
+public class ParentalControlTestActivity extends TvAppVerifierActivity
+        implements View.OnClickListener {
+    private static final String TAG = "ParentalControlTestActivity";
+
+    private static final long TIMEOUT_MS = 5l * 60l * 1000l;  // 5 mins.
+
+    private View mTurnOnParentalControlItem;
+    private View mVerifyReceiveBroadcast1Item;
+    private View mBlockTvMaItem;
+    private View mVerifyReceiveBroadcast2Item;
+    private View mBlockUnblockItem;
+
+    private Intent mTvAppIntent = null;
+
+    @Override
+    public void onClick(View v) {
+        final View postTarget = getPostTarget();
+
+        if (containsButton(mTurnOnParentalControlItem, v)) {
+            final Runnable failCallback = new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mVerifyReceiveBroadcast1Item, false);
+                }
+            };
+            postTarget.postDelayed(failCallback, TIMEOUT_MS);
+            MockTvInputService.expectBroadcast(postTarget,
+                    TvInputManager.ACTION_PARENTAL_CONTROLS_ENABLED_CHANGED, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mTurnOnParentalControlItem, true);
+                    setPassState(mVerifyReceiveBroadcast1Item, true);
+                    setButtonEnabled(mBlockTvMaItem, true);
+                }
+            });
+        } else if (containsButton(mBlockTvMaItem, v)) {
+            final Runnable failCallback = new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mVerifyReceiveBroadcast2Item, false);
+                }
+            };
+            postTarget.postDelayed(failCallback, TIMEOUT_MS);
+            MockTvInputService.expectBroadcast(postTarget,
+                    TvInputManager.ACTION_BLOCKED_RATINGS_CHANGED, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mBlockTvMaItem, true);
+                    setPassState(mVerifyReceiveBroadcast2Item, true);
+                    setButtonEnabled(mBlockUnblockItem, true);
+                }
+            });
+        } else if (containsButton(mBlockUnblockItem, v)) {
+            final Runnable failCallback = new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mBlockUnblockItem, false);
+                }
+            };
+            postTarget.postDelayed(failCallback, TIMEOUT_MS);
+            MockTvInputService.setBlockRating(TvContentRating.createRating(
+                    "com.android.cts.verifier", "CTS_VERIFIER", "FAKE"));
+            MockTvInputService.expectUnblockContent(postTarget, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mBlockUnblockItem, true);
+                    getPassButton().setEnabled(true);
+                }
+            });
+        }
+        if (mTvAppIntent == null) {
+            String[] projection = { TvContract.Channels._ID };
+            try (Cursor cursor = getContentResolver().query(TvContract.Channels.CONTENT_URI,
+                    projection, null, null, null)) {
+                if (cursor != null && cursor.moveToNext()) {
+                    mTvAppIntent = new Intent(Intent.ACTION_VIEW,
+                            TvContract.buildChannelUri(cursor.getLong(0)));
+                }
+            }
+            if (mTvAppIntent == null) {
+                Toast.makeText(this, R.string.tv_channel_not_found, Toast.LENGTH_SHORT).show();
+                return;
+            }
+        }
+        startActivity(mTvAppIntent);
+    }
+
+    @Override
+    protected void createTestItems() {
+        mTurnOnParentalControlItem = createUserItem(
+                R.string.tv_parental_control_test_turn_on_parental_control,
+                R.string.tv_launch_tv_app, this);
+        setButtonEnabled(mTurnOnParentalControlItem, true);
+        mVerifyReceiveBroadcast1Item = createAutoItem(
+                R.string.tv_parental_control_test_verify_receive_broadcast1);
+        mBlockTvMaItem = createUserItem(R.string.tv_parental_control_test_block_tv_ma,
+                R.string.tv_launch_tv_app, this);
+        mVerifyReceiveBroadcast2Item = createAutoItem(
+                R.string.tv_parental_control_test_verify_receive_broadcast2);
+        mBlockUnblockItem = createUserItem(R.string.tv_parental_control_test_block_unblock,
+                R.string.tv_launch_tv_app, this);
+    }
+
+    @Override
+    protected void setInfoResources() {
+        setInfoResources(R.string.tv_parental_control_test,
+                R.string.tv_parental_control_test_info, -1);
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java
new file mode 100644
index 0000000..3529237
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvAppVerifierActivity.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.tv;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.content.Intent;
+import android.media.tv.TvContract;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+/**
+ * Base class for TV app tests.
+ */
+public abstract class TvAppVerifierActivity extends PassFailButtons.Activity {
+    private static final String TAG = "TvAppVerifierActivity";
+
+    private static final long TIMEOUT_MS = 5l * 60l * 1000l;  // 5 mins.
+
+    private LayoutInflater mInflater;
+    private ViewGroup mItemList;
+    private View mPostTarget;
+
+    protected View getPostTarget() {
+        return mPostTarget;
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mInflater = getLayoutInflater();
+        // Reusing location_mode_main.
+        View view = mInflater.inflate(R.layout.location_mode_main, null);
+        mPostTarget = mItemList = (ViewGroup) view.findViewById(R.id.test_items);
+        createTestItems();
+        setContentView(view);
+        setPassFailButtonClickListeners();
+        setInfoResources();
+
+        getPassButton().setEnabled(false);
+    }
+
+    protected void setButtonEnabled(View item, boolean enabled) {
+        View button = item.findViewById(R.id.user_action_button);
+        button.setClickable(enabled);
+        button.setEnabled(enabled);
+    }
+
+    protected void setPassState(View item, boolean passed) {
+        ImageView status = (ImageView) item.findViewById(R.id.status);
+        status.setImageResource(passed ? R.drawable.fs_good : R.drawable.fs_error);
+        View button = item.findViewById(R.id.user_action_button);
+        button.setClickable(false);
+        button.setEnabled(false);
+        status.invalidate();
+    }
+
+    protected abstract void createTestItems();
+
+    protected abstract void setInfoResources();
+
+    /**
+     * Call this to create a test step where the user must perform some action.
+     */
+    protected View createUserItem(int instructionTextId, int buttonTextId, View.OnClickListener l) {
+        View item = mInflater.inflate(R.layout.tv_item, mItemList, false);
+        TextView instructions = (TextView) item.findViewById(R.id.instructions);
+        instructions.setText(instructionTextId);
+        Button button = (Button) item.findViewById(R.id.user_action_button);
+        button.setVisibility(View.VISIBLE);
+        button.setText(buttonTextId);
+        button.setOnClickListener(l);
+        mItemList.addView(item);
+        return item;
+    }
+
+    /**
+     * Call this to create a test step where the test automatically evaluates whether
+     * an expected condition is satisfied.
+     */
+    protected View createAutoItem(int stringId) {
+        View item = mInflater.inflate(R.layout.tv_item, mItemList, false);
+        TextView instructions = (TextView) item.findViewById(R.id.instructions);
+        instructions.setText(stringId);
+        mItemList.addView(item);
+        return item;
+    }
+
+    static boolean containsButton(View item, View button) {
+        return item.findViewById(R.id.user_action_button) == button;
+    }
+}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvInputDiscoveryTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvInputDiscoveryTestActivity.java
new file mode 100644
index 0000000..3d17a1a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/tv/TvInputDiscoveryTestActivity.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.tv;
+
+import android.content.Intent;
+import android.media.tv.TvContract;
+import android.view.View;
+
+import com.android.cts.verifier.R;
+
+/**
+ * Tests for verifying TV app behavior for third-party TV input apps.
+ */
+public class TvInputDiscoveryTestActivity extends TvAppVerifierActivity
+        implements View.OnClickListener {
+    private static final String TAG = "TvInputDiscoveryTestActivity";
+
+    private static final Intent TV_APP_INTENT = new Intent(Intent.ACTION_VIEW,
+            TvContract.buildChannelUri(0));
+
+    private static final long TIMEOUT_MS = 5l * 60l * 1000l;  // 5 mins.
+
+    private View mGoToSetupItem;
+    private View mVerifySetupItem;
+    private View mTuneToChannelItem;
+    private View mVerifyTuneItem;
+    private View mVerifyOverlayViewItem;
+    private boolean mTuneVerified;
+    private boolean mOverlayViewVerified;
+
+    @Override
+    public void onClick(View v) {
+        final View postTarget = getPostTarget();
+
+        if (containsButton(mGoToSetupItem, v)) {
+            final Runnable failCallback = new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mVerifySetupItem, false);
+                }
+            };
+            postTarget.postDelayed(failCallback, TIMEOUT_MS);
+            MockTvInputSetupActivity.expectLaunch(postTarget, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mGoToSetupItem, true);
+                    setPassState(mVerifySetupItem, true);
+                    setButtonEnabled(mTuneToChannelItem, true);
+                }
+            });
+        } else if (containsButton(mTuneToChannelItem, v)) {
+            final Runnable failCallback = new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mVerifyTuneItem, false);
+                }
+            };
+            postTarget.postDelayed(failCallback, TIMEOUT_MS);
+            MockTvInputService.expectTune(postTarget, new Runnable() {
+                @Override
+                public void run() {
+                    setPassState(mTuneToChannelItem, true);
+                    setPassState(mVerifyTuneItem, true);
+
+                    mTuneVerified = true;
+                    updatePassState(postTarget, failCallback);
+                }
+            });
+            MockTvInputService.expectOverlayView(postTarget, new Runnable() {
+                @Override
+                public void run() {
+                    postTarget.removeCallbacks(failCallback);
+                    setPassState(mVerifyOverlayViewItem, true);
+
+                    mOverlayViewVerified = true;
+                    updatePassState(postTarget, failCallback);
+                }
+            });
+        }
+        startActivity(TV_APP_INTENT);
+    }
+
+    @Override
+    protected void createTestItems() {
+        mGoToSetupItem = createUserItem(R.string.tv_input_discover_test_go_to_setup,
+                R.string.tv_launch_tv_app, this);
+        setButtonEnabled(mGoToSetupItem, true);
+        mVerifySetupItem = createAutoItem(R.string.tv_input_discover_test_verify_setup);
+        mTuneToChannelItem = createUserItem(R.string.tv_input_discover_test_tune_to_channel,
+                R.string.tv_launch_tv_app, this);
+        mVerifyTuneItem = createAutoItem(R.string.tv_input_discover_test_verify_tune);
+        mVerifyOverlayViewItem = createAutoItem(
+                R.string.tv_input_discover_test_verify_overlay_view);
+    }
+
+    private void updatePassState(View postTarget, Runnable failCallback) {
+        if (mTuneVerified && mOverlayViewVerified) {
+            postTarget.removeCallbacks(failCallback);
+            getPassButton().setEnabled(true);
+        }
+    }
+
+    @Override
+    protected void setInfoResources() {
+        setInfoResources(R.string.tv_input_discover_test,
+                R.string.tv_input_discover_test_info, -1);
+    }
+}
diff --git a/apps/NotificationBot/Android.mk b/apps/NotificationBot/Android.mk
new file mode 100644
index 0000000..9d9c9f9
--- /dev/null
+++ b/apps/NotificationBot/Android.mk
@@ -0,0 +1,36 @@
+#
+# 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_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) $(call all-Iaidl-files-under, src)
+
+LOCAL_PACKAGE_NAME := NotificationBot
+
+LOCAL_PROGUARD_FLAG_FILES := proguard.flags
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/apps/NotificationBot/AndroidManifest.xml b/apps/NotificationBot/AndroidManifest.xml
new file mode 100644
index 0000000..b63791f
--- /dev/null
+++ b/apps/NotificationBot/AndroidManifest.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!-- 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.android.cts.robot"
+      android:versionCode="1"
+      android:versionName="1.0">
+
+    <uses-sdk android:minSdkVersion="19" android:targetSdkVersion="21"/>
+
+    <application android:label="@string/app_name"
+            android:icon="@drawable/icon"
+            android:debuggable="true">
+
+        <!-- Required because a bare service won't show up in the app notifications list. -->
+        <activity android:name=".NotificationBotActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+
+        <!-- services used by the CtsVerifier to test notifications. -->
+        <receiver android:name=".NotificationBot" android:exported="true">
+            <intent-filter>
+                <action android:name="com.android.cts.robot.ACTION_POST" />
+                <action android:name="com.android.cts.robot.ACTION_CANCEL" />
+            </intent-filter>
+        </receiver>
+
+
+    </application>
+
+</manifest>
diff --git a/apps/NotificationBot/proguard.flags b/apps/NotificationBot/proguard.flags
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/apps/NotificationBot/proguard.flags
diff --git a/apps/NotificationBot/res/drawable-hdpi/icon.png b/apps/NotificationBot/res/drawable-hdpi/icon.png
new file mode 100644
index 0000000..ecaabbe
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-hdpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/drawable-ldpi/icon.png b/apps/NotificationBot/res/drawable-ldpi/icon.png
new file mode 100644
index 0000000..f2de61f
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-ldpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/drawable-mdpi/icon.png b/apps/NotificationBot/res/drawable-mdpi/icon.png
new file mode 100644
index 0000000..4950761
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-mdpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/drawable-xhdpi/icon.png b/apps/NotificationBot/res/drawable-xhdpi/icon.png
new file mode 100644
index 0000000..9b39cfb
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-xhdpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/drawable-xxhdpi/icon.png b/apps/NotificationBot/res/drawable-xxhdpi/icon.png
new file mode 100644
index 0000000..b944c10
--- /dev/null
+++ b/apps/NotificationBot/res/drawable-xxhdpi/icon.png
Binary files differ
diff --git a/apps/NotificationBot/res/layout/main.xml b/apps/NotificationBot/res/layout/main.xml
new file mode 100644
index 0000000..bf84fa9
--- /dev/null
+++ b/apps/NotificationBot/res/layout/main.xml
@@ -0,0 +1,36 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="match_parent"
+             android:layout_height="match_parent"
+             android:orientation="vertical"
+        >
+
+    <Space android:layout_width="match_parent"
+           android:layout_height="0dp"
+           android:layout_weight="1"
+            />
+    <TextView
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:text="@string/call_to_action"
+            android:textAlignment="center"
+            />
+    <Space android:layout_width="match_parent"
+           android:layout_height="0dp"
+           android:layout_weight="1"
+            />
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/tests/uirendering/res/layout/draw_activity_view.xml b/apps/NotificationBot/res/values/strings.xml
similarity index 78%
rename from tests/tests/uirendering/res/layout/draw_activity_view.xml
rename to apps/NotificationBot/res/values/strings.xml
index 54a72e3..866a9ec 100644
--- a/tests/tests/uirendering/res/layout/draw_activity_view.xml
+++ b/apps/NotificationBot/res/values/strings.xml
@@ -13,5 +13,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<android.uirendering.cts.CanvasClientView xmlns:android="http://schemas.android.com/apk/res/android"/>
+<resources>
+    <string name="app_name">CTS Robot</string>
+    <string name="call_to_action">Nothing to do here,\nPlease run the CTSVerifier App instead.</string>
+</resources>
\ No newline at end of file
diff --git a/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
new file mode 100644
index 0000000..2aa5f41
--- /dev/null
+++ b/apps/NotificationBot/src/com/android/cts/robot/NotificationBot.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.robot;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+
+public class NotificationBot extends BroadcastReceiver {
+    private static final String TAG = "NotificationBot";
+    private static final String EXTRA_ID = "ID";
+    private static final String EXTRA_NOTIFICATION = "NOTIFICATION";
+    private static final String ACTION_POST = "com.android.cts.robot.ACTION_POST";
+    private static final String ACTION_CANCEL = "com.android.cts.robot.ACTION_CANCEL";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        Log.i(TAG, "received intent: " + intent);
+        if (ACTION_POST.equals(intent.getAction())) {
+            Log.i(TAG, ACTION_POST);
+            if (!intent.hasExtra(EXTRA_NOTIFICATION) || !intent.hasExtra(EXTRA_ID)) {
+                Log.e(TAG, "received post action with missing content");
+                return;
+            }
+            int id = intent.getIntExtra(EXTRA_ID, -1);
+            Log.i(TAG, "id: " + id);
+            Notification n = (Notification) intent.getParcelableExtra(EXTRA_NOTIFICATION);
+            Log.i(TAG, "n: " + n);
+            NotificationManager noMa =
+                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+            noMa.notify(id, n);
+
+        } else if (ACTION_CANCEL.equals(intent.getAction())) {
+            Log.i(TAG, ACTION_CANCEL);
+            int id = intent.getIntExtra(EXTRA_ID, -1);
+            Log.i(TAG, "id: " + id);
+            if (id < 0) {
+                Log.e(TAG, "received cancel action with no ID");
+                return;
+            }
+            NotificationManager noMa =
+                    (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
+            noMa.cancel(id);
+
+        } else {
+            Log.i(TAG, "received unexpected action: " + intent.getAction());
+        }
+    }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java b/apps/NotificationBot/src/com/android/cts/robot/NotificationBotActivity.java
similarity index 64%
copy from common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
copy to apps/NotificationBot/src/com/android/cts/robot/NotificationBotActivity.java
index a376373..1b9408e 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
+++ b/apps/NotificationBot/src/com/android/cts/robot/NotificationBotActivity.java
@@ -13,13 +13,16 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package com.android.cts.robot;
 
-package com.android.compatibility.common.util;
+import android.app.Activity;
+import android.os.Bundle;
+import com.android.cts.robot.R;
 
-import junit.framework.TestCase;
-
-public class CommonUtilTest extends TestCase {
-
-    // TODO(stuartscott): Add tests when there is something to test.
-
-}
+public class NotificationBotActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+}
\ No newline at end of file
diff --git a/build/test_executable.mk b/build/test_executable.mk
index e0352ba..02b3e4c 100644
--- a/build/test_executable.mk
+++ b/build/test_executable.mk
@@ -43,3 +43,6 @@
 						-b $(CTS_UNSUPPORTED_ABIS) \
 						-a $(CTS_TARGET_ARCH) \
 						-o $@
+
+# Have the module name depend on the cts files; so the cts files get generated when you run mm/mmm/mma/mmma.
+$(my_register_name) : $(cts_executable_xml)
diff --git a/build/test_gtest_package.mk b/build/test_gtest_package.mk
index 6868081..dd1269b 100644
--- a/build/test_gtest_package.mk
+++ b/build/test_gtest_package.mk
@@ -52,3 +52,6 @@
 						-b $(CTS_UNSUPPORTED_ABIS) \
 						-a $(CTS_TARGET_ARCH) \
 						-o $@
+
+# Have the module name depend on the cts files; so the cts files get generated when you run mm/mmm/mma/mmma.
+$(my_register_name) : $(cts_package_apk) $(cts_package_xml)
diff --git a/build/test_host_java_library.mk b/build/test_host_java_library.mk
index baf9e75..8ed5670 100644
--- a/build/test_host_java_library.mk
+++ b/build/test_host_java_library.mk
@@ -21,14 +21,18 @@
 
 cts_library_xml := $(CTS_TESTCASES_OUT)/$(LOCAL_MODULE).xml
 
-$(cts_library_xml): PRIVATE_PATH := $(LOCAL_PATH)/src
+cts_src_dirs := $(LOCAL_PATH)/src
+cts_src_dirs += $(sort $(dir $(LOCAL_GENERATED_SOURCES)))
+cts_src_dirs := $(addprefix -s , $(cts_src_dirs))
+
+$(cts_library_xml): PRIVATE_SRC_DIRS := $(cts_src_dirs)
 $(cts_library_xml): PRIVATE_TEST_PACKAGE := $(LOCAL_CTS_TEST_PACKAGE)
 $(cts_library_xml): PRIVATE_LIBRARY := $(LOCAL_MODULE)
 $(cts_library_xml): PRIVATE_JAR_PATH := $(LOCAL_MODULE).jar
 $(cts_library_xml): $(HOST_OUT_JAVA_LIBRARIES)/$(LOCAL_MODULE).jar $(CTS_EXPECTATIONS) $(CTS_UNSUPPORTED_ABIS) $(CTS_JAVA_TEST_SCANNER_DOCLET) $(CTS_JAVA_TEST_SCANNER) $(CTS_XML_GENERATOR)
 	$(hide) echo Generating test description for host library $(PRIVATE_LIBRARY)
 	$(hide) mkdir -p $(CTS_TESTCASES_OUT)
-	$(hide) $(CTS_JAVA_TEST_SCANNER) -s $(PRIVATE_PATH) \
+	$(hide) $(CTS_JAVA_TEST_SCANNER) $(PRIVATE_SRC_DIRS) \
 						-d $(CTS_JAVA_TEST_SCANNER_DOCLET) | \
 			$(CTS_XML_GENERATOR) -t hostSideOnly \
 						-j $(PRIVATE_JAR_PATH) \
@@ -38,3 +42,6 @@
 						-b $(CTS_UNSUPPORTED_ABIS) \
 						-a $(CTS_TARGET_ARCH) \
 						-o $@
+
+# Have the module name depend on the cts files; so the cts files get generated when you run mm/mmm/mma/mmma.
+$(my_register_name) : $(cts_library_xml)
diff --git a/build/test_package.mk b/build/test_package.mk
index 353ae07..7589787 100644
--- a/build/test_package.mk
+++ b/build/test_package.mk
@@ -28,12 +28,16 @@
 cts_package_apk := $(CTS_TESTCASES_OUT)/$(LOCAL_PACKAGE_NAME).apk
 cts_package_xml := $(CTS_TESTCASES_OUT)/$(LOCAL_PACKAGE_NAME).xml
 
+cts_src_dirs := $(LOCAL_PATH)
+cts_src_dirs += $(sort $(dir $(LOCAL_GENERATED_SOURCES)))
+cts_src_dirs := $(addprefix -s , $(cts_src_dirs))
+
 $(cts_package_apk): PRIVATE_PACKAGE := $(LOCAL_PACKAGE_NAME)
 $(cts_package_apk): $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME))/package.apk | $(ACP)
 	$(hide) mkdir -p $(CTS_TESTCASES_OUT)
 	$(hide) $(ACP) -fp $(call intermediates-dir-for,APPS,$(PRIVATE_PACKAGE))/package.apk $@
 
-$(cts_package_xml): PRIVATE_PATH := $(LOCAL_PATH)
+$(cts_package_xml): PRIVATE_SRC_DIRS := $(cts_src_dirs)
 $(cts_package_xml): PRIVATE_INSTRUMENTATION := $(LOCAL_INSTRUMENTATION_FOR)
 $(cts_package_xml): PRIVATE_PACKAGE := $(LOCAL_PACKAGE_NAME)
 ifneq ($(filter cts/suite/cts/%, $(LOCAL_PATH)),)
@@ -48,7 +52,7 @@
 	$(hide) echo Generating test description for java package $(PRIVATE_PACKAGE)
 	$(hide) mkdir -p $(CTS_TESTCASES_OUT)
 	$(hide) $(CTS_JAVA_TEST_SCANNER) \
-						-s $(PRIVATE_PATH) \
+						$(PRIVATE_SRC_DIRS) \
 						-d $(CTS_JAVA_TEST_SCANNER_DOCLET) | \
 			$(CTS_XML_GENERATOR) \
 						-t $(PRIVATE_TEST_TYPE) \
@@ -60,3 +64,6 @@
 						-b $(CTS_UNSUPPORTED_ABIS) \
 						-a $(CTS_TARGET_ARCH) \
 						-o $@
+
+# Have the module name depend on the cts files; so the cts files get generated when you run mm/mmm/mma/mmma.
+$(my_register_name) : $(cts_package_apk) $(cts_package_xml)
diff --git a/build/test_target_java_library.mk b/build/test_target_java_library.mk
index c0d7a2a..04fffb9 100644
--- a/build/test_target_java_library.mk
+++ b/build/test_target_java_library.mk
@@ -44,3 +44,6 @@
 						-a $(CTS_TARGET_ARCH) \
 						-x "runtimeArgs->$(PRIVATE_RUNTIME_ARGS)" \
 						-o $@
+
+# Have the module name depend on the cts files; so the cts files get generated when you run mm/mmm/mma/mmma.
+$(my_register_name) : $(cts_library_jar) $(cts_library_xml)
diff --git a/build/test_uiautomator.mk b/build/test_uiautomator.mk
index 5e2f07a..cad6e4f 100644
--- a/build/test_uiautomator.mk
+++ b/build/test_uiautomator.mk
@@ -24,12 +24,16 @@
 cts_library_xml := $(CTS_TESTCASES_OUT)/$(LOCAL_MODULE).xml 
 cts_library_jar := $(CTS_TESTCASES_OUT)/$(LOCAL_MODULE).jar
 
+cts_src_dirs := $(LOCAL_PATH)/src
+cts_src_dirs += $(sort $(dir $(LOCAL_GENERATED_SOURCES)))
+cts_src_dirs := $(addprefix -s , $(cts_src_dirs))
+
 $(cts_library_jar): PRIVATE_MODULE := $(LOCAL_MODULE)
 $(cts_library_jar): $(call intermediates-dir-for,JAVA_LIBRARIES,$(LOCAL_MODULE))/javalib.jar | $(ACP)
 	$(hide) mkdir -p $(CTS_TESTCASES_OUT)
 	$(hide) $(ACP) -fp $(call intermediates-dir-for,JAVA_LIBRARIES,$(PRIVATE_MODULE))/javalib.jar $@
 
-$(cts_library_xml): PRIVATE_PATH := $(LOCAL_PATH)/src
+$(cts_library_xml): PRIVATE_SRC_DIRS := $(cts_src_dirs)
 $(cts_library_xml): PRIVATE_TEST_APP_PACKAGE := $(LOCAL_CTS_TEST_APP_PACKAGE)
 $(cts_library_xml): PRIVATE_TEST_PACKAGE := $(LOCAL_CTS_TEST_PACKAGE)
 $(cts_library_xml): PRIVATE_TEST_APK := $(LOCAL_CTS_TEST_APK)
@@ -38,7 +42,7 @@
 $(cts_library_xml): $(call intermediates-dir-for,JAVA_LIBRARIES,$(LOCAL_MODULE))/javalib.jar $(CTS_EXPECTATIONS) $(CTS_UNSUPPORTED_ABIS) $(CTS_JAVA_TEST_SCANNER_DOCLET) $(CTS_JAVA_TEST_SCANNER) $(CTS_XML_GENERATOR)
 	$(hide) echo Generating test description for uiautomator library $(PRIVATE_LIBRARY)
 	$(hide) mkdir -p $(CTS_TESTCASES_OUT)
-	$(hide) $(CTS_JAVA_TEST_SCANNER) -s $(PRIVATE_PATH) \
+	$(hide) $(CTS_JAVA_TEST_SCANNER) $(PRIVATE_SRC_DIRS) \
 						-d $(CTS_JAVA_TEST_SCANNER_DOCLET) | \
 			$(CTS_XML_GENERATOR) -t uiAutomator \
 						-i $(PRIVATE_TEST_APK) \
@@ -51,3 +55,6 @@
 						-b $(CTS_UNSUPPORTED_ABIS) \
 						-a $(CTS_TARGET_ARCH) \
 						-o $@
+
+# Have the module name depend on the cts files; so the cts files get generated when you run mm/mmm/mma/mmma.
+$(my_register_name) : $(cts_library_jar) $(cts_library_xml)
diff --git a/common/util/Android.mk b/common/util/Android.mk
index b7842559..84ced65 100644
--- a/common/util/Android.mk
+++ b/common/util/Android.mk
@@ -42,6 +42,8 @@
 
 LOCAL_MODULE := compatibility-common-util-hostsidelib_v2
 
+LOCAL_STATIC_JAVA_LIBRARIES := kxml2-2.3.0
+
 include $(BUILD_HOST_JAVA_LIBRARY)
 
 ###############################################################################
@@ -52,7 +54,10 @@
 
 LOCAL_SRC_FILES := $(call all-java-files-under, tests/src)
 
-LOCAL_JAVA_LIBRARIES := junit
+LOCAL_STATIC_JAVA_LIBRARIES := \
+                        junit \
+                        kxml2-2.3.0 \
+                        compatibility-common-util-hostsidelib_v2
 
 LOCAL_MODULE := compatibility-common-util-tests_v2
 
diff --git a/common/util/run_unit_tests.sh b/common/util/run_unit_tests.sh
new file mode 100755
index 0000000..04a6745
--- /dev/null
+++ b/common/util/run_unit_tests.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+
+# 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.
+
+# helper script for running the cts common unit tests
+
+checkFile() {
+    if [ ! -f "$1" ]; then
+        echo "Unable to locate $1"
+        exit
+    fi;
+}
+
+# check if in Android build env
+if [ ! -z ${ANDROID_BUILD_TOP} ]; then
+    HOST=`uname`
+    if [ "$HOST" == "Linux" ]; then
+        OS="linux-x86"
+    elif [ "$HOST" == "Darwin" ]; then
+        OS="darwin-x86"
+    else
+        echo "Unrecognized OS"
+        exit
+    fi;
+fi;
+
+JAR_DIR=${ANDROID_BUILD_TOP}/out/host/$OS/framework
+JARS="tradefed-prebuilt.jar compatibility-common-util-hostsidelib_v2.jar compatibility-common-util-tests_v2.jar"
+
+for JAR in $JARS; do
+    checkFile ${JAR_DIR}/${JAR}
+    JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}
+done
+
+java $RDBG_FLAG \
+  -cp ${JAR_PATH} com.android.tradefed.command.Console run singleCommand host -n --class com.android.compatibility.common.util.UnitTests "$@"
+
diff --git a/common/util/src/com/android/compatibility/common/util/MetricsXmlSerializer.java b/common/util/src/com/android/compatibility/common/util/MetricsXmlSerializer.java
new file mode 100644
index 0000000..0e2b004
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/MetricsXmlSerializer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.compatibility.common.util;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Serialize Metric data from {@link ReportLog} into compatibility report friendly XML
+ */
+public final class MetricsXmlSerializer {
+
+    private final XmlSerializer mXmlSerializer;
+
+    public MetricsXmlSerializer(XmlSerializer xmlSerializer) {
+        this.mXmlSerializer = xmlSerializer;
+    }
+
+    public void serialize(ReportLog reportLog) throws IOException {
+        if (reportLog == null) {
+            return;
+        }
+        ReportLog.Result summary = reportLog.getSummary();
+        List<ReportLog.Result> detailedMetrics = reportLog.getDetailedMetrics();
+        // <Summary message="Average" scoreType="lower_better" unit="ms">195.2</Summary>
+        if (summary != null) {
+            mXmlSerializer.startTag(null, "Summary");
+            mXmlSerializer.attribute(null, "message", summary.getMessage());
+            mXmlSerializer.attribute(null, "scoreType", summary.getType().getXmlString());
+            mXmlSerializer.attribute(null, "unit", summary.getUnit().getXmlString());
+            mXmlSerializer.text(Double.toString(summary.getValues()[0]));
+            mXmlSerializer.endTag(null, "Summary");
+        }
+
+        if (!detailedMetrics.isEmpty()) {
+            mXmlSerializer.startTag(null, "Details");
+            for (ReportLog.Result result : detailedMetrics) {
+                mXmlSerializer.startTag(null, "ValueArray");
+                mXmlSerializer.attribute(null, "source", result.getLocation());
+                mXmlSerializer.attribute(null, "message", result.getMessage());
+                mXmlSerializer.attribute(null, "scoreType", result.getType().getXmlString());
+                mXmlSerializer.attribute(null, "unit", result.getUnit().getXmlString());
+
+                for (double value : result.getValues()) {
+                    mXmlSerializer.startTag(null, "Value");
+                    mXmlSerializer.text(Double.toString(value));
+                    mXmlSerializer.endTag(null, "Value");
+                }
+                mXmlSerializer.endTag(null, "ValueArray");
+            }
+            mXmlSerializer.endTag(null, "Details");
+        }
+    }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/ReportLog.java b/common/util/src/com/android/compatibility/common/util/ReportLog.java
index 9e733e4..8cfc086 100644
--- a/common/util/src/com/android/compatibility/common/util/ReportLog.java
+++ b/common/util/src/com/android/compatibility/common/util/ReportLog.java
@@ -16,6 +16,9 @@
 
 package com.android.compatibility.common.util;
 
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.IOException;
 import java.io.Serializable;
 import java.util.ArrayList;
 import java.util.List;
@@ -28,8 +31,8 @@
     private Result mSummary;
     private final List<Result> mDetails = new ArrayList<Result>();
 
-    private class Result implements Serializable {
-        private static final int BASE_DEPTH = 2;// 0:constructor, 1:addValues/setSummary, 2:caller
+    class Result implements Serializable {
+        private static final int CALLER_STACKTRACE_DEPTH = 5;
         private String mLocation;
         private String mMessage;
         private double[] mValues;
@@ -53,15 +56,35 @@
         private Result(String message, double[] values, ResultType type,
                 ResultUnit unit, int depth) {
             final StackTraceElement[] trace = Thread.currentThread().getStackTrace();
-            final StackTraceElement e = trace[Math.min(BASE_DEPTH + depth, trace.length - 1)];
-            mLocation = String.format("%s#%s:%d",
-                    e.getClassName(), e.getMethodName(), e.getLineNumber());
+            final StackTraceElement e =
+                    trace[Math.min(CALLER_STACKTRACE_DEPTH + depth, trace.length - 1)];
+            mLocation = String.format(
+                    "%s#%s:%d", e.getClassName(), e.getMethodName(), e.getLineNumber());
             mMessage = message;
             mValues = values;
             mType = type;
             mUnit = unit;
         }
 
+        public String getLocation() {
+            return mLocation;
+        }
+
+        public String getMessage() {
+            return mMessage;
+        }
+
+        public double[] getValues() {
+            return mValues;
+        }
+
+        public ResultType getType() {
+            return mType;
+        }
+
+        public ResultUnit getUnit() {
+            return mUnit;
+        }
     }
 
     /**
@@ -108,4 +131,12 @@
             ResultUnit unit, int depth) {
         mSummary = new Result(message, new double[] {value}, type, unit, depth);
     }
+
+    public Result getSummary() {
+        return mSummary;
+    }
+
+    public List<Result> getDetailedMetrics() {
+        return new ArrayList<Result>(mDetails);
+    }
 }
diff --git a/common/util/tests/src/com/android/compatibility/common/util/MetricsXmlSerializerTest.java b/common/util/tests/src/com/android/compatibility/common/util/MetricsXmlSerializerTest.java
new file mode 100644
index 0000000..70da820
--- /dev/null
+++ b/common/util/tests/src/com/android/compatibility/common/util/MetricsXmlSerializerTest.java
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.compatibility.common.util;
+
+import junit.framework.TestCase;
+
+import org.xmlpull.v1.XmlPullParserFactory;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+/**
+ * Unit tests for {@link MetricsXmlSerializer}
+ */
+public class MetricsXmlSerializerTest extends TestCase {
+
+    static class LocalReportLog extends ReportLog {}
+    private static final double[] VALUES = new double[] {1, 11, 21, 1211, 111221};
+    private static final String HEADER = "<?xml version='1.0' encoding='utf-8' standalone='yes' ?>";
+    private static final String EXPECTED_XML =
+            HEADER
+            + "<Summary message=\"Sample\" scoreType=\"higher_better\" unit=\"byte\">1.0</Summary>"
+            + "<Details>"
+                    + "<ValueArray source=\"sun.reflect.NativeMethodAccessorImpl#invoke0:-2\""
+                    + " message=\"Details\" scoreType=\"neutral\" unit=\"fps\">"
+                        + "<Value>1.0</Value>"
+                        + "<Value>11.0</Value>"
+                        + "<Value>21.0</Value>"
+                        + "<Value>1211.0</Value>"
+                        + "<Value>111221.0</Value>"
+                    + "</ValueArray>"
+            + "</Details>";
+
+    private LocalReportLog mLocalReportLog;
+    private MetricsXmlSerializer mMetricsXmlSerializer;
+    private ByteArrayOutputStream mByteArrayOutputStream;
+    private XmlSerializer xmlSerializer;
+
+    @Override
+    public void setUp() throws Exception {
+        mLocalReportLog = new LocalReportLog();
+        mByteArrayOutputStream = new ByteArrayOutputStream();
+        XmlPullParserFactory factory = XmlPullParserFactory.newInstance(null, null);
+        xmlSerializer = factory.newSerializer();
+        xmlSerializer.setOutput(mByteArrayOutputStream, "utf-8");
+
+        this.mMetricsXmlSerializer = new MetricsXmlSerializer(xmlSerializer);
+    }
+
+    public void testSerialize_null() throws IOException {
+        xmlSerializer.startDocument("utf-8", true);
+        mMetricsXmlSerializer.serialize(null);
+        xmlSerializer.endDocument();
+
+        assertEquals(HEADER.length(), mByteArrayOutputStream.toByteArray().length);
+    }
+
+    public void testSerialize_noData() throws IOException {
+        xmlSerializer.startDocument("utf-8", true);
+        mMetricsXmlSerializer.serialize(mLocalReportLog);
+        xmlSerializer.endDocument();
+
+        assertEquals(HEADER.length(), mByteArrayOutputStream.toByteArray().length);
+    }
+
+    public void testSerialize() throws IOException {
+        mLocalReportLog.setSummary("Sample", 1.0, ResultType.HIGHER_BETTER, ResultUnit.BYTE);
+        mLocalReportLog.addValues("Details", VALUES, ResultType.NEUTRAL, ResultUnit.FPS);
+
+        xmlSerializer.startDocument("utf-8", true);
+        mMetricsXmlSerializer.serialize(mLocalReportLog);
+        xmlSerializer.endDocument();
+
+        assertEquals(EXPECTED_XML, mByteArrayOutputStream.toString("utf-8"));
+    }
+}
diff --git a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java b/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
similarity index 70%
copy from common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
copy to common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
index a376373..b9a17e1 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
+++ b/common/util/tests/src/com/android/compatibility/common/util/UnitTests.java
@@ -11,15 +11,21 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
- * limitations under the License.
+ * limitations under the License
  */
 
 package com.android.compatibility.common.util;
 
-import junit.framework.TestCase;
+import junit.framework.TestSuite;
 
-public class CommonUtilTest extends TestCase {
+/**
+ * A {@link TestSuite} for the common.util package.
+ */
+public class UnitTests extends TestSuite {
 
-    // TODO(stuartscott): Add tests when there is something to test.
+    public UnitTests() {
+        super();
 
+        addTestSuite(MetricsXmlSerializerTest.class);
+    }
 }
diff --git a/hostsidetests/aadb/src/com/android/cts/aadb/TestDeviceFuncTest.java b/hostsidetests/aadb/src/com/android/cts/aadb/TestDeviceFuncTest.java
index ceb9072..9f69242 100644
--- a/hostsidetests/aadb/src/com/android/cts/aadb/TestDeviceFuncTest.java
+++ b/hostsidetests/aadb/src/com/android/cts/aadb/TestDeviceFuncTest.java
@@ -38,6 +38,7 @@
 import java.io.FileOutputStream;
 import java.io.InputStream;
 import java.io.IOException;
+import java.util.TimeZone;
 
 import javax.imageio.ImageIO;
 
@@ -289,6 +290,13 @@
             stream.write(testString.getBytes());
             stream.close();
 
+            // adjust 1st file's last-modified timestamp according to persist.sys.timezone
+            String deviceTimezone = mTestDevice.getProperty("persist.sys.timezone");
+            if (deviceTimezone != null) {
+                TimeZone tz = TimeZone.getTimeZone(deviceTimezone);
+                tmpFile.setLastModified(tmpFile.lastModified() + tz.getRawOffset());
+            }
+
             assertTrue(mTestDevice.syncFiles(tmpDir, externalStorePath));
             String tmpFileContents = mTestDevice.executeShellCommand(String.format("cat %s",
                     expectedDeviceFilePath));
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
index 9dc51c9..4d9ef00 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
@@ -181,7 +181,7 @@
                     true /* reinstall */, options);
             assertNotNull("app upgrade with different cert than existing app installed " +
                     "successfully", installResult);
-            assertEquals("INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES", installResult);
+            assertEquals("INSTALL_FAILED_UPDATE_INCOMPATIBLE", installResult);
         }
         finally {
             getDevice().uninstallPackage(SIMPLE_APP_PKG);
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java
index 264c0b1..90cbed9 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java
@@ -57,7 +57,13 @@
     private static final String APK_mips64 = "CtsSplitApp_mips64.apk";
     private static final String APK_mips = "CtsSplitApp_mips.apk";
 
+    private static final String APK_DIFF_REVISION = "CtsSplitAppDiffRevision.apk";
+    private static final String APK_DIFF_REVISION_v7 = "CtsSplitAppDiffRevision_v7.apk";
+
+    private static final String APK_DIFF_VERSION = "CtsSplitAppDiffVersion.apk";
     private static final String APK_DIFF_VERSION_v7 = "CtsSplitAppDiffVersion_v7.apk";
+
+    private static final String APK_DIFF_CERT = "CtsSplitAppDiffCert.apk";
     private static final String APK_DIFF_CERT_v7 = "CtsSplitAppDiffCert_v7.apk";
 
     private static final String APK_FEATURE = "CtsSplitAppFeature.apk";
@@ -218,8 +224,6 @@
 
     public void testDiffCertInherit() throws Exception {
         new InstallMultiple().addApk(APK).run();
-        // TODO: remove this once we fix 17900178
-        runDeviceTests(PKG, ".SplitAppTest", "testSingleBase");
         new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_CERT_v7).runExpectingFailure();
     }
 
@@ -229,11 +233,33 @@
 
     public void testDiffVersionInherit() throws Exception {
         new InstallMultiple().addApk(APK).run();
-        // TODO: remove this once we fix 17900178
-        runDeviceTests(PKG, ".SplitAppTest", "testSingleBase");
         new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_VERSION_v7).runExpectingFailure();
     }
 
+    public void testDiffRevision() throws Exception {
+        new InstallMultiple().addApk(APK).addApk(APK_DIFF_REVISION_v7).run();
+        runDeviceTests(PKG, ".SplitAppTest", "testRevision0_12");
+    }
+
+    public void testDiffRevisionInheritBase() throws Exception {
+        new InstallMultiple().addApk(APK).addApk(APK_v7).run();
+        runDeviceTests(PKG, ".SplitAppTest", "testRevision0_0");
+        new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_REVISION_v7).run();
+        runDeviceTests(PKG, ".SplitAppTest", "testRevision0_12");
+    }
+
+    public void testDiffRevisionInheritSplit() throws Exception {
+        new InstallMultiple().addApk(APK).addApk(APK_v7).run();
+        runDeviceTests(PKG, ".SplitAppTest", "testRevision0_0");
+        new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_REVISION).run();
+        runDeviceTests(PKG, ".SplitAppTest", "testRevision12_0");
+    }
+
+    public void testDiffRevisionDowngrade() throws Exception {
+        new InstallMultiple().addApk(APK).addApk(APK_DIFF_REVISION_v7).run();
+        new InstallMultiple().inheritFrom(PKG).addApk(APK_v7).runExpectingFailure();
+    }
+
     public void testFeatureBase() throws Exception {
         new InstallMultiple().addApk(APK).addApk(APK_FEATURE).run();
         runDeviceTests(PKG, ".SplitAppTest", "testFeatureBase");
@@ -252,6 +278,16 @@
         // TODO: flesh out this test
     }
 
+    /**
+     * Verify that installing a new version of app wipes code cache.
+     */
+    public void testClearCodeCache() throws Exception {
+        new InstallMultiple().addApk(APK).run();
+        runDeviceTests(PKG, ".SplitAppTest", "testCodeCacheWrite");
+        new InstallMultiple().addArg("-r").addApk(APK_DIFF_VERSION).run();
+        runDeviceTests(PKG, ".SplitAppTest", "testCodeCacheRead");
+    }
+
     class InstallMultiple {
         private List<String> mArgs = new ArrayList<>();
         private List<File> mApks = new ArrayList<>();
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
index fe8f9ee..f308211 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
@@ -27,10 +27,13 @@
 import android.provider.DocumentsProvider;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
 import android.support.test.uiautomator.UiSelector;
 import android.test.InstrumentationTestCase;
 import android.test.MoreAsserts;
 import android.text.format.DateUtils;
+import android.util.Log;
 
 import com.android.cts.documentclient.MyActivity.Result;
 
@@ -45,10 +48,12 @@
  * like {@link Intent#ACTION_OPEN_DOCUMENT}.
  */
 public class DocumentsClientTest extends InstrumentationTestCase {
+    private static final String TAG = "DocumentsClientTest";
+
     private UiDevice mDevice;
     private MyActivity mActivity;
 
-    private static final long TIMEOUT = 10 * DateUtils.SECOND_IN_MILLIS;
+    private static final long TIMEOUT = 30 * DateUtils.SECOND_IN_MILLIS;
 
     @Override
     public void setUp() throws Exception {
@@ -66,6 +71,48 @@
         mActivity.finish();
     }
 
+    private UiObject findRoot(String label) throws UiObjectNotFoundException {
+        final UiSelector rootsList = new UiSelector().resourceId(
+                "com.android.documentsui:id/container_roots").childSelector(
+                new UiSelector().resourceId("android:id/list"));
+
+        // We might need to expand drawer if not visible
+        if (!new UiObject(rootsList).waitForExists(TIMEOUT)) {
+            Log.d(TAG, "Failed to find roots list; trying to expand");
+            final UiSelector hamburger = new UiSelector().resourceId(
+                    "com.android.documentsui:id/toolbar").childSelector(
+                    new UiSelector().className("android.widget.ImageButton").clickable(true));
+            new UiObject(hamburger).click();
+        }
+
+        // Wait for the first list item to appear
+        assertTrue("First list item",
+                new UiObject(rootsList.childSelector(new UiSelector())).waitForExists(TIMEOUT));
+
+        // Now scroll around to find our item
+        new UiScrollable(rootsList).scrollIntoView(new UiSelector().text(label));
+        return new UiObject(rootsList.childSelector(new UiSelector().text(label)));
+    }
+
+    private UiObject findDocument(String label) throws UiObjectNotFoundException {
+        final UiSelector docList = new UiSelector().resourceId(
+                "com.android.documentsui:id/container_directory").childSelector(
+                new UiSelector().resourceId("com.android.documentsui:id/list"));
+
+        // Wait for the first list item to appear
+        assertTrue("First list item",
+                new UiObject(docList.childSelector(new UiSelector())).waitForExists(TIMEOUT));
+
+        // Now scroll around to find our item
+        new UiScrollable(docList).scrollIntoView(new UiSelector().text(label));
+        return new UiObject(docList.childSelector(new UiSelector().text(label)));
+    }
+
+    private UiObject findSaveButton() throws UiObjectNotFoundException {
+        return new UiObject(new UiSelector().resourceId("com.android.documentsui:id/container_save")
+                .childSelector(new UiSelector().resourceId("android:id/button1")));
+    }
+
     public void testOpenSimple() throws Exception {
         if (!supportedHardware()) return;
 
@@ -83,16 +130,16 @@
 
         // Ensure that we see both of our roots
         mDevice.waitForIdle();
-        assertTrue("CtsLocal root", new UiObject(new UiSelector().text("CtsLocal")).waitForExists(TIMEOUT));
-        assertTrue("CtsCreate root", new UiObject(new UiSelector().text("CtsCreate")).exists());
-        assertFalse("CtsGetContent", new UiObject(new UiSelector().text("CtsGetContent")).exists());
+        assertTrue("CtsLocal root", findRoot("CtsLocal").exists());
+        assertTrue("CtsCreate root", findRoot("CtsCreate").exists());
+        assertFalse("CtsGetContent root", findRoot("CtsGetContent").exists());
 
         // Pick a specific file from our test provider
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("CtsLocal")).click();
+        findRoot("CtsLocal").click();
 
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("FILE1")).click();
+        findDocument("FILE1").click();
 
         final Result result = mActivity.getResult();
         final Uri uri = result.data.getData();
@@ -118,10 +165,10 @@
         mActivity.startActivityForResult(intent, 42);
 
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("CtsCreate")).click();
+        findRoot("CtsCreate").click();
+
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().resourceId("com.android.documentsui:id/container_save")
-                .childSelector(new UiSelector().resourceId("android:id/button1"))).click();
+        findSaveButton().click();
 
         final Result result = mActivity.getResult();
         final Uri uri = result.data.getData();
@@ -142,18 +189,17 @@
         mActivity.startActivityForResult(intent, 42);
 
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("CtsCreate")).click();
+        findRoot("CtsCreate").click();
 
         // Pick file2, which should be selected since MIME matches, then try
         // picking a non-matching MIME, which should leave file2 selected.
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("FILE2")).click();
+        findDocument("FILE2").click();
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("FILE1")).click();
+        findDocument("FILE1").click();
 
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().resourceId("com.android.documentsui:id/container_save")
-                .childSelector(new UiSelector().resourceId("android:id/button1"))).click();
+        findSaveButton().click();
 
         final Result result = mActivity.getResult();
         final Uri uri = result.data.getData();
@@ -168,12 +214,12 @@
         mActivity.startActivityForResult(intent, 42);
 
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("CtsCreate")).click();
+        findRoot("CtsCreate").click();
+
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("DIR2")).click();
+        findDocument("DIR2").click();
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().resourceId("com.android.documentsui:id/container_save")
-                .childSelector(new UiSelector().resourceId("android:id/button1"))).click();
+        findSaveButton().click();
 
         final Result result = mActivity.getResult();
         final Uri uri = result.data.getData();
@@ -244,12 +290,12 @@
         // Look around, we should be able to see both DocumentsProviders and
         // other GET_CONTENT sources.
         mDevice.waitForIdle();
-        assertTrue("CtsLocal root", new UiObject(new UiSelector().text("CtsLocal")).waitForExists(TIMEOUT));
-        assertTrue("CtsCreate root", new UiObject(new UiSelector().text("CtsCreate")).exists());
-        assertTrue("CtsGetContent", new UiObject(new UiSelector().text("CtsGetContent")).exists());
+        assertTrue("CtsLocal root", findRoot("CtsLocal").exists());
+        assertTrue("CtsCreate root", findRoot("CtsCreate").exists());
+        assertTrue("CtsGetContent root", findRoot("CtsGetContent").exists());
 
         mDevice.waitForIdle();
-        new UiObject(new UiSelector().text("CtsGetContent")).click();
+        findRoot("CtsGetContent").click();
 
         final Result result = mActivity.getResult();
         assertEquals("ReSuLt", result.data.getAction());
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
index 8b25f4b..bf89576 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/Android.mk
@@ -30,7 +30,31 @@
 LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
 
 LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version -c mdpi -c hdpi -c xhdpi -c xxhdpi
+LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
+
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
+
+
+#################################################
+# Define a variant with a different revision code
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsSplitAppDiffRevision
+LOCAL_PACKAGE_SPLITS := v7
+
+LOCAL_MANIFEST_FILE := revision/AndroidManifest.xml
+LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
+LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundredRevisionTwelve --replace-version
 
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_DEX_PREOPT := false
@@ -53,7 +77,7 @@
 LOCAL_PACKAGE_SPLITS := v7
 
 LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 101 --version-name OneHundredOne --replace-version -c mdpi -c hdpi -c xhdpi -c xxhdpi
+LOCAL_AAPT_FLAGS := --version-code 101 --version-name OneHundredOne --replace-version
 
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_DEX_PREOPT := false
@@ -76,7 +100,7 @@
 LOCAL_PACKAGE_SPLITS := v7
 
 LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey2
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version -c mdpi -c hdpi -c xhdpi -c xxhdpi
+LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
 
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_DEX_PREOPT := false
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk b/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk
index e93f6c3..809a6b8 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/feature/Android.mk
@@ -24,7 +24,7 @@
 LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets
 
 LOCAL_CERTIFICATE := cts/hostsidetests/appsecurity/certs/cts-testkey1
-LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version -c mdpi -c hdpi -c xhdpi -c xxhdpi
+LOCAL_AAPT_FLAGS := --version-code 100 --version-name OneHundred --replace-version
 
 LOCAL_MODULE_TAGS := tests
 
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/revision/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/SplitApp/revision/AndroidManifest.xml
new file mode 100644
index 0000000..8e053ba
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/revision/AndroidManifest.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.splitapp"
+        android:revisionCode="12">
+
+    <uses-permission android:name="android.permission.CAMERA" />
+
+    <application android:label="SplitApp">
+        <activity android:name=".MyActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <meta-data android:name="android.service.wallpaper" android:resource="@xml/my_activity_meta" />
+        </activity>
+        <receiver android:name=".MyReceiver"
+                android:enabled="@bool/my_receiver_enabled">
+            <intent-filter>
+                <action android:name="android.intent.action.DATE_CHANGED" />
+            </intent-filter>
+        </receiver>
+
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.splitapp" />
+
+</manifest>
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
index 277a1a2..3d6cee7 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
@@ -21,6 +21,7 @@
 
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
@@ -30,6 +31,7 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
 import android.util.DisplayMetrics;
 import android.util.Log;
 
@@ -37,6 +39,7 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.BufferedReader;
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.lang.reflect.Field;
@@ -211,9 +214,8 @@
         assertEquals("base", getXmlTestValue(r.getXml(R.xml.my_activity_meta)));
 
         // And that we can access resources from feature
-        // TODO: enable these once 17924027 is fixed
-//        assertEquals("red", r.getString(r.getIdentifier("feature_string", "string", PKG)));
-//        assertEquals(123, r.getInteger(r.getIdentifier("feature_integer", "integer", PKG)));
+        assertEquals("red", r.getString(r.getIdentifier("feature_string", "string", PKG)));
+        assertEquals(123, r.getInteger(r.getIdentifier("feature_integer", "integer", PKG)));
 
         final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR");
         final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null);
@@ -292,8 +294,7 @@
         assertEquals(false, r.getBoolean(R.bool.my_receiver_enabled));
 
         // And that we can access resources from feature
-        // TODO: enable these once 17924027 is fixed
-//        assertEquals(321, r.getInteger(r.getIdentifier("feature_integer", "integer", PKG)));
+        assertEquals(321, r.getInteger(r.getIdentifier("feature_integer", "integer", PKG)));
 
         final Class<?> featR = Class.forName("com.android.cts.splitapp.FeatureR");
         final int boolId = (int) featR.getDeclaredField("feature_receiver_enabled").get(null);
@@ -310,6 +311,40 @@
         assertEquals(0, result.size());
     }
 
+    public void testCodeCacheWrite() throws Exception {
+        assertTrue(new File(getContext().getFilesDir(), "normal.raw").createNewFile());
+        assertTrue(new File(getContext().getCodeCacheDir(), "cache.raw").createNewFile());
+    }
+
+    public void testCodeCacheRead() throws Exception {
+        assertTrue(new File(getContext().getFilesDir(), "normal.raw").exists());
+        assertFalse(new File(getContext().getCodeCacheDir(), "cache.raw").exists());
+    }
+
+    public void testRevision0_0() throws Exception {
+        final PackageInfo info = getContext().getPackageManager()
+                .getPackageInfo(getContext().getPackageName(), 0);
+        assertEquals(0, info.baseRevisionCode);
+        assertEquals(1, info.splitRevisionCodes.length);
+        assertEquals(0, info.splitRevisionCodes[0]);
+    }
+
+    public void testRevision12_0() throws Exception {
+        final PackageInfo info = getContext().getPackageManager()
+                .getPackageInfo(getContext().getPackageName(), 0);
+        assertEquals(12, info.baseRevisionCode);
+        assertEquals(1, info.splitRevisionCodes.length);
+        assertEquals(0, info.splitRevisionCodes[0]);
+    }
+
+    public void testRevision0_12() throws Exception {
+        final PackageInfo info = getContext().getPackageManager()
+                .getPackageInfo(getContext().getPackageName(), 0);
+        assertEquals(0, info.baseRevisionCode);
+        assertEquals(1, info.splitRevisionCodes.length);
+        assertEquals(12, info.splitRevisionCodes[0]);
+    }
+
     private static void updateDpi(Resources r, int densityDpi) {
         final Configuration c = new Configuration(r.getConfiguration());
         c.densityDpi = densityDpi;
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
index 5378266..cf16307 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
@@ -429,6 +429,7 @@
         boolean mHaveResult = false;
         boolean mGoodResult = false;
         boolean mSucceeded = false;
+        static final int TIMEOUT_MS = 30000;
         
         @Override
         public void onReceive(Context context, Intent intent) {
@@ -456,10 +457,10 @@
                 final long startTime = SystemClock.uptimeMillis();
                 while (!mHaveResult) {
                     try {
-                        wait(5000);
+                        wait(TIMEOUT_MS);
                     } catch (InterruptedException e) {
                     }
-                    if (SystemClock.uptimeMillis() >= (startTime+5000)) {
+                    if (SystemClock.uptimeMillis() >= (startTime + TIMEOUT_MS)) {
                         throw new RuntimeException("Timeout");
                     }
                 }
@@ -477,10 +478,10 @@
                 final long startTime = SystemClock.uptimeMillis();
                 while (!mHaveResult) {
                     try {
-                        wait(5000);
+                        wait(TIMEOUT_MS);
                     } catch (InterruptedException e) {
                     }
-                    if (SystemClock.uptimeMillis() >= (startTime+5000)) {
+                    if (SystemClock.uptimeMillis() >= (startTime + TIMEOUT_MS)) {
                         throw new RuntimeException("Timeout");
                     }
                 }
diff --git a/hostsidetests/appsecurity/test-apps/keysets/permDef/Android.mk b/hostsidetests/appsecurity/test-apps/keysets/permDef/Android.mk
index eb71540..715905a 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/permDef/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/keysets/permDef/Android.mk
@@ -18,7 +18,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 LOCAL_PACKAGE_NAME := CtsKeySetPermDefSigningA
@@ -31,7 +30,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 LOCAL_PACKAGE_NAME := CtsKeySetPermDefSigningB
diff --git a/hostsidetests/appsecurity/test-apps/keysets/permUse/Android.mk b/hostsidetests/appsecurity/test-apps/keysets/permUse/Android.mk
index 000b12a..eceea38 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/permUse/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/keysets/permUse/Android.mk
@@ -18,7 +18,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 LOCAL_PACKAGE_NAME := CtsKeySetPermUseSigningA
@@ -31,7 +30,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 LOCAL_PACKAGE_NAME := CtsKeySetPermUseSigningB
diff --git a/hostsidetests/appsecurity/test-apps/keysets/uA/Android.mk b/hostsidetests/appsecurity/test-apps/keysets/uA/Android.mk
index 6220790..efba345 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/uA/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/keysets/uA/Android.mk
@@ -18,7 +18,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 LOCAL_PACKAGE_NAME := CtsKeySetSigningAUpgradeA
@@ -30,7 +29,6 @@
 #apks signed by cts-keyset-test-b
 include $(CLEAR_VARS)
 LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 LOCAL_PACKAGE_NAME := CtsKeySetSigningBUpgradeA
@@ -43,7 +41,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 LOCAL_PACKAGE_NAME := CtsKeySetSigningAAndBUpgradeA
diff --git a/hostsidetests/appsecurity/test-apps/keysets/uAB/Android.mk b/hostsidetests/appsecurity/test-apps/keysets/uAB/Android.mk
index 534dba3..219689e 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/uAB/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/keysets/uAB/Android.mk
@@ -18,7 +18,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 LOCAL_PACKAGE_NAME := CtsKeySetSigningAUpgradeAAndB
diff --git a/hostsidetests/appsecurity/test-apps/keysets/uAuB/Android.mk b/hostsidetests/appsecurity/test-apps/keysets/uAuB/Android.mk
index 75729e0..040c378 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/uAuB/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/keysets/uAuB/Android.mk
@@ -18,7 +18,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 LOCAL_PACKAGE_NAME := CtsKeySetSigningAUpgradeAOrB
diff --git a/hostsidetests/appsecurity/test-apps/keysets/uB/Android.mk b/hostsidetests/appsecurity/test-apps/keysets/uB/Android.mk
index 121c342..62b5461 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/uB/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/keysets/uB/Android.mk
@@ -18,7 +18,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 LOCAL_PACKAGE_NAME := CtsKeySetSigningAUpgradeB
@@ -31,7 +30,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 LOCAL_PACKAGE_NAME := CtsKeySetSigningBUpgradeB
@@ -44,7 +42,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 LOCAL_PACKAGE_NAME := CtsKeySetSigningAAndCUpgradeB
diff --git a/hostsidetests/appsecurity/test-apps/keysets/uNone/Android.mk b/hostsidetests/appsecurity/test-apps/keysets/uNone/Android.mk
index a8746ec..5fc4ab9 100644
--- a/hostsidetests/appsecurity/test-apps/keysets/uNone/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/keysets/uNone/Android.mk
@@ -18,7 +18,6 @@
 include $(CLEAR_VARS)
 
 LOCAL_MODULE_TAGS := tests
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
 LOCAL_SDK_VERSION := current
 LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
 LOCAL_PACKAGE_NAME := CtsKeySetSigningAUpgradeNone
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
index 7a196bf..dfc7e9c 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -49,6 +49,14 @@
 
         <activity
             android:name="com.android.cts.deviceowner.LockTaskUtilityActivity" />
+
+        <!-- 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="com.android.cts.deviceowner.ApplicationRestrictionActivity" />
     </application>
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 42aa847..69c5bf7 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,12 +15,14 @@
  */
 package com.android.cts.deviceowner;
 
+import android.app.Activity;
 import android.app.ActivityManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.provider.Settings;
+import android.os.Bundle;
 
 // This is not a standard test of an android activity (such as
 // ActivityInstrumentationTestCase2) as it is attempting to test the actual
@@ -30,9 +32,12 @@
 
     private static final String TEST_PACKAGE = "com.google.android.example.somepackage";
 
-    private static final int ACTIVITY_RESUMED_TIMEOUT_MILLIS = 60000;  // 60 seconds
-    private static final int ACTIVITY_RUNNING_TIMEOUT_MILLIS = 20000;  // 20 seconds
+    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.RECEIVER_ACTIVITY_STARTED_ACTION";
     /**
      * 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
@@ -71,14 +76,30 @@
                     mIntentHandled = true;
                     LockTaskTest.this.notify();
                 }
+            } else if (RECEIVING_ACTIVITY_CREATED_ACTION.equals(action)) {
+                synchronized(mReceivingActivityCreatedLock) {
+                    mReceivingActivityWasCreated = true;
+                    mReceivingActivityCreatedLock.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 boolean mIsActivityRunning;
     private boolean mIsActivityResumed;
+    private boolean mReceivingActivityWasCreated;
     private final Object mActivityRunningLock = new Object();
     private final Object mActivityResumedLock = new Object();
+    private final Object mReceivingActivityCreatedLock = new Object();
     private Boolean mIntentHandled;
 
     @Override
@@ -90,6 +111,7 @@
         filter.addAction(LockTaskUtilityActivity.INTENT_ACTION);
         filter.addAction(LockTaskUtilityActivity.RESUME_ACTION);
         filter.addAction(LockTaskUtilityActivity.PAUSE_ACTION);
+        filter.addAction(RECEIVING_ACTIVITY_CREATED_ACTION);
         mContext.registerReceiver(mReceiver, filter);
     }
 
@@ -139,49 +161,46 @@
         stopAndFinish(activityManager);
     }
 
-    // This test has the UtilityActivity trigger starting another activity (settings)
+    // This launches an activity that is in the current task.
     // this should be permitted as a part of lock task (since it isn't a new task).
-    // As a result onPause should be called as it goes to a new activity.
     public void testStartActivityWithinTask() {
         mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
         startLockTask();
         waitForResume();
 
-        Intent launchIntent = new Intent(Settings.ACTION_SETTINGS);
+        mReceivingActivityWasCreated = false;
+        Intent launchIntent = new Intent(mContext, IntentReceivingActivity.class);
         Intent lockTaskUtility = getLockTaskUtility();
         lockTaskUtility.putExtra(LockTaskUtilityActivity.START_ACTIVITY, launchIntent);
         mContext.startActivity(lockTaskUtility);
 
-        synchronized (mActivityResumedLock) {
-            if (mIsActivityResumed) {
-                try {
-                    mActivityResumedLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
-                } catch (InterruptedException e) {
-                }
-                assertFalse(mIsActivityResumed);
+        synchronized (mReceivingActivityCreatedLock) {
+            try {
+                mReceivingActivityCreatedLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
+            } catch (InterruptedException e) {
             }
+            assertTrue(mReceivingActivityWasCreated);
         }
         stopAndFinish(null);
     }
 
     // This launches an activity that is not part of the current task and therefore
-    // should be blocked.  This is verified by making sure that the activity does
-    // not get a call to onPause.
+    // should be blocked.
     public void testCannotStartActivityOutsideTask() {
         mDevicePolicyManager.setLockTaskPackages(getWho(), new String[] { PACKAGE_NAME });
         startLockTask();
         waitForResume();
 
-        Intent launchIntent = new Intent(Settings.ACTION_SETTINGS);
+        mReceivingActivityWasCreated = false;
+        Intent launchIntent = new Intent(mContext, IntentReceivingActivity.class);
         launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         mContext.startActivity(launchIntent);
-
-        synchronized (mActivityResumedLock) {
+        synchronized (mReceivingActivityCreatedLock) {
             try {
-                mActivityResumedLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
+                mReceivingActivityCreatedLock.wait(ACTIVITY_RESUMED_TIMEOUT_MILLIS);
             } catch (InterruptedException e) {
             }
-            assertTrue(mIsActivityResumed);
+            assertFalse(mReceivingActivityWasCreated);
         }
         stopAndFinish(null);
     }
@@ -212,7 +231,7 @@
             finish();
             if (mIsActivityRunning) {
                 try {
-                    mActivityRunningLock.wait(ACTIVITY_RUNNING_TIMEOUT_MILLIS);
+                    mActivityRunningLock.wait(ACTIVITY_DESTROYED_TIMEOUT_MILLIS);
                 } catch (InterruptedException e) {
                 }
             }
@@ -220,7 +239,7 @@
     }
 
     /**
-     * Wait for onPause to be called on the LockTaskUtilityActivity.
+     * Wait for onResume to be called on the LockTaskUtilityActivity.
      */
     private void waitForResume() {
         // It may take a moment for the resume to come in.
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
index e1f6886..5bbdf76 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
@@ -24,9 +24,10 @@
     <application>
         <activity android:name="com.android.cts.intent.receiver.IntentReceiverActivity">
             <intent-filter>
+                <action android:name="com.android.cts.action.COPY_TO_CLIPBOARD" />
                 <action android:name="com.android.cts.action.READ_FROM_URI" />
-                <action android:name="com.android.cts.action.WRITE_TO_URI" />
                 <action android:name="com.android.cts.action.TAKE_PERSISTABLE_URI_PERMISSION" />
+                <action android:name="com.android.cts.action.WRITE_TO_URI" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
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 59f0752..294c678 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
@@ -16,6 +16,8 @@
 package com.android.cts.intent.receiver;
 
 import android.app.Activity;
+import android.content.ClipboardManager;
+import android.content.ClipData;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
@@ -35,21 +37,36 @@
 
     private static final String TAG = "IntentReceiverActivity";
 
-    private static final String ACTION_READ_FROM_URI = "com.android.cts.action.READ_FROM_URI";
+    private static final String ACTION_COPY_TO_CLIPBOARD =
+            "com.android.cts.action.COPY_TO_CLIPBOARD";
 
-    private static final String ACTION_WRITE_TO_URI = "com.android.cts.action.WRITE_TO_URI";
+    private static final String ACTION_READ_FROM_URI =
+            "com.android.cts.action.READ_FROM_URI";
 
     private static final String ACTION_TAKE_PERSISTABLE_URI_PERMISSION =
             "com.android.cts.action.TAKE_PERSISTABLE_URI_PERMISSION";
 
+    private static final String ACTION_WRITE_TO_URI =
+            "com.android.cts.action.WRITE_TO_URI";
+
+
     private static final String EXTRA_CAUGHT_SECURITY_EXCEPTION = "extra_caught_security_exception";
 
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        Intent received = getIntent();
-        String action = received.getAction();
-        Uri uri = getIntent().getClipData().getItemAt(0).getUri();
-        if (ACTION_READ_FROM_URI.equals(action)) {
+        final Intent received = getIntent();
+        final String action = received.getAction();
+        final ClipData clipData = getIntent().getClipData();
+        final Uri uri = clipData != null ? clipData.getItemAt(0).getUri() : null;
+        if (ACTION_COPY_TO_CLIPBOARD.equals(action)) {
+            String text = received.getStringExtra("extra_text");
+            Log.i(TAG, "Copying \"" + text + "\" to the clipboard");
+            ClipData clip = ClipData.newPlainText("", text);
+            ClipboardManager clipboard =
+                    (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+            clipboard.setPrimaryClip(clip);
+            setResult(Activity.RESULT_OK);
+        } else if (ACTION_READ_FROM_URI.equals(action)) {
             Intent result = new Intent();
             String message = null;
             try {
@@ -63,6 +80,11 @@
             Log.i(TAG, "Message received in reading test: " + message);
             result.putExtra("extra_response", message);
             setResult(Activity.RESULT_OK, result);
+        } else if (ACTION_TAKE_PERSISTABLE_URI_PERMISSION.equals(action)) {
+            Log.i(TAG, "Taking persistable uri permission to " + uri);
+            getContentResolver().takePersistableUriPermission(uri,
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            setResult(Activity.RESULT_OK);
         } else if (ACTION_WRITE_TO_URI.equals(action)) {
             Intent result = new Intent();
             String message = received.getStringExtra("extra_message");
@@ -76,11 +98,6 @@
                 Log.i(TAG, "Caught a IOException while trying to write to " + uri, e);
             }
             setResult(Activity.RESULT_OK, result);
-        } else if (ACTION_TAKE_PERSISTABLE_URI_PERMISSION.equals(action)) {
-            Log.i(TAG, "Taking persistable uri permission to " + uri);
-            getContentResolver().takePersistableUriPermission(uri,
-                    Intent.FLAG_GRANT_READ_URI_PERMISSION);
-            setResult(Activity.RESULT_OK);
         }
         finish();
     }
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderTest.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/ContentTest.java
similarity index 94%
rename from hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderTest.java
rename to hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/ContentTest.java
index 47de0da..1aaa5ab 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderTest.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/ContentTest.java
@@ -32,7 +32,7 @@
 import java.io.InputStream;
 import java.io.InputStreamReader;
 
-public class IntentSenderTest extends InstrumentationTestCase {
+public class ContentTest extends InstrumentationTestCase {
 
     private static final String MESSAGE = "Sample Message";
 
@@ -57,8 +57,8 @@
 
     @Override
     public void tearDown() throws Exception {
-        super.tearDown();
         mActivity.finish();
+        super.tearDown();
     }
 
     /**
@@ -73,7 +73,7 @@
         intent.setClipData(ClipData.newRawUri("", uri));
         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
-        final Intent result = mActivity.getResult(intent);
+        final Intent result = mActivity.getCrossProfileResult(intent);
         assertNotNull(result);
         assertEquals(MESSAGE, result.getStringExtra("extra_response"));
     }
@@ -95,7 +95,7 @@
         intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                 | Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
-        mActivity.getResult(intent);
+        mActivity.getCrossProfileResult(intent);
         assertEquals(MESSAGE, getFirstLineFromUri(uri));
     }
 
@@ -107,7 +107,7 @@
         Intent intent = new Intent(ACTION_READ_FROM_URI);
         intent.setClipData(ClipData.newRawUri("", uri));
 
-        final Intent result = mActivity.getResult(intent);
+        final Intent result = mActivity.getCrossProfileResult(intent);
         assertNotNull(result);
         assertEquals(MESSAGE, result.getStringExtra("extra_response"));
     }
@@ -136,7 +136,7 @@
         Intent notGrant = new Intent(ACTION_READ_FROM_URI);
         notGrant.setClipData(ClipData.newRawUri("", uriNotGranted));
 
-        final Intent result = mActivity.getResult(notGrant);
+        final Intent result = mActivity.getCrossProfileResult(notGrant);
         assertNotNull(result);
         // The receiver did not have permission to read the uri. So it should have caught a security
         // exception.
@@ -155,7 +155,7 @@
         intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
 
         // We're expecting to run into a security exception
-        final Intent result = mActivity.getResult(intent);
+        final Intent result = mActivity.getCrossProfileResult(intent);
         if (result == null) {
             // This is fine; probably of a SecurityException when off in the
             // system somewhere.
@@ -170,7 +170,7 @@
         grantPersistable.setClipData(ClipData.newRawUri("", uri));
         grantPersistable.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
                 | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
-        mActivity.getResult(grantPersistable);
+        mActivity.getCrossProfileResult(grantPersistable);
     }
 
     private Uri getBasicContentProviderUri(String path) {
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/CopyPasteTest.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/CopyPasteTest.java
new file mode 100644
index 0000000..a5d83db
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/CopyPasteTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.intent.sender;
+
+import android.content.ClipboardManager;
+import android.content.ClipData;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+
+public class CopyPasteTest extends InstrumentationTestCase
+        implements ClipboardManager.OnPrimaryClipChangedListener {
+
+    private IntentSenderActivity mActivity;
+    private ClipboardManager mClipboard;
+    private Semaphore mNotified;
+
+    private static String ACTION_COPY_TO_CLIPBOARD = "com.android.cts.action.COPY_TO_CLIPBOARD";
+
+    private static String INITIAL_TEXT = "initial text";
+    private static String NEW_TEXT = "sample text";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        Context context = getInstrumentation().getTargetContext();
+        mActivity = launchActivity(context.getPackageName(), IntentSenderActivity.class, null);
+        mClipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mActivity.finish();
+        super.tearDown();
+    }
+
+    public void testCanReadAcrossProfiles() throws Exception {
+        ClipData clip = ClipData.newPlainText(""/*label*/, INITIAL_TEXT);
+        mClipboard.setPrimaryClip(clip);
+        assertEquals(INITIAL_TEXT , getTextFromClipboard());
+
+        askCrossProfileReceiverToCopy(NEW_TEXT);
+
+        assertEquals(NEW_TEXT, getTextFromClipboard());
+    }
+
+    public void testCannotReadAcrossProfiles() throws Exception {
+        ClipData clip = ClipData.newPlainText(""/*label*/, INITIAL_TEXT);
+        mClipboard.setPrimaryClip(clip);
+        assertEquals(INITIAL_TEXT , getTextFromClipboard());
+
+        askCrossProfileReceiverToCopy(NEW_TEXT);
+
+        String clipboardText = getTextFromClipboard();
+        assertTrue("The clipboard text is " + clipboardText + " but should be <null> or "
+                + INITIAL_TEXT, clipboardText == null || clipboardText.equals(INITIAL_TEXT));
+    }
+
+    public void testIsNotified() throws Exception {
+        try {
+            mNotified = new Semaphore(0);
+            mActivity.addPrimaryClipChangedListener(this);
+
+            askCrossProfileReceiverToCopy(NEW_TEXT);
+
+            assertTrue(mNotified.tryAcquire(5, TimeUnit.SECONDS));
+        } finally {
+            mActivity.removePrimaryClipChangedListener(this);
+        }
+    }
+
+    private void askCrossProfileReceiverToCopy(String text) throws Exception {
+        Intent intent = new Intent(ACTION_COPY_TO_CLIPBOARD);
+        intent.putExtra("extra_text", text);
+        mActivity.getCrossProfileResult(intent);
+    }
+
+    private String getTextFromClipboard() {
+        ClipData clip = mClipboard.getPrimaryClip();
+        if (clip == null) {
+            return null;
+        }
+        ClipData.Item item = clip.getItemAt(0);
+        if (item == null) {
+            return null;
+        }
+        CharSequence text = item.getText();
+        if (text == null) {
+            return null;
+        }
+        return text.toString();
+    }
+
+
+    @Override
+    public void onPrimaryClipChanged() {
+        mNotified.release();
+    }
+
+}
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
index 00fa6b7..fd421ac 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
@@ -17,15 +17,27 @@
 package com.android.cts.intent.sender;
 
 import android.app.Activity;
+import android.content.ClipboardManager;
+import android.content.ClipboardManager.OnPrimaryClipChangedListener;
+import android.content.ComponentName;
 import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+import android.util.Log;
 
 import java.util.concurrent.SynchronousQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.List;
 
 public class IntentSenderActivity extends Activity {
 
+    private static String TAG = "IntentSenderActivity";
+
     private final SynchronousQueue<Result> mResult = new SynchronousQueue<>();
 
+    private ClipboardManager mClipboardManager;
+
     public static class Result {
         public final int resultCode;
         public final Intent data;
@@ -37,6 +49,12 @@
     }
 
     @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mClipboardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
+    }
+
+    @Override
     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
         if (resultCode == Activity.RESULT_OK) {
             try {
@@ -52,4 +70,38 @@
         final Result result = mResult.poll(30, TimeUnit.SECONDS);
         return (result != null) ? result.data : null;
     }
+
+    /**
+     * This method will send an intent accross profiles to IntentReceiverActivity, and return the
+     * result intent set by IntentReceiverActivity.
+     */
+    public Intent getCrossProfileResult(Intent intent)
+            throws Exception {
+        PackageManager pm = getPackageManager();
+        List<ResolveInfo> ris = pm.queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
+        //  There should be two matches:
+        //  - com.android.cts.intent.receiver (on the current profile).
+        //  - One that will send the intent to the other profile.
+        //  It's the second one we want to send the intent to.
+
+        for (ResolveInfo ri : ris) {
+            if (!ri.activityInfo.packageName.equals("com.android.cts.intent.receiver")) {
+                intent.setComponent(new ComponentName(ri.activityInfo.packageName,
+                        ri.activityInfo.name));
+                return getResult(intent);
+            }
+        }
+        Log.e(TAG, "The intent " + intent + " cannot be sent accross profiles");
+        return null;
+    }
+
+    public void addPrimaryClipChangedListener(OnPrimaryClipChangedListener listener) {
+        mClipboardManager.addPrimaryClipChangedListener(listener);
+    }
+
+    public void removePrimaryClipChangedListener(
+            OnPrimaryClipChangedListener listener) {
+        mClipboardManager.removePrimaryClipChangedListener(listener);
+    }
+
 }
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/Android.mk b/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
new file mode 100644
index 0000000..4517ea2
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTests/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsLauncherAppsTests
+
+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 cts-junit
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/AndroidManifest.xml b/hostsidetests/devicepolicy/app/LauncherTests/AndroidManifest.xml
new file mode 100644
index 0000000..a21b8c2
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTests/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.launchertests">
+
+    <uses-sdk android:minSdkVersion="21"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.test.InstrumentationTestRunner"
+                     android:targetPackage="com.android.cts.launchertests"
+                     android:label="Launcher Apps CTS Tests"/>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
new file mode 100644
index 0000000..3d44ecd
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/LauncherAppsTests.java
@@ -0,0 +1,287 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.launchertests;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.LauncherActivityInfo;
+import android.content.pm.LauncherApps;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Parcel;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.test.InstrumentationTestCase;
+import android.test.InstrumentationTestRunner;
+import android.util.Pair;
+
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.List;
+
+/**
+ * Tests for LauncherApps service
+ */
+public class LauncherAppsTests extends InstrumentationTestCase {
+
+    public static final String SIMPLE_APP_PACKAGE = "com.android.cts.launcherapps.simpleapp";
+
+    public static final String USER_EXTRA = "user_extra";
+    public static final String PACKAGE_EXTRA = "package_extra";
+    public static final String REPLY_EXTRA = "reply_extra";
+
+    public static final int MSG_RESULT = 0;
+    public static final int MSG_CHECK_PACKAGE_ADDED = 1;
+    public static final int MSG_CHECK_PACKAGE_REMOVED = 2;
+    public static final int MSG_CHECK_PACKAGE_CHANGED = 3;
+    public static final int MSG_CHECK_NO_CALLBACK = 4;
+
+    public static final int RESULT_PASS = 1;
+    public static final int RESULT_FAIL = 2;
+    public static final int RESULT_TIMEOUT = 3;
+
+    private LauncherApps mLauncherApps;
+    private UserHandle mUser;
+    private InstrumentationTestRunner mInstrumentation;
+    private Messenger mService;
+    private Connection mConnection;
+    private Result mResult;
+    private Messenger mResultMessenger;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mInstrumentation = (InstrumentationTestRunner) getInstrumentation();
+        Bundle arguments = mInstrumentation.getArguments();
+        UserManager userManager = (UserManager) mInstrumentation.getContext().getSystemService(
+                Context.USER_SERVICE);
+        mUser = getUserHandleArgument(userManager, "testUser", arguments);
+        mLauncherApps = (LauncherApps) mInstrumentation.getContext().getSystemService(
+                Context.LAUNCHER_APPS_SERVICE);
+
+        final Intent intent = new Intent();
+        intent.setComponent(new ComponentName("com.android.cts.launchertests.support",
+                        "com.android.cts.launchertests.support.LauncherCallbackTestsService"));
+
+        mConnection = new Connection();
+        mInstrumentation.getContext().bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+        mConnection.waitForService();
+        mResult = new Result(Looper.getMainLooper());
+        mResultMessenger = new Messenger(mResult);
+    }
+
+    public void testGetActivitiesForUserFails() throws Exception {
+        try {
+            List<LauncherActivityInfo> activities =
+                    mLauncherApps.getActivityList(null, mUser);
+            fail("getActivities for non-profile user failed to throw exception");
+        } catch (SecurityException e) {
+            // Expected.
+        }
+    }
+
+    public void testSimpleAppInstalledForUser() throws Exception {
+        List<LauncherActivityInfo> activities =
+                mLauncherApps.getActivityList(null, mUser);
+        // Check simple app is there.
+        boolean foundSimpleApp = false;
+        for (LauncherActivityInfo activity : activities) {
+            if (activity.getComponentName().getPackageName().equals(
+                    SIMPLE_APP_PACKAGE)) {
+                foundSimpleApp = true;
+            }
+            assertTrue(activity.getUser().equals(mUser));
+        }
+        assertTrue(foundSimpleApp);
+    }
+
+    public void testPackageAddedCallbackForUser() throws Throwable {
+        int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_ADDED,
+                mUser, SIMPLE_APP_PACKAGE);
+        assertEquals(RESULT_PASS, result);
+    }
+
+    public void testPackageRemovedCallbackForUser() throws Throwable {
+        int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_REMOVED,
+                mUser, SIMPLE_APP_PACKAGE);
+        assertEquals(RESULT_PASS, result);
+    }
+    public void testPackageChangedCallbackForUser() throws Throwable {
+        int result = sendMessageToCallbacksService(MSG_CHECK_PACKAGE_CHANGED,
+                mUser, SIMPLE_APP_PACKAGE);
+        assertEquals(RESULT_PASS, result);
+    }
+
+    public void testNoCallbackForUser() throws Throwable {
+        int result = sendMessageToCallbacksService(MSG_CHECK_NO_CALLBACK,
+                mUser, SIMPLE_APP_PACKAGE);
+        assertEquals(RESULT_PASS, result);
+    }
+
+    public void testLaunchNonExportActivityFails() throws Exception {
+        try {
+            mLauncherApps.startMainActivity(new ComponentName(
+                    SIMPLE_APP_PACKAGE,
+                    SIMPLE_APP_PACKAGE + ".NonExportedActivity"),
+                    mUser, null, null);
+            fail("starting non-exported activity failed to throw exception");
+        } catch (SecurityException e) {
+            // Expected.
+        }
+    }
+
+    public void testLaunchNonExportLauncherFails() throws Exception {
+        try {
+            mLauncherApps.startMainActivity(new ComponentName(
+                    SIMPLE_APP_PACKAGE,
+                    SIMPLE_APP_PACKAGE + ".NonLauncherActivity"),
+                    mUser, null, null);
+            fail("starting non-launcher activity failed to throw exception");
+        } catch (SecurityException e) {
+            // Expected.
+        }
+    }
+
+    public void testLaunchMainActivity() throws Exception {
+        ActivityLaunchedReceiver receiver = new ActivityLaunchedReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(ActivityLaunchedReceiver.ACTIVITY_LAUNCHED_ACTION);
+        mInstrumentation.getContext().registerReceiver(receiver, filter);
+        mLauncherApps.startMainActivity(new ComponentName(
+                SIMPLE_APP_PACKAGE,
+                SIMPLE_APP_PACKAGE + ".SimpleActivity"),
+                mUser, null, null);
+        assertEquals(RESULT_PASS, receiver.waitForActivity());
+        mInstrumentation.getContext().unregisterReceiver(receiver);
+    }
+
+    private UserHandle getUserHandleArgument(UserManager userManager, String key,
+            Bundle arguments) throws Exception {
+        String serial = arguments.getString(key);
+        if (serial == null) {
+            return null;
+        }
+        int serialNo = Integer.parseInt(serial);
+        return userManager.getUserForSerialNumber(serialNo);
+    }
+
+    private class Connection implements ServiceConnection {
+        private final Semaphore mSemaphore = new Semaphore(0);
+
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mService = new Messenger(service);
+            mSemaphore.release();
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            mService = null;
+        }
+
+        public void waitForService() {
+            try {
+                if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
+                    return;
+                }
+            } catch (InterruptedException e) {
+            }
+            fail("failed to connec to service");
+        }
+    };
+
+    private static class Result extends Handler {
+
+        private final Semaphore mSemaphore = new Semaphore(0);
+        public int result = 0;
+
+        public Result(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == MSG_RESULT) {
+                result = msg.arg1;
+                mSemaphore.release();
+            } else {
+                super.handleMessage(msg);
+            }
+        }
+
+        public int waitForResult() {
+            try {
+                if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
+                    return result;
+                }
+            } catch (InterruptedException e) {
+            }
+            return RESULT_TIMEOUT;
+        }
+    }
+
+    public class ActivityLaunchedReceiver extends BroadcastReceiver {
+        public static final String ACTIVITY_LAUNCHED_ACTION =
+                "com.android.cts.launchertests.LauncherAppsTests.LAUNCHED_ACTION";
+
+        private final Semaphore mSemaphore = new Semaphore(0);
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(ACTIVITY_LAUNCHED_ACTION)) {
+                mSemaphore.release();
+            }
+        }
+
+        public int waitForActivity() {
+            try {
+                if (mSemaphore.tryAcquire(5, TimeUnit.SECONDS)) {
+                    return RESULT_PASS;
+                }
+            } catch (InterruptedException e) {
+            }
+            return RESULT_TIMEOUT;
+        }
+    }
+
+    private int sendMessageToCallbacksService(int msg, UserHandle user, String packageName)
+            throws Throwable {
+        Bundle params = new Bundle();
+        params.putParcelable(USER_EXTRA, user);
+        params.putString(PACKAGE_EXTRA, packageName);
+
+        Message message = Message.obtain(null, msg, params);
+        message.replyTo = mResultMessenger;
+
+        mService.send(message);
+
+        return mResult.waitForResult();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/TestActivity.java b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/TestActivity.java
new file mode 100644
index 0000000..5d62c8f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTests/src/com/android/cts/launchertests/TestActivity.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.launchertests;
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+/**
+ * A simple activity to install and launch for various users to
+ * test LauncherApps.
+ */
+public class TestActivity extends Activity {
+    public static final String USER_EXTRA = "user_extra";
+    public static final int MSG_RESULT = 0;
+    public static final int RESULT_PASS = 1;
+
+    private static final String TAG = "SimpleActivity";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(TAG, "Created for user " + android.os.Process.myUserHandle());
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.mk b/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.mk
new file mode 100644
index 0000000..2465ef3
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsLauncherAppsTestsSupport
+
+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 cts-junit
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml b/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
new file mode 100644
index 0000000..fbd049f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.launchertests.support">
+
+    <uses-sdk android:minSdkVersion="21"/>
+
+    <application>
+        <service android:name=".LauncherCallbackTestsService" >
+            <intent-filter>
+                <action android:name="com.android.cts.launchertests.support.REGISTER_CALLBACK" />
+            </intent-filter>
+        </service>
+    </application>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherCallbackTestsService.java b/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherCallbackTestsService.java
new file mode 100644
index 0000000..8d61496
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/LauncherTestsSupport/src/com/android/cts/launchertests/support/LauncherCallbackTestsService.java
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2014 The Android Open Sour *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.LauncherApps;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.util.Log;
+import android.util.Pair;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Service that registers for LauncherApps callbacks.
+ *
+ * Registering in a service and different process so that
+ * device side code can launch it before running client
+ * side test code.
+ */
+public class LauncherCallbackTestsService extends Service {
+
+    public static final String USER_EXTRA = "user_extra";
+    public static final String PACKAGE_EXTRA = "package_extra";
+
+    public static final int MSG_RESULT = 0;
+    public static final int MSG_CHECK_PACKAGE_ADDED = 1;
+    public static final int MSG_CHECK_PACKAGE_REMOVED = 2;
+    public static final int MSG_CHECK_PACKAGE_CHANGED = 3;
+    public static final int MSG_CHECK_NO_CALLBACK = 4;
+
+    public static final int RESULT_PASS = 1;
+    public static final int RESULT_FAIL = 2;
+
+    private static final String TAG = "LauncherCallbackTests";
+
+    private static List<Pair<String, UserHandle>> mPackagesAdded
+            = new ArrayList<Pair<String, UserHandle>>();
+    private static List<Pair<String, UserHandle>> mPackagesRemoved
+            = new ArrayList<Pair<String, UserHandle>>();
+    private static List<Pair<String, UserHandle>> mPackagesChanged
+            = new ArrayList<Pair<String, UserHandle>>();
+    private static Object mPackagesLock = new Object();
+
+    private TestCallback mCallback;
+    private final Messenger mMessenger = new Messenger(new CheckHandler());
+
+    class CheckHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            Bundle params = null;
+            if (msg.obj instanceof Bundle) {
+                params = (Bundle) (msg.obj);
+            }
+            try {
+                switch (msg.what) {
+                    case MSG_CHECK_PACKAGE_ADDED: {
+                        boolean exists = eventExists(params, mPackagesAdded);
+                        teardown();
+                        msg.replyTo.send(Message.obtain(null, MSG_RESULT,
+                                        exists ? RESULT_PASS : RESULT_FAIL, 0));
+                        break;
+                    }
+                    case MSG_CHECK_PACKAGE_REMOVED: {
+                        boolean exists = eventExists(params, mPackagesRemoved);
+                        teardown();
+                        msg.replyTo.send(Message.obtain(null, MSG_RESULT,
+                                        exists ? RESULT_PASS : RESULT_FAIL, 0));
+                        break;
+                    }
+                    case MSG_CHECK_PACKAGE_CHANGED: {
+                        boolean exists = eventExists(params, mPackagesChanged);
+                        teardown();
+                        msg.replyTo.send(Message.obtain(null, MSG_RESULT,
+                                        exists ? RESULT_PASS : RESULT_FAIL, 0));
+                        break;
+                    }
+                    case MSG_CHECK_NO_CALLBACK: {
+                        boolean exists = eventExists(params, mPackagesAdded)
+                                || eventExists(params, mPackagesRemoved)
+                                || eventExists(params, mPackagesChanged);
+                        teardown();
+                        msg.replyTo.send(Message.obtain(null, MSG_RESULT,
+                                        exists ? RESULT_FAIL : RESULT_PASS, 0));
+                        break;
+                    }
+                    default:
+                        super.handleMessage(msg);
+                }
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to report test status");
+            }
+        }
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        if (intent == null) {
+            return START_NOT_STICKY;
+        }
+        if ("com.android.cts.launchertests.support.REGISTER_CALLBACK".equals(intent.getAction())) {
+            setup();
+        }
+        return START_STICKY;
+    }
+
+    private void setup() {
+        LauncherApps launcherApps = (LauncherApps) getSystemService(
+                Context.LAUNCHER_APPS_SERVICE);
+        synchronized (mPackagesLock) {
+            mPackagesAdded.clear();
+            mPackagesRemoved.clear();
+            mPackagesChanged.clear();
+            if (mCallback != null) {
+                launcherApps.unregisterCallback(mCallback);
+            }
+            mCallback = new TestCallback();
+            launcherApps.registerCallback(mCallback);
+        }
+    }
+
+    private void teardown() {
+        LauncherApps launcherApps = (LauncherApps) getSystemService(
+                Context.LAUNCHER_APPS_SERVICE);
+        synchronized (mPackagesLock) {
+            if (mCallback != null) {
+                launcherApps.unregisterCallback(mCallback);
+                mCallback = null;
+            }
+            mPackagesAdded.clear();
+            mPackagesRemoved.clear();
+            mPackagesChanged.clear();
+        }
+    }
+
+    private boolean eventExists(Bundle params, List<Pair<String, UserHandle>> events) {
+        UserHandle user = params.getParcelable(USER_EXTRA);
+        String packageName = params.getString(PACKAGE_EXTRA);
+        synchronized (mPackagesLock) {
+            if (events != null) {
+                for (Pair<String, UserHandle> added : events) {
+                    if (added.first.equals(packageName) && added.second.equals(user)) {
+                        Log.i(TAG, "Event exists " + packageName + " for user " + user);
+                        return true;
+                    }
+                }
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mMessenger.getBinder();
+    }
+
+    private class TestCallback extends LauncherApps.Callback {
+        public void onPackageRemoved(String packageName, UserHandle user) {
+            synchronized (mPackagesLock) {
+                mPackagesRemoved.add(new Pair<String, UserHandle>(packageName, user));
+            }
+        }
+
+        public void onPackageAdded(String packageName, UserHandle user) {
+            synchronized (mPackagesLock) {
+                mPackagesAdded.add(new Pair<String, UserHandle>(packageName, user));
+            }
+        }
+
+        public void onPackageChanged(String packageName, UserHandle user) {
+            synchronized (mPackagesLock) {
+                mPackagesChanged.add(new Pair<String, UserHandle>(packageName, user));
+            }
+        }
+
+        public void onPackagesAvailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+        }
+
+        public void onPackagesUnavailable(String[] packageNames, UserHandle user,
+                boolean replacing) {
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index 008ed38..301f90b 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -18,6 +18,8 @@
     package="com.android.cts.managedprofile">
 
     <uses-sdk android:minSdkVersion="20"/>
+    <uses-permission android:name="android.permission.BLUETOOTH" />
+    <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BluetoothTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BluetoothTest.java
new file mode 100644
index 0000000..4d7ddeb
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BluetoothTest.java
@@ -0,0 +1,203 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.managedprofile;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothServerSocket;
+import android.test.AndroidTestCase;
+
+import java.io.IOException;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Test that the basic bluetooth API is callable in managed profiles.
+ * These tests should only be executed if the device supports bluetooth,
+ * i.e. if it has the {@link android.content.pm.PackageManager#FEATURE_BLUETOOTH} feature.
+ *
+ * This includes tests for the {@link BluetoothAdapter}.
+ * The corresponding CTS tests in the primary profile are in
+ * {@link android.bluetooth.cts.BasicAdapterTest}.
+ * TODO: Merge the primary and managed profile tests into one.
+ */
+public class BluetoothTest extends AndroidTestCase {
+    private static final int DISABLE_TIMEOUT_MS = 8000;
+    private static final int ENABLE_TIMEOUT_MS = 10000;
+    private static final int POLL_TIME_MS = 400;
+    private static final int CHECK_WAIT_TIME_MS = 1000;
+
+    private BluetoothAdapter mAdapter;
+    private boolean mBtWasEnabled;
+
+    public void setUp() throws Exception {
+        super.setUp();
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        assertNotNull(mAdapter);
+        mBtWasEnabled = mAdapter.isEnabled();
+    }
+
+    public void tearDown() throws Exception {
+        if (mBtWasEnabled != mAdapter.isEnabled()) {
+            if (mBtWasEnabled) {
+                enable();
+            } else {
+                disable();
+            }
+        }
+        super.tearDown();
+    }
+
+    /**
+     * Checks enable(), disable(), getState(), isEnabled()
+     */
+    public void testEnableDisable() {
+        disable();
+        enable();
+    }
+
+    /**
+     * Test the getAddress() function.
+     */
+    public void testGetAddress() {
+        assertTrue(BluetoothAdapter.checkBluetoothAddress(mAdapter.getAddress()));
+    }
+
+    /**
+     * Tests the listenUsingRfcommWithServiceRecord function.
+     */
+    public void testListenUsingRfcommWithServiceRecord() throws IOException {
+        enable();
+        BluetoothServerSocket socket = mAdapter.listenUsingRfcommWithServiceRecord(
+                "test", UUID.randomUUID());
+        assertNotNull(socket);
+        socket.close();
+    }
+
+    /**
+     * Test the getRemoteDevice() function.
+     */
+    public void testGetRemoteDevice() {
+        // getRemoteDevice() should work even with Bluetooth disabled
+        disable();
+
+        // test bad addresses
+        try {
+            mAdapter.getRemoteDevice((String)null);
+            fail("IllegalArgumentException not thrown");
+        } catch (IllegalArgumentException e) {
+        }
+        try {
+            mAdapter.getRemoteDevice("00:00:00:00:00:00:00:00");
+            fail("IllegalArgumentException not thrown");
+        } catch (IllegalArgumentException e) {
+        }
+        try {
+            mAdapter.getRemoteDevice((byte[])null);
+            fail("IllegalArgumentException not thrown");
+        } catch (IllegalArgumentException e) {
+        }
+        try {
+            mAdapter.getRemoteDevice(new byte[] {0x00, 0x00, 0x00, 0x00, 0x00});
+            fail("IllegalArgumentException not thrown");
+        } catch (IllegalArgumentException e) {
+        }
+
+        // test success
+        BluetoothDevice device = mAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
+        assertNotNull(device);
+        assertEquals("00:11:22:AA:BB:CC", device.getAddress());
+        device = mAdapter.getRemoteDevice(
+                new byte[] {0x01, 0x02, 0x03, 0x04, 0x05, 0x06});
+        assertNotNull(device);
+        assertEquals("01:02:03:04:05:06", device.getAddress());
+    }
+
+    /**
+     * Helper to turn BT off.
+     * This method will either fail on an assert, or return with BT turned off.
+     * Behavior of getState() and isEnabled() are validated along the way.
+     */
+    private void disable() {
+        sleep(CHECK_WAIT_TIME_MS);
+        if (mAdapter.getState() == BluetoothAdapter.STATE_OFF) {
+            assertFalse(mAdapter.isEnabled());
+            return;
+        }
+
+        assertEquals(BluetoothAdapter.STATE_ON, mAdapter.getState());
+        assertTrue(mAdapter.isEnabled());
+        assertTrue(mAdapter.disable());
+        boolean turnOff = false;
+        for (int i=0; i<DISABLE_TIMEOUT_MS/POLL_TIME_MS; i++) {
+            sleep(POLL_TIME_MS);
+            int state = mAdapter.getState();
+            switch (state) {
+            case BluetoothAdapter.STATE_OFF:
+                assertFalse(mAdapter.isEnabled());
+                return;
+            default:
+                if (state != BluetoothAdapter.STATE_ON || turnOff) {
+                    assertEquals(BluetoothAdapter.STATE_TURNING_OFF, state);
+                    turnOff = true;
+                }
+                break;
+            }
+        }
+        fail("disable() timeout");
+    }
+
+    /**
+     * Helper to turn BT on.
+     * This method will either fail on an assert, or return with BT turned on.
+     * Behavior of getState() and isEnabled() are validated along the way.
+     */
+    private void enable() {
+        sleep(CHECK_WAIT_TIME_MS);
+        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) {
+            assertTrue(mAdapter.isEnabled());
+            return;
+        }
+
+        assertEquals(BluetoothAdapter.STATE_OFF, mAdapter.getState());
+        assertFalse(mAdapter.isEnabled());
+        assertTrue(mAdapter.enable());
+        boolean turnOn = false;
+        for (int i=0; i<ENABLE_TIMEOUT_MS/POLL_TIME_MS; i++) {
+            sleep(POLL_TIME_MS);
+            int state = mAdapter.getState();
+            switch (state) {
+            case BluetoothAdapter.STATE_ON:
+                assertTrue(mAdapter.isEnabled());
+                return;
+            default:
+                if (state != BluetoothAdapter.STATE_OFF || turnOn) {
+                    assertEquals(BluetoothAdapter.STATE_TURNING_ON, state);
+                    turnOn = true;
+                }
+                break;
+            }
+        }
+        fail("enable() timeout");
+    }
+
+    private void sleep(long t) {
+        try {
+            Thread.sleep(t);
+        } catch (InterruptedException e) {}
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
index 9615991..49001e9 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
@@ -21,8 +21,15 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.IntentFilter;
+import android.os.UserManager;
 import android.test.AndroidTestCase;
 
+/**
+ * The methods in this class are not really tests.
+ * They are just performing an action that is needed for a test.
+ * But we're still using an AndroidTestCase because it's an easy way to call
+ * device-side methods from the host.
+ */
 public class CrossProfileUtils extends AndroidTestCase {
     private static final String ACTION_READ_FROM_URI = "com.android.cts.action.READ_FROM_URI";
 
@@ -31,16 +38,14 @@
     private static final String ACTION_TAKE_PERSISTABLE_URI_PERMISSION =
             "com.android.cts.action.TAKE_PERSISTABLE_URI_PERMISSION";
 
+    private static String ACTION_COPY_TO_CLIPBOARD = "com.android.cts.action.COPY_TO_CLIPBOARD";
+
     public void addParentCanAccessManagedFilters() {
         removeAllFilters();
 
         final DevicePolicyManager dpm = (DevicePolicyManager) getContext().getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
-        IntentFilter intentFilter = new IntentFilter();
-        intentFilter.addAction(ACTION_READ_FROM_URI);
-        intentFilter.addAction(ACTION_WRITE_TO_URI);
-        intentFilter.addAction(ACTION_TAKE_PERSISTABLE_URI_PERMISSION);
-        dpm.addCrossProfileIntentFilter(ADMIN_RECEIVER_COMPONENT, intentFilter,
+        dpm.addCrossProfileIntentFilter(ADMIN_RECEIVER_COMPONENT, getIntentFilter(),
                 DevicePolicyManager.FLAG_PARENT_CAN_ACCESS_MANAGED);
     }
 
@@ -49,12 +54,17 @@
 
         final DevicePolicyManager dpm = (DevicePolicyManager) getContext().getSystemService(
                 Context.DEVICE_POLICY_SERVICE);
+        dpm.addCrossProfileIntentFilter(ADMIN_RECEIVER_COMPONENT, getIntentFilter(),
+                DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT);
+    }
+
+    public IntentFilter getIntentFilter() {
         IntentFilter intentFilter = new IntentFilter();
         intentFilter.addAction(ACTION_READ_FROM_URI);
         intentFilter.addAction(ACTION_WRITE_TO_URI);
         intentFilter.addAction(ACTION_TAKE_PERSISTABLE_URI_PERMISSION);
-        dpm.addCrossProfileIntentFilter(ADMIN_RECEIVER_COMPONENT, intentFilter,
-                DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT);
+        intentFilter.addAction(ACTION_COPY_TO_CLIPBOARD);
+        return intentFilter;
     }
 
     public void removeAllFilters() {
@@ -62,4 +72,18 @@
                 Context.DEVICE_POLICY_SERVICE);
         dpm.clearCrossProfileIntentFilters(ADMIN_RECEIVER_COMPONENT);
     }
+
+    public void disallowCrossProfileCopyPaste() {
+        DevicePolicyManager dpm = (DevicePolicyManager)
+               getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
+        dpm.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
+    }
+
+    public void allowCrossProfileCopyPaste() {
+        DevicePolicyManager dpm = (DevicePolicyManager)
+               getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
+        dpm.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
+                UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/SettingsIntentsTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/SettingsIntentsTest.java
new file mode 100644
index 0000000..e68cb48
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/SettingsIntentsTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.managedprofile;
+
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.test.ActivityInstrumentationTestCase2;
+import android.provider.Settings;
+
+
+/**
+ * Tests that make sure that some core application intents as described in Compatibility Definition
+ * Document are handled within a managed profile.
+ * Note that OEMs can replace the Settings apps, so we we can at most check if the intent resolves.
+ */
+public class SettingsIntentsTest extends ActivityInstrumentationTestCase2<TestActivity> {
+
+    private PackageManager mPackageManager;
+
+    public SettingsIntentsTest() {
+        super(TestActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mPackageManager = getActivity().getPackageManager();
+    }
+
+    public void testSettings() {
+        assertNotNull(mPackageManager.resolveActivity(
+                new Intent(Settings.ACTION_SETTINGS), 0 /* flags */));
+    }
+
+    public void testAccounts() {
+        assertNotNull(mPackageManager.resolveActivity(
+                new Intent(Settings.ACTION_SYNC_SETTINGS), 0 /* flags */));
+    }
+
+    public void testApps() {
+        assertNotNull(mPackageManager.resolveActivity(
+                new Intent(Settings.ACTION_APPLICATION_SETTINGS), 0 /* flags */));
+    }
+
+    public void testSecurity() {
+        // This leads to device administrators, screenlock etc.
+        assertNotNull(mPackageManager.resolveActivity(
+                new Intent(Settings.ACTION_SECURITY_SETTINGS), 0 /* flags */));
+    }
+
+    public void testNfc() {
+        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_NFC)) {
+            assertNotNull(mPackageManager.resolveActivity(
+                    new Intent(Settings.ACTION_NFC_SETTINGS), 0 /* flags */));
+        }
+    }
+
+    public void testLocation() {
+        assertNotNull(mPackageManager.resolveActivity(
+                new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS), 0 /* flags */));
+    }
+}
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 b548c96..76a9e44 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
@@ -45,12 +45,6 @@
     }
 
     public void testWipeData() throws InterruptedException {
-        try {
-            mDevicePolicyManager.wipeData(DevicePolicyManager.WIPE_EXTERNAL_STORAGE);
-            fail("Should not be able to wipe external storage from managed profile.");
-        } catch (SecurityException expected) {
-        }
-
         UserHandle currentUser = Process.myUserHandle();
         assertTrue(mUserManager.getUserProfiles().contains(currentUser));
 
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/Android.mk b/hostsidetests/devicepolicy/app/SimpleApp/Android.mk
new file mode 100644
index 0000000..eae0a4f
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/Android.mk
@@ -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.
+
+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 := CtsSimpleApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
new file mode 100644
index 0000000..848317c
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/AndroidManifest.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.launcherapps.simpleapp">
+
+    <application>
+        <activity android:name=".SimpleActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".NonExportedActivity">
+            android:exported="false">
+        </activity>
+        <activity android:name=".NonLauncherActivity">
+            android:exported="true">
+        </activity>
+    </application>
+
+</manifest>
+
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonExportedActivity.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonExportedActivity.java
new file mode 100644
index 0000000..4801830
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonExportedActivity.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 com.android.cts.launcherapps.simpleapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+/**
+ * A simple activity that isn't exported, shouldn't be launchable
+ * by LauncherApps.
+ */
+public class NonExportedActivity extends Activity {
+
+    private static final String TAG = "NonExportedActivity";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(TAG, "Created for user " + android.os.Process.myUserHandle());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonLauncherActivity.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonLauncherActivity.java
new file mode 100644
index 0000000..4809020
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/NonLauncherActivity.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 com.android.cts.launcherapps.simpleapp;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+/**
+ * A simple activity that isn't main / category launcher, shouldn't be launchable
+ * by LauncherApps.
+ */
+public class NonLauncherActivity extends Activity {
+
+    private static final String TAG = "NonLauncherActivity";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(TAG, "Created for user " + android.os.Process.myUserHandle());
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivity.java b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivity.java
new file mode 100644
index 0000000..1d30335
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleApp/src/com/android/cts/launcherapps/simpleapp/SimpleActivity.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.launcherapps.simpleapp;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.Override;
+
+/**
+ * A simple activity to install for various users to test LauncherApps.
+ */
+public class SimpleActivity extends Activity {
+    public static String ACTIVITY_LAUNCHED_ACTION =
+            "com.android.cts.launchertests.LauncherAppsTests.LAUNCHED_ACTION";
+
+    private static final String TAG = "SimpleActivity";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.i(TAG, "Created for user " + android.os.Process.myUserHandle());
+    }
+
+    public void onStart() {
+        super.onStart();
+        Intent reply = new Intent();
+        reply.setAction(ACTIVITY_LAUNCHED_ACTION);
+        sendBroadcast(reply);
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 544ddff..c35246d 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -47,6 +47,11 @@
  */
 public class BaseDevicePolicyTest extends DeviceTestCase implements IBuildReceiver {
 
+    protected static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
+    protected static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
+    protected static final String ADMIN_RECEIVER_TEST_CLASS =
+            MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
+
     private static final String RUNNER = "android.test.InstrumentationTestRunner";
 
     private static final String[] REQUIRED_DEVICE_FEATURES = new String[] {
@@ -72,6 +77,7 @@
 
     protected void installApp(String fileName)
             throws FileNotFoundException, DeviceNotAvailableException {
+        CLog.logAndDisplay(LogLevel.INFO, "Installing app " + fileName);
         String installResult = getDevice().installPackage(mCtsBuild.getTestApp(fileName), true);
         assertNull(String.format("Failed to install %s, Reason: %s", fileName, installResult),
                 installResult);
@@ -93,12 +99,15 @@
     }
 
     /** Initializes the user with the given id. This is required so that apps can run on it. */
-    protected void startUser(int userId) throws DeviceNotAvailableException {
+    protected void startUser(int userId) throws Exception {
         String command = "am start-user " + userId;
+        CLog.logAndDisplay(LogLevel.INFO, "Starting command " + command);
         String commandOutput = getDevice().executeShellCommand(command);
         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
         assertTrue(commandOutput + " expected to start with \"Success:\"",
                 commandOutput.startsWith("Success:"));
+        // Wait 60 seconds for intents generated to be handled.
+        Thread.sleep(60 * 1000);
     }
 
     protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
@@ -137,10 +146,13 @@
         return users;
     }
 
-    protected void removeUser(int userId) throws DeviceNotAvailableException  {
+    protected void removeUser(int userId) throws Exception  {
         String removeUserCommand = "pm remove-user " + userId;
+        CLog.logAndDisplay(LogLevel.INFO, "starting command " + removeUserCommand);
         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + removeUserCommand + ": "
                 + getDevice().executeShellCommand(removeUserCommand));
+        // Wait 60 seconds for user to finish being removed.
+        Thread.sleep(60 * 1000);
     }
 
     /** Returns true if the specified tests passed. Tests are run as user owner. */
@@ -166,12 +178,19 @@
         return runDeviceTests(pkgName, testClassName, testMethodName, userId);
     }
 
-    private boolean runDeviceTests(String pkgName, @Nullable String testClassName,
+    protected boolean runDeviceTests(String pkgName, @Nullable String testClassName,
             @Nullable String testMethodName, @Nullable Integer userId)
-                    throws DeviceNotAvailableException {
-        TestRunResult runResult = (userId == null)
+            throws DeviceNotAvailableException {
+        return runDeviceTests(pkgName, testClassName, testMethodName, userId, /*params*/ null);
+    }
+
+    protected boolean runDeviceTests(String pkgName, @Nullable String testClassName,
+            @Nullable String testMethodName, @Nullable Integer userId, @Nullable String params)
+                   throws DeviceNotAvailableException {
+        TestRunResult runResult = (userId == null && params == null)
                 ? doRunTests(pkgName, testClassName, testMethodName)
-                : doRunTestsAsUser(pkgName, testClassName, testMethodName, userId);
+                : doRunTestsAsUser(pkgName, testClassName, testMethodName,
+                        userId != null ? userId : 0, params != null ? params : "");
         printTestResult(runResult);
         return !runResult.hasFailedTests() && runResult.getNumTestsInState(TestStatus.PASSED) > 0;
     }
@@ -194,7 +213,7 @@
     }
 
     private TestRunResult doRunTestsAsUser(String pkgName, @Nullable String testClassName,
-            @Nullable String testMethodName, int userId)
+            @Nullable String testMethodName, int userId, String params)
             throws DeviceNotAvailableException {
         // TODO: move this to RemoteAndroidTestRunner once it supports users. Should be straight
         // forward to add a RemoteAndroidTestRunner.setUser(userId) method. Then we can merge both
@@ -206,8 +225,8 @@
                 testsToRun.append("#" + testMethodName);
             }
         }
-        String command = "am instrument --user " + userId + " -w -r " + testsToRun + " "
-                + pkgName + "/" + RUNNER;
+        String command = "am instrument --user " + userId + " " + params + " -w -r "
+                + testsToRun + " " + pkgName + "/" + RUNNER;
         CLog.i("Running " + command);
 
         CollectingTestListener listener = new CollectingTestListener();
@@ -228,7 +247,7 @@
         }
     }
 
-    private boolean hasDeviceFeatures(String[] requiredFeatures)
+    protected boolean hasDeviceFeatures(String[] requiredFeatures)
             throws DeviceNotAvailableException {
         // TODO: Move this logic to ITestDevice.
         String command = "pm list features";
@@ -255,4 +274,63 @@
         }
         return true;
     }
+
+    protected int createUser() throws Exception {
+        String command ="pm create-user TestUser_"+ System.currentTimeMillis();
+        CLog.logAndDisplay(LogLevel.INFO, "Starting command " + command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
+
+        // Extract the id of the new user.
+        String[] tokens = commandOutput.split("\\s+");
+        assertTrue(tokens.length > 0);
+        assertEquals("Success:", tokens[0]);
+        // Wait 60 seconds for intents generated to be handled.
+        Thread.sleep(60 * 1000);
+        return Integer.parseInt(tokens[tokens.length-1]);
+    }
+
+    protected int createManagedProfile() throws DeviceNotAvailableException {
+        String command =
+                "pm create-user --profileOf 0 --managed TestProfile_" + System.currentTimeMillis();
+        CLog.logAndDisplay(LogLevel.INFO, "Starting command " + command);
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
+
+        // Extract the id of the new user.
+        String[] tokens = commandOutput.split("\\s+");
+        assertTrue(commandOutput + " expected to have format \"Success: {USER_ID}\"",
+                tokens.length > 0);
+        assertEquals(commandOutput, "Success:", tokens[0]);
+        return Integer.parseInt(tokens[tokens.length-1]);
+    }
+
+
+    protected int getUserSerialNumber(int userId) throws DeviceNotAvailableException{
+        // dumpsys user return lines like "UserInfo{0:Owner:13} serialNo=0"
+        String commandOutput = getDevice().executeShellCommand("dumpsys user");
+        String[] tokens = commandOutput.split("\\n");
+        for (String token : tokens) {
+            token = token.trim();
+            if (token.contains("UserInfo{" + userId + ":")) {
+                String[] split = token.split("serialNo=");
+                assertTrue(split.length == 2);
+                int serialNumber = Integer.parseInt(split[1]);
+                CLog.logAndDisplay(LogLevel.INFO, "Serial number of user " + userId + ": "
+                        + serialNumber);
+                return serialNumber;
+            }
+        }
+        fail("Couldn't find user " + userId);
+        return -1;
+    }
+
+    protected void setProfileOwner(String componentName, int userId)
+            throws DeviceNotAvailableException {
+        String command = "dpm set-profile-owner '" + componentName + "' " + userId;
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
+        assertTrue(commandOutput + " expected to start with \"Success:\"",
+                commandOutput.startsWith("Success:"));
+    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
new file mode 100644
index 0000000..dec8bd2
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseLauncherAppsTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Common code for the various LauncherApps tests.
+ */
+public class BaseLauncherAppsTest extends BaseDevicePolicyTest {
+
+    protected static final String SIMPLE_APP_PKG = "com.android.cts.launcherapps.simpleapp";
+    protected static final String SIMPLE_APP_APK = "CtsSimpleApp.apk";
+    protected static final String LAUNCHER_TESTS_PKG = "com.android.cts.launchertests";
+    protected static final String LAUNCHER_TESTS_CLASS = LAUNCHER_TESTS_PKG + ".LauncherAppsTests";
+    private static final String LAUNCHER_TESTS_APK = "CtsLauncherAppsTests.apk";
+    private static final String LAUNCHER_TESTS_SUPPORT_PKG =
+            "com.android.cts.launchertests.support";
+    private static final String LAUNCHER_TESTS_SUPPORT_APK = "CtsLauncherAppsTestsSupport.apk";
+
+    protected void installTestApps() throws Exception {
+        uninstallTestApps();
+        installApp(LAUNCHER_TESTS_APK);
+        installApp(LAUNCHER_TESTS_SUPPORT_APK);
+    }
+
+    protected void uninstallTestApps() throws Exception {
+        getDevice().uninstallPackage(LAUNCHER_TESTS_PKG);
+        getDevice().uninstallPackage(LAUNCHER_TESTS_SUPPORT_PKG);
+        getDevice().uninstallPackage(SIMPLE_APP_PKG);
+    }
+
+    protected void removeTestUsers() throws Exception {
+        for (int userId : listUsers()) {
+            if (userId != 0) {
+                removeUser(userId);
+            }
+        }
+    }
+
+    protected void startCallbackService() throws Exception {
+        String command = "am startservice --user 0 "
+                + "-a " + LAUNCHER_TESTS_SUPPORT_PKG + ".REGISTER_CALLBACK "
+                + LAUNCHER_TESTS_SUPPORT_PKG + "/.LauncherCallbackTestsService";
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": "
+              + getDevice().executeShellCommand(command));
+
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
new file mode 100644
index 0000000..0af38a4
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsMultiUserTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Set of tests for LauncherApps attempting to access a non-profiles
+ * apps.
+ */
+public class LauncherAppsMultiUserTest extends BaseLauncherAppsTest {
+
+    private int mSecondaryUserId;
+    private int mSecondaryUserSerialNumber;
+
+    private boolean mMultiUserSupported;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // We need multi user to be supported in order to create a secondary user
+        // and api level 21 to support LauncherApps
+        mMultiUserSupported = getMaxNumberOfUsersSupported() > 1 && getDevice().getApiLevel() >= 21;
+
+        if (mMultiUserSupported) {
+            removeTestUsers();
+            installTestApps();
+            // Create a secondary user.
+            mSecondaryUserId = createUser();
+            mSecondaryUserSerialNumber = getUserSerialNumber(mSecondaryUserId);
+            startUser(mSecondaryUserId);
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mMultiUserSupported) {
+            removeUser(mSecondaryUserId);
+            uninstallTestApps();
+        }
+        super.tearDown();
+    }
+
+    public void testGetActivitiesForNonProfileFails() throws Exception {
+        if (!mMultiUserSupported) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                    "testGetActivitiesForUserFails",
+                            0, "-e testUser " + mSecondaryUserSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testNoLauncherCallbackPackageAddedSecondaryUser() throws Exception {
+        if (!mMultiUserSupported) {
+            return;
+        }
+        startCallbackService();
+        installApp(SIMPLE_APP_APK);
+        try {
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testNoCallbackForUser",
+                            0, "-e testUser " + mSecondaryUserSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
new file mode 100644
index 0000000..f8c2e7d
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Set of tests for LauncherApps with managed profiles.
+ */
+public class LauncherAppsProfileTest extends BaseLauncherAppsTest {
+
+    private int mProfileUserId;
+    private int mProfileSerialNumber;
+    private int mMainUserSerialNumber;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // We need multi user to be supported in order to create a profile of the user owner.
+        mHasFeature = mHasFeature && (getMaxNumberOfUsersSupported() > 1);
+
+        if (mHasFeature) {
+            removeTestUsers();
+            installTestApps();
+            // Create a managed profile
+            mProfileUserId = createManagedProfile();
+            installApp(MANAGED_PROFILE_APK);
+            setProfileOwner(MANAGED_PROFILE_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mProfileUserId);
+            mProfileSerialNumber = getUserSerialNumber(mProfileUserId);
+            mMainUserSerialNumber = getUserSerialNumber(0);
+            startUser(mProfileUserId);
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mHasFeature) {
+            removeUser(mProfileUserId);
+            uninstallTestApps();
+            getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
+        }
+        super.tearDown();
+    }
+
+    public void testGetActivitiesWithProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // Install app for all users.
+        installApp(SIMPLE_APP_APK);
+        try {
+            // Run tests to check SimpleApp exists in both profile and main user.
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                    "testSimpleAppInstalledForUser",
+                            0, "-e testUser " + mProfileSerialNumber));
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_PKG + ".LauncherAppsTests", "testSimpleAppInstalledForUser",
+                            0, "-e testUser " + mMainUserSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageAddedProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        startCallbackService();
+        installApp(SIMPLE_APP_APK);
+        try {
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageAddedCallbackForUser",
+                            0, "-e testUser " + mProfileSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageRemovedProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            startCallbackService();
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageRemovedCallbackForUser",
+                            0, "-e testUser " + mProfileSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageChangedProfile() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            startCallbackService();
+            installApp(SIMPLE_APP_APK);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageChangedCallbackForUser",
+                            0, "-e testUser " + mProfileSerialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
new file mode 100644
index 0000000..32be962
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsSingleUserTest.java
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+/**
+ * Set of tests for LauncherApps with managed profiles.
+ */
+public class LauncherAppsSingleUserTest extends BaseLauncherAppsTest {
+
+    private boolean mHasLauncherApps;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mHasLauncherApps = getDevice().getApiLevel() >= 21;
+
+        if (mHasLauncherApps) {
+            installTestApps();
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mHasLauncherApps) {
+            uninstallTestApps();
+        }
+        super.tearDown();
+    }
+
+    public void testInstallAppMainUser() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS, "testSimpleAppInstalledForUser",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageAddedMainUser() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        startCallbackService();
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageAddedCallbackForUser",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageRemovedMainUser() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            startCallbackService();
+            int serialNumber = getUserSerialNumber(0);
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageRemovedCallbackForUser",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherCallbackPackageChangedMainUser() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            startCallbackService();
+            int serialNumber = getUserSerialNumber(0);
+            installApp(SIMPLE_APP_APK);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS,
+                            "testPackageChangedCallbackForUser",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLauncherNonExportedAppFails() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS, "testLaunchNonExportActivityFails",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLaunchNonExportActivityFails() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS, "testLaunchNonExportLauncherFails",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+
+    public void testLaunchMainActivity() throws Exception {
+        if (!mHasLauncherApps) {
+            return;
+        }
+        installApp(SIMPLE_APP_APK);
+        try {
+            int serialNumber = getUserSerialNumber(0);
+            assertTrue(runDeviceTests(LAUNCHER_TESTS_PKG,
+                    LAUNCHER_TESTS_CLASS, "testLaunchMainActivity",
+                            0, "-e testUser " + serialNumber));
+        } finally {
+            getDevice().uninstallPackage(SIMPLE_APP_PKG);
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 6ece85c..2ef7cad 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -37,6 +37,7 @@
     private static final String ADMIN_RECEIVER_TEST_CLASS =
             MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
 
+    private static final String FEATURE_BLUETOOTH = "android.hardware.bluetooth";
     private int mUserId;
 
     @Override
@@ -49,6 +50,8 @@
         if (mHasFeature) {
             mUserId = createManagedProfile();
             installApp(MANAGED_PROFILE_APK);
+            installApp(INTENT_RECEIVER_APK);
+            installApp(INTENT_SENDER_APK);
             setProfileOwner(MANAGED_PROFILE_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
             startUser(mUserId);
         }
@@ -59,8 +62,9 @@
         if (mHasFeature) {
             removeUser(mUserId);
             getDevice().uninstallPackage(MANAGED_PROFILE_PKG);
+            getDevice().uninstallPackage(INTENT_SENDER_PKG);
+            getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
         }
-
         super.tearDown();
     }
 
@@ -106,7 +110,14 @@
               + getDevice().executeShellCommand(command));
         assertTrue(runDeviceTests(MANAGED_PROFILE_PKG, MANAGED_PROFILE_PKG + ".PrimaryUserTest"));
         // TODO: Test with startActivity
-        // TODO: Test with CtsVerifier for disambiguation cases
+    }
+
+    public void testSettingsIntents() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".SettingsIntentsTest", mUserId));
     }
 
     public void testCrossProfileContent() throws Exception {
@@ -114,30 +125,58 @@
             return;
         }
 
-        try {
-            getDevice().uninstallPackage(INTENT_SENDER_PKG);
-            getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
-            installAppAsUser(INTENT_SENDER_APK, 0);
-            installAppAsUser(INTENT_RECEIVER_APK, mUserId);
+        // Test from parent to managed
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "removeAllFilters", mUserId));
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "addManagedCanAccessParentFilters", mUserId));
+        assertTrue(runDeviceTestsAsUser(INTENT_SENDER_PKG, ".ContentTest", 0));
 
-            // Test from parent to managed
-            assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
-                    "addManagedCanAccessParentFilters", mUserId));
-            assertTrue(runDeviceTestsAsUser(INTENT_SENDER_PKG, ".IntentSenderTest", 0));
+        // Test from managed to parent
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "removeAllFilters", mUserId));
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "addParentCanAccessManagedFilters", mUserId));
+        assertTrue(runDeviceTestsAsUser(INTENT_SENDER_PKG, ".ContentTest", mUserId));
 
-            getDevice().uninstallPackage(INTENT_SENDER_PKG);
-            getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
-            installAppAsUser(INTENT_SENDER_APK, mUserId);
-            installAppAsUser(INTENT_RECEIVER_APK, 0);
+    }
 
-            // Test from managed to parent
-            assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
-                    "addParentCanAccessManagedFilters", mUserId));
-            assertTrue(runDeviceTestsAsUser(INTENT_SENDER_PKG, ".IntentSenderTest", mUserId));
+    public void testCrossProfileCopyPaste() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
 
-        } finally {
-            getDevice().uninstallPackage(INTENT_SENDER_PKG);
-            getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "allowCrossProfileCopyPaste", mUserId));
+        // Test that managed can see what is copied in the parent.
+        testCrossProfileCopyPasteInternal(mUserId, true);
+        // Test that the parent can see what is copied in managed.
+        testCrossProfileCopyPasteInternal(0, true);
+
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "disallowCrossProfileCopyPaste", mUserId));
+        // Test that managed can still see what is copied in the parent.
+        testCrossProfileCopyPasteInternal(mUserId, true);
+        // Test that the parent cannot see what is copied in managed.
+        testCrossProfileCopyPasteInternal(0, false);
+    }
+
+    private void testCrossProfileCopyPasteInternal(int userId, boolean shouldSucceed)
+            throws DeviceNotAvailableException {
+        final String direction = (userId == 0) ? "addManagedCanAccessParentFilters"
+                : "addParentCanAccessManagedFilters";
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "removeAllFilters", mUserId));
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                direction, mUserId));
+        if (shouldSucceed) {
+            assertTrue(runDeviceTestsAsUser(INTENT_SENDER_PKG, ".CopyPasteTest",
+                    "testCanReadAcrossProfiles", userId));
+            assertTrue(runDeviceTestsAsUser(INTENT_SENDER_PKG, ".CopyPasteTest",
+                    "testIsNotified", userId));
+        } else {
+            assertTrue(runDeviceTestsAsUser(INTENT_SENDER_PKG, ".CopyPasteTest",
+                    "testCannotReadAcrossProfiles", userId));
         }
     }
 
@@ -167,6 +206,23 @@
                 addRestrictionCommandOutput.contains("SecurityException"));
     }
 
+    // Test the bluetooth API from a managed profile.
+    public void testBluetooth() throws Exception {
+        boolean mHasBluetooth = hasDeviceFeatures(new String[] {FEATURE_BLUETOOTH});
+        if (!mHasFeature || !mHasBluetooth) {
+            return ;
+        }
+
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".BluetoothTest",
+                "testEnableDisable", mUserId));
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".BluetoothTest",
+                "testGetAddress", mUserId));
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".BluetoothTest",
+                "testListenUsingRfcommWithServiceRecord", mUserId));
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".BluetoothTest",
+                "testGetRemoteDevice", mUserId));
+    }
+
     private void disableActivityForUser(String activityName, int userId)
             throws DeviceNotAvailableException {
         String command = "am start -W --user " + userId
@@ -189,27 +245,4 @@
                 "Output for command " + adbCommand + ": " + commandOutput);
         return commandOutput;
     }
-
-    private int createManagedProfile() throws DeviceNotAvailableException {
-        String command =
-                "pm create-user --profileOf 0 --managed TestProfile_" + System.currentTimeMillis();
-        String commandOutput = getDevice().executeShellCommand(command);
-        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
-
-        // Extract the id of the new user.
-        String[] tokens = commandOutput.split("\\s+");
-        assertTrue(commandOutput + " expected to have format \"Success: {USER_ID}\"",
-                tokens.length > 0);
-        assertEquals(commandOutput, "Success:", tokens[0]);
-        return Integer.parseInt(tokens[tokens.length-1]);
-    }
-
-    private void setProfileOwner(String componentName, int userId)
-            throws DeviceNotAvailableException {
-        String command = "dpm set-profile-owner '" + componentName + "' " + userId;
-        String commandOutput = getDevice().executeShellCommand(command);
-        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
-        assertTrue(commandOutput + " expected to start with \"Success:\"",
-                commandOutput.startsWith("Success:"));
-    }
 }
diff --git a/hostsidetests/dumpsys/Android.mk b/hostsidetests/dumpsys/Android.mk
new file mode 100644
index 0000000..51ea31f
--- /dev/null
+++ b/hostsidetests/dumpsys/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+# Must match the package name in CtsTestCaseList.mk
+LOCAL_MODULE := CtsDumpsysHostTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt
+
+LOCAL_CTS_TEST_PACKAGE := android.host.dumpsys
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java
new file mode 100644
index 0000000..94862e7
--- /dev/null
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/DumpsysHostTest.java
@@ -0,0 +1,835 @@
+/*
+ * 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.dumpsys.cts;
+
+import com.android.ddmlib.Log;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceTestCase;
+
+import java.io.BufferedReader;
+import java.io.StringReader;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Test to check the format of the dumps of various services (currently only procstats is tested).
+ */
+public class DumpsysHostTest extends DeviceTestCase {
+    private static final String TAG = "DumpsysHostTest";
+
+    /**
+     * A reference to the device under test.
+     */
+    private ITestDevice mDevice;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mDevice = getDevice();
+    }
+
+    /**
+     * Tests the output of "dumpsys procstats -c". This is a proxy for testing "dumpsys procstats
+     * --checkin", since the latter is not idempotent.
+     *
+     * @throws Exception
+     */
+    public void testProcstatsOutput() throws Exception {
+        if (mDevice.getApiLevel() < 19) {
+            Log.i(TAG, "No Procstats output before KitKat, skipping test.");
+            return;
+        }
+
+        String procstats = mDevice.executeShellCommand("dumpsys procstats -c");
+        assertNotNull(procstats);
+        assertTrue(procstats.length() > 0);
+
+        Set<String> seenTags = new HashSet<>();
+        int version = -1;
+
+        try (BufferedReader reader = new BufferedReader(
+                new StringReader(procstats))) {
+
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (line.isEmpty()) {
+                    continue;
+                }
+
+                // extra space to make sure last column shows up.
+                if (line.endsWith(",")) {
+                  line = line + " ";
+                }
+                String[] parts = line.split(",");
+                seenTags.add(parts[0]);
+
+                switch (parts[0]) {
+                    case "vers":
+                        assertEquals(2, parts.length);
+                        version = Integer.parseInt(parts[1]);
+                        break;
+                    case "period":
+                        checkPeriod(parts);
+                        break;
+                    case "pkgproc":
+                        checkPkgProc(parts, version);
+                        break;
+                    case "pkgpss":
+                        checkPkgPss(parts, version);
+                        break;
+                    case "pkgsvc-bound":
+                    case "pkgsvc-exec":
+                    case "pkgsvc-run":
+                    case "pkgsvc-start":
+                        checkPkgSvc(parts, version);
+                        break;
+                    case "pkgkills":
+                        checkPkgKills(parts, version);
+                        break;
+                    case "proc":
+                        checkProc(parts);
+                        break;
+                    case "pss":
+                        checkPss(parts);
+                        break;
+                    case "kills":
+                        checkKills(parts);
+                        break;
+                    case "total":
+                        checkTotal(parts);
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        // spot check a few tags
+        assertSeenTag(seenTags, "pkgproc");
+        assertSeenTag(seenTags, "proc");
+        assertSeenTag(seenTags, "pss");
+        assertSeenTag(seenTags, "total");
+    }
+
+    private void checkPeriod(String[] parts) {
+        assertEquals(5, parts.length);
+        assertNotNull(parts[1]); // date
+        assertInteger(parts[2]); // start time (msec)
+        assertInteger(parts[3]); // end time (msec)
+        assertNotNull(parts[4]); // status
+    }
+
+    private void checkPkgProc(String[] parts, int version) {
+        int statesStartIndex;
+
+        if (version < 4) {
+            assertTrue(parts.length >= 4);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertNotNull(parts[3]); // process
+            statesStartIndex = 4;
+        } else {
+            assertTrue(parts.length >= 5);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertInteger(parts[3]); // app version
+            assertNotNull(parts[4]); // process
+            statesStartIndex = 5;
+        }
+
+        for (int i = statesStartIndex; i < parts.length; i++) {
+            String[] subparts = parts[i].split(":");
+            assertEquals(2, subparts.length);
+            checkTag(subparts[0], true); // tag
+            assertInteger(subparts[1]); // duration (msec)
+        }
+    }
+
+    private void checkTag(String tag, boolean hasProcess) {
+        assertEquals(hasProcess ? 3 : 2, tag.length());
+
+        // screen: 0 = off, 1 = on
+        char s = tag.charAt(0);
+        if (s != '0' && s != '1') {
+            fail("malformed tag: " + tag);
+        }
+
+        // memory: n = normal, m = moderate, l = low, c = critical
+        char m = tag.charAt(1);
+        if (m != 'n' && m != 'm' && m != 'l' && m != 'c') {
+            fail("malformed tag: " + tag);
+        }
+
+        if (hasProcess) {
+            char p = tag.charAt(2);
+            assertTrue("malformed tag: " + tag, p >= 'a' && p <= 'z');
+        }
+    }
+
+    private void checkPkgPss(String[] parts, int version) {
+        int statesStartIndex;
+
+        if (version < 4) {
+            assertTrue(parts.length >= 4);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertNotNull(parts[3]); // process
+            statesStartIndex = 4;
+        } else {
+            assertTrue(parts.length >= 5);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertInteger(parts[3]); // app version
+            assertNotNull(parts[4]); // process
+            statesStartIndex = 5;
+        }
+
+        for (int i = statesStartIndex; i < parts.length; i++) {
+            String[] subparts = parts[i].split(":");
+            assertEquals(8, subparts.length);
+            checkTag(subparts[0], true); // tag
+            assertInteger(subparts[1]); // sample size
+            assertInteger(subparts[2]); // pss min
+            assertInteger(subparts[3]); // pss avg
+            assertInteger(subparts[4]); // pss max
+            assertInteger(subparts[5]); // uss min
+            assertInteger(subparts[6]); // uss avg
+            assertInteger(subparts[7]); // uss max
+        }
+    }
+
+    private void checkPkgSvc(String[] parts, int version) {
+        int statesStartIndex;
+
+        if (version < 4) {
+            assertTrue(parts.length >= 5);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertNotNull(parts[3]); // service name
+            assertInteger(parts[4]); // count
+            statesStartIndex = 5;
+        } else {
+            assertTrue(parts.length >= 6);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertInteger(parts[3]); // app version
+            assertNotNull(parts[4]); // service name
+            assertInteger(parts[5]); // count
+            statesStartIndex = 6;
+        }
+
+        for (int i = statesStartIndex; i < parts.length; i++) {
+            String[] subparts = parts[i].split(":");
+            assertEquals(2, subparts.length);
+            checkTag(subparts[0], false); // tag
+            assertInteger(subparts[1]); // duration (msec)
+        }
+    }
+
+    private void checkPkgKills(String[] parts, int version) {
+        String pssStr;
+
+        if (version < 4) {
+            assertEquals(8, parts.length);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertNotNull(parts[3]); // process
+            assertInteger(parts[4]); // wakes
+            assertInteger(parts[5]); // cpu
+            assertInteger(parts[6]); // cached
+            pssStr = parts[7];
+        } else {
+            assertEquals(9, parts.length);
+            assertNotNull(parts[1]); // package name
+            assertInteger(parts[2]); // uid
+            assertInteger(parts[3]); // app version
+            assertNotNull(parts[4]); // process
+            assertInteger(parts[5]); // wakes
+            assertInteger(parts[6]); // cpu
+            assertInteger(parts[7]); // cached
+            pssStr = parts[8];
+        }
+
+        String[] subparts = pssStr.split(":");
+        assertEquals(3, subparts.length);
+        assertInteger(subparts[0]); // pss min
+        assertInteger(subparts[1]); // pss avg
+        assertInteger(subparts[2]); // pss max
+    }
+
+    private void checkProc(String[] parts) {
+        assertTrue(parts.length >= 3);
+        assertNotNull(parts[1]); // package name
+        assertInteger(parts[2]); // uid
+
+        for (int i = 3; i < parts.length; i++) {
+            String[] subparts = parts[i].split(":");
+            assertEquals(2, subparts.length);
+            checkTag(subparts[0], true); // tag
+            assertInteger(subparts[1]); // duration (msec)
+        }
+    }
+
+    private void checkPss(String[] parts) {
+        assertTrue(parts.length >= 3);
+        assertNotNull(parts[1]); // package name
+        assertInteger(parts[2]); // uid
+
+        for (int i = 3; i < parts.length; i++) {
+            String[] subparts = parts[i].split(":");
+            assertEquals(8, subparts.length);
+            checkTag(subparts[0], true); // tag
+            assertInteger(subparts[1]); // sample size
+            assertInteger(subparts[2]); // pss min
+            assertInteger(subparts[3]); // pss avg
+            assertInteger(subparts[4]); // pss max
+            assertInteger(subparts[5]); // uss min
+            assertInteger(subparts[6]); // uss avg
+            assertInteger(subparts[7]); // uss max
+        }
+    }
+
+    private void checkKills(String[] parts) {
+        assertEquals(7, parts.length);
+        assertNotNull(parts[1]); // package name
+        assertInteger(parts[2]); // uid
+        assertInteger(parts[3]); // wakes
+        assertInteger(parts[4]); // cpu
+        assertInteger(parts[5]); // cached
+        String pssStr = parts[6];
+
+        String[] subparts = pssStr.split(":");
+        assertEquals(3, subparts.length);
+        assertInteger(subparts[0]); // pss min
+        assertInteger(subparts[1]); // pss avg
+        assertInteger(subparts[2]); // pss max
+    }
+
+    private void checkTotal(String[] parts) {
+        assertTrue(parts.length >= 2);
+        for (int i = 1; i < parts.length; i++) {
+            String[] subparts = parts[i].split(":");
+            checkTag(subparts[0], false); // tag
+
+            if (subparts[1].contains("sysmemusage")) {
+                break; // see b/18340771
+            }
+            assertInteger(subparts[1]); // duration (msec)
+        }
+    }
+
+    /**
+     * Tests the output of "dumpsys batterystats --checkin".
+     *
+     * @throws Exception
+     */
+    public void testBatterystatsOutput() throws Exception {
+        if (mDevice.getApiLevel() < 21) {
+            Log.i(TAG, "Batterystats output before Lollipop, skipping test.");
+            return;
+        }
+
+        String batterystats = mDevice.executeShellCommand("dumpsys batterystats --checkin");
+        assertNotNull(batterystats);
+        assertTrue(batterystats.length() > 0);
+
+        Set<String> seenTags = new HashSet<>();
+        int version = -1;
+
+        try (BufferedReader reader = new BufferedReader(
+                new StringReader(batterystats))) {
+
+            String line;
+            while ((line = reader.readLine()) != null) {
+                if (line.isEmpty()) {
+                    continue;
+                }
+
+                String[] parts = line.split(",");
+                assertInteger(parts[0]); // old version
+                assertInteger(parts[1]); // UID
+                switch (parts[2]) { // aggregation type
+                    case "i":
+                    case "l":
+                    case "c":
+                    case "u":
+                        break;
+                    default:
+                        fail("malformed stat: " + parts[2]);
+                }
+                assertNotNull(parts[3]);
+                seenTags.add(parts[3]);
+
+                // Note the time fields are measured in milliseconds by default.
+                switch (parts[3]) {
+                    case "vers":
+                        checkVersion(parts);
+                        break;
+                    case "uid":
+                        checkUid(parts);
+                        break;
+                    case "apk":
+                        checkApk(parts);
+                        break;
+                    case "pr":
+                        checkProcess(parts);
+                        break;
+                    case "sr":
+                        checkSensor(parts);
+                        break;
+                    case "vib":
+                        checkVibrator(parts);
+                        break;
+                    case "fg":
+                        checkForeground(parts);
+                        break;
+                    case "st":
+                        checkStateTime(parts);
+                        break;
+                    case "wl":
+                        checkWakelock(parts);
+                        break;
+                    case "sy":
+                        checkSync(parts);
+                        break;
+                    case "jb":
+                        checkJob(parts);
+                        break;
+                    case "kwl":
+                        checkKernelWakelock(parts);
+                        break;
+                    case "wr":
+                        checkWakeupReason(parts);
+                        break;
+                    case "nt":
+                        checkNetwork(parts);
+                        break;
+                    case "ua":
+                        checkUserActivity(parts);
+                        break;
+                    case "bt":
+                        checkBattery(parts);
+                        break;
+                    case "dc":
+                        checkBatteryDischarge(parts);
+                        break;
+                    case "lv":
+                        checkBatteryLevel(parts);
+                        break;
+                    case "wfl":
+                        checkWifi(parts);
+                        break;
+                    case "m":
+                        checkMisc(parts);
+                        break;
+                    case "gn":
+                        checkGlobalNetwork(parts);
+                        break;
+                    case "br":
+                        checkScreenBrightness(parts);
+                        break;
+                    case "sgt":
+                    case "sgc":
+                        checkSignalStrength(parts);
+                        break;
+                    case "sst":
+                        checkSignalScanningTime(parts);
+                        break;
+                    case "dct":
+                    case "dcc":
+                        checkDataConnection(parts);
+                        break;
+                    case "wst":
+                    case "wsc":
+                        checkWifiState(parts);
+                        break;
+                    case "wsst":
+                    case "wssc":
+                        checkWifiSupplState(parts);
+                        break;
+                    case "wsgt":
+                    case "wsgc":
+                        checkWifiSignalStrength(parts);
+                        break;
+                    case "bst":
+                    case "bsc":
+                        checkBluetoothState(parts);
+                        break;
+                    case "pws":
+                        checkPowerUseSummary(parts);
+                        break;
+                    case "pwi":
+                        checkPowerUseItem(parts);
+                        break;
+                    case "dsd":
+                    case "csd":
+                        checkChargeDischargeStep(parts);
+                        break;
+                    case "dtr":
+                        checkDischargeTimeRemain(parts);
+                        break;
+                    case "ctr":
+                        checkChargeTimeRemain(parts);
+                        break;
+                    default:
+                        break;
+                }
+            }
+        }
+
+        // spot check a few tags
+        assertSeenTag(seenTags, "vers");
+        assertSeenTag(seenTags, "bt");
+        assertSeenTag(seenTags, "dc");
+        assertSeenTag(seenTags, "m");
+    }
+
+    private void checkVersion(String[] parts) {
+        assertEquals(8, parts.length);
+        assertInteger(parts[4]); // checkinVersion
+        assertInteger(parts[5]); // parcelVersion
+        assertNotNull(parts[6]); // startPlatformVersion
+        assertNotNull(parts[7]); // endPlatformVersion
+    }
+
+    private void checkUid(String[] parts) {
+        assertEquals(6, parts.length);
+        assertInteger(parts[4]); // uid
+        assertNotNull(parts[5]); // pkgName
+    }
+
+    private void checkApk(String[] parts) {
+        assertEquals(10, parts.length);
+        assertInteger(parts[4]); // wakeups
+        assertNotNull(parts[5]); // apk
+        assertNotNull(parts[6]); // service
+        assertInteger(parts[7]); // startTime
+        assertInteger(parts[8]); // starts
+        assertInteger(parts[9]); // launches
+    }
+
+    private void checkProcess(String[] parts) {
+        assertTrue(parts.length >= 9);
+        assertNotNull(parts[4]); // process
+        assertInteger(parts[5]); // userMillis
+        assertInteger(parts[6]); // systemMillis
+        assertInteger(parts[7]); // foregroundMillis
+        assertInteger(parts[8]); // starts
+    }
+
+    private void checkSensor(String[] parts) {
+        assertEquals(7, parts.length);
+        assertInteger(parts[4]); // sensorNumber
+        assertInteger(parts[5]); // totalTime
+        assertInteger(parts[6]); // count
+    }
+
+    private void checkVibrator(String[] parts) {
+        assertEquals(6, parts.length);
+        assertInteger(parts[4]); // totalTime
+        assertInteger(parts[5]); // count
+    }
+
+    private void checkForeground(String[] parts) {
+        assertEquals(6, parts.length);
+        assertInteger(parts[4]); // totalTime
+        assertInteger(parts[5]); // count
+    }
+
+    private void checkStateTime(String[] parts) {
+        assertEquals(7, parts.length);
+        assertInteger(parts[4]); // foreground
+        assertInteger(parts[5]); // active
+        assertInteger(parts[6]); // running
+    }
+
+    private void checkWakelock(String[] parts) {
+        assertEquals(14, parts.length);
+        assertNotNull(parts[4]);      // wakelock
+        assertInteger(parts[5]);      // full totalTime
+        assertEquals("f", parts[6]);  // full
+        assertInteger(parts[7]);      // full count
+        assertInteger(parts[8]);      // partial totalTime
+        assertEquals("p", parts[9]);  // partial
+        assertInteger(parts[10]);     // partial count
+        assertInteger(parts[11]);     // window totalTime
+        assertEquals("w", parts[12]); // window
+        assertInteger(parts[13]);     // window count
+    }
+
+    private void checkSync(String[] parts) {
+        assertEquals(7, parts.length);
+        assertNotNull(parts[4]); // sync
+        assertInteger(parts[5]); // totalTime
+        assertInteger(parts[6]); // count
+    }
+
+    private void checkJob(String[] parts) {
+        assertEquals(7, parts.length);
+        assertNotNull(parts[4]); // job
+        assertInteger(parts[5]); // totalTime
+        assertInteger(parts[6]); // count
+    }
+
+    private void checkKernelWakelock(String[] parts) {
+        assertEquals(7, parts.length);
+        assertNotNull(parts[4]); // kernel wakelock
+        assertInteger(parts[5]); // totalTime
+        assertInteger(parts[6]); // count
+    }
+
+    private void checkWakeupReason(String[] parts) {
+        assertTrue(parts.length >= 7);
+        for (int i = 4; i < parts.length-2; i++) {
+            assertNotNull(parts[i]); // part of wakeup
+        }
+        assertInteger(parts[parts.length-2]); // totalTime
+        assertInteger(parts[parts.length-1]); // count
+    }
+
+    private void checkNetwork(String[] parts) {
+        assertEquals(14, parts.length);
+        assertInteger(parts[4]);  // mobileBytesRx
+        assertInteger(parts[5]);  // mobileBytesTx
+        assertInteger(parts[6]);  // wifiBytesRx
+        assertInteger(parts[7]);  // wifiBytesTx
+        assertInteger(parts[8]);  // mobilePacketsRx
+        assertInteger(parts[9]);  // mobilePacketsTx
+        assertInteger(parts[10]); // wifiPacketsRx
+        assertInteger(parts[11]); // wifiPacketsTx
+        assertInteger(parts[12]); // mobileActiveTime (usec)
+        assertInteger(parts[13]); // mobileActiveCount
+    }
+
+    private void checkUserActivity(String[] parts) {
+        assertEquals(7, parts.length);
+        assertInteger(parts[4]); // other
+        assertInteger(parts[5]); // button
+        assertInteger(parts[6]); // touch
+    }
+
+    private void checkBattery(String[] parts) {
+        assertEquals(12, parts.length);
+        if (!parts[4].equals("N/A")) {
+            assertInteger(parts[4]);  // startCount
+        }
+        assertInteger(parts[5]);  // batteryRealtime
+        assertInteger(parts[6]);  // batteryUptime
+        assertInteger(parts[7]);  // totalRealtime
+        assertInteger(parts[8]);  // totalUptime
+        assertInteger(parts[9]);  // startClockTime
+        assertInteger(parts[10]); // batteryScreenOffRealtime
+        assertInteger(parts[11]); // batteryScreenOffUptime
+    }
+
+    private void checkBatteryDischarge(String[] parts) {
+        assertEquals(8, parts.length);
+        assertInteger(parts[4]); // low
+        assertInteger(parts[5]); // high
+        assertInteger(parts[6]); // screenOn
+        assertInteger(parts[7]); // screenOff
+    }
+
+    private void checkBatteryLevel(String[] parts) {
+        assertEquals(6, parts.length);
+        assertInteger(parts[4]); // startLevel
+        assertInteger(parts[5]); // currentLevel
+    }
+
+    private void checkWifi(String[] parts) {
+        assertEquals(7, parts.length);
+        assertInteger(parts[4]); // fullWifiLockOnTime (usec)
+        assertInteger(parts[5]); // wifiScanTime (usec)
+        assertInteger(parts[6]); // uidWifiRunningTime (usec)
+    }
+
+    private void checkMisc(String[] parts) {
+        assertTrue(parts.length >= 20);
+        assertInteger(parts[4]);      // screenOnTime
+        assertInteger(parts[5]);      // phoneOnTime
+        assertInteger(parts[6]);      // wifiOnTime
+        assertInteger(parts[7]);      // wifiRunningTime
+        assertInteger(parts[8]);      // bluetoothOnTime
+        assertInteger(parts[9]);      // mobileRxTotalBytes
+        assertInteger(parts[10]);     // mobileTxTotalBytes
+        assertInteger(parts[11]);     // wifiRxTotalBytes
+        assertInteger(parts[12]);     // wifiTxTotalBytes
+        assertInteger(parts[13]);     // fullWakeLockTimeTotal
+        assertInteger(parts[14]);     // partialWakeLockTimeTotal
+        assertEquals("0", parts[15]); // legacy input event count
+        assertInteger(parts[16]);     // mobileRadioActiveTime
+        assertInteger(parts[17]);     // mobileRadioActiveAdjustedTime
+        assertInteger(parts[18]);     // interactiveTime
+        assertInteger(parts[19]);     // lowPowerModeEnabledTime
+    }
+
+    private void checkGlobalNetwork(String[] parts) {
+        assertEquals(12, parts.length);
+        assertInteger(parts[4]);  // mobileRxTotalBytes
+        assertInteger(parts[5]);  // mobileTxTotalBytes
+        assertInteger(parts[6]);  // wifiRxTotalBytes
+        assertInteger(parts[7]);  // wifiTxTotalBytes
+        assertInteger(parts[8]);  // mobileRxTotalPackets
+        assertInteger(parts[9]);  // mobileTxTotalPackets
+        assertInteger(parts[10]); // wifiRxTotalPackets
+        assertInteger(parts[11]); // wifiTxTotalPackets
+    }
+
+    private void checkScreenBrightness(String[] parts) {
+        assertEquals(9, parts.length);
+        assertInteger(parts[4]); // dark
+        assertInteger(parts[5]); // dim
+        assertInteger(parts[6]); // medium
+        assertInteger(parts[7]); // light
+        assertInteger(parts[8]); // bright
+    }
+
+    private void checkSignalStrength(String[] parts) {
+        assertEquals(9, parts.length);
+        assertInteger(parts[4]); // none
+        assertInteger(parts[5]); // poor
+        assertInteger(parts[6]); // moderate
+        assertInteger(parts[7]); // good
+        assertInteger(parts[8]); // great
+    }
+
+    private void checkSignalScanningTime(String[] parts) {
+        assertEquals(5, parts.length);
+        assertInteger(parts[4]); // signalScanningTime
+    }
+
+    private void checkDataConnection(String[] parts) {
+        assertEquals(21, parts.length);
+        assertInteger(parts[4]);  // none
+        assertInteger(parts[5]);  // gprs
+        assertInteger(parts[6]);  // edge
+        assertInteger(parts[7]);  // umts
+        assertInteger(parts[8]);  // cdma
+        assertInteger(parts[9]);  // evdo_0
+        assertInteger(parts[10]); // evdo_A
+        assertInteger(parts[11]); // 1xrtt
+        assertInteger(parts[12]); // hsdpa
+        assertInteger(parts[13]); // hsupa
+        assertInteger(parts[14]); // hspa
+        assertInteger(parts[15]); // iden
+        assertInteger(parts[16]); // evdo_b
+        assertInteger(parts[17]); // lte
+        assertInteger(parts[18]); // ehrpd
+        assertInteger(parts[19]); // hspap
+        assertInteger(parts[20]); // other
+    }
+
+    private void checkWifiState(String[] parts) {
+        assertEquals(12, parts.length);
+        assertInteger(parts[4]);  // off
+        assertInteger(parts[5]);  // scanning
+        assertInteger(parts[6]);  // no_net
+        assertInteger(parts[7]);  // disconn
+        assertInteger(parts[8]);  // sta
+        assertInteger(parts[9]);  // p2p
+        assertInteger(parts[10]); // sta_p2p
+        assertInteger(parts[11]); // soft_ap
+    }
+
+    private void checkWifiSupplState(String[] parts) {
+        assertEquals(17, parts.length);
+        assertInteger(parts[4]);  // inv
+        assertInteger(parts[5]);  // dsc
+        assertInteger(parts[6]);  // dis
+        assertInteger(parts[7]);  // inact
+        assertInteger(parts[8]);  // scan
+        assertInteger(parts[9]);  // auth
+        assertInteger(parts[10]); // ascing
+        assertInteger(parts[11]); // asced
+        assertInteger(parts[12]); // 4-way
+        assertInteger(parts[13]); // group
+        assertInteger(parts[14]); // compl
+        assertInteger(parts[15]); // dorm
+        assertInteger(parts[16]); // uninit
+    }
+
+    private void checkWifiSignalStrength(String[] parts) {
+        assertEquals(9, parts.length);
+        assertInteger(parts[4]); // none
+        assertInteger(parts[5]); // poor
+        assertInteger(parts[6]); // moderate
+        assertInteger(parts[7]); // good
+        assertInteger(parts[8]); // great
+    }
+
+    private void checkBluetoothState(String[] parts) {
+        assertEquals(8, parts.length);
+        assertInteger(parts[4]); // inactive
+        assertInteger(parts[5]); // low
+        assertInteger(parts[6]); // med
+        assertInteger(parts[7]); // high
+    }
+
+    private void checkPowerUseSummary(String[] parts) {
+        assertEquals(8, parts.length);
+        assertDouble(parts[4]); // batteryCapacity
+        assertDouble(parts[5]); // computedPower
+        assertDouble(parts[6]); // minDrainedPower
+        assertDouble(parts[7]); // maxDrainedPower
+    }
+
+    private void checkPowerUseItem(String[] parts) {
+        assertEquals(6, parts.length);
+        assertNotNull(parts[4]); // label
+        assertDouble(parts[5]);  // mAh
+    }
+
+    private void checkChargeDischargeStep(String[] parts) {
+        assertEquals(8, parts.length);
+        assertInteger(parts[4]); // duration
+        if (!parts[5].equals("?")) {
+            assertInteger(parts[5]); // level
+        }
+        assertNotNull(parts[6]); // screen
+        assertNotNull(parts[7]); // power-save
+    }
+
+    private void checkDischargeTimeRemain(String[] parts) {
+        assertEquals(5, parts.length);
+        assertInteger(parts[4]); // batteryTimeRemaining
+    }
+
+    private void checkChargeTimeRemain(String[] parts) {
+        assertEquals(5, parts.length);
+        assertInteger(parts[4]); // chargeTimeRemaining
+    }
+
+    private static void assertInteger(String input) {
+        try {
+            Long.parseLong(input);
+        } catch (NumberFormatException e) {
+            fail("Expected an integer but found \"" + input + "\"");
+        }
+    }
+
+    private static void assertDouble(String input) {
+        try {
+            Double.parseDouble(input);
+        } catch (NumberFormatException e) {
+            fail("Expected a double but found \"" + input + "\"");
+        }
+    }
+
+    private static void assertSeenTag(Set<String> seenTags, String tag) {
+        assertTrue("No line starting with \"" + tag + ",\"", seenTags.contains(tag));
+    }
+}
diff --git a/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java b/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java
index f141d8f..997f7c6 100644
--- a/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java
+++ b/hostsidetests/monkey/src/com/android/cts/monkey/MonkeyTest.java
@@ -37,7 +37,8 @@
     }
 
     private void assertIsUserAMonkey(boolean isMonkey) throws DeviceNotAvailableException {
-        String logs = mDevice.executeAdbCommand("logcat", "-d", "MonkeyActivity:I", "*:S");
+        String logs = mDevice.executeAdbCommand(
+                "logcat", "-v", "brief", "-d", "MonkeyActivity:I", "*:S");
         boolean monkeyLogsFound = false;
         Scanner s = new Scanner(logs);
         try {
diff --git a/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml b/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml
index 849d6a4..a3dfc83 100644
--- a/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml
+++ b/hostsidetests/monkey/test-apps/CtsMonkeyApp/AndroidManifest.xml
@@ -18,14 +18,14 @@
 
     <application android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
 
-        <activity android:name=".MonkeyActivity" android:screenOrientation="portrait">
+        <activity android:name=".MonkeyActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
         
-        <activity android:name=".BaboonActivity" android:screenOrientation="portrait">
+        <activity android:name=".BaboonActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.MONKEY" />
diff --git a/hostsidetests/net/Android.mk b/hostsidetests/net/Android.mk
new file mode 100644
index 0000000..6637d61
--- /dev/null
+++ b/hostsidetests/net/Android.mk
@@ -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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := CtsHostsideNetworkTests
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt
+
+LOCAL_CTS_TEST_PACKAGE := android.net.hostsidenetwork
+
+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/net/app/Android.mk b/hostsidetests/net/app/Android.mk
new file mode 100644
index 0000000..29b620d
--- /dev/null
+++ b/hostsidetests/net/app/Android.mk
@@ -0,0 +1,32 @@
+#
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := current
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner ub-uiautomator
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsHostsideNetworkTestsApp
+
+LOCAL_PROGUARD_ENABLED := disabled
+LOCAL_DEX_PREOPT := false
+
+include $(BUILD_PACKAGE)
diff --git a/hostsidetests/net/app/AndroidManifest.xml b/hostsidetests/net/app/AndroidManifest.xml
new file mode 100644
index 0000000..cdde7dc
--- /dev/null
+++ b/hostsidetests/net/app/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.net.hostside">
+
+    <uses-permission android:name="android.permission.INTERNET"/>
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name=".MyActivity" />
+        <service android:name=".MyVpnService"
+                android:permission="android.permission.BIND_VPN_SERVICE">
+            <intent-filter>
+                <action android:name="android.net.VpnService"/>
+            </intent-filter>
+        </service>
+    </application>
+
+    <instrumentation
+        android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.net.hostside" />
+
+</manifest>
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/MyActivity.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/MyActivity.java
new file mode 100644
index 0000000..375c852
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/MyActivity.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.VpnService;
+import android.os.Bundle;
+import android.os.ParcelFileDescriptor;
+import android.view.WindowManager;
+
+import java.util.Arrays;
+import java.util.ArrayList;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class MyActivity extends Activity {
+    private final LinkedBlockingQueue<Integer> mResult = new LinkedBlockingQueue<>(1);
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (mResult.offer(resultCode) == false) {
+            throw new RuntimeException("Queue is full! This should never happen");
+        }
+    }
+
+    public Integer getResult(int timeoutMs) throws InterruptedException {
+        return mResult.poll(timeoutMs, TimeUnit.MILLISECONDS);
+    }
+}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/MyVpnService.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/MyVpnService.java
new file mode 100644
index 0000000..a3f400c
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/MyVpnService.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside;
+
+import android.content.Intent;
+import android.net.VpnService;
+import android.os.ParcelFileDescriptor;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+
+public class MyVpnService extends VpnService {
+
+    private static String TAG = "MyVpnService";
+    private static int MTU = 1799;
+
+    private ParcelFileDescriptor mFd = null;
+    private PacketReflector mPacketReflector = null;
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        String packageName = getPackageName();
+        String cmd = intent.getStringExtra(packageName + ".cmd");
+        if ("disconnect".equals(cmd)) {
+            stop();
+        } else if ("connect".equals(cmd)) {
+            start(packageName, intent);
+        }
+
+        return START_NOT_STICKY;
+    }
+
+    private void start(String packageName, Intent intent) {
+        Builder builder = new Builder();
+
+        String addresses = intent.getStringExtra(packageName + ".addresses");
+        if (addresses != null) {
+            String[] addressArray = addresses.split(",");
+            for (int i = 0; i < addressArray.length; i++) {
+                String[] prefixAndMask = addressArray[i].split("/");
+                try {
+                    InetAddress address = InetAddress.getByName(prefixAndMask[0]);
+                    int prefixLength = Integer.parseInt(prefixAndMask[1]);
+                    builder.addAddress(address, prefixLength);
+                } catch (UnknownHostException|NumberFormatException|
+                         ArrayIndexOutOfBoundsException e) {
+                    continue;
+                }
+            }
+        }
+
+        String routes = intent.getStringExtra(packageName + ".routes");
+        if (routes != null) {
+            String[] routeArray = routes.split(",");
+            for (int i = 0; i < routeArray.length; i++) {
+                String[] prefixAndMask = routeArray[i].split("/");
+                try {
+                    InetAddress address = InetAddress.getByName(prefixAndMask[0]);
+                    int prefixLength = Integer.parseInt(prefixAndMask[1]);
+                    builder.addRoute(address, prefixLength);
+                } catch (UnknownHostException|NumberFormatException|
+                         ArrayIndexOutOfBoundsException e) {
+                    continue;
+                }
+            }
+        }
+
+        String allowed = intent.getStringExtra(packageName + ".allowedapplications");
+        if (allowed != null) {
+            String[] packageArray = allowed.split(",");
+            for (int i = 0; i < packageArray.length; i++) {
+                String allowedPackage = packageArray[i];
+                if (!TextUtils.isEmpty(allowedPackage)) {
+                    try {
+                        builder.addAllowedApplication(allowedPackage);
+                    } catch(NameNotFoundException e) {
+                        continue;
+                    }
+                }
+            }
+        }
+
+        String disallowed = intent.getStringExtra(packageName + ".disallowedapplications");
+        if (disallowed != null) {
+            String[] packageArray = disallowed.split(",");
+            for (int i = 0; i < packageArray.length; i++) {
+                String disallowedPackage = packageArray[i];
+                if (!TextUtils.isEmpty(disallowedPackage)) {
+                    try {
+                        builder.addDisallowedApplication(disallowedPackage);
+                    } catch(NameNotFoundException e) {
+                        continue;
+                    }
+                }
+            }
+        }
+
+        builder.setMtu(MTU);
+        builder.setBlocking(true);
+        builder.setSession("MyVpnService");
+
+        Log.i(TAG, "Establishing VPN,"
+                + " addresses=" + addresses
+                + " routes=" + routes
+                + " allowedApplications=" + allowed
+                + " disallowedApplications=" + disallowed);
+
+        mFd = builder.establish();
+        Log.i(TAG, "Established, fd=" + (mFd == null ? "null" : mFd.getFd()));
+
+        mPacketReflector = new PacketReflector(mFd.getFileDescriptor(), MTU);
+        mPacketReflector.start();
+    }
+
+    private void stop() {
+        if (mPacketReflector != null) {
+            mPacketReflector.interrupt();
+            mPacketReflector = null;
+        }
+        try {
+            if (mFd != null) {
+                Log.i(TAG, "Closing filedescriptor");
+                mFd.close();
+            }
+        } catch(IOException e) {
+        } finally {
+            mFd = null;
+        }
+    }
+
+    @Override
+    public void onDestroy() {
+        stop();
+        super.onDestroy();
+    }
+}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/PacketReflector.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/PacketReflector.java
new file mode 100644
index 0000000..dd0f792
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/PacketReflector.java
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside;
+
+import android.system.ErrnoException;
+import android.system.Os;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+public class PacketReflector extends Thread {
+
+    private static int IPV4_HEADER_LENGTH = 20;
+    private static int IPV6_HEADER_LENGTH = 40;
+
+    private static int IPV4_ADDR_OFFSET = 12;
+    private static int IPV6_ADDR_OFFSET = 8;
+    private static int IPV4_ADDR_LENGTH = 4;
+    private static int IPV6_ADDR_LENGTH = 16;
+
+    private static int IPV4_PROTO_OFFSET = 9;
+    private static int IPV6_PROTO_OFFSET = 6;
+
+    private static final byte IPPROTO_ICMP = 1;
+    private static final byte IPPROTO_TCP = 6;
+    private static final byte IPPROTO_UDP = 17;
+    private static final byte IPPROTO_ICMPV6 = 58;
+
+    private static int ICMP_HEADER_LENGTH = 8;
+    private static int TCP_HEADER_LENGTH = 20;
+    private static int UDP_HEADER_LENGTH = 8;
+
+    private static final byte ICMP_ECHO = 8;
+    private static final byte ICMP_ECHOREPLY = 0;
+    private static final byte ICMPV6_ECHO_REQUEST = (byte) 128;
+    private static final byte ICMPV6_ECHO_REPLY = (byte) 129;
+
+    private static String TAG = "PacketReflector";
+
+    private FileDescriptor mFd;
+    private byte[] mBuf;
+
+    public PacketReflector(FileDescriptor fd, int mtu) {
+        super("PacketReflector");
+        mFd = fd;
+        mBuf = new byte[mtu];
+    }
+
+    private static void swapBytes(byte[] buf, int pos1, int pos2, int len) {
+        for (int i = 0; i < len; i++) {
+            byte b = buf[pos1 + i];
+            buf[pos1 + i] = buf[pos2 + i];
+            buf[pos2 + i] = b;
+        }
+    }
+
+    private static void swapAddresses(byte[] buf, int version) {
+        int addrPos, addrLen;
+        switch(version) {
+            case 4:
+                addrPos = IPV4_ADDR_OFFSET;
+                addrLen = IPV4_ADDR_LENGTH;
+                break;
+            case 6:
+                addrPos = IPV6_ADDR_OFFSET;
+                addrLen = IPV6_ADDR_LENGTH;
+                break;
+            default:
+                throw new IllegalArgumentException();
+        }
+        swapBytes(buf, addrPos, addrPos + addrLen, addrLen);
+    }
+
+    // Reflect TCP packets: swap the source and destination addresses, but don't change the ports.
+    // This is used by the test to "connect to itself" through the VPN.
+    private void processTcpPacket(byte[] buf, int version, int len, int hdrLen) {
+        if (len < hdrLen + TCP_HEADER_LENGTH) {
+            return;
+        }
+
+        // Swap src and dst IP addresses.
+        swapAddresses(buf, version);
+
+        // Send the packet back.
+        writePacket(buf, len);
+    }
+
+    // Echo UDP packets: swap source and destination addresses, and source and destination ports.
+    // This is used by the test to check that the bytes it sends are echoed back.
+    private void processUdpPacket(byte[] buf, int version, int len, int hdrLen) {
+        if (len < hdrLen + UDP_HEADER_LENGTH) {
+            return;
+        }
+
+        // Swap src and dst IP addresses.
+        swapAddresses(buf, version);
+
+        // Swap dst and src ports.
+        int portOffset = hdrLen;
+        swapBytes(buf, portOffset, portOffset + 2, 2);
+
+        // Send the packet back.
+        writePacket(buf, len);
+    }
+
+    private void processIcmpPacket(byte[] buf, int version, int len, int hdrLen) {
+        if (len < hdrLen + ICMP_HEADER_LENGTH) {
+            return;
+        }
+
+        byte type = buf[hdrLen];
+        if (!(version == 4 && type == ICMP_ECHO) &&
+            !(version == 6 && type == ICMPV6_ECHO_REQUEST)) {
+            return;
+        }
+
+        // Save the ping packet we received.
+        byte[] request = buf.clone();
+
+        // Swap src and dst IP addresses, and send the packet back.
+        // This effectively pings the device to see if it replies.
+        swapAddresses(buf, version);
+        writePacket(buf, len);
+
+        // The device should have replied, and buf should now contain a ping response.
+        int received = readPacket(buf);
+        if (received != len) {
+            Log.i(TAG, "Reflecting ping did not result in ping response: " +
+                       "read=" + received + " expected=" + len);
+            return;
+        }
+
+        // Compare the response we got with the original packet.
+        // The only thing that should have changed are addresses, type and checksum.
+        // Overwrite them with the received bytes and see if the packet is otherwise identical.
+        request[hdrLen] = buf[hdrLen];          // Type.
+        request[hdrLen + 2] = buf[hdrLen + 2];  // Checksum byte 1.
+        request[hdrLen + 3] = buf[hdrLen + 3];  // Checksum byte 2.
+        for (int i = 0; i < len; i++) {
+            if (buf[i] != request[i]) {
+                Log.i(TAG, "Received non-matching packet when expecting ping response.");
+                return;
+            }
+        }
+
+        // Now swap the addresses again and reflect the packet. This sends a ping reply.
+        swapAddresses(buf, version);
+        writePacket(buf, len);
+    }
+
+    private void writePacket(byte[] buf, int len) {
+        try {
+            Os.write(mFd, buf, 0, len);
+        } catch (ErrnoException|IOException e) {
+            Log.e(TAG, "Error writing packet: " + e.getMessage());
+        }
+    }
+
+    private int readPacket(byte[] buf) {
+        int len;
+        try {
+            len = Os.read(mFd, buf, 0, buf.length);
+        } catch (ErrnoException|IOException e) {
+            Log.e(TAG, "Error reading packet: " + e.getMessage());
+            len = -1;
+        }
+        return len;
+    }
+
+    // Reads one packet from our mFd, and possibly writes the packet back.
+    private void processPacket() {
+        int len = readPacket(mBuf);
+        if (len < 1) {
+            return;
+        }
+
+        int version = mBuf[0] >> 4;
+        int addrPos, protoPos, hdrLen, addrLen;
+        if (version == 4) {
+            hdrLen = IPV4_HEADER_LENGTH;
+            protoPos = IPV4_PROTO_OFFSET;
+            addrPos = IPV4_ADDR_OFFSET;
+            addrLen = IPV4_ADDR_LENGTH;
+        } else if (version == 6) {
+            hdrLen = IPV6_HEADER_LENGTH;
+            protoPos = IPV6_PROTO_OFFSET;
+            addrPos = IPV6_ADDR_OFFSET;
+            addrLen = IPV6_ADDR_LENGTH;
+        } else {
+            return;
+        }
+
+        if (len < hdrLen) {
+            return;
+        }
+
+        byte proto = mBuf[protoPos];
+        switch (proto) {
+            case IPPROTO_ICMP:
+            case IPPROTO_ICMPV6:
+                processIcmpPacket(mBuf, version, len, hdrLen);
+                break;
+            case IPPROTO_TCP:
+                processTcpPacket(mBuf, version, len, hdrLen);
+                break;
+            case IPPROTO_UDP:
+                processUdpPacket(mBuf, version, len, hdrLen);
+                break;
+        }
+    }
+
+    public void run() {
+        Log.i(TAG, "PacketReflector starting fd=" + mFd + " valid=" + mFd.valid());
+        while (!interrupted() && mFd.valid()) {
+            processPacket();
+        }
+        Log.i(TAG, "PacketReflector exiting fd=" + mFd + " valid=" + mFd.valid());
+    }
+}
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/VpnTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/VpnTest.java
new file mode 100644
index 0000000..8bb2a2c
--- /dev/null
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/VpnTest.java
@@ -0,0 +1,461 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net.hostside;
+
+import static android.system.OsConstants.*;
+
+import android.content.Intent;
+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.NetworkRequest;
+import android.net.VpnService;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject;
+import android.support.test.uiautomator.UiObjectNotFoundException;
+import android.support.test.uiautomator.UiScrollable;
+import android.support.test.uiautomator.UiSelector;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.StructPollfd;
+import android.test.InstrumentationTestCase;
+import android.test.MoreAsserts;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.DatagramPacket;
+import java.net.DatagramSocket;
+import java.net.Inet6Address;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.util.Random;
+
+/**
+ * Tests for the VpnService API.
+ *
+ * These tests establish a VPN via the VpnService API, and have the service reflect the packets back
+ * to the device without causing any network traffic. This allows testing the local VPN data path
+ * without a network connection or a VPN server.
+ *
+ * Note: in Lollipop, VPN functionality relies on kernel support for UID-based routing. If these
+ * tests fail, it may be due to the lack of kernel support. The necessary patches can be
+ * cherry-picked from the Android common kernel trees:
+ *
+ * android-3.10:
+ *   https://android-review.googlesource.com/#/c/99220/
+ *   https://android-review.googlesource.com/#/c/100545/
+ *
+ * android-3.4:
+ *   https://android-review.googlesource.com/#/c/99225/
+ *   https://android-review.googlesource.com/#/c/100557/
+ *
+ */
+public class VpnTest extends InstrumentationTestCase {
+
+    public static String TAG = "VpnTest";
+    public static int TIMEOUT_MS = 3 * 1000;
+    public static int SOCKET_TIMEOUT_MS = 100;
+
+    private UiDevice mDevice;
+    private MyActivity mActivity;
+    private String mPackageName;
+    private ConnectivityManager mCM;
+    Network mNetwork;
+    NetworkCallback mCallback;
+    final Object mLock = new Object();
+
+    private boolean supportedHardware() {
+        final PackageManager pm = getInstrumentation().getContext().getPackageManager();
+        return !pm.hasSystemFeature("android.hardware.type.television") &&
+               !pm.hasSystemFeature("android.hardware.type.watch");
+    }
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mNetwork = null;
+        mCallback = null;
+
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        mActivity = launchActivity(getInstrumentation().getTargetContext().getPackageName(),
+                MyActivity.class, null);
+        mPackageName = mActivity.getPackageName();
+        mCM = (ConnectivityManager) mActivity.getSystemService(mActivity.CONNECTIVITY_SERVICE);
+        mDevice.waitForIdle();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        if (mCallback != null) {
+            mCM.unregisterNetworkCallback(mCallback);
+        }
+        Log.i(TAG, "Stopping VPN");
+        stopVpn();
+        mActivity.finish();
+        super.tearDown();
+    }
+
+    private void prepareVpn() throws Exception {
+        final int REQUEST_ID = 42;
+
+        // Attempt to prepare.
+        Log.i(TAG, "Preparing VPN");
+        Intent intent = VpnService.prepare(mActivity);
+
+        if (intent != null) {
+            // Start the confirmation dialog and click OK.
+            mActivity.startActivityForResult(intent, REQUEST_ID);
+            mDevice.waitForIdle();
+
+            String packageName = intent.getComponent().getPackageName();
+            String resourceIdRegex = "android:id/button1$|button_start_vpn";
+            final UiObject okButton = new UiObject(new UiSelector()
+                    .className("android.widget.Button")
+                    .packageName(packageName)
+                    .resourceIdMatches(resourceIdRegex));
+            if (okButton.waitForExists(TIMEOUT_MS) == false) {
+                mActivity.finishActivity(REQUEST_ID);
+                fail("VpnService.prepare returned an Intent for '" + intent.getComponent() + "' " +
+                     "to display the VPN confirmation dialog, but this test could not find the " +
+                     "button to allow the VPN application to connect. Please ensure that the "  +
+                     "component displays a button with a resource ID matching the regexp: '" +
+                     resourceIdRegex + "'.");
+            }
+
+            // Click the button and wait for RESULT_OK.
+            okButton.click();
+            try {
+                int result = mActivity.getResult(TIMEOUT_MS);
+                if (result != MyActivity.RESULT_OK) {
+                    fail("The VPN confirmation dialog did not return RESULT_OK when clicking on " +
+                         "the button matching the regular expression '" + resourceIdRegex +
+                         "' of " + intent.getComponent() + "'. Please ensure that clicking on " +
+                         "that button allows the VPN application to connect. " +
+                         "Return value: " + result);
+                }
+            } catch (InterruptedException e) {
+                fail("VPN confirmation dialog did not return after " + TIMEOUT_MS + "ms");
+            }
+
+            // Now we should be prepared.
+            intent = VpnService.prepare(mActivity);
+            if (intent != null) {
+                fail("VpnService.prepare returned non-null even after the VPN dialog " +
+                     intent.getComponent() + "returned RESULT_OK.");
+            }
+        }
+    }
+
+    private void startVpn(
+            String[] addresses, String[] routes,
+            String allowedApplications, String disallowedApplications) throws Exception {
+
+        prepareVpn();
+
+        // Register a callback so we will be notified when our VPN comes up.
+        final NetworkRequest request = new NetworkRequest.Builder()
+                .addTransportType(NetworkCapabilities.TRANSPORT_VPN)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+                .removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+                .build();
+        mCallback = new NetworkCallback() {
+            public void onAvailable(Network network) {
+                synchronized (mLock) {
+                    Log.i(TAG, "Got available callback for network=" + network);
+                    mNetwork = network;
+                    mLock.notify();
+                }
+            }
+        };
+        mCM.registerNetworkCallback(request, mCallback);  // Unregistered in tearDown.
+
+        // Start the service and wait up for TIMEOUT_MS ms for the VPN to come up.
+        Intent intent = new Intent(mActivity, MyVpnService.class)
+                .putExtra(mPackageName + ".cmd", "connect")
+                .putExtra(mPackageName + ".addresses", TextUtils.join(",", addresses))
+                .putExtra(mPackageName + ".routes", TextUtils.join(",", routes))
+                .putExtra(mPackageName + ".allowedapplications", allowedApplications)
+                .putExtra(mPackageName + ".disallowedapplications", disallowedApplications);
+        mActivity.startService(intent);
+        synchronized (mLock) {
+            if (mNetwork == null) {
+                 mLock.wait(TIMEOUT_MS);
+            }
+        }
+
+        if (mNetwork == null) {
+            fail("VPN did not become available after " + TIMEOUT_MS + "ms");
+        }
+
+        // Unfortunately, when the available callback fires, the VPN UID ranges are not yet
+        // configured. Give the system some time to do so. http://b/18436087 .
+        try { Thread.sleep(300); } catch(InterruptedException e) {}
+    }
+
+    private void stopVpn() {
+        // Simply calling mActivity.stopService() won't stop the service, because the system binds
+        // to the service for the purpose of sending it a revoke command if another VPN comes up,
+        // and stopping a bound service has no effect. Instead, "start" the service again with an
+        // Intent that tells it to disconnect.
+        Intent intent = new Intent(mActivity, MyVpnService.class)
+                .putExtra(mPackageName + ".cmd", "disconnect");
+        mActivity.startService(intent);
+    }
+
+    private static void closeQuietly(Closeable c) {
+        if (c != null) {
+            try {
+                c.close();
+            } catch (IOException e) {
+            }
+        }
+    }
+
+    private static void checkPing(String to) throws IOException, ErrnoException {
+        InetAddress address = InetAddress.getByName(to);
+        FileDescriptor s;
+        final int LENGTH = 64;
+        byte[] packet = new byte[LENGTH];
+        byte[] header;
+
+        // Construct a ping packet.
+        Random random = new Random();
+        random.nextBytes(packet);
+        if (address instanceof Inet6Address) {
+            s = Os.socket(AF_INET6, SOCK_DGRAM, IPPROTO_ICMPV6);
+            header = new byte[] { (byte) 0x80, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
+        } else {
+            // Note that this doesn't actually work due to http://b/18558481 .
+            s = Os.socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP);
+            header = new byte[] { (byte) 0x08, (byte) 0x00, (byte) 0x00, (byte) 0x00 };
+        }
+        System.arraycopy(header, 0, packet, 0, header.length);
+
+        // Send the packet.
+        int port = random.nextInt(65534) + 1;
+        Os.connect(s, address, port);
+        Os.write(s, packet, 0, packet.length);
+
+        // Expect a reply.
+        StructPollfd pollfd = new StructPollfd();
+        pollfd.events = (short) POLLIN;  // "error: possible loss of precision"
+        pollfd.fd = s;
+        int ret = Os.poll(new StructPollfd[] { pollfd }, SOCKET_TIMEOUT_MS);
+        assertEquals("Expected reply after sending ping", 1, ret);
+
+        byte[] reply = new byte[LENGTH];
+        int read = Os.read(s, reply, 0, LENGTH);
+        assertEquals(LENGTH, read);
+
+        // Find out what the kernel set the ICMP ID to.
+        InetSocketAddress local = (InetSocketAddress) Os.getsockname(s);
+        port = local.getPort();
+        packet[4] = (byte) ((port >> 8) & 0xff);
+        packet[5] = (byte) (port & 0xff);
+
+        // Check the contents.
+        if (packet[0] == (byte) 0x80) {
+            packet[0] = (byte) 0x81;
+        } else {
+            packet[0] = 0;
+        }
+        // Zero out the checksum in the reply so it matches the uninitialized checksum in packet.
+        reply[2] = reply[3] = 0;
+        MoreAsserts.assertEquals(packet, reply);
+    }
+
+    // Writes data to out and checks that it appears identically on in.
+    private static void writeAndCheckData(
+            OutputStream out, InputStream in, byte[] data) throws IOException {
+        out.write(data, 0, data.length);
+        out.flush();
+
+        byte[] read = new byte[data.length];
+        int bytesRead = 0, totalRead = 0;
+        do {
+            bytesRead = in.read(read, totalRead, read.length - totalRead);
+            totalRead += bytesRead;
+        } while (bytesRead >= 0 && totalRead < data.length);
+        assertEquals(totalRead, data.length);
+        MoreAsserts.assertEquals(data, read);
+    }
+
+    private static void checkTcpReflection(String to, String expectedFrom) throws IOException {
+        // Exercise TCP over the VPN by "connecting to ourselves". We open a server socket and a
+        // client socket, and connect the client socket to a remote host, with the port of the
+        // server socket. The PacketReflector reflects the packets, changing the source addresses
+        // but not the ports, so our client socket is connected to our server socket, though both
+        // sockets think their peers are on the "remote" IP address.
+
+        // Open a listening socket.
+        ServerSocket listen = new ServerSocket(0, 10, InetAddress.getByName("::"));
+
+        // Connect the client socket to it.
+        InetAddress toAddr = InetAddress.getByName(to);
+        Socket client = new Socket();
+        try {
+            client.connect(new InetSocketAddress(toAddr, listen.getLocalPort()), SOCKET_TIMEOUT_MS);
+            if (expectedFrom == null) {
+                closeQuietly(listen);
+                closeQuietly(client);
+                fail("Expected connection to fail, but it succeeded.");
+            }
+        } catch (IOException e) {
+            if (expectedFrom != null) {
+                closeQuietly(listen);
+                fail("Expected connection to succeed, but it failed.");
+            } else {
+                // We expected the connection to fail, and it did, so there's nothing more to test.
+                return;
+            }
+        }
+
+        // The connection succeeded, and we expected it to succeed. Send some data; if things are
+        // working, the data will be sent to the VPN, reflected by the PacketReflector, and arrive
+        // at our server socket. For good measure, send some data in the other direction.
+        Socket server = null;
+        try {
+            // Accept the connection on the server side.
+            listen.setSoTimeout(SOCKET_TIMEOUT_MS);
+            server = listen.accept();
+
+            // Check that the source and peer addresses are as expected.
+            assertEquals(expectedFrom, client.getLocalAddress().getHostAddress());
+            assertEquals(expectedFrom, server.getLocalAddress().getHostAddress());
+            assertEquals(
+                    new InetSocketAddress(toAddr, client.getLocalPort()),
+                    server.getRemoteSocketAddress());
+            assertEquals(
+                    new InetSocketAddress(toAddr, server.getLocalPort()),
+                    client.getRemoteSocketAddress());
+
+            // Now write some data.
+            final int LENGTH = 32768;
+            byte[] data = new byte[LENGTH];
+            new Random().nextBytes(data);
+
+            // Make sure our writes don't block or time out, because we're single-threaded and can't
+            // read and write at the same time.
+            server.setReceiveBufferSize(LENGTH * 2);
+            client.setSendBufferSize(LENGTH * 2);
+            client.setSoTimeout(SOCKET_TIMEOUT_MS);
+            server.setSoTimeout(SOCKET_TIMEOUT_MS);
+
+            // Send some data from client to server, then from server to client.
+            writeAndCheckData(client.getOutputStream(), server.getInputStream(), data);
+            writeAndCheckData(server.getOutputStream(), client.getInputStream(), data);
+        } finally {
+            closeQuietly(listen);
+            closeQuietly(client);
+            closeQuietly(server);
+        }
+    }
+
+    private static void checkUdpEcho(String to, String expectedFrom) throws IOException {
+        DatagramSocket s;
+        InetAddress address = InetAddress.getByName(to);
+        if (address instanceof Inet6Address) {  // http://b/18094870
+            s = new DatagramSocket(0, InetAddress.getByName("::"));
+        } else {
+            s = new DatagramSocket();
+        }
+        s.setSoTimeout(SOCKET_TIMEOUT_MS);
+
+        Random random = new Random();
+        byte[] data = new byte[random.nextInt(1650)];
+        random.nextBytes(data);
+        DatagramPacket p = new DatagramPacket(data, data.length);
+        s.connect(address, 7);
+
+        if (expectedFrom != null) {
+            assertEquals("Unexpected source address: ",
+                         expectedFrom, s.getLocalAddress().getHostAddress());
+        }
+
+        try {
+            if (expectedFrom != null) {
+                s.send(p);
+                s.receive(p);
+                MoreAsserts.assertEquals(data, p.getData());
+            } else {
+                try {
+                    s.send(p);
+                    s.receive(p);
+                    fail("Received unexpected reply");
+                } catch(IOException expected) {}
+            }
+        } finally {
+            s.close();
+        }
+    }
+
+    private void checkTrafficOnVpn() throws IOException, ErrnoException {
+        checkUdpEcho("192.0.2.251", "192.0.2.2");
+        checkUdpEcho("2001:db8:dead:beef::f00", "2001:db8:1:2::ffe");
+        checkPing("2001:db8:dead:beef::f00");
+        checkTcpReflection("192.0.2.252", "192.0.2.2");
+        checkTcpReflection("2001:db8:dead:beef::f00", "2001:db8:1:2::ffe");
+    }
+
+    private void checkNoTrafficOnVpn() throws IOException, ErrnoException {
+        checkUdpEcho("192.0.2.251", null);
+        checkUdpEcho("2001:db8:dead:beef::f00", null);
+        checkTcpReflection("192.0.2.252", null);
+        checkTcpReflection("2001:db8:dead:beef::f00", null);
+    }
+
+    public void testDefault() throws Exception {
+        if (!supportedHardware()) return;
+
+        startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+                 new String[] {"192.0.2.0/24", "2001:db8::/32"},
+                 "", "");
+
+        checkTrafficOnVpn();
+    }
+
+    public void testAppAllowed() throws Exception {
+        if (!supportedHardware()) return;
+
+        startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+                 new String[] {"0.0.0.0/0", "::/0"},
+                 mPackageName, "");
+
+        checkTrafficOnVpn();
+    }
+
+    public void testAppDisallowed() throws Exception {
+        if (!supportedHardware()) return;
+
+        startVpn(new String[] {"192.0.2.2/32", "2001:db8:1:2::ffe/128"},
+                 new String[] {"192.0.2.0/24", "2001:db8::/32"},
+                 "", mPackageName);
+
+        checkNoTrafficOnVpn();
+    }
+}
diff --git a/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTests.java b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTests.java
new file mode 100644
index 0000000..a7698f3
--- /dev/null
+++ b/hostsidetests/net/src/com/android/cts/net/HostsideNetworkTests.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.net;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+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.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IAbiReceiver;
+import com.android.tradefed.testtype.IBuildReceiver;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.result.CollectingTestListener;
+
+import java.util.Map;
+
+public class HostsideNetworkTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+    private static final String TEST_PKG = "com.android.cts.net.hostside";
+    private static final String TEST_APK = "CtsHostsideNetworkTestsApp.apk";
+
+    private IAbi mAbi;
+    private CtsBuildHelper mCtsBuild;
+
+    @Override
+    public void setAbi(IAbi abi) {
+        mAbi = abi;
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        assertNotNull(mAbi);
+        assertNotNull(mCtsBuild);
+
+        getDevice().uninstallPackage(TEST_PKG);
+
+        assertNull(getDevice().installPackage(mCtsBuild.getTestApp(TEST_APK), false));
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        getDevice().uninstallPackage(TEST_PKG);
+    }
+
+    public void testVpn() throws Exception {
+        runDeviceTests(TEST_PKG, ".VpnTest");
+    }
+
+    public void runDeviceTests(String packageName, String testClassName)
+           throws DeviceNotAvailableException {
+        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(packageName,
+                "android.support.test.runner.AndroidJUnitRunner", getDevice().getIDevice());
+
+        final CollectingTestListener listener = new CollectingTestListener();
+        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.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/sample/src/android/sample/cts/SampleHostTest.java b/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
index 3cc4aa9..ab7e0b0 100644
--- a/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
+++ b/hostsidetests/sample/src/android/sample/cts/SampleHostTest.java
@@ -123,7 +123,7 @@
         // Start the APK and wait for it to complete.
         mDevice.executeShellCommand(START_COMMAND);
         // Dump logcat.
-        String logs = mDevice.executeAdbCommand("logcat", "-d", CLASS + ":I", "*:S");
+        String logs = mDevice.executeAdbCommand("logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
         // Search for string.
         String testString = "";
         Scanner in = new Scanner(logs);
diff --git a/hostsidetests/security/Android.mk b/hostsidetests/security/Android.mk
index a42ee8a..0c976a3 100644
--- a/hostsidetests/security/Android.mk
+++ b/hostsidetests/security/Android.mk
@@ -23,12 +23,27 @@
 # Must match the package name in CtsTestCaseList.mk
 LOCAL_MODULE := CtsSecurityHostTestCases
 
+LOCAL_MODULE_CLASS := JAVA_LIBRARIES
+
 LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt
 
 LOCAL_CTS_TEST_PACKAGE := android.host.security
 
 LOCAL_JAVA_RESOURCE_FILES := $(HOST_OUT_EXECUTABLES)/sepolicy-analyze
 
+selinux_general_policy := $(call intermediates-dir-for,ETC,general_sepolicy.conf)/general_sepolicy.conf
+
+selinux_neverallow_gen := cts/tools/selinux/SELinuxNeverallowTestGen.py
+
+selinux_neverallow_gen_data := cts/tools/selinux/SELinuxNeverallowTestFrame.py
+
+LOCAL_GENERATED_SOURCES := $(call local-generated-sources-dir)/android/cts/security/SELinuxNeverallowRulesTest.java
+
+$(LOCAL_GENERATED_SOURCES) : PRIVATE_SELINUX_GENERAL_POLICY := $(selinux_general_policy)
+$(LOCAL_GENERATED_SOURCES) : $(selinux_neverallow_gen) $(selinux_general_policy) $(selinux_neverallow_gen_data)
+	mkdir -p $(dir $@)
+	$< $(PRIVATE_SELINUX_GENERAL_POLICY) $@
+
 include $(BUILD_CTS_HOST_JAVA_LIBRARY)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java b/hostsidetests/security/src/android/cts/security/SELinuxHostTest.java
similarity index 76%
rename from hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
rename to hostsidetests/security/src/android/cts/security/SELinuxHostTest.java
index e2c98fe..96845b1 100644
--- a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/cts/security/SELinuxHostTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.security.cts;
+package android.cts.security;
 
 import com.android.cts.tradefed.build.CtsBuildHelper;
 import com.android.ddmlib.Log;
@@ -26,9 +26,10 @@
 
 import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
-import java.io.FileOutputStream;
 import java.lang.String;
 import java.net.URL;
 import java.util.Scanner;
@@ -42,15 +43,42 @@
  */
 public class SELinuxHostTest extends DeviceTestCase {
 
+    private File sepolicyAnalyze;
+    private File devicePolicyFile;
+
     /**
      * A reference to the device under test.
      */
     private ITestDevice mDevice;
 
+    private File copyResourceToTempFile(String resName) throws IOException {
+        InputStream is = this.getClass().getResourceAsStream(resName);
+        File tempFile = File.createTempFile("SELinuxHostTest", ".tmp");
+        FileOutputStream os = new FileOutputStream(tempFile);
+        int rByte = 0;
+        while ((rByte = is.read()) != -1) {
+            os.write(rByte);
+        }
+        os.flush();
+        os.close();
+        tempFile.deleteOnExit();
+        return tempFile;
+    }
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
         mDevice = getDevice();
+
+        /* retrieve the sepolicy-analyze executable from jar */
+        sepolicyAnalyze = copyResourceToTempFile("/sepolicy-analyze");
+        sepolicyAnalyze.setExecutable(true);
+
+        /* obtain sepolicy file from running device */
+        devicePolicyFile = File.createTempFile("sepolicy", ".tmp");
+        devicePolicyFile.deleteOnExit();
+        mDevice.executeAdbCommand("pull", "/sys/fs/selinux/policy",
+                devicePolicyFile.getAbsolutePath());
     }
 
     /**
@@ -60,25 +88,9 @@
      */
     public void testAllEnforcing() throws Exception {
 
-        /* retrieve the sepolicy-analyze executable from jar */
-        InputStream is = this.getClass().getResourceAsStream("/sepolicy-analyze");
-        File execFile = File.createTempFile("sepolicy-analyze", ".tmp");
-        FileOutputStream os = new FileOutputStream(execFile);
-        int rByte = 0;
-        while ((rByte = is.read()) != -1) {
-            os.write(rByte);
-        }
-        os.flush();
-        os.close();
-        execFile.setExecutable(true);
-
-        /* obtain sepolicy file from running device */
-        File policyFile = File.createTempFile("sepolicy", ".tmp");
-        mDevice.executeAdbCommand("pull", "/sys/fs/selinux/policy", policyFile.getAbsolutePath());
-
         /* run sepolicy-analyze permissive check on policy file */
-        ProcessBuilder pb = new ProcessBuilder(execFile.getAbsolutePath(), "-p", "-P",
-                policyFile.getAbsolutePath());
+        ProcessBuilder pb = new ProcessBuilder(sepolicyAnalyze.getAbsolutePath(),
+                devicePolicyFile.getAbsolutePath(), "permissive");
         pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
         pb.redirectErrorStream(true);
         Process p = pb.start();
@@ -90,10 +102,6 @@
             errorString.append(line);
             errorString.append("\n");
         }
-
-        /* clean up and check condition */
-        execFile.delete();
-        policyFile.delete();
         assertTrue("The following SELinux domains were found to be in permissive mode:\n"
                    + errorString, errorString.length() == 0);
     }
diff --git a/hostsidetests/theme/assets/21/hdpi.zip b/hostsidetests/theme/assets/21/hdpi.zip
index 9cc179c..0fa67b7 100644
--- a/hostsidetests/theme/assets/21/hdpi.zip
+++ b/hostsidetests/theme/assets/21/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/21/ldpi.zip b/hostsidetests/theme/assets/21/ldpi.zip
index 059bf33..e33f2f0 100644
--- a/hostsidetests/theme/assets/21/ldpi.zip
+++ b/hostsidetests/theme/assets/21/ldpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/21/mdpi.zip b/hostsidetests/theme/assets/21/mdpi.zip
index 0fb0778..f74739e 100644
--- a/hostsidetests/theme/assets/21/mdpi.zip
+++ b/hostsidetests/theme/assets/21/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/22/400dpi.zip b/hostsidetests/theme/assets/22/400dpi.zip
new file mode 100644
index 0000000..6d62e5b
--- /dev/null
+++ b/hostsidetests/theme/assets/22/400dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/22/560dpi.zip b/hostsidetests/theme/assets/22/560dpi.zip
new file mode 100644
index 0000000..eff363c
--- /dev/null
+++ b/hostsidetests/theme/assets/22/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/22/hdpi.zip b/hostsidetests/theme/assets/22/hdpi.zip
new file mode 100644
index 0000000..0fa67b7
--- /dev/null
+++ b/hostsidetests/theme/assets/22/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/22/ldpi.zip b/hostsidetests/theme/assets/22/ldpi.zip
new file mode 100644
index 0000000..e33f2f0
--- /dev/null
+++ b/hostsidetests/theme/assets/22/ldpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/22/mdpi.zip b/hostsidetests/theme/assets/22/mdpi.zip
new file mode 100644
index 0000000..f74739e
--- /dev/null
+++ b/hostsidetests/theme/assets/22/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/22/tvdpi.zip b/hostsidetests/theme/assets/22/tvdpi.zip
new file mode 100644
index 0000000..fbe1781
--- /dev/null
+++ b/hostsidetests/theme/assets/22/tvdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/22/xhdpi.zip b/hostsidetests/theme/assets/22/xhdpi.zip
new file mode 100644
index 0000000..de6e2e1
--- /dev/null
+++ b/hostsidetests/theme/assets/22/xhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/22/xxhdpi.zip b/hostsidetests/theme/assets/22/xxhdpi.zip
new file mode 100644
index 0000000..9f0d778
--- /dev/null
+++ b/hostsidetests/theme/assets/22/xxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java b/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
index 33e67e5..ba880d7 100644
--- a/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
+++ b/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
@@ -37,9 +37,7 @@
 
     private static final int IMAGE_THRESHOLD = 2;
 
-    private static final String STORAGE_PATH_DEVICE = "/storage/emulated/legacy/cts-holo-assets/%s.png";
-
-    private static final String STORAGE_PATH_EMULATOR = "/sdcard/cts-holo-assets/%s.png";
+    private static final String STORAGE_PATH_DEVICE = "/sdcard/cts-holo-assets/%s.png";
 
     private final ITestDevice mDevice;
 
@@ -47,18 +45,10 @@
 
     private final String mName;
 
-    private final String mStoragePath;
-
     public ComparisonTask(ITestDevice device, File reference, String name) {
         mDevice = device;
         mReference = reference;
         mName = name;
-
-        if (mDevice.getSerialNumber().startsWith("emulator-")) {
-            mStoragePath = STORAGE_PATH_EMULATOR;
-        } else {
-            mStoragePath = STORAGE_PATH_DEVICE;
-        }
     }
 
     public Boolean call() {
@@ -67,7 +57,7 @@
         try {
             generated = File.createTempFile("gen_" + mName, ".png");
 
-            final String remoteGenerated = String.format(mStoragePath, mName);
+            final String remoteGenerated = String.format(STORAGE_PATH_DEVICE, mName);
             if (!mDevice.doesFileExist(remoteGenerated)) {
                 Log.logAndDisplay(LogLevel.ERROR, TAG, "File " + remoteGenerated + " have not been saved on device");
                 return false;
@@ -84,7 +74,7 @@
                 Log.logAndDisplay(LogLevel.INFO, TAG, "Diff created: " + diff.getPath());
             }
         } catch (Exception e) {
-            Log.logAndDisplay(LogLevel.ERROR, TAG, String.format(mStoragePath, mName));
+            Log.logAndDisplay(LogLevel.ERROR, TAG, String.format(STORAGE_PATH_DEVICE, mName));
             Log.logAndDisplay(LogLevel.ERROR, TAG, e.toString());
             e.printStackTrace();
         } finally {
diff --git a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
index 90a0c72..da94b15 100644
--- a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
+++ b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
@@ -322,7 +322,8 @@
         boolean waiting = true;
         while (waiting) {
             // Dump logcat.
-            final String logs = mDevice.executeAdbCommand("logcat", "-d", CLASS + ":I", "*:S");
+            final String logs = mDevice.executeAdbCommand(
+                    "logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
             // Search for string.
             final Scanner in = new Scanner(logs);
             while (in.hasNextLine()) {
diff --git a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
index 4736e51..3af52c0 100644
--- a/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
+++ b/hostsidetests/usb/src/com/android/cts/usb/TestUsbTest.java
@@ -40,13 +40,11 @@
  */
 public class TestUsbTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
 
-    private static final String LOG_TAG = "TestUsbTest";
     private static final String CTS_RUNNER = "android.support.test.runner.AndroidJUnitRunner";
     private static final String PACKAGE_NAME = "com.android.cts.usb.serialtest";
     private static final String APK_NAME="CtsUsbSerialTestApp.apk";
     private ITestDevice mDevice;
     private IAbi mAbi;
-    private String mAbiBitness;
     private CtsBuildHelper mBuild;
 
     @Override
@@ -118,7 +116,8 @@
         if (runResult.isRunFailure()) {
             fail(runResult.getRunFailureMessage());
         }
-        String logs = mDevice.executeAdbCommand("logcat", "-d", "CtsUsbSerialTest:W", "*:S");
+        String logs = mDevice.executeAdbCommand(
+                "logcat", "-v", "brief", "-d", "CtsUsbSerialTest:W", "*:S");
         pattern = Pattern.compile("^.*CtsUsbSerialTest\\(.*\\):\\s+([a-zA-Z0-9]{6,20})",
                 Pattern.MULTILINE);
         matcher = pattern.matcher(logs);
diff --git a/libs/deviceutil/src/android/cts/util/MediaUtils.java b/libs/deviceutil/src/android/cts/util/MediaUtils.java
new file mode 100644
index 0000000..20153c5
--- /dev/null
+++ b/libs/deviceutil/src/android/cts/util/MediaUtils.java
@@ -0,0 +1,337 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.cts.util;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.net.Uri;
+import java.lang.reflect.Method;
+import static java.lang.reflect.Modifier.isPublic;
+import static java.lang.reflect.Modifier.isStatic;
+import java.util.Map;
+import android.util.Log;
+
+import java.io.IOException;
+
+public class MediaUtils {
+    private static final String TAG = "MediaUtils";
+
+    private static final int ALL_AV_TRACKS = -1;
+
+    private static final MediaCodecList sMCL = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+
+    /**
+     * Returns the test name (heuristically).
+     *
+     * Since it uses heuristics, this method has only been verified for media
+     * tests. This centralizes the way to signal errors during a test.
+     */
+    public static String getTestName() {
+        int bestScore = -1;
+        String testName = "test???";
+        Map<Thread, StackTraceElement[]> traces = Thread.getAllStackTraces();
+        for (Map.Entry<Thread, StackTraceElement[]> entry : traces.entrySet()) {
+            StackTraceElement[] stack = entry.getValue();
+            for (int index = 0; index < stack.length; ++index) {
+                // method name must start with "test"
+                String methodName = stack[index].getMethodName();
+                if (!methodName.startsWith("test")) {
+                    continue;
+                }
+
+                int score = 0;
+                // see if there is a public non-static void method that takes no argument
+                Class<?> clazz;
+                try {
+                    clazz = Class.forName(stack[index].getClassName());
+                    ++score;
+                    for (final Method method : clazz.getDeclaredMethods()) {
+                        if (method.getName().equals(methodName)
+                                && isPublic(method.getModifiers())
+                                && !isStatic(method.getModifiers())
+                                && method.getParameterTypes().length == 0
+                                && method.getReturnType().equals(Void.TYPE)) {
+                            ++score;
+                            break;
+                        }
+                    }
+                    if (score == 1) {
+                        // if we could read the class, but method is not public void, it is
+                        // not a candidate
+                        continue;
+                    }
+                } catch (ClassNotFoundException e) {
+                }
+
+                // even if we cannot verify the method signature, there are signals in the stack
+
+                // usually test method is invoked by reflection
+                int depth = 1;
+                while (index + depth < stack.length
+                        && stack[index + depth].getMethodName().equals("invoke")
+                        && stack[index + depth].getClassName().equals(
+                                "java.lang.reflect.Method")) {
+                    ++depth;
+                }
+                if (depth > 1) {
+                    ++score;
+                    // and usually test method is run by runMethod method in android.test package
+                    if (index + depth < stack.length) {
+                        if (stack[index + depth].getClassName().startsWith("android.test.")) {
+                            ++score;
+                        }
+                        if (stack[index + depth].getMethodName().equals("runMethod")) {
+                            ++score;
+                        }
+                    }
+                }
+
+                if (score > bestScore) {
+                    bestScore = score;
+                    testName = methodName;
+                }
+            }
+        }
+        return testName;
+    }
+
+    /**
+     * Finds test name (heuristically) and prints out standard skip message.
+     *
+     * Since it uses heuristics, this method has only been verified for media
+     * tests. This centralizes the way to signal a skipped test.
+     */
+    public static void skipTest(String tag, String reason) {
+        Log.i(tag, "SKIPPING " + getTestName() + "(): " + reason);
+    }
+
+    /**
+     * Finds test name (heuristically) and prints out standard skip message.
+     *
+     * Since it uses heuristics, this method has only been verified for media
+     * tests.  This centralizes the way to signal a skipped test.
+     */
+    public static void skipTest(String reason) {
+        skipTest(TAG, reason);
+    }
+
+    public static boolean check(boolean result, String message) {
+        if (!result) {
+            skipTest(message);
+        }
+        return result;
+    }
+
+    public static boolean canDecode(MediaFormat format) {
+        if (sMCL.findDecoderForFormat(format) == null) {
+            Log.i(TAG, "no decoder for " + format);
+            return false;
+        }
+        return true;
+    }
+
+    public static boolean hasCodecForTrack(MediaExtractor ex, int track) {
+        int count = ex.getTrackCount();
+        if (track < 0 || track >= count) {
+            throw new IndexOutOfBoundsException(track + " not in [0.." + (count - 1) + "]");
+        }
+        return canDecode(ex.getTrackFormat(track));
+    }
+
+    /**
+     * return true iff all audio and video tracks are supported
+     */
+    public static boolean hasCodecsForMedia(MediaExtractor ex) {
+        for (int i = 0; i < ex.getTrackCount(); ++i) {
+            MediaFormat format = ex.getTrackFormat(i);
+            // only check for audio and video codecs
+            String mime = format.getString(MediaFormat.KEY_MIME).toLowerCase();
+            if (!mime.startsWith("audio/") && !mime.startsWith("video/")) {
+                continue;
+            }
+            if (!canDecode(format)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * return true iff any track starting with mimePrefix is supported
+     */
+    public static boolean hasCodecForMediaAndDomain(MediaExtractor ex, String mimePrefix) {
+        mimePrefix = mimePrefix.toLowerCase();
+        for (int i = 0; i < ex.getTrackCount(); ++i) {
+            MediaFormat format = ex.getTrackFormat(i);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            if (mime.toLowerCase().startsWith(mimePrefix)) {
+                if (canDecode(format)) {
+                    return true;
+                }
+                Log.i(TAG, "no decoder for " + format);
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasCodecsForResourceCombo(
+            Context context, int resourceId, int track, String mimePrefix) {
+        try {
+            AssetFileDescriptor afd = null;
+            MediaExtractor ex = null;
+            try {
+                afd = context.getResources().openRawResourceFd(resourceId);
+                ex = new MediaExtractor();
+                ex.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+                if (mimePrefix != null) {
+                    return hasCodecForMediaAndDomain(ex, mimePrefix);
+                } else if (track == ALL_AV_TRACKS) {
+                    return hasCodecsForMedia(ex);
+                } else {
+                    return hasCodecForTrack(ex, track);
+                }
+            } finally {
+                if (ex != null) {
+                    ex.release();
+                }
+                if (afd != null) {
+                    afd.close();
+                }
+            }
+        } catch (IOException e) {
+            Log.i(TAG, "could not open resource");
+        }
+        return false;
+    }
+
+    /**
+     * return true iff all audio and video tracks are supported
+     */
+    public static boolean hasCodecsForResource(Context context, int resourceId) {
+        return hasCodecsForResourceCombo(context, resourceId, ALL_AV_TRACKS, null /* mimePrefix */);
+    }
+
+    public static boolean checkCodecsForResource(Context context, int resourceId) {
+        return check(hasCodecsForResource(context, resourceId), "no decoder found");
+    }
+
+    /**
+     * return true iff track is supported.
+     */
+    public static boolean hasCodecForResource(Context context, int resourceId, int track) {
+        return hasCodecsForResourceCombo(context, resourceId, track, null /* mimePrefix */);
+    }
+
+    public static boolean checkCodecForResource(Context context, int resourceId, int track) {
+        return check(hasCodecForResource(context, resourceId, track), "no decoder found");
+    }
+
+    /**
+     * return true iff any track starting with mimePrefix is supported
+     */
+    public static boolean hasCodecForResourceAndDomain(
+            Context context, int resourceId, String mimePrefix) {
+        return hasCodecsForResourceCombo(context, resourceId, ALL_AV_TRACKS, mimePrefix);
+    }
+
+    /**
+     * return true iff all audio and video tracks are supported
+     */
+    public static boolean hasCodecsForPath(Context context, String path) {
+        MediaExtractor ex = null;
+        try {
+            ex = new MediaExtractor();
+            Uri uri = Uri.parse(path);
+            String scheme = uri.getScheme();
+            if (scheme == null) { // file
+                ex.setDataSource(path);
+            } else if (scheme.equalsIgnoreCase("file")) {
+                ex.setDataSource(uri.getPath());
+            } else {
+                ex.setDataSource(context, uri, null);
+            }
+            return hasCodecsForMedia(ex);
+        } catch (IOException e) {
+            Log.i(TAG, "could not open path " + path);
+        } finally {
+            if (ex != null) {
+                ex.release();
+            }
+        }
+        return false;
+    }
+
+    public static boolean checkCodecsForPath(Context context, String path) {
+        return check(hasCodecsForPath(context, path), "no decoder found");
+    }
+
+    private static boolean hasCodecForMime(boolean encoder, String mime) {
+        for (MediaCodecInfo info : sMCL.getCodecInfos()) {
+            if (encoder != info.isEncoder()) {
+                continue;
+            }
+
+            for (String type : info.getSupportedTypes()) {
+                if (type.equalsIgnoreCase(mime)) {
+                    Log.i(TAG, "found codec " + info.getName() + " for mime " + mime);
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    private static boolean hasCodecForMimes(boolean encoder, String[] mimes) {
+        for (String mime : mimes) {
+            if (!hasCodecForMime(encoder, mime)) {
+                Log.i(TAG, "no " + (encoder ? "encoder" : "decoder") + " for mime " + mime);
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    public static boolean hasEncoder(String... mimes) {
+        return hasCodecForMimes(true /* encoder */, mimes);
+    }
+
+    public static boolean hasDecoder(String... mimes) {
+        return hasCodecForMimes(false /* encoder */, mimes);
+    }
+
+    public static boolean checkDecoder(String... mimes) {
+        return check(hasCodecForMimes(false /* encoder */, mimes), "no decoder found");
+    }
+
+    public static boolean checkEncoder(String... mimes) {
+        return check(hasCodecForMimes(true /* encoder */, mimes), "no encoder found");
+    }
+
+    public static boolean canDecodeVideo(String mime, int width, int height, float rate) {
+        MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
+        format.setFloat(MediaFormat.KEY_FRAME_RATE, rate);
+        return canDecode(format);
+    }
+
+    public static boolean checkDecoderForFormat(MediaFormat format) {
+        return check(canDecode(format), "no decoder for " + format);
+    }
+}
diff --git a/suite/cts/deviceTests/tvproviderperf/Android.mk b/suite/cts/deviceTests/tvproviderperf/Android.mk
new file mode 100644
index 0000000..e268955
--- /dev/null
+++ b/suite/cts/deviceTests/tvproviderperf/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2014 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsDeviceTvProviderPerf
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
+
diff --git a/suite/cts/deviceTests/tvproviderperf/AndroidManifest.xml b/suite/cts/deviceTests/tvproviderperf/AndroidManifest.xml
new file mode 100644
index 0000000..d345ab2
--- /dev/null
+++ b/suite/cts/deviceTests/tvproviderperf/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2014 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.tvproviderperf">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
+    <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.cts.tvproviderperf"
+            android:label="TvProvider performance measurement" />
+</manifest>
diff --git a/suite/cts/deviceTests/tvproviderperf/src/com/android/cts/tvproviderperf/TvProviderPerfTest.java b/suite/cts/deviceTests/tvproviderperf/src/com/android/cts/tvproviderperf/TvProviderPerfTest.java
new file mode 100644
index 0000000..f43beb5
--- /dev/null
+++ b/suite/cts/deviceTests/tvproviderperf/src/com/android/cts/tvproviderperf/TvProviderPerfTest.java
@@ -0,0 +1,301 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.tvproviderperf;
+
+import android.content.ComponentName;
+import android.content.ContentProviderOperation;
+import android.content.ContentProviderResult;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.ContentValues;
+import android.content.OperationApplicationException;
+import android.content.pm.PackageManager;
+import android.database.Cursor;
+import android.cts.util.CtsAndroidTestCase;
+import android.media.tv.TvContract;
+import android.media.tv.TvContract.Channels;
+import android.media.tv.TvContract.Programs;
+import android.net.Uri;
+import android.os.RemoteException;
+
+import com.android.cts.util.MeasureRun;
+import com.android.cts.util.MeasureTime;
+import com.android.cts.util.ResultType;
+import com.android.cts.util.ResultUnit;
+import com.android.cts.util.ReportLog;
+import com.android.cts.util.TimeoutReq;
+import com.android.cts.util.Stat;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test performance of TvProvider on a device. TvProvider typically handles hundreds of
+ * thousands of records periodically, so it is desirable to have performance under a reasonable
+ * bar.
+ */
+public class TvProviderPerfTest extends CtsAndroidTestCase {
+    private ContentResolver mContentResolver;
+    private String mInputId;
+    private boolean mHasTvInputFramework;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mHasTvInputFramework = getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_LIVE_TV);
+        if (!mHasTvInputFramework) return;
+        mContentResolver = getContext().getContentResolver();
+        mInputId = TvContract.buildInputId(new ComponentName(getContext(), getClass()));
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        try {
+            if (!mHasTvInputFramework) return;
+            mContentResolver.delete(Programs.CONTENT_URI, null, null);
+            mContentResolver.delete(Channels.CONTENT_URI, null, null);
+        } finally {
+            super.tearDown();
+        }
+    }
+
+    @TimeoutReq(minutes = 10)
+    public void testChannels() throws Exception {
+        if (!mHasTvInputFramework) return;
+        double[] averages = new double[3];
+
+        // Insert
+        final ArrayList<ContentProviderOperation> operations = new ArrayList<>();
+        final int TRANSACTION_SIZE = 1000;
+        final int TRANSACTION_RUNS = 100;
+        double[] applyBatchTimes = MeasureTime.measure(TRANSACTION_RUNS, new MeasureRun() {
+            @Override
+            public void run(int i) {
+                operations.clear();
+                for (int j = 0; j < TRANSACTION_SIZE; ++j) {
+                    ContentValues values = new ContentValues();
+                    values.put(Channels.COLUMN_INPUT_ID, mInputId);
+                    values.put(Channels.COLUMN_SERVICE_TYPE,
+                            Channels.SERVICE_TYPE_AUDIO_VIDEO);
+                    values.put(Channels.COLUMN_TYPE, Channels.TYPE_OTHER);
+                    operations.add(
+                            ContentProviderOperation.newInsert(Channels.CONTENT_URI)
+                            .withValues(values).build());
+                }
+                try {
+                    mContentResolver.applyBatch(TvContract.AUTHORITY, operations);
+                } catch (OperationApplicationException | RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+        getReportLog().printArray("Elapsed time for insert: ",
+                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        averages[0] = Stat.getAverage(applyBatchTimes);
+
+        // Update
+        final String[] projection = { Channels._ID };
+        try (final Cursor cursor = mContentResolver.query(Channels.CONTENT_URI,
+                projection, null, null, null)) {
+            applyBatchTimes = MeasureTime.measure(TRANSACTION_RUNS, new MeasureRun() {
+                @Override
+                public void run(int i) {
+                    operations.clear();
+                    for (int j = 0; j < TRANSACTION_SIZE && cursor.moveToNext(); ++j) {
+                        Uri channelUri = TvContract.buildChannelUri(cursor.getLong(0));
+                        String number = Integer.toString(i * TRANSACTION_SIZE + j);
+                        operations.add(
+                                ContentProviderOperation.newUpdate(channelUri)
+                                .withValue(Channels.COLUMN_DISPLAY_NUMBER, number)
+                                .build());
+                    }
+                    try {
+                        mContentResolver.applyBatch(TvContract.AUTHORITY, operations);
+                    } catch (OperationApplicationException | RemoteException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            });
+        }
+        getReportLog().printArray("Elapsed time for update: ",
+                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        averages[1] = Stat.getAverage(applyBatchTimes);
+
+        // Delete
+        applyBatchTimes = MeasureTime.measure(1, new MeasureRun() {
+            @Override
+            public void run(int i) {
+                mContentResolver.delete(TvContract.buildChannelsUriForInput(mInputId), null, null);
+            }
+        });
+        getReportLog().printArray("Elapsed time for delete: ",
+                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        averages[2] = Stat.getAverage(applyBatchTimes);
+
+        // Query is not interesting for channels.
+        getReportLog().printArray("Average elapsed time for (insert, update, delete): ",
+                averages, ResultType.LOWER_BETTER, ResultUnit.MS);
+    }
+
+    @TimeoutReq(minutes = 15)
+    public void testPrograms() throws Exception {
+        if (!mHasTvInputFramework) return;
+        double[] averages = new double[5];
+
+        // Prepare (insert channels)
+        final ArrayList<ContentProviderOperation> operations = new ArrayList<>();
+        final int TRANSACTION_SIZE = 1000;
+        final int NUM_CHANNELS = 100;
+        final List<Uri> channelUris = new ArrayList<>();
+
+        operations.clear();
+        for (int i = 0; i < NUM_CHANNELS; ++i) {
+            ContentValues values = new ContentValues();
+            values.put(Channels.COLUMN_INPUT_ID, mInputId);
+            values.put(Channels.COLUMN_SERVICE_TYPE,
+                    Channels.SERVICE_TYPE_AUDIO_VIDEO);
+            values.put(Channels.COLUMN_TYPE, Channels.TYPE_OTHER);
+            operations.add(
+                    ContentProviderOperation.newInsert(Channels.CONTENT_URI)
+                    .withValues(values).build());
+        }
+        try {
+            ContentProviderResult[] results =
+                    mContentResolver.applyBatch(TvContract.AUTHORITY, operations);
+            for (ContentProviderResult result : results) {
+                channelUris.add(result.uri);
+            }
+        } catch (OperationApplicationException | RemoteException e) {
+            throw new RuntimeException(e);
+        }
+
+        // Insert
+        double[] applyBatchTimes = MeasureTime.measure(NUM_CHANNELS, new MeasureRun() {
+            @Override
+            public void run(int i) {
+                operations.clear();
+                Uri channelUri = channelUris.get(i);
+                long channelId = ContentUris.parseId(channelUri);
+                for (int j = 0; j < TRANSACTION_SIZE; ++j) {
+                    ContentValues values = new ContentValues();
+                    values.put(Programs.COLUMN_CHANNEL_ID, channelId);
+                    operations.add(
+                            ContentProviderOperation.newInsert(Programs.CONTENT_URI)
+                            .withValues(values).build());
+                }
+                try {
+                    mContentResolver.applyBatch(TvContract.AUTHORITY, operations);
+                } catch (OperationApplicationException | RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+        getReportLog().printArray("Elapsed time for insert: ",
+                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        averages[0] = Stat.getAverage(applyBatchTimes);
+
+        // Update
+        final long PROGRAM_DURATION_MS = 60 * 1000;
+        final String[] projection = { Programs._ID };
+        applyBatchTimes = MeasureTime.measure(NUM_CHANNELS, new MeasureRun() {
+            @Override
+            public void run(int i) {
+                Uri channelUri = channelUris.get(i);
+                operations.clear();
+                try (final Cursor cursor = mContentResolver.query(
+                        TvContract.buildProgramsUriForChannel(channelUri),
+                        projection, null, null, null)) {
+                    long startTimeMs = 0;
+                    long endTimeMs = 0;
+                    while (cursor.moveToNext()) {
+                        Uri programUri = TvContract.buildProgramUri(cursor.getLong(0));
+                        endTimeMs += PROGRAM_DURATION_MS;
+                        operations.add(
+                                ContentProviderOperation.newUpdate(programUri)
+                                .withValue(Programs.COLUMN_START_TIME_UTC_MILLIS, startTimeMs)
+                                .withValue(Programs.COLUMN_END_TIME_UTC_MILLIS, endTimeMs)
+                                .build());
+                        startTimeMs = endTimeMs;
+                    }
+                }
+                try {
+                    mContentResolver.applyBatch(TvContract.AUTHORITY, operations);
+                } catch (OperationApplicationException | RemoteException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+        });
+        getReportLog().printArray("Elapsed time for update: ",
+                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        averages[1] = Stat.getAverage(applyBatchTimes);
+
+        // Query
+        applyBatchTimes = MeasureTime.measure(NUM_CHANNELS, new MeasureRun() {
+            @Override
+            public void run(int i) {
+                Uri channelUri = channelUris.get(i);
+                int j = 0;
+                try (final Cursor cursor = mContentResolver.query(
+                        TvContract.buildProgramsUriForChannel(
+                                channelUri, 0,
+                                PROGRAM_DURATION_MS * TRANSACTION_SIZE / 2),
+                        projection, null, null, null)) {
+                    while (cursor.moveToNext()) {
+                        ++j;
+                    }
+                }
+            }
+        });
+        getReportLog().printArray("Elapsed time for query: ",
+                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        averages[2] = Stat.getAverage(applyBatchTimes);
+
+        // Delete programs
+        applyBatchTimes = MeasureTime.measure(NUM_CHANNELS, new MeasureRun() {
+            @Override
+            public void run(int i) {
+                Uri channelUri = channelUris.get(i);
+                mContentResolver.delete(
+                        TvContract.buildProgramsUriForChannel(
+                                channelUri,
+                                PROGRAM_DURATION_MS * TRANSACTION_SIZE / 2,
+                                PROGRAM_DURATION_MS * TRANSACTION_SIZE),
+                        null, null);
+            }
+        });
+        getReportLog().printArray("Elapsed time for delete programs: ",
+                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        averages[3] = Stat.getAverage(applyBatchTimes);
+
+        // Delete channels
+        applyBatchTimes = MeasureTime.measure(NUM_CHANNELS, new MeasureRun() {
+            @Override
+            public void run(int i) {
+                Uri channelUri = channelUris.get(i);
+                mContentResolver.delete(channelUri, null, null);
+            }
+        });
+        getReportLog().printArray("Elapsed time for delete channels: ",
+                applyBatchTimes, ResultType.LOWER_BETTER, ResultUnit.MS);
+        averages[4] = Stat.getAverage(applyBatchTimes);
+
+        getReportLog().printArray("Average elapsed time for (insert, update, query, "
+                + "delete channels, delete programs): ",
+                averages, ResultType.LOWER_BETTER, ResultUnit.MS);
+    }
+}
diff --git a/suite/cts/deviceTests/videoperf/Android.mk b/suite/cts/deviceTests/videoperf/Android.mk
index cb398a9..a393683 100644
--- a/suite/cts/deviceTests/videoperf/Android.mk
+++ b/suite/cts/deviceTests/videoperf/Android.mk
@@ -17,14 +17,21 @@
 
 # don't include this package in any target
 LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner
+# include both the 32 and 64 bit versions
+LOCAL_MULTILIB := both
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctsmediautil ctsdeviceutil ctstestrunner
+
+LOCAL_JNI_SHARED_LIBRARIES := libctsmediacodec_jni
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_PACKAGE_NAME := CtsDeviceVideoPerf
 
-LOCAL_SDK_VERSION := 16
+LOCAL_SDK_VERSION := current
 
 include $(BUILD_CTS_PACKAGE)
 
diff --git a/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/CodecInfo.java b/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/CodecInfo.java
index b9edfa4..6459c86 100644
--- a/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/CodecInfo.java
+++ b/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/CodecInfo.java
@@ -19,7 +19,9 @@
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecInfo.VideoCapabilities;
 import android.media.MediaCodecList;
+import android.media.MediaFormat;
 import android.util.Log;
 
 
@@ -38,21 +40,38 @@
     public boolean mSupportPlanar = false;
 
     private static final String TAG = "CodecInfo";
-    private static final String VIDEO_AVC = "video/avc";
+    private static final String VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
     /**
      * Check if given codec with given (w,h) is supported.
-     * @param mimeType codec type in mime format like "video/avc"
+     * @param mimeType codec type in mime format like MediaFormat.MIMETYPE_VIDEO_AVC
      * @param w video width
      * @param h video height
      * @param isEncoder whether the codec is encoder or decoder
      * @return null if the configuration is not supported.
      */
-    public static CodecInfo getSupportedFormatInfo(String mimeType, int w, int h,
-            boolean isEncoder) {
-        CodecCapabilities cap = getCodecCapability(mimeType, isEncoder);
-        if (cap == null) { // not supported
+    public static CodecInfo getSupportedFormatInfo(
+            String mimeType, int w, int h, boolean isEncoder) {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        MediaFormat format = MediaFormat.createVideoFormat(mimeType, w, h);
+        String codec = isEncoder
+                ? mcl.findEncoderForFormat(format)
+                : mcl.findDecoderForFormat(format);
+        if (codec == null) { // not supported
             return null;
         }
+        CodecCapabilities cap = null;
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (info.getName().equals(codec)) {
+                cap = info.getCapabilitiesForType(mimeType);
+                break;
+            }
+        }
+
+        if (cap.colorFormats.length == 0) {
+            Log.w(TAG, "no supported color format");
+            return null;
+        }
+
         CodecInfo info = new CodecInfo();
         for (int color : cap.colorFormats) {
             if (color == CodecCapabilities.COLOR_FormatYUV420SemiPlanar) {
@@ -63,136 +82,16 @@
             }
         }
         printIntArray("supported colors", cap.colorFormats);
-        //  either YUV420 planar or semiplanar should be supported
-        if (!info.mSupportPlanar && !info.mSupportSemiPlanar) {
-            Log.i(TAG, "no supported color format");
-            return null;
-        }
 
+        VideoCapabilities vidCap = cap.getVideoCapabilities();
         if (mimeType.equals(VIDEO_AVC)) {
-            int highestLevel = 0;
-            for (CodecProfileLevel lvl : cap.profileLevels) {
-                if (lvl.level > highestLevel) {
-                    highestLevel = lvl.level;
-                }
-            }
-            Log.i(TAG, "Avc highest level " + Integer.toHexString(highestLevel));
-            int maxW = 0;
-            int maxH = 0;
-            int bitRate = 0;
-            int mbW = (w + 15) / 16; // size in macroblocks
-            int mbH = (h + 15) / 16;
-            int maxMacroblocksPerSecond = 0; // max decoding speed
-            switch(highestLevel) {
-            // Do not support Level 1 to 2.
-            case CodecProfileLevel.AVCLevel1:
-            case CodecProfileLevel.AVCLevel11:
-            case CodecProfileLevel.AVCLevel12:
-            case CodecProfileLevel.AVCLevel13:
-            case CodecProfileLevel.AVCLevel1b:
-            case CodecProfileLevel.AVCLevel2:
-                return null;
-            case CodecProfileLevel.AVCLevel21:
-                maxW = 352;
-                maxH = 576;
-                bitRate = 4000000;
-                maxMacroblocksPerSecond = 19800;
-                break;
-            case CodecProfileLevel.AVCLevel22:
-                maxW = 720;
-                maxH = 480;
-                bitRate = 4000000;
-                maxMacroblocksPerSecond = 20250;
-                break;
-            case CodecProfileLevel.AVCLevel3:
-                maxW = 720;
-                maxH = 480;
-                bitRate = 10000000;
-                maxMacroblocksPerSecond = 40500;
-                break;
-            case CodecProfileLevel.AVCLevel31:
-                maxW = 1280;
-                maxH = 720;
-                bitRate = 14000000;
-                maxMacroblocksPerSecond = 108000;
-                break;
-            case CodecProfileLevel.AVCLevel32:
-                maxW = 1280;
-                maxH = 720;
-                bitRate = 20000000;
-                maxMacroblocksPerSecond = 216000;
-                break;
-            case CodecProfileLevel.AVCLevel4:
-                maxW = 1920;
-                maxH = 1080;
-                bitRate = 20000000;
-                maxMacroblocksPerSecond = 245760;
-                break;
-            case CodecProfileLevel.AVCLevel41:
-                maxW = 1920;
-                maxH = 1080;
-                bitRate = 50000000;
-                maxMacroblocksPerSecond = 245760;
-                break;
-            case CodecProfileLevel.AVCLevel42:
-                maxW = 2048;
-                maxH = 1080;
-                bitRate = 50000000;
-                maxMacroblocksPerSecond = 522240;
-                break;
-            case CodecProfileLevel.AVCLevel5:
-                maxW = 3672;
-                maxH = 1536;
-                bitRate = 135000000;
-                maxMacroblocksPerSecond = 589824;
-                break;
-            case CodecProfileLevel.AVCLevel51:
-            default:
-                maxW = 4096;
-                maxH = 2304;
-                bitRate = 240000000;
-                maxMacroblocksPerSecond = 983040;
-                break;
-            }
-            if ((w > maxW) || (h > maxH)) {
-                Log.i(TAG, "Requested resolution (" + w + "," + h + ") exceeds (" +
-                        maxW + "," + maxH + ")");
-                return null;
-            }
-            info.mFps = maxMacroblocksPerSecond / mbH / mbW;
-            info.mBitRate = bitRate;
-            Log.i(TAG, "AVC Level " + Integer.toHexString(highestLevel) + " bit rate " + bitRate +
-                    " fps " + info.mFps);
+            info.mFps = vidCap.getSupportedFrameRatesFor(w, h).getUpper().intValue();
+            info.mBitRate = vidCap.getBitrateRange().getUpper();
+            Log.i(TAG, "AVC bit rate " + info.mBitRate + " fps " + info.mFps);
         }
         return info;
     }
 
-    /**
-     * Search for given codecName and returns CodecCapabilities if found
-     * @param codecName
-     * @param isEncoder true for encoder, false for decoder
-     * @return null if the codec is not supported
-     */
-    private static CodecCapabilities getCodecCapability(
-            String codecName, boolean isEncoder) {
-        int codecCount = MediaCodecList.getCodecCount();
-        for (int i = 0; i < codecCount; ++i) {
-            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
-            String[] types = info.getSupportedTypes();
-            if (isEncoder != info.isEncoder()) {
-                continue;
-            }
-            for (int j = 0; j < types.length; ++j) {
-                if (types[j].compareTo(codecName) == 0) {
-                    CodecCapabilities cap = info.getCapabilitiesForType(types[j]);
-                    Log.i(TAG, "Use codec " + info.getName());
-                    return cap;
-                }
-            }
-        }
-        return null;
-    }
-
     // for debugging
     private static void printIntArray(String msg, int[] data) {
         StringBuilder builder = new StringBuilder();
diff --git a/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java b/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
index a009ce2..bf02d9c 100644
--- a/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
+++ b/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
@@ -16,8 +16,14 @@
 
 package com.android.cts.videoperf;
 
+import android.graphics.ImageFormat;
 import android.graphics.Point;
+import android.media.cts.CodecImage;
+import android.media.cts.CodecUtils;
+import android.media.Image;
+import android.media.Image.Plane;
 import android.media.MediaCodec;
+import android.media.MediaCodecList;
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaFormat;
 import android.util.Log;
@@ -27,6 +33,7 @@
 import com.android.cts.util.ResultUnit;
 import com.android.cts.util.Stat;
 
+import java.io.IOException;
 import java.nio.ByteBuffer;
 import java.lang.System;
 import java.util.Random;
@@ -49,7 +56,7 @@
     // is not very high.
     private static final long VIDEO_CODEC_WAIT_TIME_US = 5000;
     private static final boolean VERBOSE = false;
-    private static final String VIDEO_AVC = "video/avc";
+    private static final String VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
     private static final int TOTAL_FRAMES = 300;
     private static final int NUMBER_OF_REPEAT = 10;
     // i frame interval for encoder
@@ -58,12 +65,10 @@
     private static final int Y_CLAMP_MIN = 16;
     private static final int Y_CLAMP_MAX = 235;
     private static final int YUV_PLANE_ADDITIONAL_LENGTH = 200;
-    private ByteBuffer mYBuffer;
-    private ByteBuffer mUVBuffer;
-    // if input raw data is semi-planar
-    private boolean mSrcSemiPlanar;
-    // if output raw data is semi-planar
-    private boolean mDstSemiPlanar;
+    private ByteBuffer mYBuffer, mYDirectBuffer;
+    private ByteBuffer mUVBuffer, mUVDirectBuffer;
+    private int mSrcColorFormat;
+    private int mDstColorFormat;
     private int mBufferWidth;
     private int mBufferHeight;
     private int mVideoWidth;
@@ -93,6 +98,8 @@
         mEncodedOutputBuffer = null;
         mYBuffer = null;
         mUVBuffer = null;
+        mYDirectBuffer = null;
+        mUVDirectBuffer = null;
         mRandom = null;
         super.tearDown();
     }
@@ -122,6 +129,33 @@
         doTest(VIDEO_AVC, 1920, 1072, NUMBER_OF_REPEAT);
     }
 
+    private boolean isSrcSemiPlanar() {
+        return mSrcColorFormat == CodecCapabilities.COLOR_FormatYUV420SemiPlanar;
+    }
+
+    private boolean isSrcFlexYUV() {
+        return mSrcColorFormat == CodecCapabilities.COLOR_FormatYUV420Flexible;
+    }
+
+    private boolean isDstSemiPlanar() {
+        return mDstColorFormat == CodecCapabilities.COLOR_FormatYUV420SemiPlanar;
+    }
+
+    private boolean isDstFlexYUV() {
+        return mDstColorFormat == CodecCapabilities.COLOR_FormatYUV420Flexible;
+    }
+
+    private static int getColorFormat(CodecInfo info) {
+        if (info.mSupportSemiPlanar) {
+            return CodecCapabilities.COLOR_FormatYUV420SemiPlanar;
+        } else if (info.mSupportPlanar) {
+            return CodecCapabilities.COLOR_FormatYUV420Planar;
+        } else {
+            // FlexYUV must be supported
+            return CodecCapabilities.COLOR_FormatYUV420Flexible;
+        }
+    }
+
     /**
      * Run encoding / decoding test for given mimeType of codec
      * @param mimeType like video/avc
@@ -130,17 +164,23 @@
      * @param numberRepeat how many times to repeat the encoding / decoding process
      */
     private void doTest(String mimeType, int w, int h, int numberRepeat) throws Exception {
-        CodecInfo infoEnc = CodecInfo.getSupportedFormatInfo(mimeType, w, h, true);
+        CodecInfo infoEnc = CodecInfo.getSupportedFormatInfo(mimeType, w, h, true /* encoder */);
         if (infoEnc == null) {
-            Log.i(TAG, "Codec " + mimeType + "with " + w + "," + h + " not supported");
+            Log.i(TAG, "Encoder " + mimeType + " with " + w + "," + h + " not supported");
             return;
         }
-        CodecInfo infoDec = CodecInfo.getSupportedFormatInfo(mimeType, w, h, false);
+        CodecInfo infoDec = CodecInfo.getSupportedFormatInfo(mimeType, w, h, false /* encoder */);
         assertNotNull(infoDec);
         mVideoWidth = w;
         mVideoHeight = h;
-        initYUVPlane(w + YUV_PLANE_ADDITIONAL_LENGTH, h + YUV_PLANE_ADDITIONAL_LENGTH,
-                infoEnc.mSupportSemiPlanar, infoDec.mSupportSemiPlanar);
+
+        mSrcColorFormat = getColorFormat(infoEnc);
+        mDstColorFormat = getColorFormat(infoDec);
+        Log.i(TAG, "Testing video resolution " + w + "x" + h +
+                   ": enc format " + mSrcColorFormat +
+                   ", dec format " + mDstColorFormat);
+
+        initYUVPlane(w + YUV_PLANE_ADDITIONAL_LENGTH, h + YUV_PLANE_ADDITIONAL_LENGTH);
         double[] encoderFpsResults = new double[numberRepeat];
         double[] decoderFpsResults = new double[numberRepeat];
         double[] totalFpsResults = new double[numberRepeat];
@@ -152,9 +192,7 @@
             format.setInteger(MediaFormat.KEY_BIT_RATE, infoEnc.mBitRate);
             format.setInteger(MediaFormat.KEY_WIDTH, w);
             format.setInteger(MediaFormat.KEY_HEIGHT, h);
-            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                    infoEnc.mSupportSemiPlanar ? CodecCapabilities.COLOR_FormatYUV420SemiPlanar :
-                        CodecCapabilities.COLOR_FormatYUV420Planar);
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, mSrcColorFormat);
             format.setInteger(MediaFormat.KEY_FRAME_RATE, infoEnc.mFps);
             mFrameRate = infoEnc.mFps;
             format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, KEY_I_FRAME_INTERVAL);
@@ -164,9 +202,7 @@
             format.setString(MediaFormat.KEY_MIME, mimeType);
             format.setInteger(MediaFormat.KEY_WIDTH, w);
             format.setInteger(MediaFormat.KEY_HEIGHT, h);
-            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                    infoDec.mSupportSemiPlanar ? CodecCapabilities.COLOR_FormatYUV420SemiPlanar :
-                        CodecCapabilities.COLOR_FormatYUV420Planar);
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, mDstColorFormat);
             double[] decoderResult = runDecoder(VIDEO_AVC, format);
             if (decoderResult == null) {
                 success = false;
@@ -207,8 +243,11 @@
      * @return time taken in ms to encode the frames. This does not include initialization time.
      */
     private double runEncoder(String mimeType, MediaFormat format, int totalFrames) {
-        MediaCodec codec = MediaCodec.createEncoderByType(mimeType);
+        MediaCodec codec = null;
         try {
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            String encoderName = mcl.findEncoderForFormat(format);
+            codec = MediaCodec.createByCodecName(encoderName);
             codec.configure(
                     format,
                     null /* surface */,
@@ -216,10 +255,13 @@
                     MediaCodec.CONFIGURE_FLAG_ENCODE);
         } catch (IllegalStateException e) {
             Log.e(TAG, "codec '" + mimeType + "' failed configuration.");
+            codec.release();
             assertTrue("codec '" + mimeType + "' failed configuration.", false);
+        } catch (IOException | NullPointerException e) {
+            Log.i(TAG, "could not find codec for " + format);
+            return Double.NaN;
         }
         codec.start();
-        ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
         ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
 
         int numBytesSubmitted = 0;
@@ -232,10 +274,24 @@
             if (inFramesCount < totalFrames) {
                 index = codec.dequeueInputBuffer(VIDEO_CODEC_WAIT_TIME_US /* timeoutUs */);
                 if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
-                    int size = queueInputBufferEncoder(
-                            codec, codecInputBuffers, index, inFramesCount,
-                            (inFramesCount == (totalFrames - 1)) ?
-                                    MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+                    int size;
+                    // when encoder only supports flexYUV, use Image only; otherwise,
+                    // use ByteBuffer & Image each on half of the frames to test both
+                    if (isSrcFlexYUV() || inFramesCount % 2 == 0) {
+                        Image image = codec.getInputImage(index);
+                        // image should always be available
+                        assertTrue(image != null);
+                        size = queueInputImageEncoder(
+                                codec, image, index, inFramesCount,
+                                (inFramesCount == (totalFrames - 1)) ?
+                                        MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+                    } else {
+                        ByteBuffer buffer = codec.getInputBuffer(index);
+                        size = queueInputBufferEncoder(
+                                codec, buffer, index, inFramesCount,
+                                (inFramesCount == (totalFrames - 1)) ?
+                                        MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+                    }
                     inFramesCount++;
                     numBytesSubmitted += size;
                     if (VERBOSE) {
@@ -281,8 +337,7 @@
      * @return size of enqueued data.
      */
     private int queueInputBufferEncoder(
-            MediaCodec codec, ByteBuffer[] inputBuffers, int index, int frameCount, int flags) {
-        ByteBuffer buffer = inputBuffers[index];
+            MediaCodec codec, ByteBuffer buffer, int index, int frameCount, int flags) {
         buffer.clear();
 
         Point origin = getOrigin(frameCount);
@@ -293,7 +348,7 @@
             buffer.put(yBuffer, srcOffsetY, mVideoWidth);
             srcOffsetY += mBufferWidth;
         }
-        if (mSrcSemiPlanar) {
+        if (isSrcSemiPlanar()) {
             int srcOffsetU = origin.y / 2 * mBufferWidth + origin.x / 2 * 2;
             final byte[] uvBuffer = mUVBuffer.array();
             for (int i = 0; i < mVideoHeight / 2; i++) {
@@ -304,16 +359,161 @@
             int srcOffsetU = origin.y / 2 * mBufferWidth / 2 + origin.x / 2;
             int srcOffsetV = srcOffsetU + mBufferWidth / 2 * mBufferHeight / 2;
             final byte[] uvBuffer = mUVBuffer.array();
-            for (int i = 0; i < mVideoHeight /2; i++) { //U only
+            for (int i = 0; i < mVideoHeight / 2; i++) { //U only
                 buffer.put(uvBuffer, srcOffsetU, mVideoWidth / 2);
                 srcOffsetU += mBufferWidth / 2;
             }
-            for (int i = 0; i < mVideoHeight /2; i++) { //V only
+            for (int i = 0; i < mVideoHeight / 2; i++) { //V only
                 buffer.put(uvBuffer, srcOffsetV, mVideoWidth / 2);
                 srcOffsetV += mBufferWidth / 2;
             }
         }
-        int size = mVideoHeight * mVideoWidth * 3 /2;
+        int size = mVideoHeight * mVideoWidth * 3 / 2;
+        long ptsUsec = computePresentationTime(frameCount);
+
+        codec.queueInputBuffer(index, 0 /* offset */, size, ptsUsec /* timeUs */, flags);
+        if (VERBOSE && (frameCount == 0)) {
+            printByteArray("Y ", mYBuffer.array(), 0, 20);
+            printByteArray("UV ", mUVBuffer.array(), 0, 20);
+            printByteArray("UV ", mUVBuffer.array(), mBufferWidth * 60, 20);
+        }
+        return size;
+    }
+
+    class YUVImage extends CodecImage {
+        private final int mImageWidth;
+        private final int mImageHeight;
+        private final Plane[] mPlanes;
+
+        YUVImage(
+                Point origin,
+                int imageWidth, int imageHeight,
+                int arrayWidth, int arrayHeight,
+                boolean semiPlanar,
+                ByteBuffer bufferY, ByteBuffer bufferUV) {
+            mImageWidth = imageWidth;
+            mImageHeight = imageHeight;
+            ByteBuffer dupY = bufferY.duplicate();
+            ByteBuffer dupUV = bufferUV.duplicate();
+            mPlanes = new Plane[3];
+
+            int srcOffsetY = origin.x + origin.y * arrayWidth;
+
+            mPlanes[0] = new YUVPlane(
+                        mImageWidth, mImageHeight, arrayWidth, 1,
+                        dupY, srcOffsetY);
+
+            if (semiPlanar) {
+                int srcOffsetUV = origin.y / 2 * arrayWidth + origin.x / 2 * 2;
+
+                mPlanes[1] = new YUVPlane(
+                        mImageWidth / 2, mImageHeight / 2, arrayWidth, 2,
+                        dupUV, srcOffsetUV);
+                mPlanes[2] = new YUVPlane(
+                        mImageWidth / 2, mImageHeight / 2, arrayWidth, 2,
+                        dupUV, srcOffsetUV + 1);
+            } else {
+                int srcOffsetU = origin.y / 2 * arrayWidth / 2 + origin.x / 2;
+                int srcOffsetV = srcOffsetU + arrayWidth / 2 * arrayHeight / 2;
+
+                mPlanes[1] = new YUVPlane(
+                        mImageWidth / 2, mImageHeight / 2, arrayWidth / 2, 1,
+                        dupUV, srcOffsetU);
+                mPlanes[2] = new YUVPlane(
+                        mImageWidth / 2, mImageHeight / 2, arrayWidth / 2, 1,
+                        dupUV, srcOffsetV);
+            }
+        }
+
+        @Override
+        public int getFormat() {
+            return ImageFormat.YUV_420_888;
+        }
+
+        @Override
+        public int getWidth() {
+            return mImageWidth;
+        }
+
+        @Override
+        public int getHeight() {
+            return mImageHeight;
+        }
+
+        @Override
+        public long getTimestamp() {
+            return 0;
+        }
+
+        @Override
+        public Plane[] getPlanes() {
+            return mPlanes;
+        }
+
+        @Override
+        public void close() {
+            mPlanes[0] = null;
+            mPlanes[1] = null;
+            mPlanes[2] = null;
+        }
+
+        class YUVPlane extends CodecImage.Plane {
+            private final int mRowStride;
+            private final int mPixelStride;
+            private final ByteBuffer mByteBuffer;
+
+            YUVPlane(int w, int h, int rowStride, int pixelStride,
+                    ByteBuffer buffer, int offset) {
+                mRowStride = rowStride;
+                mPixelStride = pixelStride;
+
+                // only safe to access length bytes starting from buffer[offset]
+                int length = (h - 1) * rowStride + (w - 1) * pixelStride + 1;
+
+                buffer.position(offset);
+                mByteBuffer = buffer.slice();
+                mByteBuffer.limit(length);
+            }
+
+            @Override
+            public int getRowStride() {
+                return mRowStride;
+            }
+
+            @Override
+            public int getPixelStride() {
+                return mPixelStride;
+            }
+
+            @Override
+            public ByteBuffer getBuffer() {
+                return mByteBuffer;
+            }
+        }
+    }
+
+    /**
+     * Fills input image for encoder from YUV buffers.
+     * @return size of enqueued data.
+     */
+    private int queueInputImageEncoder(
+            MediaCodec codec, Image image, int index, int frameCount, int flags) {
+        assertTrue(image.getFormat() == ImageFormat.YUV_420_888);
+
+
+        Point origin = getOrigin(frameCount);
+
+        // Y color first
+        CodecImage srcImage = new YUVImage(
+                origin,
+                mVideoWidth, mVideoHeight,
+                mBufferWidth, mBufferHeight,
+                isSrcSemiPlanar(),
+                mYDirectBuffer, mUVDirectBuffer);
+
+        CodecUtils.copyFlexYUVImage(image, srcImage);
+
+        int size = mVideoHeight * mVideoWidth * 3 / 2;
         long ptsUsec = computePresentationTime(frameCount);
 
         codec.queueInputBuffer(index, 0 /* offset */, size, ptsUsec /* timeUs */, flags);
@@ -347,11 +547,18 @@
      * @return returns length-2 array with 0: time for decoding, 1 : rms error of pixels
      */
     private double[] runDecoder(String mimeType, MediaFormat format) {
-        MediaCodec codec = MediaCodec.createDecoderByType(mimeType);
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        String decoderName = mcl.findDecoderForFormat(format);
+        MediaCodec codec = null;
+        try {
+            codec = MediaCodec.createByCodecName(decoderName);
+        } catch (IOException | NullPointerException e) {
+            Log.i(TAG, "could not find codec for " + format);
+            return null;
+        }
         codec.configure(format, null /* surface */, null /* crypto */, 0 /* flags */);
         codec.start();
         ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
-        ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
 
         double totalErrorSquared = 0;
 
@@ -390,22 +597,49 @@
 
                 // only do YUV compare on EOS frame if the buffer size is none-zero
                 if (info.size > 0) {
-                    ByteBuffer buf = codecOutputBuffers[outputBufIndex];
-                    if (VERBOSE && (outFrameCount == 0)) {
-                        printByteBuffer("Y ", buf, 0, 20);
-                        printByteBuffer("UV ", buf, mVideoWidth * mVideoHeight, 20);
-                        printByteBuffer("UV ", buf,
-                                mVideoWidth * mVideoHeight + mVideoWidth * 60, 20);
-                    }
                     Point origin = getOrigin(outFrameCount);
-                    for (int i = 0; i < PIXEL_CHECK_PER_FRAME; i++) {
+                    int i;
+
+                    // if decoder supports planar or semiplanar, check output with
+                    // ByteBuffer & Image each on half of the points
+                    int pixelCheckPerFrame = PIXEL_CHECK_PER_FRAME;
+                    if (!isDstFlexYUV()) {
+                        pixelCheckPerFrame /= 2;
+                        ByteBuffer buf = codec.getOutputBuffer(outputBufIndex);
+                        if (VERBOSE && (outFrameCount == 0)) {
+                            printByteBuffer("Y ", buf, 0, 20);
+                            printByteBuffer("UV ", buf, mVideoWidth * mVideoHeight, 20);
+                            printByteBuffer("UV ", buf,
+                                    mVideoWidth * mVideoHeight + mVideoWidth * 60, 20);
+                        }
+                        for (i = 0; i < pixelCheckPerFrame; i++) {
+                            int w = mRandom.nextInt(mVideoWidth);
+                            int h = mRandom.nextInt(mVideoHeight);
+                            getPixelValuesFromYUVBuffers(origin.x, origin.y, w, h, expected);
+                            getPixelValuesFromOutputBuffer(buf, w, h, decoded);
+                            if (VERBOSE) {
+                                Log.i(TAG, outFrameCount + "-" + i + "- th round: ByteBuffer:"
+                                        + " expected "
+                                        + expected.mY + "," + expected.mU + "," + expected.mV
+                                        + " decoded "
+                                        + decoded.mY + "," + decoded.mU + "," + decoded.mV);
+                            }
+                            totalErrorSquared += expected.calcErrorSquared(decoded);
+                        }
+                    }
+
+                    Image image = codec.getOutputImage(outputBufIndex);
+                    assertTrue(image != null);
+                    for (i = 0; i < pixelCheckPerFrame; i++) {
                         int w = mRandom.nextInt(mVideoWidth);
                         int h = mRandom.nextInt(mVideoHeight);
                         getPixelValuesFromYUVBuffers(origin.x, origin.y, w, h, expected);
-                        getPixelValuesFromOutputBuffer(buf, w, h, decoded);
+                        getPixelValuesFromImage(image, w, h, decoded);
                         if (VERBOSE) {
-                            Log.i(TAG, outFrameCount + "-" + i + "- th round expcted " + expected.mY
-                                    + "," + expected.mU + "," + expected.mV + "  decoded "
+                            Log.i(TAG, outFrameCount + "-" + i + "- th round: FlexYUV:"
+                                    + " expcted "
+                                    + expected.mY + "," + expected.mU + "," + expected.mV
+                                    + " decoded "
                                     + decoded.mY + "," + decoded.mU + "," + decoded.mV);
                         }
                         totalErrorSquared += expected.calcErrorSquared(decoded);
@@ -417,23 +651,17 @@
                     Log.d(TAG, "saw output EOS.");
                     sawOutputEOS = true;
                 }
-            } else if (res == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-                codecOutputBuffers = codec.getOutputBuffers();
-                Log.d(TAG, "output buffers have changed.");
             } else if (res == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
                 MediaFormat oformat = codec.getOutputFormat();
                 Log.d(TAG, "output format has changed to " + oformat);
                 int colorFormat = oformat.getInteger(MediaFormat.KEY_COLOR_FORMAT);
-                if (colorFormat == CodecCapabilities.COLOR_FormatYUV420SemiPlanar ) {
-                    mDstSemiPlanar = true;
-                } else if (colorFormat == CodecCapabilities.COLOR_FormatYUV420Planar ) {
-                    mDstSemiPlanar = false;
+                if (colorFormat == CodecCapabilities.COLOR_FormatYUV420SemiPlanar
+                        || colorFormat == CodecCapabilities.COLOR_FormatYUV420Planar) {
+                    mDstColorFormat = colorFormat;
                 } else {
+                    mDstColorFormat = CodecCapabilities.COLOR_FormatYUV420Flexible;
                     Log.w(TAG, "output format changed to unsupported one " +
-                            Integer.toHexString(colorFormat));
-                    // give up and return as nothing can be done
-                    codec.release();
-                    return null;
+                            Integer.toHexString(colorFormat) + ", using FlexYUV");
                 }
             }
         }
@@ -474,12 +702,12 @@
      * @param semiPlanarEnc
      * @param semiPlanarDec
      */
-    private void initYUVPlane(int w, int h, boolean semiPlanarEnc, boolean semiPlanarDec) {
+    private void initYUVPlane(int w, int h) {
         int bufferSizeY = w * h;
         mYBuffer = ByteBuffer.allocate(bufferSizeY);
         mUVBuffer = ByteBuffer.allocate(bufferSizeY / 2);
-        mSrcSemiPlanar = semiPlanarEnc;
-        mDstSemiPlanar = semiPlanarDec;
+        mYDirectBuffer = ByteBuffer.allocateDirect(bufferSizeY);
+        mUVDirectBuffer = ByteBuffer.allocateDirect(bufferSizeY / 2);
         mBufferWidth = w;
         mBufferHeight = h;
         final byte[] yArray = mYBuffer.array();
@@ -489,7 +717,7 @@
                 yArray[i * w + j]  = clampY((i + j) & 0xff);
             }
         }
-        if (semiPlanarEnc) {
+        if (isSrcSemiPlanar()) {
             for (int i = 0; i < h/2; i++) {
                 for (int j = 0; j < w/2; j++) {
                     uvArray[i * w + 2 * j]  = (byte) (i & 0xff);
@@ -505,6 +733,10 @@
                 }
             }
         }
+        mYDirectBuffer.put(yArray);
+        mUVDirectBuffer.put(uvArray);
+        mYDirectBuffer.rewind();
+        mUVDirectBuffer.rewind();
     }
 
     /**
@@ -539,7 +771,7 @@
     private void getPixelValuesFromYUVBuffers(int originX, int originY, int x, int y,
             YUVValue result) {
         result.mY = mYBuffer.get((originY + y) * mBufferWidth + (originX + x));
-        if (mSrcSemiPlanar) {
+        if (isSrcSemiPlanar()) {
             int index = (originY + y) / 2 * mBufferWidth + (originX + x) / 2 * 2;
             //Log.d(TAG, "YUV " + originX + "," + originY + "," + x + "," + y + "," + index);
             result.mU = mUVBuffer.get(index);
@@ -560,7 +792,7 @@
      */
     private void getPixelValuesFromOutputBuffer(ByteBuffer buffer, int x, int y, YUVValue result) {
         result.mY = buffer.get(y * mVideoWidth + x);
-        if (mDstSemiPlanar) {
+        if (isDstSemiPlanar()) {
             int index = mVideoWidth * mVideoHeight + y / 2 * mVideoWidth + x / 2 * 2;
             //Log.d(TAG, "Decoded " + x + "," + y + "," + index);
             result.mU = buffer.get(index);
@@ -573,6 +805,22 @@
         }
     }
 
+    private void getPixelValuesFromImage(Image image, int x, int y, YUVValue result) {
+        assertTrue(image.getFormat() == ImageFormat.YUV_420_888);
+
+        Plane[] planes = image.getPlanes();
+        assertTrue(planes.length == 3);
+
+        result.mY = getPixelFromPlane(planes[0], x, y);
+        result.mU = getPixelFromPlane(planes[1], x / 2, y / 2);
+        result.mV = getPixelFromPlane(planes[2], x / 2, y / 2);
+    }
+
+    private byte getPixelFromPlane(Plane plane, int x, int y) {
+        ByteBuffer buf = plane.getBuffer();
+        return buf.get(y * plane.getRowStride() + x * plane.getPixelStride());
+    }
+
     /**
      * Y cannot have full range. clamp it to prevent invalid value.
      */
diff --git a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
index a0177e2..38a753d 100644
--- a/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
+++ b/tests/JobScheduler/src/android/jobscheduler/MockJobService.java
@@ -34,7 +34,7 @@
     private static final String TAG = "MockJobService";
 
     /** Wait this long before timing out the test. */
-    private static final long DEFAULT_TIMEOUT_MILLIS = 5000L; // 5 seconds.
+    private static final long DEFAULT_TIMEOUT_MILLIS = 30000L; // 30 seconds.
 
     @Override
     public void onCreate() {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
index 36f44ef..ed9cadd 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/TimingConstraintsTest.java
@@ -29,7 +29,7 @@
 
     public void testScheduleOnce() throws Exception {
         JobInfo oneTimeJob = new JobInfo.Builder(TIMING_JOB_ID, kJobServiceComponent)
-                        .setOverrideDeadline(1000)  // 1 secs
+                        .setOverrideDeadline(5000)  // 5 secs
                         .build();
 
         kTestEnvironment.setExpectedExecutions(1);
@@ -41,7 +41,7 @@
     public void testSchedulePeriodic() throws Exception {
         JobInfo periodicJob =
                 new JobInfo.Builder(TIMING_JOB_ID, kJobServiceComponent)
-                        .setPeriodic(1000L)  // 1 second period.
+                        .setPeriodic(5000L)  // 5 second period.
                         .build();
 
         kTestEnvironment.setExpectedExecutions(3);
@@ -52,7 +52,8 @@
 
     public void testCancel() throws Exception {
         JobInfo cancelJob = new JobInfo.Builder(CANCEL_JOB_ID, kJobServiceComponent)
-                .setOverrideDeadline(2000L)
+                .setMinimumLatency(5000L) // make sure it doesn't actually run immediately
+                .setOverrideDeadline(7000L)
                 .build();
 
         kTestEnvironment.setExpectedExecutions(0);
diff --git a/tests/app/AndroidManifest.xml b/tests/app/AndroidManifest.xml
index b628a0c..0d61e20 100644
--- a/tests/app/AndroidManifest.xml
+++ b/tests/app/AndroidManifest.xml
@@ -40,6 +40,7 @@
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
     <uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.BODY_SENSORS" />
 
     <application android:label="Android TestCase"
                 android:icon="@drawable/size_48x48"
diff --git a/tests/app/src/android/app/cts/MockAlarmReceiver.java b/tests/app/src/android/app/cts/MockAlarmReceiver.java
index 5060cef..8745db6 100644
--- a/tests/app/src/android/app/cts/MockAlarmReceiver.java
+++ b/tests/app/src/android/app/cts/MockAlarmReceiver.java
@@ -25,17 +25,21 @@
  * this class  receive alarm from AlarmManagerTest
  */
 public class MockAlarmReceiver extends BroadcastReceiver {
-    public boolean alarmed = false;
-    private Object mSync = new Object();
-    public static final String MOCKACTION = "android.app.AlarmManagerTest.TEST_ALARMRECEIVER";
+    private final Object mSync = new Object();
+    public final String mTargetAction;
 
-    public long elapsedTime;
-    public long rtcTime;
+    public volatile boolean alarmed = false;
+    public volatile long elapsedTime;
+    public volatile long rtcTime;
+
+    public MockAlarmReceiver(String targetAction) {
+        mTargetAction = targetAction;
+    }
 
     @Override
     public void onReceive(Context context, Intent intent) {
         final String action = intent.getAction();
-        if (action.equals(MOCKACTION)) {
+        if (action.equals(mTargetAction)) {
             synchronized (mSync) {
                 alarmed = true;
                 elapsedTime = SystemClock.elapsedRealtime();
diff --git a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java b/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
index 5196df1..57945f8 100644
--- a/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
+++ b/tests/core/runner/src/com/android/cts/runner/CtsTestRunListener.java
@@ -35,6 +35,7 @@
 import java.net.CookieHandler;
 import java.net.ResponseCache;
 import java.util.Locale;
+import java.util.Properties;
 import java.util.TimeZone;
 
 import javax.net.ssl.HostnameVerifier;
@@ -57,7 +58,7 @@
 
     @Override
     public void testRunStarted(Description description) throws Exception {
-        mEnvironment = new TestEnvironment();
+        mEnvironment = new TestEnvironment(getInstrumentation().getContext());
 
         // We might want to move this to /sdcard, if is is mounted/writable.
         File cacheDir = getInstrumentation().getTargetContext().getCacheDir();
@@ -149,21 +150,29 @@
     static class TestEnvironment {
         private final Locale mDefaultLocale;
         private final TimeZone mDefaultTimeZone;
-        private final String mJavaIoTmpDir;
         private final HostnameVerifier mHostnameVerifier;
         private final SSLSocketFactory mSslSocketFactory;
+        private final Properties mProperties = new Properties();
 
-        TestEnvironment() {
+        TestEnvironment(Context context) {
             mDefaultLocale = Locale.getDefault();
             mDefaultTimeZone = TimeZone.getDefault();
-            mJavaIoTmpDir = System.getProperty("java.io.tmpdir");
             mHostnameVerifier = HttpsURLConnection.getDefaultHostnameVerifier();
             mSslSocketFactory = HttpsURLConnection.getDefaultSSLSocketFactory();
+
+            mProperties.setProperty("user.home", "");
+            mProperties.setProperty("java.io.tmpdir", System.getProperty("java.io.tmpdir"));
+            // The CDD mandates that devices that support WiFi are the only ones that will have 
+            // multicast.
+            PackageManager pm = context.getPackageManager();
+            mProperties.setProperty("android.cts.device.multicast",
+                    Boolean.toString(pm.hasSystemFeature(PackageManager.FEATURE_WIFI)));
+
         }
 
         void reset() {
             System.setProperties(null);
-            System.setProperty("java.io.tmpdir", mJavaIoTmpDir);
+            System.setProperties(mProperties);
             Locale.setDefault(mDefaultLocale);
             TimeZone.setDefault(mDefaultTimeZone);
             Authenticator.setDefault(null);
diff --git a/tests/expectations/knownfailures.txt b/tests/expectations/knownfailures.txt
index dd8660f..3b2e3f0 100644
--- a/tests/expectations/knownfailures.txt
+++ b/tests/expectations/knownfailures.txt
@@ -22,6 +22,30 @@
   bug: 17595050
 },
 {
+  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: "Not all jdwp features are currently supported. These tests will fail",
   names: [
     "org.apache.harmony.jpda.tests.jdwp.DebuggerOnDemand.OnthrowDebuggerLaunchTest#testDebuggerLaunch001",
@@ -70,6 +94,13 @@
   bug: 17748398
 },
 {
+  description: "WebGL test uniformMatrixBadArgs is too strict. Disabled until it's fixed upstream.",
+  names: [
+    "android.webgl.cts.WebGLTest#test_conformance_more_functions_uniformMatrixBadArgs_html"
+  ],
+  bug: 18638404
+},
+{
   description: "permissions for the API previously used in the test has changed, making it impossible to pass",
   names: [
     "android.openglperf.cts.GlAppSwitchTest#testGlActivitySwitchingFast",
@@ -93,37 +124,6 @@
   bug: 17530117
 },
 {
-  description: "this test removes the stay-awake option, causing the screen to turn off during the execution of subsequent tests",
-  names: [
-    "android.admin.cts.DevicePolicyManagerTest#testMaximumTimeToLock"
-  ],
-  bug: 18002490
-},
-{
-  description: "these tests locks the screen with an emtpy password or swipe-to-unlock, blocking subsequent test to dismiss keyguard",
-  names: [
-    "android.admin.cts.DevicePolicyManagerTest#testPasswordQuality_something",
-    "android.admin.cts.DevicePolicyManagerTest#testPasswordQuality_numeric",
-    "android.admin.cts.DevicePolicyManagerTest#testPasswordQuality_alphabetic",
-    "android.admin.cts.DevicePolicyManagerTest#testPasswordQuality_alphanumeric",
-    "android.admin.cts.DevicePolicyManagerTest#testPasswordQuality_complexUpperCase",
-    "android.admin.cts.DevicePolicyManagerTest#testPasswordQuality_complexLowerCase",
-    "android.admin.cts.DevicePolicyManagerTest#testPasswordQuality_complexLetters",
-    "android.admin.cts.DevicePolicyManagerTest#testPasswordQuality_complexNumeric",
-    "android.admin.cts.DevicePolicyManagerTest#testPasswordQuality_complexSymbols",
-    "android.admin.cts.DevicePolicyManagerTest#testPasswordQuality_complexNonLetter",
-    "android.admin.cts.DevicePolicyManagerTest#testGetMaximumFailedPasswordsForWipe"
-  ],
-  bug: 17496766
-},
-{
-  description: "these tests locks the screen with an emtpy password or swipe-to-unlock, blocking subsequent test to dismiss keyguard",
-  names: [
-    "com.android.cts.devicepolicy.DeviceOwnerTest#testKeyManagement"
-  ],
-  bug: 17496766
-},
-{
   description: "Current implementation of uninstallAllUserCaCerts does not throw expected security exception, wait for fix from framework",
   names: [
     "android.admin.cts.DevicePolicyManagerTest#testUninstallAllUserCaCerts_failIfNotProfileOwner"
@@ -133,40 +133,22 @@
 {
   description: "New tests recently added for Android Enterprise. To be moved out of CTS-staging as soon as they show that they are stable",
   names: [
-    "com.android.cts.devicepolicy.DeviceOwnerTest#testApplicationRestrictions",
-    "com.android.cts.devicepolicy.DeviceOwnerTest#testCaCertManagement",
-    "com.android.cts.devicepolicy.DeviceOwnerTest#testDeviceOwnerSetup",
-    "com.android.cts.devicepolicy.DeviceOwnerTest#testPersistentIntentResolving",
-    "com.android.cts.devicepolicy.DeviceOwnerTest#testScreenCaptureDisabled",
-    "com.android.cts.devicepolicy.ManagedProfileTest#testManagedProfileSetup",
-    "com.android.cts.devicepolicy.ManagedProfileTest#testWipeData",
-    "com.android.cts.devicepolicy.ManagedProfileTest#testCrossProfileIntentFilters",
-    "com.android.cts.devicepolicy.ManagedProfileTest#testCrossProfileContent",
-    "com.android.cts.devicepolicy.ManagedProfileTest#testNoDebuggingFeaturesRestriction"
+    "com.android.cts.devicepolicy.LauncherAppsMultiUserTest#testGetActivitiesForNonProfileFails",
+    "com.android.cts.devicepolicy.LauncherAppsMultiUserTest#testNoLauncherCallbackPackageAddedSecondaryUser",
+    "com.android.cts.devicepolicy.LauncherAppsProfileTest#testGetActivitiesWithProfile",
+    "com.android.cts.devicepolicy.LauncherAppsProfileTest#testLauncherCallbackPackageAddedProfile",
+    "com.android.cts.devicepolicy.LauncherAppsProfileTest#testLauncherCallbackPackageRemovedProfile",
+    "com.android.cts.devicepolicy.LauncherAppsProfileTest#testLauncherCallbackPackageChangedProfile",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testInstallAppMainUser",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLauncherCallbackPackageAddedMainUser",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLauncherCallbackPackageRemovedMainUser",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLauncherCallbackPackageChangedMainUser",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLauncherNonExportedAppFails",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLaunchNonExportActivityFails",
+    "com.android.cts.devicepolicy.LauncherAppsSingleUserTest#testLaunchMainActivity"
   ]
 },
 {
-  description: "Flaky test which ocassionally fails",
-  names: [
-    "com.android.cts.devicepolicy.DeviceOwnerTest#testLockTask"
-  ],
-  bug: 17890673
-},
-{
-
-  description: "These tests fail on some devices.",
-  names: [
-    "android.uirendering.cts.testclasses.ExactCanvasTests#testBlueRect",
-    "android.uirendering.cts.testclasses.ExactCanvasTests#testBluePaddedSquare",
-    "android.uirendering.cts.testclasses.ViewClippingTests#testSimplePaddingClip",
-    "android.uirendering.cts.testclasses.ViewClippingTests#testSimpleClipBoundsClip",
-    "android.uirendering.cts.testclasses.ViewClippingTests#testSimpleOutlineClip",
-    "android.uirendering.cts.testclasses.ViewClippingTests#testSimpleBoundsClip",
-    "android.uirendering.cts.testclasses.InfrastructureTests#testViewInitializer"
-  ],
-  bug: 17511118
-},
-{
   description: "This test failed on devices that use effect off loading. In addition it uses hidden apis",
   names: [
     "android.media.cts.AudioEffectTest#test1_1ConstructorFromUuid"
@@ -335,10 +317,7 @@
     "android.hardware.cts.SingleSensorTests#testLinearAcceleration_10hz",
     "android.hardware.cts.SingleSensorTests#testLinearAcceleration_5hz",
     "android.hardware.cts.SingleSensorTests#testLinearAcceleration_1hz",
-    "android.hardware.cts.SensorTest#testValuesForAllSensors",
-    "android.hardware.cts.SensorTest#testSensorTimeStamps",
-    "android.hardware.cts.SensorTest#testBatchAndFlush",
-    "android.hardware.cts.SensorTest#testBatchAndFlushWithHandler"
+    "android.hardware.cts.SensorTest#testSensorTimeStamps"
   ],
   bug: 17675466
 },
@@ -364,5 +343,19 @@
     "com.android.org.conscrypt.SignatureTest#test_getInstance_OpenSSL_ENGINE"
   ],
   bug: 18030049
+},
+{
+  description: "The new recording test is not yet passing on all devices",
+  names: [
+    "android.hardware.camera2.cts.RecordingTest#testRecordingFramerateLowToHigh"
+  ],
+  bug: 18705837
+},
+{
+  description: "The new image reader test is not yet passing on all devices",
+  names: [
+    "android.hardware.camera2.cts.ImageReaderTest#testAllOutputYUVResolutions"
+  ],
+  bug: 18689511
 }
 ]
diff --git a/tests/signature/src/android/signature/cts/JDiffClassDescription.java b/tests/signature/src/android/signature/cts/JDiffClassDescription.java
index 7e36c1c..afcaa15 100644
--- a/tests/signature/src/android/signature/cts/JDiffClassDescription.java
+++ b/tests/signature/src/android/signature/cts/JDiffClassDescription.java
@@ -731,7 +731,7 @@
                     Type type = f.getGenericType();
                     if (type != null) {
                         genericTypeName = type instanceof Class ? ((Class) type).getName() :
-                            type.toString();
+                            type.toString().replace('$', '.');
                     }
                     if (genericTypeName == null || !genericTypeName.equals(field.mFieldType)) {
                         mResultObserver.notifyFailure(
diff --git a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
index 87f0238..f5e1d48 100644
--- a/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
+++ b/tests/tests/accessibility/src/android/view/accessibility/cts/AccessibilityNodeInfoTest.java
@@ -35,7 +35,7 @@
 public class AccessibilityNodeInfoTest extends AndroidTestCase {
 
     /** The number of properties of the {@link AccessibilityNodeInfo} class. */
-    private static final int NON_STATIC_FIELD_COUNT = 28;
+    private static final int NON_STATIC_FIELD_COUNT = 30;
 
     @SmallTest
     public void testMarshaling() throws Exception {
@@ -54,7 +54,7 @@
         AccessibilityNodeInfo receivedInfo = AccessibilityNodeInfo.CREATOR.createFromParcel(parcel);
 
         // make sure all fields properly marshaled
-        assertEqualsAccessiblityNodeInfo(sentInfo, receivedInfo);
+        assertEqualsAccessibilityNodeInfo(sentInfo, receivedInfo);
     }
 
     /**
@@ -204,7 +204,7 @@
      * <code>receviedInfo</code> to verify that the received node info is
      * the one that is expected.
      */
-    public static void assertEqualsAccessiblityNodeInfo(AccessibilityNodeInfo expectedInfo,
+    public static void assertEqualsAccessibilityNodeInfo(AccessibilityNodeInfo expectedInfo,
             AccessibilityNodeInfo receivedInfo) {
         Rect expectedBounds = new Rect();
         Rect receivedBounds = new Rect();
diff --git a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
index 39b116a..b11248a 100644
--- a/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
+++ b/tests/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityEndToEndTest.java
@@ -342,6 +342,7 @@
                             (NotificationManager) getActivity().getSystemService(
                                     Service.NOTIFICATION_SERVICE);
                         notificationManager.notify(notificationId, notification);
+                        getActivity().finish();
                     }
                 });
             }},
diff --git a/tests/tests/accounts/AndroidManifest.xml b/tests/tests/accounts/AndroidManifest.xml
index 22362ab..c671ff0 100644
--- a/tests/tests/accounts/AndroidManifest.xml
+++ b/tests/tests/accounts/AndroidManifest.xml
@@ -35,6 +35,9 @@
             </intent-filter>
         </activity>
 
+        <activity android:name="android.accounts.cts.AccountRemovalDummyActivity" >
+        </activity>
+
         <service android:name="MockAccountService" android:exported="true"
                  android:process="android.accounts.cts">
             <intent-filter>
diff --git a/tests/tests/accounts/src/android/accounts/cts/AccountManagerTest.java b/tests/tests/accounts/src/android/accounts/cts/AccountManagerTest.java
index e5cc45a..b2a48e6 100644
--- a/tests/tests/accounts/src/android/accounts/cts/AccountManagerTest.java
+++ b/tests/tests/accounts/src/android/accounts/cts/AccountManagerTest.java
@@ -26,6 +26,7 @@
 import android.accounts.OperationCanceledException;
 import android.app.Activity;
 import android.content.Context;
+import android.content.Intent;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -81,6 +82,8 @@
     public static final String USERDATA_VALUE_2 = "user.data.value.2";
 
     public static final Account ACCOUNT = new Account(ACCOUNT_NAME, ACCOUNT_TYPE);
+    public static final Account ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API = new Account(
+            MockAccountAuthenticator.ACCOUNT_NAME_FOR_NEW_REMOVE_API, ACCOUNT_TYPE);
     public static final Account ACCOUNT_SAME_TYPE = new Account(ACCOUNT_NAME_OTHER, ACCOUNT_TYPE);
 
     private static MockAccountAuthenticator mockAuthenticator;
@@ -122,8 +125,10 @@
         mockAuthenticator.clearData();
 
         // Need to clean up created account
-        assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
-        assertTrue(removeAccount(am, ACCOUNT_SAME_TYPE, null /* callback */));
+        assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
+                AccountManager.KEY_BOOLEAN_RESULT));
+        assertTrue(removeAccount(am, ACCOUNT_SAME_TYPE, mActivity, null /* callback */).getBoolean(
+                AccountManager.KEY_BOOLEAN_RESULT));
 
         // need to clean up the authenticator cached data
         mockAuthenticator.clearData();
@@ -174,7 +179,7 @@
         assertTrue(options.containsKey(AccountManager.KEY_CALLER_UID));
         assertTrue(options.containsKey(AccountManager.KEY_CALLER_PID));
     }
-    
+
     private void validateCredentials() {
         assertEquals(ACCOUNT, mockAuthenticator.getAccount());
     }
@@ -229,6 +234,38 @@
         return resultBoolean;
     }
 
+    private Bundle removeAccountWithIntentLaunch(AccountManager am, Account account,
+            Activity activity, AccountManagerCallback<Bundle> callback) throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        AccountManagerFuture<Bundle> futureBundle = am.removeAccount(account,
+                activity,
+                callback,
+                null /* handler */);
+        Bundle resultBundle = futureBundle.getResult();
+        assertTrue(futureBundle.isDone());
+
+        return resultBundle;
+    }
+
+    private Bundle removeAccount(AccountManager am, Account account, Activity activity,
+            AccountManagerCallback<Bundle> callback) throws IOException, AuthenticatorException,
+                OperationCanceledException {
+
+        AccountManagerFuture<Bundle> futureBundle = am.removeAccount(account,
+                activity,
+                callback,
+                null /* handler */);
+        Bundle resultBundle = futureBundle.getResult();
+        assertTrue(futureBundle.isDone());
+
+        return resultBundle;
+    }
+
+    private boolean removeAccountExplicitly(AccountManager am, Account account) {
+        return am.removeAccountExplicitly(account);
+    }
+
     private void addAccountExplicitly(Account account, String password, Bundle userdata) {
         assertTrue(am.addAccountExplicitly(account, password, userdata));
     }
@@ -380,6 +417,61 @@
         assertEquals(1 + expectedAccountsCount, accounts.length);
         assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
         // Need to clean up
+        assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
+                AccountManager.KEY_BOOLEAN_RESULT));
+
+        // and verify that we go back to the initial state
+        accounts = am.getAccounts();
+        assertNotNull(accounts);
+        assertEquals(expectedAccountsCount, accounts.length);
+    }
+
+    /**
+     * Test addAccountExplicitly(), renameAccount() and removeAccount().
+     */
+    public void testAddAccountExplicitlyAndRemoveAccountWithNewApi() throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        final int expectedAccountsCount = getAccountsCount();
+
+        addAccountExplicitly(ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API, ACCOUNT_PASSWORD, null /* userData */);
+
+        // Assert that we have one more account
+        Account[] accounts = am.getAccounts();
+        assertNotNull(accounts);
+        assertEquals(1 + expectedAccountsCount, accounts.length);
+        assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API));
+        // Deprecated API should not work
+        assertFalse(removeAccount(am, ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API, null /* callback */));
+        accounts = am.getAccounts();
+        assertNotNull(accounts);
+        assertEquals(1 + expectedAccountsCount, accounts.length);
+        assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API));
+        // Check removal of account
+        assertTrue(removeAccountWithIntentLaunch(am, ACCOUNT_FOR_NEW_REMOVE_ACCOUNT_API, mActivity, null /* callback */)
+                .getBoolean(AccountManager.KEY_BOOLEAN_RESULT));
+        // and verify that we go back to the initial state
+        accounts = am.getAccounts();
+        assertNotNull(accounts);
+        assertEquals(expectedAccountsCount, accounts.length);
+    }
+
+    /**
+     * Test addAccountExplicitly(), renameAccount() and removeAccount().
+     */
+    public void testAddAccountExplicitlyAndRemoveAccountWithDeprecatedApi() throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        final int expectedAccountsCount = getAccountsCount();
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        // Assert that we have one more account
+        Account[] accounts = am.getAccounts();
+        assertNotNull(accounts);
+        assertEquals(1 + expectedAccountsCount, accounts.length);
+        assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
+        // Need to clean up
         assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
 
         // and verify that we go back to the initial state
@@ -389,6 +481,30 @@
     }
 
     /**
+     * Test addAccountExplicitly() and removeAccountExplictly().
+     */
+    public void testAddAccountExplicitlyAndRemoveAccountExplicitly() throws IOException,
+            AuthenticatorException, OperationCanceledException {
+
+        final int expectedAccountsCount = getAccountsCount();
+
+        addAccountExplicitly(ACCOUNT, ACCOUNT_PASSWORD, null /* userData */);
+
+        // Assert that we have one more account
+        Account[] accounts = am.getAccounts();
+        assertNotNull(accounts);
+        assertEquals(1 + expectedAccountsCount, accounts.length);
+        assertTrue(isAccountPresent(am.getAccounts(), ACCOUNT));
+        // Need to clean up
+        assertTrue(removeAccountExplicitly(am, ACCOUNT));
+
+        // and verify that we go back to the initial state
+        accounts = am.getAccounts();
+        assertNotNull(accounts);
+        assertEquals(expectedAccountsCount, accounts.length);
+    }
+
+    /**
      * Test setUserData() and getUserData().
      */
     public void testAccountRenameAndGetPreviousName()
@@ -430,7 +546,8 @@
         assertEquals(ACCOUNT.name, am.getPreviousName(renamedAccount));
 
        // Need to clean up
-        assertTrue(removeAccount(am, renamedAccount, null /* callback */));
+        assertTrue(removeAccount(am, renamedAccount, mActivity, null /* callback */).getBoolean(
+                AccountManager.KEY_BOOLEAN_RESULT));
     }
 
     /**
@@ -1148,19 +1265,22 @@
                 false /* updateImmediately */);
 
         // Need to cleanup intermediate state
-        assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
+        assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
+                AccountManager.KEY_BOOLEAN_RESULT));
 
         testAddOnAccountsUpdatedListenerWithHandler(null /* handler */,
                 true /* updateImmediately */);
 
         // Need to cleanup intermediate state
-        assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
+        assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
+                AccountManager.KEY_BOOLEAN_RESULT));
 
         testAddOnAccountsUpdatedListenerWithHandler(new Handler(Looper.getMainLooper()),
                 false /* updateImmediately */);
 
         // Need to cleanup intermediate state
-        assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
+        assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
+                AccountManager.KEY_BOOLEAN_RESULT));
 
         testAddOnAccountsUpdatedListenerWithHandler(new Handler(Looper.getMainLooper()),
                 true /* updateImmediately */);
@@ -1204,7 +1324,8 @@
         testRemoveOnAccountsUpdatedListenerWithHandler(null /* handler */);
 
         // Need to cleanup intermediate state
-        assertTrue(removeAccount(am, ACCOUNT, null /* callback */));
+        assertTrue(removeAccount(am, ACCOUNT, mActivity, null /* callback */).getBoolean(
+                AccountManager.KEY_BOOLEAN_RESULT));
 
         testRemoveOnAccountsUpdatedListenerWithHandler(new Handler(Looper.getMainLooper()));
     }
diff --git a/tests/tests/accounts/src/android/accounts/cts/AccountRemovalDummyActivity.java b/tests/tests/accounts/src/android/accounts/cts/AccountRemovalDummyActivity.java
new file mode 100644
index 0000000..3ad9429
--- /dev/null
+++ b/tests/tests/accounts/src/android/accounts/cts/AccountRemovalDummyActivity.java
@@ -0,0 +1,49 @@
+/*
+ * 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.accounts.cts;
+
+import android.accounts.AccountAuthenticatorResponse;
+import android.accounts.AccountManager;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * An activity.
+ */
+public class AccountRemovalDummyActivity extends Activity {
+
+    public static Intent createIntent(Context context) {
+        return new Intent(context, AccountRemovalDummyActivity.class);
+    }
+
+    private AccountAuthenticatorResponse mResponse;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Bundle input = (savedInstanceState == null) ? getIntent().getExtras() : savedInstanceState;
+        mResponse = input
+                .getParcelable(MockAccountAuthenticator.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
+        // Do verification and then remove the account
+        final Bundle result = new Bundle();
+        result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+        mResponse.onResult(result);
+        finish();
+    }
+}
diff --git a/tests/tests/accounts/src/android/accounts/cts/MockAccountAuthenticator.java b/tests/tests/accounts/src/android/accounts/cts/MockAccountAuthenticator.java
index 2d5c395..b5804d9 100644
--- a/tests/tests/accounts/src/android/accounts/cts/MockAccountAuthenticator.java
+++ b/tests/tests/accounts/src/android/accounts/cts/MockAccountAuthenticator.java
@@ -22,6 +22,7 @@
 import android.accounts.AccountManager;
 import android.accounts.NetworkErrorException;
 import android.content.Context;
+import android.content.Intent;
 import android.os.Bundle;
 
 import java.util.ArrayList;
@@ -31,21 +32,27 @@
  */
 public class MockAccountAuthenticator extends AbstractAccountAuthenticator {
 
-    private AccountAuthenticatorResponse mResponse;
-    private String mAccountType;
-    private String mAuthTokenType;
-    private String[] mRequiredFeatures;
+    public static String KEY_ACCOUNT_INFO = "key_account_info";
+    public static String KEY_ACCOUNT_AUTHENTICATOR_RESPONSE = "key_account_authenticator_response";
+    public static String ACCOUNT_NAME_FOR_NEW_REMOVE_API = "call new removeAccount api";
+
+    private final Context mContext;
+    AccountAuthenticatorResponse mResponse;
+    String mAccountType;
+    String mAuthTokenType;
+    String[] mRequiredFeatures;
     public Bundle mOptionsUpdateCredentials;
     public Bundle mOptionsConfirmCredentials;
     public Bundle mOptionsAddAccount;
     public Bundle mOptionsGetAuthToken;
-    private Account mAccount;
-    private String[] mFeatures;
+    Account mAccount;
+    String[] mFeatures;
 
-    private final ArrayList<String> mockFeatureList = new ArrayList<String>();
+    final ArrayList<String> mockFeatureList = new ArrayList<String>();
 
     public MockAccountAuthenticator(Context context) {
         super(context);
+        mContext = context;
 
         // Create some mock features
         mockFeatureList.add(AccountManagerTest.FEATURE_1);
@@ -213,4 +220,26 @@
         }
         return result;
     }
+
+    @Override
+    public Bundle getAccountRemovalAllowed(AccountAuthenticatorResponse response,
+            Account account) throws NetworkErrorException {
+        final Bundle result = new Bundle();
+        if (ACCOUNT_NAME_FOR_NEW_REMOVE_API.equals(account.name)) {
+            Intent intent = AccountRemovalDummyActivity.createIntent(mContext);
+            // Pass in the authenticator response, so that account removal can
+            // be
+            // completed
+            intent.putExtra(KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response);
+            intent.putExtra(KEY_ACCOUNT_INFO, account);
+            result.putParcelable(AccountManager.KEY_INTENT, intent);
+            // Adding this following line to reject account installation
+            // requests
+            // coming from old removeAccount API.
+            result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, false);
+        } else {
+            result.putBoolean(AccountManager.KEY_BOOLEAN_RESULT, true);
+        }
+        return result;
+    }
 }
\ No newline at end of file
diff --git a/tests/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java b/tests/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
index d0255f3..04060e5 100644
--- a/tests/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
+++ b/tests/tests/admin/src/android/admin/cts/DevicePolicyManagerTest.java
@@ -486,24 +486,6 @@
         }
     }
 
-    public void testMaximumTimeToLock() {
-        if (!mDeviceAdmin) {
-            Log.w(TAG, "Skipping testMaximumTimeToLock");
-            return;
-        }
-        long originalValue = mDevicePolicyManager.getMaximumTimeToLock(mComponent);
-        try {
-            for (long testLength : new long[] {
-                    5000L /* 5 sec */, 60000L /* 1 min */, 1800000 /* 30 min */}) {
-                mDevicePolicyManager.setMaximumTimeToLock(mComponent, testLength);
-                assertEquals(testLength,
-                        mDevicePolicyManager.getMaximumTimeToLock(mComponent));
-            }
-        } finally {
-            mDevicePolicyManager.setMaximumTimeToLock(mComponent, originalValue);
-        }
-    }
-
     public void testCreateUser_failIfNotDeviceOwner() {
         if (!mDeviceAdmin) {
             Log.w(TAG, "Skipping testCreateUser_failIfNotDeviceOwner");
diff --git a/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java b/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
index 35a0b4c..0163a58 100644
--- a/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
+++ b/tests/tests/animation/src/android/animation/cts/AnimatorSetTest.java
@@ -16,8 +16,11 @@
 package android.animation.cts;
 
 import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.Set;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
@@ -25,6 +28,7 @@
 import android.test.ActivityInstrumentationTestCase2;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.AccelerateInterpolator;
+import android.view.animation.LinearInterpolator;
 
 public class AnimatorSetTest extends
         ActivityInstrumentationTestCase2<AnimationActivity> {
@@ -34,6 +38,7 @@
     private Object object;
     private ObjectAnimator yAnimator;
     private ObjectAnimator xAnimator;
+    Set<Integer> identityHashes = new HashSet<Integer>();
 
     public AnimatorSetTest() {
         super(AnimationActivity.class);
@@ -158,4 +163,84 @@
             }
         });
     }
+
+    private void assertUnique(Object object) {
+        assertUnique(object, "");
+    }
+
+    private void assertUnique(Object object, String msg) {
+        final int code = System.identityHashCode(object);
+        assertTrue("object should be unique " + msg + ", obj:" + object, identityHashes.add(code));
+
+    }
+
+    public void testClone() throws Throwable {
+        final AnimatorSet set1 = new AnimatorSet();
+        final AnimatorListenerAdapter setListener = new AnimatorListenerAdapter() {};
+        set1.addListener(setListener);
+        ObjectAnimator animator1 = new ObjectAnimator();
+        animator1.setDuration(100);
+        animator1.setPropertyName("x");
+        animator1.setIntValues(5);
+        animator1.setInterpolator(new LinearInterpolator());
+        AnimatorListenerAdapter listener1 = new AnimatorListenerAdapter(){};
+        AnimatorListenerAdapter listener2 = new AnimatorListenerAdapter(){};
+        animator1.addListener(listener1);
+
+        ObjectAnimator animator2 = new ObjectAnimator();
+        animator2.setDuration(100);
+        animator2.setInterpolator(new LinearInterpolator());
+        animator2.addListener(listener2);
+        animator2.setPropertyName("y");
+        animator2.setIntValues(10);
+
+        set1.playTogether(animator1, animator2);
+
+        AnimateObject target = new AnimateObject();
+        set1.setTarget(target);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                set1.start();
+            }
+        });
+        assertTrue(set1.isStarted());
+
+        animator1.getListeners();
+        AnimatorSet set2 = set1.clone();
+        assertFalse(set2.isStarted());
+
+        assertUnique(set1);
+        assertUnique(animator1);
+        assertUnique(animator2);
+
+        assertUnique(set2);
+        assertEquals(2, set2.getChildAnimations().size());
+
+        Animator clone1 = set2.getChildAnimations().get(0);
+        Animator clone2 = set2.getChildAnimations().get(1);
+
+        for (Animator animator : set2.getChildAnimations()) {
+            assertUnique(animator);
+        }
+
+        assertTrue(clone1.getListeners().contains(listener1));
+        assertTrue(clone2.getListeners().contains(listener2));
+
+        assertTrue(set2.getListeners().contains(setListener));
+
+        for (Animator.AnimatorListener listener : set1.getListeners()) {
+            assertTrue(set2.getListeners().contains(listener));
+        }
+
+        assertEquals(animator1.getDuration(), clone1.getDuration());
+        assertEquals(animator2.getDuration(), clone2.getDuration());
+        assertSame(animator1.getInterpolator(), clone1.getInterpolator());
+        assertSame(animator2.getInterpolator(), clone2.getInterpolator());
+    }
+
+    class AnimateObject {
+        int x = 1;
+        int y = 2;
+    }
 }
diff --git a/tests/tests/animation/src/android/animation/cts/AnimatorTest.java b/tests/tests/animation/src/android/animation/cts/AnimatorTest.java
index fac9ff9..a08a5eb 100644
--- a/tests/tests/animation/src/android/animation/cts/AnimatorTest.java
+++ b/tests/tests/animation/src/android/animation/cts/AnimatorTest.java
@@ -182,6 +182,27 @@
         assertNull(listListenersTwo);
     }
 
+    public void testNullObjectAnimator()  throws Throwable {
+        Object object = mActivity.view.newBall;
+        final ObjectAnimator animator = ObjectAnimator.ofFloat(object, "y", 0, 100);
+        MyListener listener = new MyListener();
+        animator.addListener(listener);
+        mActivity.view.newBall.setY(0);
+        startAnimation(animator);
+        int sleepCount = 0;
+        while (mActivity.view.newBall.getY() == 0 && sleepCount++ < 50) {
+            Thread.sleep(1);
+        }
+        assertNotSame(0, mActivity.view.newBall.getY());
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                animator.setTarget(null);
+            }
+        });
+        assertTrue(listener.mCancel);
+    }
+
     class MyListener implements Animator.AnimatorListener{
         boolean mStart = false;
         boolean mEnd = false;
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 5b1909a..d3890df 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
@@ -132,11 +132,12 @@
                 Activities.ActivityThree.class,
         };
 
+        final long startTime = System.currentTimeMillis() - MINUTE;
+
         // Launch the series of Activities.
         launchSubActivities(activitySequence);
 
         final long endTime = System.currentTimeMillis();
-        final long startTime = endTime - DAY;
         UsageEvents events = mUsageStatsManager.queryEvents(startTime, endTime);
 
         // Consume all the events.
@@ -147,15 +148,26 @@
             eventList.add(event);
         }
 
+        // Find the last Activity's MOVE_TO_FOREGROUND event.
+        int end = eventList.size();
+        while (end > 0) {
+            UsageEvents.Event event = eventList.get(end - 1);
+            if (event.getClassName().equals(activitySequence[activitySequence.length - 1].getName())
+                    && event.getEventType() == UsageEvents.Event.MOVE_TO_FOREGROUND) {
+                break;
+            }
+            end--;
+        }
+
         // We expect 2 events per Activity launched (foreground + background)
         // except for the last Activity, which was in the foreground when
         // we queried the event log.
-        assertTrue(eventList.size() >= (activitySequence.length * 2) - 1);
-        final int offset = eventList.size() - ((activitySequence.length * 2) - 1);
+        final int start = end - ((activitySequence.length * 2) - 1);
+        assertTrue("Not enough events", start >= 0);
 
         final int activityCount = activitySequence.length;
         for (int i = 0; i < activityCount; i++) {
-            int index = offset + (i * 2);
+            final int index = start + (i * 2);
 
             // Check for foreground event.
             UsageEvents.Event event = eventList.get(index);
@@ -163,10 +175,10 @@
             assertEquals(activitySequence[i].getName(), event.getClassName());
             assertEquals(UsageEvents.Event.MOVE_TO_FOREGROUND, event.getEventType());
 
-            index += 1;
+            // Only check for the background event if this is not the
+            // last activity.
             if (i < activityCount - 1) {
-                // Check for background event.
-                event = eventList.get(index);
+                event = eventList.get(index + 1);
                 assertEquals(mTargetPackage, event.getPackageName());
                 assertEquals(activitySequence[i].getName(), event.getClassName());
                 assertEquals(UsageEvents.Event.MOVE_TO_BACKGROUND, event.getEventType());
@@ -182,6 +194,7 @@
     @Ignore
     public void ignore_testStatsAreShiftedInTimeWhenSystemTimeChanges() throws Exception {
         launchSubActivity(Activities.ActivityOne.class);
+        launchSubActivity(Activities.ActivityThree.class);
 
         long endTime = System.currentTimeMillis();
         long startTime = endTime - MINUTE;
@@ -214,18 +227,19 @@
     }
 
     public void testUsageEventsParceling() throws Exception {
-        final long startTime = System.currentTimeMillis();
+        final long startTime = System.currentTimeMillis() - MINUTE;
 
+        // Ensure some data is in the UsageStats log.
         @SuppressWarnings("unchecked")
         Class<? extends Activity>[] activityClasses = new Class[] {
+                Activities.ActivityTwo.class,
                 Activities.ActivityOne.class,
                 Activities.ActivityThree.class,
-                Activities.ActivityTwo.class,
         };
         launchSubActivities(activityClasses);
 
         final long endTime = System.currentTimeMillis();
-        UsageEvents events = mUsageStatsManager.queryEvents(startTime - TIME_DIFF_THRESHOLD, endTime);
+        UsageEvents events = mUsageStatsManager.queryEvents(startTime, endTime);
         assertTrue(events.getNextEvent(new UsageEvents.Event()));
 
         Parcel p = Parcel.obtain();
@@ -255,6 +269,7 @@
 
         // Launch an Activity.
         launchSubActivity(Activities.ActivityFour.class);
+        launchSubActivity(Activities.ActivityThree.class);
 
         final long endTime = System.currentTimeMillis();
 
@@ -287,10 +302,12 @@
     }
 
     public void testNoAccessSilentlyFails() throws Exception {
+        final long startTime = System.currentTimeMillis() - MINUTE;
+
         launchSubActivity(Activities.ActivityOne.class);
+        launchSubActivity(Activities.ActivityThree.class);
 
         final long endTime = System.currentTimeMillis();
-        final long startTime = endTime - MINUTE;
         List<UsageStats> stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST,
                 startTime, endTime);
         assertFalse(stats.isEmpty());
diff --git a/tests/tests/app/src/android/app/cts/AlarmManagerTest.java b/tests/tests/app/src/android/app/cts/AlarmManagerTest.java
index 0780101..b85c616 100644
--- a/tests/tests/app/src/android/app/cts/AlarmManagerTest.java
+++ b/tests/tests/app/src/android/app/cts/AlarmManagerTest.java
@@ -18,52 +18,97 @@
 
 
 import android.app.AlarmManager;
+import android.app.AlarmManager.AlarmClockInfo;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.cts.util.PollingCheck;
+import android.os.Build;
 import android.os.SystemClock;
 import android.test.AndroidTestCase;
+import android.util.Log;
+import android.test.MoreAsserts;
 
 public class AlarmManagerTest extends AndroidTestCase {
-    private AlarmManager mAlarmManager;
+    public static final String MOCKACTION = "android.app.AlarmManagerTest.TEST_ALARMRECEIVER";
+    public static final String MOCKACTION2 = "android.app.AlarmManagerTest.TEST_ALARMRECEIVER2";
+
+    private AlarmManager mAm;
     private Intent mIntent;
     private PendingIntent mSender;
-    private Intent mServiceIntent;
+    private Intent mIntent2;
+    private PendingIntent mSender2;
 
     /*
      *  The default snooze delay: 5 seconds
      */
-    private final long SNOOZE_DELAY = 5 * 1000L;
+    private static final long SNOOZE_DELAY = 5 * 1000L;
     private long mWakeupTime;
     private MockAlarmReceiver mMockAlarmReceiver;
+    private MockAlarmReceiver mMockAlarmReceiver2;
 
-    private final int TIME_DELTA = 1000;
-    private final int TIME_DELAY = 5000;
+    private static final int TIME_DELTA = 1000;
+    private static final int TIME_DELAY = 10000;
+    private static final int REPEAT_PERIOD = 60000;
 
-    class Sync {
-        public boolean mIsConnected;
-        public boolean mIsDisConnected;
-    }
+    // Receiver registration/unregistration between tests races with the system process, so
+    // we add a little buffer time here to allow the system to process before we proceed.
+    // This value is in milliseconds.
+    private static final long REGISTER_PAUSE = 250;
+
+    // Constants used for validating exact vs inexact alarm batching immunity.  We run a few
+    // trials of an exact alarm that is placed within an inexact alarm's window of opportunity,
+    // and mandate that the average observed delivery skew between the two be statistically
+    // significant -- i.e. that the two alarms are not being coalesced.  We also place an
+    // additional exact alarm only a short time after the inexact alarm's nominal trigger time.
+    // If exact alarms are allowed to batch with inexact ones this will tend to have no effect,
+    // but in the correct behavior -- inexact alarms not permitted to batch with exact ones --
+    // this additional exact alarm will have the effect of guaranteeing that the inexact alarm
+    // must fire no later than it -- i.e. a considerable time before the significant, later
+    // exact alarm.
+    //
+    // The test essentially amounts to requiring that the inexact MOCKACTION alarm and
+    // the much later exact MOCKACTION2 alarm fire far apart, always; with an implicit
+    // insistence that alarm batches are delivered at the head of their window.
+    private static final long TEST_WINDOW_LENGTH = 5 * 1000L;
+    private static final long TEST_ALARM_FUTURITY = 6 * 1000L;
+    private static final long FAIL_DELTA = 50;
+    private static final long NUM_TRIALS = 5;
+    private static final long MAX_NEAR_DELIVERIES = 2;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mAlarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
-        mIntent = new Intent(MockAlarmReceiver.MOCKACTION);
+
+        mAm = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+
+        mIntent = new Intent(MOCKACTION)
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
         mSender = PendingIntent.getBroadcast(mContext, 0, mIntent, 0);
-        mMockAlarmReceiver = new MockAlarmReceiver();
-        IntentFilter filter = new IntentFilter(MockAlarmReceiver.MOCKACTION);
+        mMockAlarmReceiver = new MockAlarmReceiver(mIntent.getAction());
+
+        mIntent2 = new Intent(MOCKACTION2)
+                .addFlags(Intent.FLAG_RECEIVER_FOREGROUND | Intent.FLAG_RECEIVER_REGISTERED_ONLY);
+        mSender2 = PendingIntent.getBroadcast(mContext, 0, mIntent2, 0);
+        mMockAlarmReceiver2 = new MockAlarmReceiver(mIntent2.getAction());
+
+        IntentFilter filter = new IntentFilter(mIntent.getAction());
         mContext.registerReceiver(mMockAlarmReceiver, filter);
+
+        IntentFilter filter2 = new IntentFilter(mIntent2.getAction());
+        mContext.registerReceiver(mMockAlarmReceiver2, filter2);
+
+        Thread.sleep(REGISTER_PAUSE);
     }
 
     @Override
     protected void tearDown() throws Exception {
         super.tearDown();
-        if (mServiceIntent != null) {
-            mContext.stopService(mServiceIntent);
-        }
+        mContext.unregisterReceiver(mMockAlarmReceiver);
+        mContext.unregisterReceiver(mMockAlarmReceiver2);
+
+        Thread.sleep(REGISTER_PAUSE);
     }
 
     public void testSetTypes() throws Exception {
@@ -73,7 +118,7 @@
         // test parameter type is RTC_WAKEUP
         mMockAlarmReceiver.setAlarmedFalse();
         mWakeupTime = System.currentTimeMillis() + SNOOZE_DELAY;
-        mAlarmManager.set(AlarmManager.RTC_WAKEUP, mWakeupTime, mSender);
+        mAm.setExact(AlarmManager.RTC_WAKEUP, mWakeupTime, mSender);
         new PollingCheck(SNOOZE_DELAY + TIME_DELAY) {
             @Override
             protected boolean check() {
@@ -85,7 +130,7 @@
         // test parameter type is RTC
         mMockAlarmReceiver.setAlarmedFalse();
         mWakeupTime = System.currentTimeMillis() + SNOOZE_DELAY;
-        mAlarmManager.set(AlarmManager.RTC, mWakeupTime, mSender);
+        mAm.setExact(AlarmManager.RTC, mWakeupTime, mSender);
         new PollingCheck(SNOOZE_DELAY + TIME_DELAY) {
             @Override
             protected boolean check() {
@@ -97,7 +142,7 @@
         // test parameter type is ELAPSED_REALTIME
         mMockAlarmReceiver.setAlarmedFalse();
         mWakeupTime = SystemClock.elapsedRealtime() + SNOOZE_DELAY;
-        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME, mWakeupTime, mSender);
+        mAm.setExact(AlarmManager.ELAPSED_REALTIME, mWakeupTime, mSender);
         new PollingCheck(SNOOZE_DELAY + TIME_DELAY) {
             @Override
             protected boolean check() {
@@ -109,7 +154,7 @@
         // test parameter type is ELAPSED_REALTIME_WAKEUP
         mMockAlarmReceiver.setAlarmedFalse();
         mWakeupTime = SystemClock.elapsedRealtime() + SNOOZE_DELAY;
-        mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, mWakeupTime, mSender);
+        mAm.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, mWakeupTime, mSender);
         new PollingCheck(SNOOZE_DELAY + TIME_DELAY) {
             @Override
             protected boolean check() {
@@ -125,7 +170,7 @@
         // that would instead cause such alarms to never be triggered.
         mMockAlarmReceiver.setAlarmedFalse();
         mWakeupTime = -1000;
-        mAlarmManager.set(AlarmManager.RTC, mWakeupTime, mSender);
+        mAm.set(AlarmManager.RTC, mWakeupTime, mSender);
         new PollingCheck(TIME_DELAY) {
             @Override
             protected boolean check() {
@@ -134,53 +179,104 @@
         }.run();
     }
 
+    public void testExactAlarmBatching() throws Exception {
+        int deliveriesTogether = 0;
+        for (int i = 0; i < NUM_TRIALS; i++) {
+            final long now = System.currentTimeMillis();
+            final long windowStart = now + TEST_ALARM_FUTURITY;
+            final long exactStart = windowStart + TEST_WINDOW_LENGTH - 1;
+
+            mMockAlarmReceiver.setAlarmedFalse();
+            mMockAlarmReceiver2.setAlarmedFalse();
+            mAm.setWindow(AlarmManager.RTC_WAKEUP, windowStart, TEST_WINDOW_LENGTH, mSender);
+            mAm.setExact(AlarmManager.RTC_WAKEUP, exactStart, mSender2);
+
+            // Wait until a half-second beyond its target window, just to provide a
+            // little safety slop.
+            new PollingCheck(TEST_WINDOW_LENGTH + (windowStart - now) + 500) {
+                @Override
+                protected boolean check() {
+                    return mMockAlarmReceiver.alarmed;
+                }
+            }.run();
+
+            // Now wait until 1 sec beyond the expected exact alarm fire time, or for at
+            // least one second if we're already past the nominal exact alarm fire time
+            long timeToExact = Math.max(exactStart - System.currentTimeMillis() + 1000, 1000);
+            new PollingCheck(timeToExact) {
+                @Override
+                protected boolean check() {
+                    return mMockAlarmReceiver2.alarmed;
+                }
+            }.run();
+
+            // Success when we observe that the exact and windowed alarm are not being often
+            // delivered close together -- that is, when we can be confident that they are not
+            // being coalesced.
+            final long delta = Math.abs(mMockAlarmReceiver2.rtcTime - mMockAlarmReceiver.rtcTime);
+            Log.i("TEST", "[" + i + "]  delta = " + delta);
+            if (delta < FAIL_DELTA) {
+                deliveriesTogether++;
+                assertTrue("Exact alarms appear to be coalescing with inexact alarms",
+                        deliveriesTogether <= MAX_NEAR_DELIVERIES);
+            }
+        }
+    }
+
     public void testSetRepeating() throws Exception {
         mMockAlarmReceiver.setAlarmedFalse();
-        mWakeupTime = System.currentTimeMillis() + SNOOZE_DELAY;
-        mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, mWakeupTime, TIME_DELAY / 2, mSender);
-        new PollingCheck(SNOOZE_DELAY + TIME_DELAY) {
+        mWakeupTime = System.currentTimeMillis() + TEST_ALARM_FUTURITY;
+        mAm.setRepeating(AlarmManager.RTC_WAKEUP, mWakeupTime, REPEAT_PERIOD, mSender);
+
+        // wait beyond the initial alarm's possible delivery window to verify that it fires the first time
+        new PollingCheck(TEST_ALARM_FUTURITY + REPEAT_PERIOD) {
             @Override
             protected boolean check() {
                 return mMockAlarmReceiver.alarmed;
             }
         }.run();
+        assertTrue(mMockAlarmReceiver.alarmed);
+
+        // Now reset the receiver and wait for the intended repeat alarm to fire as expected
         mMockAlarmReceiver.setAlarmedFalse();
-        new PollingCheck(TIME_DELAY) {
+        new PollingCheck(REPEAT_PERIOD*2) {
             @Override
             protected boolean check() {
                 return mMockAlarmReceiver.alarmed;
             }
         }.run();
-        mAlarmManager.cancel(mSender);
+        assertTrue(mMockAlarmReceiver.alarmed);
+
+        mAm.cancel(mSender);
     }
 
     public void testCancel() throws Exception {
         mMockAlarmReceiver.setAlarmedFalse();
-        mWakeupTime = System.currentTimeMillis() + SNOOZE_DELAY;
-        mAlarmManager.setRepeating(AlarmManager.RTC_WAKEUP, mWakeupTime, 1000, mSender);
-        new PollingCheck(SNOOZE_DELAY + TIME_DELAY) {
-            @Override
-            protected boolean check() {
-                return mMockAlarmReceiver.alarmed;
-            }
-        }.run();
-        mMockAlarmReceiver.setAlarmedFalse();
+        mMockAlarmReceiver2.setAlarmedFalse();
+
+        // set two alarms
+        final long when1 = System.currentTimeMillis() + TEST_ALARM_FUTURITY;
+        mAm.setExact(AlarmManager.RTC_WAKEUP, when1, mSender);
+        final long when2 = when1 + TIME_DELTA; // will fire after when1's target time
+        mAm.setExact(AlarmManager.RTC_WAKEUP, when2, mSender2);
+
+        // cancel the earlier one
+        mAm.cancel(mSender);
+
+        // and verify that only the later one fired
         new PollingCheck(TIME_DELAY) {
             @Override
             protected boolean check() {
-                return mMockAlarmReceiver.alarmed;
+                return mMockAlarmReceiver2.alarmed;
             }
         }.run();
-        mAlarmManager.cancel(mSender);
-        Thread.sleep(TIME_DELAY);
-        mMockAlarmReceiver.setAlarmedFalse();
-        Thread.sleep(TIME_DELAY * 5);
+
         assertFalse(mMockAlarmReceiver.alarmed);
+        assertTrue(mMockAlarmReceiver2.alarmed);
     }
 
     public void testSetInexactRepeating() throws Exception {
-
-        mAlarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
+        mAm.setInexactRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
                 AlarmManager.INTERVAL_FIFTEEN_MINUTES, mSender);
         SystemClock.setCurrentTimeMillis(System.currentTimeMillis()
                 + AlarmManager.INTERVAL_FIFTEEN_MINUTES);
@@ -189,4 +285,66 @@
         // " Unable to open alarm driver: Permission denied". But still fail
         // after tried many permission.
     }
+
+    public void testSetAlarmClock() throws Exception {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            mMockAlarmReceiver.setAlarmedFalse();
+            mMockAlarmReceiver2.setAlarmedFalse();
+
+            // Set first alarm clock.
+            final long wakeupTimeFirst = System.currentTimeMillis()
+                    + 2 * TEST_ALARM_FUTURITY;
+            mAm.setAlarmClock(new AlarmClockInfo(wakeupTimeFirst, null), mSender);
+
+            // Verify getNextAlarmClock returns first alarm clock.
+            AlarmClockInfo nextAlarmClock = mAm.getNextAlarmClock();
+            assertEquals(wakeupTimeFirst, nextAlarmClock.getTriggerTime());
+            assertNull(nextAlarmClock.getShowIntent());
+
+            // Set second alarm clock, earlier than first.
+            final long wakeupTimeSecond = System.currentTimeMillis()
+                    + TEST_ALARM_FUTURITY;
+            PendingIntent showIntentSecond = PendingIntent.getBroadcast(getContext(), 0,
+                    new Intent(getContext(), AlarmManagerTest.class).setAction("SHOW_INTENT"), 0);
+            mAm.setAlarmClock(new AlarmClockInfo(wakeupTimeSecond, showIntentSecond),
+                    mSender2);
+
+            // Verify getNextAlarmClock returns second alarm clock now.
+            nextAlarmClock = mAm.getNextAlarmClock();
+            assertEquals(wakeupTimeSecond, nextAlarmClock.getTriggerTime());
+            assertEquals(showIntentSecond, nextAlarmClock.getShowIntent());
+
+            // Cancel second alarm.
+            mAm.cancel(mSender2);
+
+            // Verify getNextAlarmClock returns first alarm clock again.
+            nextAlarmClock = mAm.getNextAlarmClock();
+            assertEquals(wakeupTimeFirst, nextAlarmClock.getTriggerTime());
+            assertNull(nextAlarmClock.getShowIntent());
+
+            // Wait for first alarm to trigger.
+            assertFalse(mMockAlarmReceiver.alarmed);
+            new PollingCheck(2 * TEST_ALARM_FUTURITY + TIME_DELAY) {
+                @Override
+                protected boolean check() {
+                    return mMockAlarmReceiver.alarmed;
+                }
+            }.run();
+
+            // Verify first alarm fired at the right time.
+            assertEquals(mMockAlarmReceiver.rtcTime, wakeupTimeFirst, TIME_DELTA);
+
+            // Verify second alarm didn't fire.
+            assertFalse(mMockAlarmReceiver2.alarmed);
+
+            // Verify the next alarm is not returning neither the first nor the second alarm.
+            nextAlarmClock = mAm.getNextAlarmClock();
+            MoreAsserts.assertNotEqual(wakeupTimeFirst, nextAlarmClock != null
+                    ? nextAlarmClock.getTriggerTime()
+                    : null);
+            MoreAsserts.assertNotEqual(wakeupTimeSecond, nextAlarmClock != null
+                    ? nextAlarmClock.getTriggerTime()
+                    : null);
+        }
+    }
 }
diff --git a/tests/tests/app/src/android/app/cts/DialogTest.java b/tests/tests/app/src/android/app/cts/DialogTest.java
index 56e731b..feb4940 100644
--- a/tests/tests/app/src/android/app/cts/DialogTest.java
+++ b/tests/tests/app/src/android/app/cts/DialogTest.java
@@ -393,25 +393,28 @@
         d.isOnTouchEventCalled = false;
         assertTrue(d.isShowing());
 
-        // Send a touch event outside the activity.  This time the dialog will be dismissed
-        // because closeOnTouchOutside is true.
-        d.setCanceledOnTouchOutside(true);
+        // Watch activities cover the entire screen, so there is no way to touch outside.
+        if (!mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)) {
+            // Send a touch event outside the activity.  This time the dialog will be dismissed
+            // because closeOnTouchOutside is true.
+            d.setCanceledOnTouchOutside(true);
 
-        touchMotionEvent = MotionEvent.obtain(now, now + 1, MotionEvent.ACTION_DOWN,
-                1, 100, 0);
-        mInstrumentation.sendPointerSync(touchMotionEvent);
+            touchMotionEvent = MotionEvent.obtain(now, now + 1, MotionEvent.ACTION_DOWN,
+                    1, 100, 0);
+            mInstrumentation.sendPointerSync(touchMotionEvent);
 
-        new PollingCheck(TEST_TIMEOUT) {
-            protected boolean check() {
-                return d.dispatchTouchEventResult;
-            }
-        }.run();
+            new PollingCheck(TEST_TIMEOUT) {
+                protected boolean check() {
+                    return d.dispatchTouchEventResult;
+                }
+            }.run();
 
-        assertMotionEventEquals(touchMotionEvent, d.touchEvent);
+            assertMotionEventEquals(touchMotionEvent, d.touchEvent);
 
-        assertTrue(d.isOnTouchEventCalled);
-        assertMotionEventEquals(touchMotionEvent, d.onTouchEvent);
-        assertFalse(d.isShowing());
+            assertTrue(d.isOnTouchEventCalled);
+            assertMotionEventEquals(touchMotionEvent, d.onTouchEvent);
+            assertFalse(d.isShowing());
+        }
     }
 
     public void testTrackballEvent() {
@@ -670,19 +673,36 @@
         mInstrumentation.waitForIdleSync();
     }
 
-    public void testSetFeatureDrawableUri() {
+    public void testSetFeatureDrawableUri() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
-        mActivity.getDialog().setFeatureDrawableUri(0, Uri.parse("http://www.google.com"));
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                mActivity.getDialog().setFeatureDrawableUri(Window.FEATURE_LEFT_ICON,
+                        Uri.parse("http://www.google.com"));
+            }
+        });
+        mInstrumentation.waitForIdleSync();
     }
 
-    public void testSetFeatureDrawable() {
+    public void testSetFeatureDrawable() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
-        mActivity.getDialog().setFeatureDrawable(0, new MockDrawable());
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                mActivity.getDialog().setFeatureDrawable(Window.FEATURE_LEFT_ICON, 
+                        new MockDrawable());
+            }
+        });
+        mInstrumentation.waitForIdleSync();
     }
 
-    public void testSetFeatureDrawableAlpha() {
+    public void testSetFeatureDrawableAlpha() throws Throwable {
         startDialogActivity(DialogStubActivity.TEST_ONSTART_AND_ONSTOP);
-        mActivity.getDialog().setFeatureDrawableAlpha(0, 0);
+        runTestOnUiThread(new Runnable() {
+            public void run() {
+                mActivity.getDialog().setFeatureDrawableAlpha(Window.FEATURE_LEFT_ICON, 0);
+            }
+        });
+        mInstrumentation.waitForIdleSync();
     }
 
     public void testGetLayoutInflater() {
diff --git a/tests/tests/app/src/android/app/cts/DownloadManagerTest.java b/tests/tests/app/src/android/app/cts/DownloadManagerTest.java
index 84faffa..a68d860 100644
--- a/tests/tests/app/src/android/app/cts/DownloadManagerTest.java
+++ b/tests/tests/app/src/android/app/cts/DownloadManagerTest.java
@@ -49,7 +49,7 @@
     private static final int MINIMUM_DOWNLOAD_BYTES = 100 * 1024 * 1024;
 
     private static final long SHORT_TIMEOUT = 5 * DateUtils.SECOND_IN_MILLIS;
-    private static final long LONG_TIMEOUT = 2 * DateUtils.MINUTE_IN_MILLIS;
+    private static final long LONG_TIMEOUT = 3 * DateUtils.MINUTE_IN_MILLIS;
 
     private DownloadManager mDownloadManager;
 
diff --git a/tests/tests/app/src/android/app/cts/InstrumentationTest.java b/tests/tests/app/src/android/app/cts/InstrumentationTest.java
index 0c2e9fa..b21148e 100644
--- a/tests/tests/app/src/android/app/cts/InstrumentationTest.java
+++ b/tests/tests/app/src/android/app/cts/InstrumentationTest.java
@@ -196,10 +196,12 @@
 
     public void testInvokeMenuActionSync() throws Exception {
         final int resId = R.id.goto_menu_id;
-        mInstrumentation.invokeMenuActionSync(mActivity, resId, 0);
-        mInstrumentation.waitForIdleSync();
-
-        assertEquals(resId, mActivity.getMenuID());
+        if (mActivity.getWindow().hasFeature(Window.FEATURE_OPTIONS_PANEL)) {
+            mInstrumentation.invokeMenuActionSync(mActivity, resId, 0);
+            mInstrumentation.waitForIdleSync();
+    
+            assertEquals(resId, mActivity.getMenuID());
+        }
     }
 
     public void testCallActivityOnPostCreate() throws Throwable {
diff --git a/tests/tests/app/src/android/app/cts/ServiceTest.java b/tests/tests/app/src/android/app/cts/ServiceTest.java
index b66f4c8..8ba1816 100644
--- a/tests/tests/app/src/android/app/cts/ServiceTest.java
+++ b/tests/tests/app/src/android/app/cts/ServiceTest.java
@@ -448,4 +448,17 @@
             // expected
         }
     }
+
+    @MediumTest
+    public void testImplicitIntentFailsOnApiLevel21() throws Exception {
+        Intent intent = new Intent(LocalService.SERVICE_LOCAL);
+        EmptyConnection conn = new EmptyConnection();
+        try {
+            mContext.bindService(intent, conn, 0);
+            mContext.unbindService(conn);
+            fail("Implicit intents should be disallowed for apps targeting API 21+");
+        } catch (IllegalArgumentException e) {
+            // expected
+        }
+    }
 }
diff --git a/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java b/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java
index 165e67b..4e57d31 100644
--- a/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java
+++ b/tests/tests/app/src/android/app/cts/SystemFeaturesTest.java
@@ -32,6 +32,9 @@
 import android.hardware.SensorManager;
 import android.hardware.Camera.CameraInfo;
 import android.hardware.Camera.Parameters;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
 import android.location.LocationManager;
 import android.net.sip.SipManager;
 import android.net.wifi.WifiManager;
@@ -59,6 +62,7 @@
     private SensorManager mSensorManager;
     private TelephonyManager mTelephonyManager;
     private WifiManager mWifiManager;
+    private CameraManager mCameraManager;
 
     @Override
     protected void setUp() throws Exception {
@@ -77,6 +81,7 @@
         mSensorManager = (SensorManager) mContext.getSystemService(Context.SENSOR_SERVICE);
         mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
         mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE);
+        mCameraManager = (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
     }
 
     /**
@@ -106,7 +111,7 @@
         }
     }
 
-    public void testCameraFeatures() {
+    public void testCameraFeatures() throws Exception {
         int numCameras = Camera.getNumberOfCameras();
         if (numCameras == 0) {
             assertNotAvailable(PackageManager.FEATURE_CAMERA);
@@ -114,6 +119,11 @@
             assertNotAvailable(PackageManager.FEATURE_CAMERA_FLASH);
             assertNotAvailable(PackageManager.FEATURE_CAMERA_FRONT);
             assertNotAvailable(PackageManager.FEATURE_CAMERA_ANY);
+            assertNotAvailable(PackageManager.FEATURE_CAMERA_LEVEL_FULL);
+            assertNotAvailable(PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR);
+            assertNotAvailable(PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING);
+            assertNotAvailable(PackageManager.FEATURE_CAMERA_CAPABILITY_RAW);
+
             assertFalse("Devices supporting external cameras must have a representative camera " +
                     "connected for testing",
                     mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL));
@@ -121,9 +131,48 @@
             assertAvailable(PackageManager.FEATURE_CAMERA_ANY);
             checkFrontCamera();
             checkRearCamera();
+            checkCamera2Features();
         }
     }
 
+    private void checkCamera2Features() throws Exception {
+        String[] cameraIds = mCameraManager.getCameraIdList();
+        boolean fullCamera = false;
+        boolean manualSensor = false;
+        boolean manualPostProcessing = false;
+        boolean raw = false;
+        CameraCharacteristics[] cameraChars = new CameraCharacteristics[cameraIds.length];
+        for (String cameraId : cameraIds) {
+            CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId);
+            Integer hwLevel = chars.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+            int[] capabilities = chars.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+            if (hwLevel == CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_FULL) {
+                fullCamera = true;
+            }
+            for (int capability : capabilities) {
+                switch (capability) {
+                    case CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR:
+                        manualSensor = true;
+                        break;
+                    case CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING:
+                        manualPostProcessing = true;
+                        break;
+                    case CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW:
+                        raw = true;
+                        break;
+                    default:
+                        // Capabilities don't have a matching system feature
+                        break;
+                }
+            }
+        }
+        assertFeature(fullCamera, PackageManager.FEATURE_CAMERA_LEVEL_FULL);
+        assertFeature(manualSensor, PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_SENSOR);
+        assertFeature(manualPostProcessing,
+                PackageManager.FEATURE_CAMERA_CAPABILITY_MANUAL_POST_PROCESSING);
+        assertFeature(raw, PackageManager.FEATURE_CAMERA_CAPABILITY_RAW);
+    }
+
     private void checkFrontCamera() {
         CameraInfo info = new CameraInfo();
         int numCameras = Camera.getNumberOfCameras();
@@ -243,15 +292,58 @@
                 Sensor.TYPE_STEP_COUNTER);
         assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_STEP_DETECTOR,
                 Sensor.TYPE_STEP_DETECTOR);
-        assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_HEART_RATE,
-                Sensor.TYPE_HEART_RATE);
-        assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_HEART_RATE_ECG,
-                Sensor.TYPE_HEART_RATE);
         assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_AMBIENT_TEMPERATURE,
                 Sensor.TYPE_AMBIENT_TEMPERATURE);
         assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_RELATIVE_HUMIDITY,
                 Sensor.TYPE_RELATIVE_HUMIDITY);
 
+
+        /*
+         * We have three cases to test for :
+         * Case 1:  Device does not have an HRM
+         * FEATURE_SENSOR_HEART_RATE               false
+         * FEATURE_SENSOR_HEART_RATE_ECG           false
+         * assertFeatureForSensor(TYPE_HEART_RATE) false
+         *
+         * Case 2:  Device has a PPG HRM
+         * FEATURE_SENSOR_HEART_RATE               true
+         * FEATURE_SENSOR_HEART_RATE_ECG           false
+         * assertFeatureForSensor(TYPE_HEART_RATE) true
+         *
+         * Case 3:  Device has an ECG HRM
+         * FEATURE_SENSOR_HEART_RATE               false
+         * FEATURE_SENSOR_HEART_RATE_ECG           true
+         * assertFeatureForSensor(TYPE_HEART_RATE) true
+         */
+
+        if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_HEART_RATE_ECG)) {
+                /* Case 3 for FEATURE_SENSOR_HEART_RATE_ECG true case */
+                assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_HEART_RATE_ECG,
+                        Sensor.TYPE_HEART_RATE);
+
+                /* Remove HEART_RATE from featuresLeft, no way to test that one */
+                assertTrue("Features left " + featuresLeft + " to check did not include "
+                        + PackageManager.FEATURE_SENSOR_HEART_RATE,
+                        featuresLeft.remove(PackageManager.FEATURE_SENSOR_HEART_RATE));
+        } else if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_SENSOR_HEART_RATE)) {
+                /* Case 1 & 2 for FEATURE_SENSOR_HEART_RATE_ECG false case */
+                assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_HEART_RATE_ECG,
+                        Sensor.TYPE_HEART_RATE);
+
+                /* Case 1 & 3 for FEATURE_SENSOR_HEART_RATE false case */
+                assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_HEART_RATE,
+                        Sensor.TYPE_HEART_RATE);
+        } else {
+                /* Case 2 for FEATURE_SENSOR_HEART_RATE true case */
+                assertFeatureForSensor(featuresLeft, PackageManager.FEATURE_SENSOR_HEART_RATE,
+                        Sensor.TYPE_HEART_RATE);
+
+                /* Remove HEART_RATE_ECG from featuresLeft, no way to test that one */
+                assertTrue("Features left " + featuresLeft + " to check did not include "
+                        + PackageManager.FEATURE_SENSOR_HEART_RATE_ECG,
+                        featuresLeft.remove(PackageManager.FEATURE_SENSOR_HEART_RATE_ECG));
+        }
+
         assertTrue("Assertions need to be added to this test for " + featuresLeft,
                 featuresLeft.isEmpty());
     }
@@ -400,4 +492,12 @@
         assertFalse("PackageManager#getSystemAvailableFeatures should NOT have " + feature,
                 mAvailableFeatures.contains(feature));
     }
+
+    private void assertFeature(boolean exist, String feature) {
+        if (exist) {
+            assertAvailable(feature);
+        } else {
+            assertNotAvailable(feature);
+        }
+    }
 }
diff --git a/tests/tests/content/src/android/content/cts/IntentTest.java b/tests/tests/content/src/android/content/cts/IntentTest.java
index d4fac55..0f9a5cc 100644
--- a/tests/tests/content/src/android/content/cts/IntentTest.java
+++ b/tests/tests/content/src/android/content/cts/IntentTest.java
@@ -45,6 +45,7 @@
 import java.io.Serializable;
 import java.net.URISyntaxException;
 import java.util.ArrayList;
+import java.util.Objects;
 import java.util.Set;
 
 public class IntentTest extends AndroidTestCase {
@@ -676,7 +677,7 @@
 
         final String compnent =
                 "component(" + mContext.getPackageName() + "!" + MockActivity.class.getName() + ")";
-        uri = "testdata#action(test)categories(test!test2)type(mtype)launchFlags(1)" + compnent
+        uri = "testdata#action(test)categories(test!test2)type(mtype)launchFlags(5)" + compnent
                 + "extras(Stest=testString!btestbyte=1!"
                 + "Btestboolean=true!ctestchar=a!dtestdouble=1d!"
                 + "itestint=1!ltestlong=1!stestshort=1!ftestfloat=1f)";
@@ -686,7 +687,7 @@
         assertEquals(mComponentName, mIntent.getComponent());
         assertEquals("test", (String) (mIntent.getCategories().toArray()[0]));
         assertEquals("mtype", mIntent.getType());
-        assertEquals(1, mIntent.getFlags());
+        assertEquals(4, mIntent.getFlags());
         assertEquals("testString", mIntent.getStringExtra("test"));
         assertTrue(mIntent.getBooleanExtra("testboolean", false));
         final byte b = 1;
@@ -812,10 +813,13 @@
         target = Intent.getIntent(uri);
         assertEquals(TEST_TYPE, target.getType());
 
-        mIntent.setFlags(1);
+        mIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
+                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
         uri = mIntent.toURI();
         target = Intent.getIntent(uri);
-        assertEquals(1, target.getFlags());
+        assertEquals(Intent.FLAG_ACTIVITY_NEW_DOCUMENT, target.getFlags());
 
         String stringValue = "testString";
         mIntent.putExtra(TEST_EXTRA_NAME, stringValue);
@@ -869,8 +873,8 @@
     }
 
     public void testToURI() {
-        mIntent.setFlags(0);
-        assertEquals("#Intent;end", mIntent.toURI());
+        mIntent = new Intent();
+        assertEquals("", mIntent.toURI());
 
         mIntent.setData(TEST_URI);
         assertTrue(mIntent.toURI().indexOf(TEST_URI.toString()) != -1);
@@ -937,6 +941,321 @@
         return uri.toString();
     }
 
+    static Intent makeSelector(Intent baseIntent, Intent selectorIntent) {
+        baseIntent.setSelector(selectorIntent);
+        return baseIntent;
+    }
+
+    public void testUris() {
+        checkIntentUri(
+                "intent:#Intent;action=android.test.FOO;end",
+                null,
+                new Intent().setAction("android.test.FOO"));
+        checkIntentUri(
+                "intent:#Intent;category=android.test.FOO;end",
+                null,
+                new Intent().setAction(Intent.ACTION_VIEW).addCategory("android.test.FOO"));
+        checkIntentUri(
+                "intent:#Intent;action=android.test.FOO;launchFlags=0x20;end",
+                null,
+                new Intent().setAction("android.test.FOO").setFlags(0x20));
+        checkIntentUri(
+                "intent://www.example.com/blah#Intent;scheme=http;end",
+                null,
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setData(Uri.parse("http://www.example.com/blah")));
+        checkIntentUri(
+                "intent://www.example.com/blah#Intent;scheme=http;component=com.exfoo/com.argh.Bar;end",
+                null,
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setData(Uri.parse("http://www.example.com/blah"))
+                        .setComponent(new ComponentName("com.exfoo", "com.argh.Bar")));
+        checkIntentUri(
+                "intent://www.example.com/blah#fragment#Intent;scheme=http;end",
+                null,
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setData(Uri.parse("http://www.example.com/blah#fragment")));
+        checkIntentUri(
+                "intent://www.example.com/blah#Intent;scheme=http;action=android.test.foo;end",
+                null,
+                new Intent().setAction("android.test.foo")
+                        .setData(Uri.parse("http://www.example.com/blah")));
+        checkIntentUri(
+                "intent:foo#Intent;scheme=mailto;type=image/foo;end",
+                null,
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setDataAndType(Uri.parse("mailto:foo"), "image/foo"));
+        checkIntentUri(
+                "intent:foo#Intent;scheme=mailto;S.string=text;end",
+                null,
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setData(Uri.parse("mailto:foo"))
+                        .putExtra("string", "text"));
+        checkIntentUri(
+                "intent:#Intent;action=android.test.FOO;S.string=text;end",
+                null,
+                new Intent().setAction("android.test.FOO").putExtra("string", "text"));
+        checkIntentUri(
+                "intent:foo#Intent;scheme=mailto;i.int=1000;end",
+                null,
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setData(Uri.parse("mailto:foo"))
+                        .putExtra("int", 1000));
+        checkIntentUri(
+                "intent:foo#Intent;scheme=mailto;l.long=1000;end",
+                null,
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setData(Uri.parse("mailto:foo"))
+                        .putExtra("long", (long) 1000));
+        checkIntentUri(
+                "intent:foo#Intent;scheme=mailto;B.boolean=true;end",
+                null,
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setData(Uri.parse("mailto:foo"))
+                        .putExtra("boolean", true));
+        checkIntentUri(
+                "intent:foo#Intent;scheme=mailto;f.float=10.4;end",
+                null,
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setData(Uri.parse("mailto:foo"))
+                        .putExtra("float", 10.4f));
+        checkIntentUri(
+                "intent:foo#Intent;scheme=mailto;d.double=10.4;end",
+                null,
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setData(Uri.parse("mailto:foo"))
+                        .putExtra("double", (double) 10.4));
+        checkIntentUri(
+                "intent:#Intent;S.string=text;i.int=1000;l.long=1000;B.boolean=true;f.float=10.4;end",
+                null,
+                new Intent().setAction(Intent.ACTION_VIEW).putExtra("string", "text")
+                        .putExtra("int", 1000).putExtra("long", (long) 1000)
+                        .putExtra("boolean", true).putExtra("float", 10.4f));
+        checkIntentUri(
+                "intent:foo#Intent;scheme=mailto;SEL;scheme=foobar;action=android.test.FOO;end",
+                null,
+                makeSelector(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("mailto:foo")),
+                        new Intent("android.test.FOO").setData(Uri.parse("foobar:"))));
+        checkIntentUri(
+                "intent:foo#Intent;scheme=mailto;SEL;action=android.test.FOO;package=com.myapp;end",
+                null,
+                makeSelector(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("mailto:foo")),
+                        new Intent("android.test.FOO").setPackage("com.myapp")));
+        checkIntentUri(
+                "intent:foo#Intent;scheme=mailto;SEL;action=android.test.FOO;component=com.exfoo/com.argh.Bar;end",
+                null,
+                makeSelector(new Intent(Intent.ACTION_VIEW).setData(Uri.parse("mailto:foo")),
+                        new Intent("android.test.FOO")
+                                .setComponent(new ComponentName("com.exfoo", "com.argh.Bar"))));
+
+        checkIntentUri(
+                "intent:#Intent;action=android.test.FOO;package=com.myapp;end",
+                "android-app://com.myapp#Intent;action=android.test.FOO;end",
+                new Intent().setAction("android.test.FOO").setPackage("com.myapp"));
+        checkIntentUri(
+                "intent:#Intent;action=android.intent.action.MAIN;package=com.myapp;end",
+                "android-app://com.myapp",
+                new Intent().setAction(Intent.ACTION_MAIN).setPackage("com.myapp"));
+        checkIntentUri(
+                "intent:#Intent;package=com.myapp;end",
+                "android-app://com.myapp#Intent;action=android.intent.action.VIEW;end",
+                new Intent().setAction(Intent.ACTION_VIEW).setPackage("com.myapp"));
+        checkIntentUri(
+                "intent:#Intent;category=android.test.FOO;package=com.myapp;end",
+                "android-app://com.myapp#Intent;action=android.intent.action.VIEW;category=android.test.FOO;end",
+                new Intent().setAction(Intent.ACTION_VIEW).addCategory("android.test.FOO")
+                        .setPackage("com.myapp"));
+        checkIntentUri(
+                "intent:#Intent;action=android.test.FOO;launchFlags=0x20;package=com.myapp;end",
+                "android-app://com.myapp#Intent;action=android.test.FOO;launchFlags=0x20;end",
+                new Intent().setAction("android.test.FOO").setFlags(0x20)
+                        .setPackage("com.myapp"));
+        checkIntentUri(
+                "intent://www.example.com/blah#Intent;scheme=http;package=com.myapp;end",
+                "android-app://com.myapp/http/www.example.com/blah",
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setData(Uri.parse("http://www.example.com/blah"))
+                        .setPackage("com.myapp"));
+        checkIntentUri(
+                "intent://www.example.com/blah#Intent;scheme=http;package=com.myapp;component=com.exfoo/com.argh.Bar;end",
+                "android-app://com.myapp/http/www.example.com/blah#Intent;component=com.exfoo/com.argh.Bar;end",
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setData(Uri.parse("http://www.example.com/blah"))
+                        .setComponent(new ComponentName("com.exfoo", "com.argh.Bar"))
+                        .setPackage("com.myapp"));
+        checkIntentUri(
+                "intent://www.example.com/blah#fragment#Intent;scheme=http;package=com.myapp;end",
+                "android-app://com.myapp/http/www.example.com/blah#fragment",
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setData(Uri.parse("http://www.example.com/blah#fragment"))
+                        .setPackage("com.myapp"));
+        checkIntentUri(
+                "intent://www.example.com/blah#fragment#Intent;scheme=http;action=android.test.FOO;package=com.myapp;end",
+                "android-app://com.myapp/http/www.example.com/blah#fragment#Intent;action=android.test.FOO;end",
+                new Intent().setAction("android.test.FOO")
+                        .setData(Uri.parse("http://www.example.com/blah#fragment"))
+                        .setPackage("com.myapp"));
+        checkIntentUri(
+                "intent://www.example.com/blah#Intent;scheme=http;package=com.myapp;end",
+                "android-app://com.myapp/http/www.example.com/blah",
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setData(Uri.parse("http://www.example.com/blah"))
+                        .setPackage("com.myapp"));
+        checkIntentUri(
+                "intent:#Intent;scheme=mailto;type=image/foo;package=com.myapp;end",
+                "android-app://com.myapp/mailto#Intent;type=image/foo;end",
+                new Intent().setAction(Intent.ACTION_VIEW)
+                        .setDataAndType(Uri.parse("mailto:"), "image/foo")
+                        .setPackage("com.myapp"));
+        checkIntentUri(
+                "intent:#Intent;scheme=mailto;package=com.myapp;S.string=text;end",
+                "android-app://com.myapp/mailto#Intent;S.string=text;end",
+                new Intent().setAction(Intent.ACTION_VIEW).putExtra("string", "text")
+                        .setData(Uri.parse("mailto:")).setPackage("com.myapp"));
+        checkIntentUri(
+                "intent:#Intent;action=android.test.FOO;package=com.myapp;S.string=text;end",
+                "android-app://com.myapp#Intent;action=android.test.FOO;S.string=text;end",
+                new Intent().setAction("android.test.FOO").putExtra("string", "text")
+                        .setPackage("com.myapp"));
+        checkIntentUri(
+                "intent:#Intent;scheme=mailto;package=com.myapp;i.int=1000;end",
+                "android-app://com.myapp/mailto#Intent;i.int=1000;end",
+                new Intent().setAction(Intent.ACTION_VIEW).putExtra("int", 1000)
+                        .setData(Uri.parse("mailto:")).setPackage("com.myapp"));
+        checkIntentUri(
+                "intent:#Intent;scheme=mailto;package=com.myapp;l.long=1000;end",
+                "android-app://com.myapp/mailto#Intent;l.long=1000;end",
+                new Intent().setAction(Intent.ACTION_VIEW).putExtra("long", (long) 1000)
+                        .setData(Uri.parse("mailto:")).setPackage("com.myapp"));
+        checkIntentUri(
+                "intent:#Intent;scheme=mailto;package=com.myapp;B.boolean=true;end",
+                "android-app://com.myapp/mailto#Intent;B.boolean=true;end",
+                new Intent().setAction(Intent.ACTION_VIEW).putExtra("boolean", true)
+                        .setData(Uri.parse("mailto:")).setPackage("com.myapp"));
+        checkIntentUri(
+                "intent:#Intent;scheme=mailto;package=com.myapp;f.float=10.4;end",
+                "android-app://com.myapp/mailto#Intent;f.float=10.4;end",
+                new Intent().setAction(Intent.ACTION_VIEW).putExtra("float", 10.4f)
+                        .setData(Uri.parse("mailto:")).setPackage("com.myapp"));
+        checkIntentUri(
+                "intent:#Intent;scheme=mailto;package=com.myapp;d.double=10.4;end",
+                "android-app://com.myapp/mailto#Intent;d.double=10.4;end",
+                new Intent().setAction(Intent.ACTION_VIEW).putExtra("double", (double) 10.4)
+                        .setData(Uri.parse("mailto:")).setPackage("com.myapp"));
+        checkIntentUri(
+                "intent:#Intent;package=com.myapp;S.string=text;i.int=1000;l.long=1000;B.boolean=true;f.float=10.4;end",
+                "android-app://com.myapp#Intent;action=android.intent.action.VIEW;S.string=text;i.int=1000;l.long=1000;B.boolean=true;f.float=10.4;end",
+                new Intent().setAction(Intent.ACTION_VIEW).putExtra("string", "text")
+                        .putExtra("int", 1000).putExtra("long", (long) 1000)
+                        .putExtra("boolean", true).putExtra("float", 10.4f)
+                        .setPackage("com.myapp"));
+    }
+
+    private boolean compareIntents(Intent expected, Intent actual) {
+        if (!Objects.equals(expected.getAction(), actual.getAction())) {
+            return false;
+        }
+        if (!Objects.equals(expected.getData(), actual.getData())) {
+            return false;
+        }
+        if (!Objects.equals(expected.getType(), actual.getType())) {
+            return false;
+        }
+        if (!Objects.equals(expected.getPackage(), actual.getPackage())) {
+            return false;
+        }
+        if (!Objects.equals(expected.getComponent(), actual.getComponent())) {
+            return false;
+        }
+        if (expected.getFlags() != actual.getFlags()) {
+            return false;
+        }
+        Set<String> expectedCat = expected.getCategories();
+        Set<String> actualCat = actual.getCategories();
+        if (expectedCat != actualCat) {
+            if (expectedCat == null || actualCat == null) {
+                return false;
+            }
+            for (String cat : expectedCat) {
+                if (!actual.hasCategory(cat)) {
+                    return false;
+                }
+            }
+            for (String cat : actualCat) {
+                if (!expected.hasCategory(cat)) {
+                    return false;
+                }
+            }
+        }
+        Bundle extras1 = expected.getExtras();
+        Bundle extras2 = actual.getExtras();
+        if (extras1 != extras2) {
+            if (extras1 == null || extras2 == null) {
+                return false;
+            }
+            for (String key : extras1.keySet()) {
+                if (!Objects.equals(extras1.get(key), extras2.get(key))) {
+                    return false;
+                }
+            }
+            for (String key : extras2.keySet()) {
+                if (!Objects.equals(extras1.get(key), extras2.get(key))) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private void assertEqualsIntent(String msg, Intent expected, Intent actual) {
+        if (!compareIntents(expected, actual)) {
+            failNotEquals(msg, expected, actual);
+        }
+        Intent expectedSel = expected.getSelector();
+        Intent actualSel = actual.getSelector();
+        if (expectedSel != actualSel) {
+            if (expectedSel == null || actualSel == null) {
+                failNotEquals(msg, expected, actual);
+            }
+            if (!compareIntents(expectedSel, actualSel)) {
+                failNotEquals(msg, expected, actual);
+            }
+        }
+    }
+
+    private void checkIntentUri(String intentSchemeUri, String androidAppSchemeUri, Intent intent) {
+        if (intentSchemeUri != null) {
+            try {
+                Intent genIntent = Intent.parseUri(intentSchemeUri, 0);
+                assertEqualsIntent("Implicitly converting " + intentSchemeUri + " to Intent",
+                        intent, genIntent);
+                genIntent = Intent.parseUri(intentSchemeUri, Intent.URI_INTENT_SCHEME);
+                assertEqualsIntent("Explicitly converting " + intentSchemeUri + " to Intent",
+                        intent, genIntent);
+            } catch (URISyntaxException e) {
+                fail("Failure parsing " + intentSchemeUri + ": " + e);
+            }
+            String genUri = intent.toUri(Intent.URI_INTENT_SCHEME);
+            assertEquals("Converting " + intent + " to intent: uri",
+                    intentSchemeUri, genUri);
+        }
+        if (androidAppSchemeUri != null) {
+            try {
+                Intent genIntent = Intent.parseUri(androidAppSchemeUri, 0);
+                assertEqualsIntent("Implicitly converting " + androidAppSchemeUri + " to Intent",
+                        intent, genIntent);
+                genIntent = Intent.parseUri(intentSchemeUri, Intent.URI_ANDROID_APP_SCHEME);
+                assertEqualsIntent("Explicitly converting " + androidAppSchemeUri + " to Intent",
+                        intent, genIntent);
+            } catch (URISyntaxException e) {
+                fail("Failure parsing " + androidAppSchemeUri + ": " + e);
+            }
+            String genUri = intent.toUri(Intent.URI_ANDROID_APP_SCHEME);
+            assertEquals("Converting " + intent + " to android-app: uri",
+                    androidAppSchemeUri, genUri);
+        }
+    }
+
     public void testAccessFlags() {
         int expected = 1;
         mIntent.setFlags(expected);
diff --git a/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java b/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
new file mode 100644
index 0000000..35466be
--- /dev/null
+++ b/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.content.res.cts;
+
+import android.content.res.Resources;
+import android.test.AndroidTestCase;
+import android.util.TypedValue;
+
+/**
+ * Tests that private attributes are correctly placed in a separate type to
+ * prevent future releases from stomping over private attributes with new public ones.
+ */
+public class PrivateAttributeTest extends AndroidTestCase {
+
+    private static final int sLastPublicAttr = 0x010104d5;
+
+    public void testNoAttributesAfterLastPublicAttribute() throws Exception {
+        final Resources res = getContext().getResources();
+
+        final String lastPublicName;
+        try {
+            lastPublicName = res.getResourceEntryName(sLastPublicAttr);
+        } catch (Resources.NotFoundException e) {
+            throw new AssertionError("Last public resource was not found", e);
+        }
+
+        int currentAttr = sLastPublicAttr;
+        while (currentAttr < 0x0101ffff) {
+            currentAttr++;
+            try {
+                final String name = res.getResourceEntryName(currentAttr);
+                throw new AssertionError("Found attribute '" + name + "'"
+                        + " (0x" + Integer.toHexString(currentAttr) + ")"
+                        + " after last public framework attribute "
+                        + "'" + lastPublicName + "'"
+                        + " (0x" + Integer.toHexString(sLastPublicAttr) + ")");
+            } catch (Resources.NotFoundException e) {
+                // continue
+            }
+        }
+    }
+}
diff --git a/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java b/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
index f2f859a..872de91 100644
--- a/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/VirtualDisplayTest.java
@@ -57,7 +57,7 @@
     private static final int WIDTH = 720;
     private static final int HEIGHT = 480;
     private static final int DENSITY = DisplayMetrics.DENSITY_MEDIUM;
-    private static final int TIMEOUT = 10000;
+    private static final int TIMEOUT = 40000;
 
     // Colors that we use as a signal to determine whether some desired content was
     // drawn.  The colors themselves doesn't matter but we choose them to have with distinct
diff --git a/tests/tests/graphics/res/xml/anim_list_missing_list_attrs.xml b/tests/tests/graphics/res/xml/anim_list_missing_list_attrs.xml
deleted file mode 100644
index 25d2dfe..0000000
--- a/tests/tests/graphics/res/xml/anim_list_missing_list_attrs.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
- * Copyright (C) 2008 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy of
- * the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- */
--->
-
-<animation-list xmlns:android="http://schemas.android.com/apk/res/android" >
-    <item android:drawable="@drawable/testimage"
-        android:duration="2000" />
-</animation-list>
-
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimationDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimationDrawableTest.java
index c278ed2..28a3c6b 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/AnimationDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/AnimationDrawableTest.java
@@ -76,7 +76,7 @@
         // Check the values set in the constructor
         assertNotNull(mAnimationDrawable.getConstantState());
         assertFalse(mAnimationDrawable.isRunning());
-        assertTrue(mAnimationDrawable.isOneShot());
+        assertFalse(mAnimationDrawable.isOneShot());
     }
 
     public void testSetVisible() throws Throwable {
@@ -277,68 +277,62 @@
         assertStoppedAnimation(THIRD_FRAME_INDEX, THIRD_FRAME_DURATION);
     }
 
-    public void testInflate() throws XmlPullParserException, IOException {
-        mAnimationDrawable = new AnimationDrawable();
-        DrawableContainerState drawableContainerState =
-            (DrawableContainerState) mAnimationDrawable.getConstantState();
-
+    public void testInflateCorrect() throws XmlPullParserException, IOException {
         XmlResourceParser parser = getResourceParser(R.xml.anim_list_correct);
-        mAnimationDrawable.inflate(mResources, parser, Xml.asAttributeSet(parser));
+        AnimationDrawable dr = new AnimationDrawable();
+        dr.inflate(mResources, parser, Xml.asAttributeSet(parser));
         // android:visible="false"
-        assertFalse(mAnimationDrawable.isVisible());
+        assertFalse(dr.isVisible());
         // android:oneShot="true"
-        assertTrue(mAnimationDrawable.isOneShot());
+        assertTrue(dr.isOneShot());
         // android:variablePadding="true"
-        assertNull(drawableContainerState.getConstantPadding());
-        assertEquals(2, mAnimationDrawable.getNumberOfFrames());
-        assertEquals(2000, mAnimationDrawable.getDuration(0));
-        assertEquals(1000, mAnimationDrawable.getDuration(1));
-        assertSame(mAnimationDrawable.getFrame(0), mAnimationDrawable.getCurrent());
+        DrawableContainerState state =
+                (DrawableContainerState) dr.getConstantState();
+        assertNull(state.getConstantPadding());
+        assertEquals(2, dr.getNumberOfFrames());
+        assertEquals(2000, dr.getDuration(0));
+        assertEquals(1000, dr.getDuration(1));
+        assertSame(dr.getFrame(0), dr.getCurrent());
+    }
 
-        parser = getResourceParser(R.xml.anim_list_missing_list_attrs);
-        mAnimationDrawable.inflate(mResources, parser, Xml.asAttributeSet(parser));
-        // use current the visibility
-        assertFalse(mAnimationDrawable.isVisible());
-        // default value of android:oneShot is false
-        assertFalse(mAnimationDrawable.isOneShot());
-        // default value of android:variablePadding is false
-        // TODO: its not clear what the value of constant padding should be when variablePadding
-        // is false
-        //assertNotNull(drawableContainerState.getConstantPadding());
-        // add a new frame from xml
-        assertEquals(3, mAnimationDrawable.getNumberOfFrames());
-        assertEquals(2000, mAnimationDrawable.getDuration(0));
-        assertEquals(1000, mAnimationDrawable.getDuration(1));
-        assertEquals(2000, mAnimationDrawable.getDuration(2));
-        assertSame(mAnimationDrawable.getFrame(0), mAnimationDrawable.getCurrent());
-
-        parser = getResourceParser(R.xml.anim_list_missing_item_drawable);
+    public void testInflateMissingDrawable() throws XmlPullParserException, IOException {
+        XmlResourceParser parser = getResourceParser(R.xml.anim_list_missing_item_drawable);
+        AnimationDrawable dr = new AnimationDrawable();
         try {
-            mAnimationDrawable.inflate(mResources, parser, Xml.asAttributeSet(parser));
+            dr.inflate(mResources, parser, Xml.asAttributeSet(parser));
             fail("Should throw XmlPullParserException if drawable of item is missing");
         } catch (XmlPullParserException e) {
             // expected
         }
     }
 
-    public void testInflateWithNullParameters() throws XmlPullParserException, IOException {
+    public void testInflateNullResources() throws XmlPullParserException, IOException {
         XmlResourceParser parser = getResourceParser(R.drawable.animationdrawable);
+        AnimationDrawable dr = new AnimationDrawable();
         try {
-            mAnimationDrawable.inflate(null, parser, Xml.asAttributeSet(parser));
+            dr.inflate(null, parser, Xml.asAttributeSet(parser));
             fail("Should throw NullPointerException if resource is null");
         } catch (NullPointerException e) {
             // expected
         }
+    }
 
+    public void testInflateNullXmlPullParser() throws XmlPullParserException, IOException {
+        XmlResourceParser parser = getResourceParser(R.drawable.animationdrawable);
+        AnimationDrawable dr = new AnimationDrawable();
         try {
-            mAnimationDrawable.inflate(mResources, null, Xml.asAttributeSet(parser));
+            dr.inflate(mResources, null, Xml.asAttributeSet(parser));
             fail("Should throw NullPointerException if parser is null");
         } catch (NullPointerException e) {
             // expected
         }
+    }
 
+    public void testInflateNullAttributeSet() throws XmlPullParserException, IOException {
+        XmlResourceParser parser = getResourceParser(R.drawable.animationdrawable);
+        AnimationDrawable dr = new AnimationDrawable();
         try {
-            mAnimationDrawable.inflate(mResources, parser, null);
+            dr.inflate(mResources, parser, null);
             fail("Should throw NullPointerException if AttributeSet is null");
         } catch (NullPointerException e) {
             // expected
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/ScaleDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/ScaleDrawableTest.java
index 190fffa..6281582 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/ScaleDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/ScaleDrawableTest.java
@@ -413,8 +413,8 @@
         attrs = DrawableTestUtils.getAttributeSet(parser, "scale_nodrawable");
         try {
             scaleDrawable.inflate(res, parser, attrs);
-            fail("Should throw IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
+            fail("Should throw XmlPullParserException");
+        } catch (XmlPullParserException e) {
         }
 
         try {
diff --git a/tests/tests/hardware/AndroidManifest.xml b/tests/tests/hardware/AndroidManifest.xml
index ca148f9..1a02d0a 100644
--- a/tests/tests/hardware/AndroidManifest.xml
+++ b/tests/tests/hardware/AndroidManifest.xml
@@ -23,6 +23,7 @@
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.BODY_SENSORS" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/AllocationTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/AllocationTest.java
index 92a5e58..d4fb235 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/AllocationTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/AllocationTest.java
@@ -310,7 +310,7 @@
      */
     private static float[] convertPixelYuvToRgb(byte[] yuvData) {
         final int CHANNELS = 3; // yuv
-        final float COLOR_RANGE = 256f;
+        final float COLOR_RANGE = 255f;
 
         assertTrue("YUV pixel must be at least 3 bytes large", CHANNELS <= yuvData.length);
 
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/BurstCaptureTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/BurstCaptureTest.java
new file mode 100644
index 0000000..fa5b606
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/BurstCaptureTest.java
@@ -0,0 +1,362 @@
+/*
+ * 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.hardware.camera2.cts;
+
+import static android.hardware.camera2.cts.CameraTestUtils.*;
+
+import android.graphics.ImageFormat;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.Image;
+import android.util.Log;
+import android.util.Range;
+import android.util.Size;
+
+import java.util.List;
+import java.util.ArrayList;
+
+public class BurstCaptureTest extends Camera2SurfaceViewTestCase {
+    private static final String TAG = "BurstCaptureTest";
+    private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    /**
+     * Test BURST_CAPTURE capability with full-AUTO capture.
+     * Also verifies sensor settings operation if READ_SENSOR_SETTINGS is available.
+     */
+    public void testYuvBurst() throws Exception {
+        for (int i = 0; i < mCameraIds.length; i++) {
+            try {
+                String id = mCameraIds[i];
+                Log.i(TAG, "Testing YUV Burst for camera " + id);
+                openDevice(id);
+                if (!mStaticInfo.isCapabilitySupported(
+                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE)) {
+                    Log.i(TAG, "BURST_CAPTURE capability is not supported in camera " + id +
+                            ". Skip the test");
+                    continue;
+                }
+
+                yuvBurstTestByCamera(id);
+            } finally {
+                closeDevice();
+                closeImageReader();
+            }
+        }
+    }
+
+    public void yuvBurstTestByCamera(String cameraId) throws Exception {
+        // Parameters
+        final int MAX_CONVERGENCE_FRAMES = 150; // 5 sec at 30fps
+        final long MAX_PREVIEW_RESULT_TIMEOUT_MS = 1000;
+        final int BURST_SIZE = 100;
+        final float FRAME_DURATION_MARGIN_FRACTION = 0.1f;
+
+        // Find a good preview size (bound to 1080p)
+        final Size previewSize = mOrderedPreviewSizes.get(0);
+
+        // Get maximum YUV_420_888 size
+        final Size stillSize = getMaxPreviewSize(cameraId, mCameraManager);
+
+        // Find max pipeline depth and sync latency
+        final int maxPipelineDepth = mStaticInfo.getCharacteristics().get(
+            CameraCharacteristics.REQUEST_PIPELINE_MAX_DEPTH);
+        final int maxSyncLatency = mStaticInfo.getCharacteristics().get(
+            CameraCharacteristics.SYNC_MAX_LATENCY);
+
+        assertTrue("Cam " + cameraId + ": maxSyncLatency is UNKNOWN;" +
+            " not allowed for BURST_CAPTURE capability",
+            maxSyncLatency >= 0);
+
+        // Find minimum frame duration for full-res YUV_420_888
+        StreamConfigurationMap config = mStaticInfo.getCharacteristics().get(
+            CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+        final long minStillFrameDuration =
+                config.getOutputMinFrameDuration(ImageFormat.YUV_420_888, stillSize);
+
+        // Find suitable target FPS range - as high as possible
+        Range<Integer>[] fpsRanges = mStaticInfo.getAeAvailableTargetFpsRangesChecked();
+        int minBurstFps = (int) Math.floor(1e9 / minStillFrameDuration);
+        Range<Integer> targetRange = null;
+        for (Range<Integer> candidateRange : fpsRanges) {
+            if (candidateRange.getLower() >= minBurstFps) {
+                if (targetRange == null) {
+                    targetRange = candidateRange;
+                } else if (candidateRange.getLower() > targetRange.getLower()) {
+                    targetRange = candidateRange;
+                } else if (candidateRange.getUpper() > targetRange.getUpper()) {
+                    targetRange = candidateRange;
+                }
+            }
+        }
+        assertTrue(String.format("Cam %s: No target FPS range found with minimum FPS above " +
+                        " 1/minFrameDuration (%d fps, duration %d ns) for full-resolution YUV",
+                cameraId, minBurstFps, minStillFrameDuration),
+            targetRange != null);
+
+        Log.i(TAG, String.format("Selected frame rate range %d - %d for YUV burst",
+                        targetRange.getLower(), targetRange.getUpper()));
+
+        // Check if READ_SENSOR_SETTINGS is supported
+        final boolean checkSensorSettings = mStaticInfo.isCapabilitySupported(
+            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS);
+
+        // Configure basic preview and burst settings
+
+        CaptureRequest.Builder previewBuilder =
+            mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+        CaptureRequest.Builder burstBuilder =
+            mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+
+        previewBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
+                targetRange);
+        burstBuilder.set(CaptureRequest.CONTROL_AE_TARGET_FPS_RANGE,
+                targetRange);
+        burstBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
+        burstBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
+
+        // Create session and start up preview
+
+        SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+        SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
+
+        prepareCaptureAndStartPreview(
+            previewBuilder, burstBuilder,
+            previewSize, stillSize,
+            ImageFormat.YUV_420_888, resultListener,
+            /*maxNumImages*/ 3, imageListener);
+
+        // Create burst
+
+        List<CaptureRequest> burst = new ArrayList<>();
+        for (int i = 0; i < BURST_SIZE; i++) {
+            burst.add(burstBuilder.build());
+        }
+
+        // Converge AE/AWB
+
+        int frameCount = 0;
+        while (true) {
+            CaptureResult result = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
+            int aeState = result.get(CaptureResult.CONTROL_AE_STATE);
+            int awbState = result.get(CaptureResult.CONTROL_AWB_STATE);
+
+            if (DEBUG) {
+                Log.d(TAG, "aeState: " + aeState + ". awbState: " + awbState);
+            }
+
+            if ((aeState == CaptureResult.CONTROL_AE_STATE_CONVERGED ||
+                    aeState == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED) &&
+                    awbState == CaptureResult.CONTROL_AWB_STATE_CONVERGED) {
+                break;
+            }
+            frameCount++;
+            assertTrue(String.format("Cam %s: Can not converge AE and AWB within %d frames",
+                    cameraId, MAX_CONVERGENCE_FRAMES),
+                frameCount < MAX_CONVERGENCE_FRAMES);
+        }
+
+        // Lock AF if there's a focuser
+
+        if (mStaticInfo.hasFocuser()) {
+            previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                CaptureRequest.CONTROL_AF_TRIGGER_START);
+            mSession.capture(previewBuilder.build(), resultListener, mHandler);
+            previewBuilder.set(CaptureRequest.CONTROL_AF_TRIGGER,
+                CaptureRequest.CONTROL_AF_TRIGGER_IDLE);
+
+            frameCount = 0;
+            while (true) {
+                CaptureResult result = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
+                int afState = result.get(CaptureResult.CONTROL_AF_STATE);
+
+                if (DEBUG) {
+                    Log.d(TAG, "afState: " + afState);
+                }
+
+                if (afState == CaptureResult.CONTROL_AF_STATE_FOCUSED_LOCKED ||
+                    afState == CaptureResult.CONTROL_AF_STATE_NOT_FOCUSED_LOCKED) {
+                    break;
+                }
+                frameCount++;
+                assertTrue(String.format("Cam %s: Cannot lock AF within %d frames", cameraId,
+                        MAX_CONVERGENCE_FRAMES),
+                    frameCount < MAX_CONVERGENCE_FRAMES);
+            }
+        }
+
+        // Lock AE/AWB
+
+        previewBuilder.set(CaptureRequest.CONTROL_AE_LOCK, true);
+        previewBuilder.set(CaptureRequest.CONTROL_AWB_LOCK, true);
+
+        CaptureRequest lockedRequest = previewBuilder.build();
+        mSession.setRepeatingRequest(lockedRequest, resultListener, mHandler);
+
+        // Wait for first result with locking
+
+        CaptureResult lockedResult =
+                resultListener.getCaptureResultForRequest(lockedRequest, maxPipelineDepth);
+
+        int pipelineDepth = lockedResult.get(CaptureResult.REQUEST_PIPELINE_DEPTH);
+
+        // Then start waiting on results to get the first result that should be synced
+        // up, and also fire the burst as soon as possible
+
+        if (maxSyncLatency == CameraCharacteristics.SYNC_MAX_LATENCY_PER_FRAME_CONTROL) {
+            // The locked result we have is already synchronized so start the burst
+            mSession.captureBurst(burst, resultListener, mHandler);
+        } else {
+            // Need to get a synchronized result, and may need to start burst later to
+            // be synchronized correctly
+
+            boolean burstSent = false;
+
+            // Calculate how many requests we need to still send down to camera before we
+            // know the settings have settled for the burst
+
+            int requestsNeededToSync = maxSyncLatency - pipelineDepth;
+            for (int i = 0; i < maxSyncLatency; i++) {
+                if (!burstSent && requestsNeededToSync <= 0) {
+                    mSession.captureBurst(burst, resultListener, mHandler);
+                    burstSent = true;
+                }
+                lockedResult = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
+                requestsNeededToSync--;
+            }
+
+            assertTrue("Cam " + cameraId + ": Burst failed to fire!", burstSent);
+        }
+
+        // Read in locked settings if supported
+
+        long burstExposure = 0;
+        long burstFrameDuration = 0;
+        int burstSensitivity = 0;
+        if (checkSensorSettings) {
+            burstExposure = lockedResult.get(CaptureResult.SENSOR_EXPOSURE_TIME);
+            burstFrameDuration = lockedResult.get(CaptureResult.SENSOR_FRAME_DURATION);
+            burstSensitivity = lockedResult.get(CaptureResult.SENSOR_SENSITIVITY);
+
+            assertTrue(String.format("Cam %s: Frame duration %d ns too short compared to " +
+                    "exposure time %d ns", cameraId, burstFrameDuration, burstExposure),
+                burstFrameDuration >= burstExposure);
+
+            assertTrue(String.format("Cam %s: Exposure time is not valid: %d",
+                    cameraId, burstExposure),
+                burstExposure > 0);
+            assertTrue(String.format("Cam %s: Frame duration is not valid: %d",
+                    cameraId, burstFrameDuration),
+                burstFrameDuration > 0);
+            assertTrue(String.format("Cam %s: Sensitivity is not valid: %d",
+                    cameraId, burstSensitivity),
+                burstSensitivity > 0);
+        }
+
+        // Process burst images
+        for (int i = 0; i < BURST_SIZE; i++) {
+            Image img = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
+
+            img.close();
+        }
+
+        // Process burst results
+        int burstIndex = 0;
+        CaptureResult burstResult =
+                resultListener.getCaptureResultForRequest(burst.get(burstIndex),
+                    maxPipelineDepth + 1);
+        long prevTimestamp = -1;
+        final long frameDurationBound = (long)
+                (minStillFrameDuration * (1 + FRAME_DURATION_MARGIN_FRACTION) );
+
+        List<Long> frameDurations = new ArrayList<>();
+
+        while(true) {
+            // Verify the result
+            assertTrue("Cam " + cameraId + ": Result doesn't match expected request",
+                    burstResult.getRequest() == burst.get(burstIndex));
+
+            // Verify locked settings
+            if (checkSensorSettings) {
+                long exposure = burstResult.get(CaptureResult.SENSOR_EXPOSURE_TIME);
+                int sensitivity = burstResult.get(CaptureResult.SENSOR_SENSITIVITY);
+                assertTrue("Cam " + cameraId + ": Exposure not locked!",
+                    exposure == burstExposure);
+                assertTrue("Cam " + cameraId + ": Sensitivity not locked!",
+                    sensitivity == burstSensitivity);
+            }
+
+            // Collect inter-frame durations
+            long timestamp = burstResult.get(CaptureResult.SENSOR_TIMESTAMP);
+            if (prevTimestamp != -1) {
+                long frameDuration = timestamp - prevTimestamp;
+                frameDurations.add(frameDuration);
+                if (DEBUG) {
+                    Log.i(TAG, String.format("Frame %03d    Duration %.2f ms", burstIndex,
+                            frameDuration/1e6));
+                }
+            }
+            prevTimestamp = timestamp;
+
+            // Get next result
+            burstIndex++;
+            if (burstIndex == BURST_SIZE) break;
+            burstResult = resultListener.getCaptureResult(MAX_PREVIEW_RESULT_TIMEOUT_MS);
+        }
+
+        // Verify inter-frame durations
+
+        long meanFrameSum = 0;
+        for (Long duration : frameDurations) {
+            meanFrameSum += duration;
+        }
+        float meanFrameDuration = (float) meanFrameSum / frameDurations.size();
+
+        float stddevSum = 0;
+        for (Long duration : frameDurations) {
+            stddevSum += (duration - meanFrameDuration) * (duration - meanFrameDuration);
+        }
+        float stddevFrameDuration = (float)
+                Math.sqrt(1.f / (frameDurations.size() - 1 ) * stddevSum);
+
+        Log.i(TAG, String.format("Cam %s: Burst frame duration mean: %.1f, stddev: %.1f", cameraId,
+                meanFrameDuration, stddevFrameDuration));
+
+        assertTrue(
+            String.format("Cam %s: Burst frame duration mean %.1f ns is larger than acceptable, " +
+                "expecting below %d ns, allowing below %d", cameraId,
+                meanFrameDuration, minStillFrameDuration, frameDurationBound),
+            meanFrameDuration <= frameDurationBound);
+
+        // Calculate upper 97.5% bound (assuming durations are normally distributed...)
+        float limit95FrameDuration = meanFrameDuration + 2 * stddevFrameDuration;
+
+        // Don't enforce this yet, but warn
+        if (limit95FrameDuration > frameDurationBound) {
+            Log.w(TAG,
+                String.format("Cam %s: Standard deviation is too large compared to limit: " +
+                    "mean: %.1f ms, stddev: %.1f ms: 95%% bound: %f ms", cameraId,
+                    meanFrameDuration/1e6, stddevFrameDuration/1e6,
+                    limit95FrameDuration/1e6));
+        }
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
index 9f50b43..29c7362 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraDeviceTest.java
@@ -28,6 +28,7 @@
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
@@ -1014,6 +1015,24 @@
         }
     }
 
+    private void checkAntiBandingMode(CaptureRequest.Builder request, int template) {
+        if (template == CameraDevice.TEMPLATE_MANUAL) {
+            return;
+        }
+
+        List<Integer> availableAntiBandingModes =
+                Arrays.asList(toObject(mStaticInfo.getAeAvailableAntiBandingModesChecked()));
+
+        if (availableAntiBandingModes.contains(CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_AUTO)) {
+            mCollector.expectKeyValueEquals(request, CONTROL_AE_ANTIBANDING_MODE,
+                    CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_AUTO);
+        } else {
+            mCollector.expectKeyValueIsIn(request, CONTROL_AE_ANTIBANDING_MODE,
+                    CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_50HZ,
+                    CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_60HZ);
+        }
+    }
+
     /**
      * <p>Check if 3A metering settings are "up to HAL" in request template</p>
      *
@@ -1058,6 +1077,7 @@
 
         checkAfMode(request, template, props);
         checkFpsRange(request, template, props);
+        checkAntiBandingMode(request, template);
 
         if (template == CameraDevice.TEMPLATE_MANUAL) {
             mCollector.expectKeyValueEquals(request, CONTROL_MODE, CaptureRequest.CONTROL_MODE_OFF);
@@ -1068,8 +1088,6 @@
         } else {
             mCollector.expectKeyValueEquals(request, CONTROL_AE_MODE,
                     CaptureRequest.CONTROL_AE_MODE_ON);
-            mCollector.expectKeyValueNotEquals(request, CONTROL_AE_ANTIBANDING_MODE,
-                    CaptureRequest.CONTROL_AE_ANTIBANDING_MODE_OFF);
             mCollector.expectKeyValueEquals(request, CONTROL_AE_EXPOSURE_COMPENSATION, 0);
             mCollector.expectKeyValueEquals(request, CONTROL_AE_LOCK, false);
             mCollector.expectKeyValueEquals(request, CONTROL_AE_PRECAPTURE_TRIGGER,
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraManagerTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraManagerTest.java
index 192fb85..27ff6d1 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraManagerTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraManagerTest.java
@@ -41,7 +41,9 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.concurrent.LinkedBlockingQueue;
 
 /**
  * <p>Basic test for CameraManager class.</p>
@@ -485,7 +487,132 @@
         mCameraManager.registerAvailabilityCallback(mListener, mHandler);
         mCameraManager.unregisterAvailabilityCallback(mListener);
         mCameraManager.unregisterAvailabilityCallback(mListener);
-
-        // TODO: test the listener callbacks
     }
+
+    /**
+     * Test that the availability callbacks fire when expected
+     */
+    public void testCameraManagerListenerCallbacks() throws Exception {
+        final int AVAILABILITY_TIMEOUT_MS = 10;
+
+        final LinkedBlockingQueue<String> availableEventQueue = new LinkedBlockingQueue<>();
+        final LinkedBlockingQueue<String> unavailableEventQueue = new LinkedBlockingQueue<>();
+
+        CameraManager.AvailabilityCallback ac = new CameraManager.AvailabilityCallback() {
+            @Override
+            public void onCameraAvailable(String cameraId) {
+                availableEventQueue.offer(cameraId);
+            }
+
+            @Override
+            public void onCameraUnavailable(String cameraId) {
+                unavailableEventQueue.offer(cameraId);
+            }
+        };
+
+        mCameraManager.registerAvailabilityCallback(ac, mHandler);
+        String[] cameras = mCameraManager.getCameraIdList();
+
+        if (cameras.length == 0) {
+            Log.i(TAG, "No cameras present, skipping test");
+            return;
+        }
+
+        // Verify we received available for all cameras' initial state in a reasonable amount of time
+        HashSet<String> expectedAvailableCameras = new HashSet<String>(Arrays.asList(cameras));
+        while (expectedAvailableCameras.size() > 0) {
+            String id = availableEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
+                    java.util.concurrent.TimeUnit.MILLISECONDS);
+            assertTrue("Did not receive initial availability notices for some cameras",
+                       id != null);
+            expectedAvailableCameras.remove(id);
+        }
+        // Verify no unavailable cameras were reported
+        assertTrue("Some camera devices are initially unavailable",
+                unavailableEventQueue.size() == 0);
+
+        // Verify transitions for individual cameras
+        for (String id : cameras) {
+            MockStateCallback mockListener = MockStateCallback.mock();
+            mCameraListener = new BlockingStateCallback(mockListener);
+
+            mCameraManager.openCamera(id, mCameraListener, mHandler);
+
+            // Block until opened
+            mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED,
+                    CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
+            // Then verify only open happened, and get the camera handle
+            CameraDevice camera = verifyCameraStateOpened(id, mockListener);
+
+            // Verify that we see the expected 'unavailable' event.
+            String candidateId = unavailableEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
+                    java.util.concurrent.TimeUnit.MILLISECONDS);
+            assertTrue(String.format("Received unavailability notice for wrong ID " +
+                            "(expected %s, got %s)", id, candidateId),
+                    id == candidateId);
+            assertTrue("Availability events received unexpectedly",
+                    availableEventQueue.size() == 0);
+
+            // Verify that we see the expected 'available' event after closing the camera
+
+            camera.close();
+
+            mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED,
+                    CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS);
+
+            candidateId = availableEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
+                    java.util.concurrent.TimeUnit.MILLISECONDS);
+            assertTrue(String.format("Received availability notice for wrong ID " +
+                            "(expected %s, got %s)", id, candidateId),
+                    id == candidateId);
+            assertTrue("Unavailability events received unexpectedly",
+                    unavailableEventQueue.size() == 0);
+
+        }
+
+        // Verify that we can unregister the listener and see no more events
+        assertTrue("Availability events received unexpectedly",
+                availableEventQueue.size() == 0);
+        assertTrue("Unavailability events received unexpectedly",
+                    unavailableEventQueue.size() == 0);
+
+        mCameraManager.unregisterAvailabilityCallback(ac);
+
+        {
+            // Open an arbitrary camera and make sure we don't hear about it
+
+            MockStateCallback mockListener = MockStateCallback.mock();
+            mCameraListener = new BlockingStateCallback(mockListener);
+
+            mCameraManager.openCamera(cameras[0], mCameraListener, mHandler);
+
+            // Block until opened
+            mCameraListener.waitForState(BlockingStateCallback.STATE_OPENED,
+                    CameraTestUtils.CAMERA_IDLE_TIMEOUT_MS);
+            // Then verify only open happened, and close the camera
+            CameraDevice camera = verifyCameraStateOpened(cameras[0], mockListener);
+
+            camera.close();
+
+            mCameraListener.waitForState(BlockingStateCallback.STATE_CLOSED,
+                    CameraTestUtils.CAMERA_CLOSE_TIMEOUT_MS);
+
+            // No unavailability or availability callback should have occured
+            String candidateId = unavailableEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
+                    java.util.concurrent.TimeUnit.MILLISECONDS);
+            assertTrue(String.format("Received unavailability notice for ID %s unexpectedly ",
+                            candidateId),
+                    candidateId == null);
+
+            candidateId = availableEventQueue.poll(AVAILABILITY_TIMEOUT_MS,
+                    java.util.concurrent.TimeUnit.MILLISECONDS);
+            assertTrue(String.format("Received availability notice for ID %s unexpectedly ",
+                            candidateId),
+                    candidateId == null);
+
+
+        }
+
+    } // testCameraManagerListenerCallbacks
+
 }
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
index 0c36832..872f951 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -18,6 +18,7 @@
 
 import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
 
+import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.ImageFormat;
 import android.graphics.PointF;
@@ -63,6 +64,7 @@
 import java.util.List;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicLong;
 
 /**
  * A package private utility class for wrapping up the camera2 cts test common utility functions
@@ -199,6 +201,7 @@
     public static class SimpleCaptureCallback extends CameraCaptureSession.CaptureCallback {
         private final LinkedBlockingQueue<CaptureResult> mQueue =
                 new LinkedBlockingQueue<CaptureResult>();
+        private AtomicLong mNumFramesArrived = new AtomicLong(0);
 
         @Override
         public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
@@ -210,6 +213,7 @@
         public void onCaptureCompleted(CameraCaptureSession session, CaptureRequest request,
                 TotalCaptureResult result) {
             try {
+                mNumFramesArrived.incrementAndGet();
                 mQueue.put(result);
             } catch (InterruptedException e) {
                 throw new UnsupportedOperationException(
@@ -227,6 +231,10 @@
                 long frameNumber) {
         }
 
+        public long getTotalNumFrames() {
+            return mNumFramesArrived.get();
+        }
+
         public CaptureResult getCaptureResult(long timeout) {
             try {
                 CaptureResult result = mQueue.poll(timeout, TimeUnit.MILLISECONDS);
@@ -439,22 +447,26 @@
             assertTrue("rowStride " + rowStride + " should be >= width " + w , rowStride >= w);
             for (int row = 0; row < h; row++) {
                 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8;
+                int length;
                 if (pixelStride == bytesPerPixel) {
                     // Special case: optimized read of the entire row
-                    int length = w * bytesPerPixel;
+                    length = w * bytesPerPixel;
                     buffer.get(data, offset, length);
-                    // Advance buffer the remainder of the row stride
-                    buffer.position(buffer.position() + rowStride - length);
                     offset += length;
                 } else {
                     // Generic case: should work for any pixelStride but slower.
                     // Use intermediate buffer to avoid read byte-by-byte from
                     // DirectByteBuffer, which is very bad for performance
-                    buffer.get(rowData, 0, rowStride);
+                    length = (w - 1) * pixelStride + bytesPerPixel;
+                    buffer.get(rowData, 0, length);
                     for (int col = 0; col < w; col++) {
                         data[offset++] = rowData[col * pixelStride];
                     }
                 }
+                // Advance buffer the remainder of the row stride
+                if (row < h - 1) {
+                    buffer.position(buffer.position() + rowStride - length);
+                }
             }
             if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);
             buffer.rewind();
@@ -485,6 +497,23 @@
         }
     }
 
+    public static void dumpFile(String fileName, Bitmap data) {
+        FileOutputStream outStream;
+        try {
+            Log.v(TAG, "output will be saved as " + fileName);
+            outStream = new FileOutputStream(fileName);
+        } catch (IOException ioe) {
+            throw new RuntimeException("Unable to create debug output file " + fileName, ioe);
+        }
+
+        try {
+            data.compress(Bitmap.CompressFormat.JPEG, /*quality*/90, outStream);
+            outStream.close();
+        } catch (IOException ioe) {
+            throw new RuntimeException("failed writing data to file " + fileName, ioe);
+        }
+    }
+
     public static void dumpFile(String fileName, byte[] data) {
         FileOutputStream outStream;
         try {
@@ -675,6 +704,21 @@
     }
 
     /**
+     * Returns true if the given {@code array} contains the given element.
+     *
+     * @param array {@code array} to check for {@code elem}
+     * @param elem {@code elem} to test for
+     * @return {@code true} if the given element is contained
+     */
+    public static boolean contains(int[] array, int elem) {
+        if (array == null) return false;
+        for (int i = 0; i < array.length; i++) {
+            if (elem == array[i]) return true;
+        }
+        return false;
+    }
+
+    /**
      * Get object array from byte array.
      *
      * @param array Input byte array to be converted
@@ -772,6 +816,7 @@
                 validateJpegData(data, width, height, filePath);
                 break;
             case ImageFormat.YUV_420_888:
+            case ImageFormat.YV12:
                 validateYuvData(data, width, height, format, image.getTimestamp(), filePath);
                 break;
             case ImageFormat.RAW_SENSOR:
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 5346ae1..d70be69 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -282,11 +282,6 @@
             try {
                 openDevice(mCameraIds[i]);
 
-                if (mStaticInfo.isHardwareLevelLegacy()) {
-                    Log.i(TAG, "Skipping test on legacy devices");
-                    continue;
-                }
-
                 Size maxPreviewSz = mOrderedPreviewSizes.get(0); // Max preview size.
 
                 // Update preview surface with given size for all sub-tests.
@@ -326,7 +321,13 @@
                 flashTestByAeMode(listener, CaptureRequest.CONTROL_AE_MODE_ON);
 
                 // LEGACY won't support AE mode OFF
-                if (mStaticInfo.isHardwareLevelLimitedOrBetter()) {
+                boolean aeOffModeSupported = false;
+                for (int aeMode : mStaticInfo.getAeAvailableModesChecked()) {
+                    if (aeMode == CaptureRequest.CONTROL_AE_MODE_OFF) {
+                        aeOffModeSupported = true;
+                    }
+                }
+                if (aeOffModeSupported) {
                     flashTestByAeMode(listener, CaptureRequest.CONTROL_AE_MODE_OFF);
                 }
 
@@ -345,11 +346,6 @@
             try {
                 openDevice(mCameraIds[i]);
 
-                if (mStaticInfo.isHardwareLevelLegacy()) {
-                    Log.i(TAG, "Skipping test on legacy devices");
-                    continue;
-                }
-
                 faceDetectionTestByCamera();
             } finally {
                 closeDevice();
@@ -2102,15 +2098,18 @@
 
             resultListener = new SimpleCaptureCallback();
             startPreview(requestBuilder, previewSz, resultListener);
-            long[] frameDurationRange =
-                    new long[]{(long) (1e9 / fpsRange.getUpper()), (long) (1e9 / fpsRange.getLower())};
+            waitForSettingsApplied(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
+
+            long[] frameDurationRange = new long[]{
+                    (long) (1e9 / fpsRange.getUpper()), (long) (1e9 / fpsRange.getLower())};
             for (int j = 0; j < numFramesVerified; j++) {
                 CaptureResult result =
                         resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
                 validatePipelineDepth(result);
                 long frameDuration = getValueNotNull(result, CaptureResult.SENSOR_FRAME_DURATION);
                 mCollector.expectInRange(
-                        "Frame duration must be in the range of " + Arrays.toString(frameDurationRange),
+                        "Frame duration must be in the range of " +
+                                Arrays.toString(frameDurationRange),
                         frameDuration,
                         (long) (frameDurationRange[0] * (1 - FRAME_DURATION_ERROR_MARGIN)),
                         (long) (frameDurationRange[1] * (1 + FRAME_DURATION_ERROR_MARGIN)));
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/DngCreatorTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/DngCreatorTest.java
index 9ec649e..d5972e2 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/DngCreatorTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/DngCreatorTest.java
@@ -16,15 +16,22 @@
 
 package android.hardware.camera2.cts;
 
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
-import android.hardware.camera2.CameraCaptureSession;
+import android.graphics.RectF;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.DngCreator;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.rs.BitmapUtils;
+import android.hardware.camera2.cts.rs.RawConverter;
+import android.hardware.camera2.cts.rs.RenderScriptSingleton;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
 import android.location.Location;
 import android.media.ExifInterface;
@@ -37,11 +44,14 @@
 
 import java.io.ByteArrayOutputStream;
 import java.io.FileOutputStream;
+import java.nio.channels.FileChannel;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
 import java.util.List;
 
-import static android.hardware.camera2.cts.CameraTestUtils.configureCameraSession;
 import static android.hardware.camera2.cts.helpers.AssertHelpers.*;
+import static junit.framework.Assert.assertTrue;
 
 /**
  * Tests for the DngCreator API.
@@ -51,6 +61,9 @@
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
     private static final String DEBUG_DNG_FILE = "raw16.dng";
 
+    private static final double IMAGE_DIFFERENCE_TOLERANCE = 60;
+    private static final int DEFAULT_PATCH_DIMEN = 512;
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -61,6 +74,13 @@
         super.tearDown();
     }
 
+    @Override
+    public synchronized void setContext(Context context) {
+        super.setContext(context);
+
+        RenderScriptSingleton.setContext(context);
+    }
+
     /**
      * Test basic raw capture and DNG saving functionality for each of the available cameras.
      *
@@ -120,14 +140,15 @@
                 dngCreator.writeImage(outputStream, resultPair.first);
 
                 if (VERBOSE) {
-                    String filePath = DEBUG_FILE_NAME_BASE + "camera_" + deviceId + "_" +
+                    // Write DNG to file
+                    String dngFilePath = DEBUG_FILE_NAME_BASE + "/camera_basic_" + deviceId + "_" +
                             DEBUG_DNG_FILE;
                     // Write out captured DNG file for the first camera device if setprop is enabled
-                    fileStream = new FileOutputStream(filePath);
+                    fileStream = new FileOutputStream(dngFilePath);
                     fileStream.write(outputStream.toByteArray());
                     fileStream.flush();
                     fileStream.close();
-                    Log.v(TAG, "Test DNG file for camera " + deviceId + " saved to " + filePath);
+                    Log.v(TAG, "Test DNG file for camera " + deviceId + " saved to " + dngFilePath);
                 }
             } finally {
                 closeDevice(deviceId);
@@ -231,7 +252,7 @@
                 dngCreator.writeImage(outputStream, resultPair.first.get(0));
 
                 if (VERBOSE) {
-                    String filePath = DEBUG_FILE_NAME_BASE + "camera_" + deviceId + "_" +
+                    String filePath = DEBUG_FILE_NAME_BASE + "/camera_thumb_" + deviceId + "_" +
                             DEBUG_DNG_FILE;
                     // Write out captured DNG file for the first camera device if setprop is enabled
                     fileStream = new FileOutputStream(filePath);
@@ -257,6 +278,222 @@
         }
     }
 
+    /**
+     * Test basic RAW capture, and ensure that the rendered RAW output is similar to the JPEG
+     * created for the same frame.
+     *
+     * <p>
+     * This test renders the RAW buffer into an RGB bitmap using a rendering pipeline
+     * similar to one in the Adobe DNG validation tool.  JPEGs produced by the vendor hardware may
+     * have different tonemapping and saturation applied than the RGB bitmaps produced
+     * from this DNG rendering pipeline, and this test allows for fairly wide variations
+     * between the histograms for the RAW and JPEG buffers to avoid false positives.
+     * </p>
+     *
+     * <p>
+     * To ensure more subtle errors in the colorspace transforms returned for the HAL's RAW
+     * metadata, the DNGs and JPEGs produced here should also be manually compared using external
+     * DNG rendering tools.  The DNG, rendered RGB bitmap, and JPEG buffer for this test can be
+     * dumped to the SD card for further examination by enabling the 'verbose' mode for this test
+     * using:
+     * adb shell setprop log.tag.DngCreatorTest VERBOSE
+     * </p>
+     */
+    public void testRaw16JpegConsistency() throws Exception {
+        for (int i = 0; i < mCameraIds.length; i++) {
+            String deviceId = mCameraIds[i];
+            List<ImageReader> captureReaders = new ArrayList<ImageReader>();
+            List<CameraTestUtils.SimpleImageReaderListener> captureListeners =
+                    new ArrayList<CameraTestUtils.SimpleImageReaderListener>();
+            FileOutputStream fileStream = null;
+            ByteArrayOutputStream outputStream = null;
+            FileChannel fileChannel = null;
+            try {
+                openDevice(deviceId);
+
+                if (!mStaticInfo.isCapabilitySupported(
+                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
+                    Log.i(TAG, "RAW capability is not supported in camera " + mCameraIds[i] +
+                            ". Skip the test.");
+                    continue;
+                }
+
+                Size[] targetCaptureSizes =
+                        mStaticInfo.getAvailableSizesForFormatChecked(ImageFormat.RAW_SENSOR,
+                                StaticMetadata.StreamDirection.Output);
+
+                assertTrue("No capture sizes available for RAW format!",
+                        targetCaptureSizes.length != 0);
+                Rect activeArray = mStaticInfo.getActiveArraySizeChecked();
+                Size activeArraySize = new Size(activeArray.width(), activeArray.height());
+                assertTrue("Active array has invalid size!", activeArray.width() > 0 &&
+                        activeArray.height() > 0);
+                // TODO: Allow PixelArraySize also.
+                assertArrayContains("Available sizes for RAW format must include ActiveArraySize",
+                        targetCaptureSizes, activeArraySize);
+
+                // Get largest jpeg size
+                Size[] targetJpegSizes =
+                        mStaticInfo.getAvailableSizesForFormatChecked(ImageFormat.JPEG,
+                                StaticMetadata.StreamDirection.Output);
+
+                Size largestJpegSize = Collections.max(Arrays.asList(targetJpegSizes),
+                        new CameraTestUtils.SizeComparator());
+
+                // Create raw image reader and capture listener
+                CameraTestUtils.SimpleImageReaderListener rawListener
+                        = new CameraTestUtils.SimpleImageReaderListener();
+                captureReaders.add(createImageReader(activeArraySize, ImageFormat.RAW_SENSOR, 2,
+                        rawListener));
+                captureListeners.add(rawListener);
+
+
+                // Create jpeg image reader and capture listener
+                CameraTestUtils.SimpleImageReaderListener jpegListener
+                        = new CameraTestUtils.SimpleImageReaderListener();
+                captureReaders.add(createImageReader(largestJpegSize, ImageFormat.JPEG, 2,
+                        jpegListener));
+                captureListeners.add(jpegListener);
+
+                Pair<List<Image>, CaptureResult> resultPair = captureSingleRawShot(activeArraySize,
+                        captureReaders, captureListeners);
+                CameraCharacteristics characteristics = mStaticInfo.getCharacteristics();
+                Image raw = resultPair.first.get(0);
+                Image jpeg = resultPair.first.get(1);
+
+                Bitmap rawBitmap = Bitmap.createBitmap(raw.getWidth(), raw.getHeight(),
+                        Bitmap.Config.ARGB_8888);
+                byte[] rawPlane = new byte[raw.getPlanes()[0].getRowStride() * raw.getHeight()];
+
+                // Render RAW image to a bitmap
+                raw.getPlanes()[0].getBuffer().get(rawPlane);
+                raw.getPlanes()[0].getBuffer().rewind();
+                RawConverter.convertToSRGB(RenderScriptSingleton.getRS(), raw.getWidth(),
+                        raw.getHeight(), rawPlane, characteristics,
+                        resultPair.second, /*offsetX*/0, /*offsetY*/0, /*out*/rawBitmap);
+
+                // Decompress JPEG image to a bitmap
+                byte[] compressedJpegData = CameraTestUtils.getDataFromImage(jpeg);
+
+                BitmapFactory.Options opt = new BitmapFactory.Options();
+                opt.inPreferredConfig = Bitmap.Config.ARGB_8888;
+                Bitmap fullSizeJpegBmap = BitmapFactory.decodeByteArray(compressedJpegData,
+                        /*offset*/0, compressedJpegData.length, /*inout*/opt);
+                Rect jpegDimens = new Rect(0, 0, fullSizeJpegBmap.getWidth(),
+                        fullSizeJpegBmap.getHeight());
+
+                if (VERBOSE) {
+                    // Generate DNG file
+                    DngCreator dngCreator = new DngCreator(characteristics, resultPair.second);
+                    outputStream = new ByteArrayOutputStream();
+                    dngCreator.writeImage(outputStream, raw);
+
+                    // Write DNG to file
+                    String dngFilePath = DEBUG_FILE_NAME_BASE + "/camera_" + deviceId + "_" +
+                            DEBUG_DNG_FILE;
+                    // Write out captured DNG file for the first camera device if setprop is enabled
+                    fileStream = new FileOutputStream(dngFilePath);
+                    fileStream.write(outputStream.toByteArray());
+                    fileStream.flush();
+                    fileStream.close();
+                    Log.v(TAG, "Test DNG file for camera " + deviceId + " saved to " + dngFilePath);
+
+                    // Write JPEG to file
+                    String jpegFilePath = DEBUG_FILE_NAME_BASE + "/camera_" + deviceId + "_jpeg.jpg";
+                    // Write out captured DNG file for the first camera device if setprop is enabled
+                    fileChannel = new FileOutputStream(jpegFilePath).getChannel();
+                    fileChannel.write(jpeg.getPlanes()[0].getBuffer());
+                    fileChannel.close();
+                    Log.v(TAG, "Test JPEG file for camera " + deviceId + " saved to " +
+                            jpegFilePath);
+
+                    // Write jpeg generated from demosaiced RAW frame to file
+                    String rawFilePath = DEBUG_FILE_NAME_BASE + "/camera_" + deviceId + "_raw.jpg";
+                    // Write out captured DNG file for the first camera device if setprop is enabled
+                    fileStream = new FileOutputStream(rawFilePath);
+                    rawBitmap.compress(Bitmap.CompressFormat.JPEG, 90, fileStream);
+                    fileStream.flush();
+                    fileStream.close();
+                    Log.v(TAG, "Test converted RAW file for camera " + deviceId + " saved to " +
+                            rawFilePath);
+                }
+
+                Size rawBitmapSize = new Size(rawBitmap.getWidth(), rawBitmap.getHeight());
+                assertTrue("Raw bitmap size must be equal to active array size.",
+                        rawBitmapSize.equals(activeArraySize));
+
+                // Get square center patch from JPEG and RAW bitmaps
+                RectF jpegRect = new RectF(jpegDimens);
+                RectF rawRect = new RectF(0, 0, rawBitmap.getWidth(), rawBitmap.getHeight());
+                int sideDimen = Math.min(Math.min(Math.min(Math.min(DEFAULT_PATCH_DIMEN,
+                        jpegDimens.width()), jpegDimens.height()), rawBitmap.getWidth()),
+                        rawBitmap.getHeight());
+
+                RectF jpegIntermediate = new RectF(0, 0, sideDimen, sideDimen);
+                jpegIntermediate.offset(jpegRect.centerX() - jpegIntermediate.centerX(),
+                        jpegRect.centerY() - jpegIntermediate.centerY());
+                RectF rawIntermediate = new RectF(0, 0, sideDimen, sideDimen);
+                rawIntermediate.offset(rawRect.centerX() - rawIntermediate.centerX(),
+                        rawRect.centerY() - rawIntermediate.centerY());
+                Rect jpegFinal = new Rect();
+                jpegIntermediate.roundOut(jpegFinal);
+                Rect rawFinal = new Rect();
+                rawIntermediate.roundOut(rawFinal);
+
+                Bitmap jpegPatch = Bitmap.createBitmap(fullSizeJpegBmap, jpegFinal.left,
+                        jpegFinal.top, jpegFinal.width(), jpegFinal.height());
+                Bitmap rawPatch = Bitmap.createBitmap(rawBitmap, rawFinal.left, rawFinal.top,
+                        rawFinal.width(), rawFinal.height());
+
+                // Compare center patch from JPEG and rendered RAW bitmap
+                double difference = BitmapUtils.calcDifferenceMetric(jpegPatch, rawPatch);
+                if (difference > IMAGE_DIFFERENCE_TOLERANCE) {
+                    // Write JPEG patch to file
+                    String jpegFilePath = DEBUG_FILE_NAME_BASE + "/camera_" + deviceId +
+                            "_jpeg_patch.jpg";
+                    fileStream = new FileOutputStream(jpegFilePath);
+                    jpegPatch.compress(Bitmap.CompressFormat.JPEG, 90, fileStream);
+                    fileStream.flush();
+                    fileStream.close();
+                    Log.e(TAG, "Failed JPEG patch file for camera " + deviceId + " saved to " +
+                            jpegFilePath);
+
+                    // Write RAW patch to file
+                    String rawFilePath = DEBUG_FILE_NAME_BASE + "/camera_" + deviceId +
+                            "_raw_patch.jpg";
+                    fileStream = new FileOutputStream(rawFilePath);
+                    rawPatch.compress(Bitmap.CompressFormat.JPEG, 90, fileStream);
+                    fileStream.flush();
+                    fileStream.close();
+                    Log.e(TAG, "Failed RAW patch file for camera " + deviceId + " saved to " +
+                            rawFilePath);
+
+                    fail("Camera " + mCamera.getId() + ": RAW and JPEG image at  for the same " +
+                            "frame are not similar, center patches have difference metric of " +
+                            difference);
+                }
+
+            } finally {
+                closeDevice(deviceId);
+                for (ImageReader r : captureReaders) {
+                    closeImageReader(r);
+                }
+
+                if (fileChannel != null) {
+                    fileChannel.close();
+                }
+
+                if (outputStream != null) {
+                    outputStream.close();
+                }
+
+                if (fileStream != null) {
+                    fileStream.close();
+                }
+            }
+        }
+    }
+
     private Pair<Image, CaptureResult> captureSingleRawShot(Size s, ImageReader captureReader,
             CameraTestUtils.SimpleImageReaderListener captureListener) throws Exception {
         List<ImageReader> readers = new ArrayList<ImageReader>();
@@ -268,22 +505,27 @@
         return new Pair<Image, CaptureResult>(res.first.get(0), res.second);
     }
 
+    private Pair<List<Image>, CaptureResult> captureSingleRawShot(Size s, List<ImageReader> captureReaders,
+            List<CameraTestUtils.SimpleImageReaderListener> captureListeners) throws Exception {
+        return captureRawShots(s, captureReaders, captureListeners, 1).get(0);
+    }
+
     /**
-     * Capture a single raw image.
+     * Capture raw images.
      *
-     * <p>Capture an raw image for a given size.</p>
+     * <p>Capture raw images for a given size.</p>
      *
      * @param s The size of the raw image to capture.  Must be one of the available sizes for this
      *          device.
-     * @return a pair containing the {@link Image} and {@link CaptureResult} used for this capture.
+     * @return a list of pairs containing a {@link Image} and {@link CaptureResult} used for
+     *          each capture.
      */
-    private Pair<List<Image>, CaptureResult> captureSingleRawShot(Size s, List<ImageReader> captureReaders,
-            List<CameraTestUtils.SimpleImageReaderListener> captureListeners) throws Exception {
+    private List<Pair<List<Image>, CaptureResult>> captureRawShots(Size s, List<ImageReader> captureReaders,
+            List<CameraTestUtils.SimpleImageReaderListener> captureListeners, int numShots) throws Exception {
         if (VERBOSE) {
             Log.v(TAG, "captureSingleRawShot - Capturing raw image.");
         }
 
-        Size maxYuvSz = mOrderedPreviewSizes.get(0);
         Size[] targetCaptureSizes =
                 mStaticInfo.getAvailableSizesForFormatChecked(ImageFormat.RAW_SENSOR,
                         StaticMetadata.StreamDirection.Output);
@@ -312,23 +554,29 @@
         CameraTestUtils.SimpleCaptureCallback resultListener =
                 new CameraTestUtils.SimpleCaptureCallback();
 
-        startCapture(request.build(), /*repeating*/false, resultListener, mHandler);
+        CaptureRequest request1 = request.build();
+        for (int i = 0; i < numShots; i++) {
+            startCapture(request1, /*repeating*/false, resultListener, mHandler);
+        }
+        List<Pair<List<Image>, CaptureResult>> ret = new ArrayList<>();
+        for (int i = 0; i < numShots; i++) {
+            // Verify capture result and images
+            CaptureResult result = resultListener.getCaptureResult(CAPTURE_WAIT_TIMEOUT_MS);
 
-        // Verify capture result and images
-        CaptureResult result = resultListener.getCaptureResult(CAPTURE_WAIT_TIMEOUT_MS);
-
-        List<Image> resultImages = new ArrayList<Image>();
-        for (CameraTestUtils.SimpleImageReaderListener captureListener : captureListeners) {
-            Image captureImage = captureListener.getImage(CAPTURE_WAIT_TIMEOUT_MS);
+            List<Image> resultImages = new ArrayList<Image>();
+            for (CameraTestUtils.SimpleImageReaderListener captureListener : captureListeners) {
+                Image captureImage = captureListener.getImage(CAPTURE_WAIT_TIMEOUT_MS);
 
             /*CameraTestUtils.validateImage(captureImage, s.getWidth(), s.getHeight(),
                     ImageFormat.RAW_SENSOR, null);*/
-            resultImages.add(captureImage);
+                resultImages.add(captureImage);
+            }
+            ret.add(new Pair<List<Image>, CaptureResult>(resultImages, result));
         }
         // Stop capture, delete the streams.
         stopCapture(/*fast*/false);
 
-        return new Pair<List<Image>, CaptureResult>(resultImages, result);
+        return ret;
     }
 
     private CaptureRequest.Builder prepareCaptureRequestForSurfaces(List<Surface> surfaces)
@@ -336,7 +584,7 @@
         createSession(surfaces);
 
         CaptureRequest.Builder captureBuilder =
-                mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+                mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
         assertNotNull("Fail to get captureRequest", captureBuilder);
         for (Surface surface : surfaces) {
             captureBuilder.addTarget(surface);
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index 1a00d9e..02dfccd 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -18,6 +18,7 @@
 
 import android.content.Context;
 import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraCharacteristics.Key;
 import android.hardware.camera2.CameraManager;
@@ -25,10 +26,13 @@
 import android.hardware.camera2.params.BlackLevelPattern;
 import android.hardware.camera2.params.ColorSpaceTransform;
 import android.hardware.camera2.params.StreamConfigurationMap;
+import android.media.ImageReader;
 import android.test.AndroidTestCase;
 import android.util.Log;
 import android.util.Rational;
+import android.util.Range;
 import android.util.Size;
+import android.view.Surface;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -75,6 +79,8 @@
             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE;
     private static final int MANUAL_SENSOR =
             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR;
+    private static final int MANUAL_POSTPROC =
+            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING;
     private static final int RAW =
             CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW;
 
@@ -248,8 +254,8 @@
                 expectKeyAvailable(c, CameraCharacteristics.STATISTICS_INFO_AVAILABLE_HOT_PIXEL_MAP_MODES   , OPT      ,   RAW                  );
                 expectKeyAvailable(c, CameraCharacteristics.STATISTICS_INFO_MAX_FACE_COUNT                  , LEGACY   ,   BC                   );
                 expectKeyAvailable(c, CameraCharacteristics.SYNC_MAX_LATENCY                                , LEGACY   ,   BC                   );
-                expectKeyAvailable(c, CameraCharacteristics.TONEMAP_AVAILABLE_TONE_MAP_MODES                , FULL     ,   MANUAL_SENSOR        );
-                expectKeyAvailable(c, CameraCharacteristics.TONEMAP_MAX_CURVE_POINTS                        , FULL     ,   MANUAL_SENSOR        );
+                expectKeyAvailable(c, CameraCharacteristics.TONEMAP_AVAILABLE_TONE_MAP_MODES                , FULL     ,   MANUAL_POSTPROC      );
+                expectKeyAvailable(c, CameraCharacteristics.TONEMAP_MAX_CURVE_POINTS                        , FULL     ,   MANUAL_POSTPROC      );
 
                 // Future: Use column editors for modifying above, ignore line length to keep 1 key per line
 
@@ -267,7 +273,8 @@
         int counter = 0;
         for (CameraCharacteristics c : mCharacteristics) {
             int[] actualCapabilities = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
-            assertNotNull("android.request.availableCapabilities must never be null");
+            assertNotNull("android.request.availableCapabilities must never be null",
+                    actualCapabilities);
             if (!arrayContains(actualCapabilities,
                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
                 Log.i(TAG, "RAW capability is not supported in camera " + counter++ +
@@ -339,6 +346,274 @@
     }
 
     /**
+     * Test values for static metadata used by the BURST capability.
+     */
+    public void testStaticBurstCharacteristics() {
+        int counter = 0;
+        for (CameraCharacteristics c : mCharacteristics) {
+            int[] actualCapabilities = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+            assertNotNull("android.request.availableCapabilities must never be null",
+                    actualCapabilities);
+
+            // Check if the burst capability is defined
+            boolean haveBurstCapability = arrayContains(actualCapabilities,
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE);
+
+            StreamConfigurationMap config =
+                    c.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+            assertNotNull(String.format("No stream configuration map found for: ID %s",
+                    mIds[counter]), config);
+
+            // Ensure that max YUV size matches max JPEG size
+            Size maxYuvSize = CameraTestUtils.getMaxSize(
+                    config.getOutputSizes(ImageFormat.YUV_420_888));
+            Size maxJpegSize = CameraTestUtils.getMaxSize(config.getOutputSizes(ImageFormat.JPEG));
+
+            boolean haveMaxYuv = maxYuvSize != null ?
+                (maxJpegSize.getWidth() <= maxYuvSize.getWidth() &&
+                        maxJpegSize.getHeight() <= maxYuvSize.getHeight()) : false;
+
+            // Ensure that YUV output is fast enough - needs to be at least 20 fps
+
+            long maxYuvRate =
+                config.getOutputMinFrameDuration(ImageFormat.YUV_420_888, maxYuvSize);
+            final long MIN_DURATION_BOUND_NS = 50000000; // 50 ms, 20 fps
+
+            boolean haveMaxYuvRate = maxYuvRate <= MIN_DURATION_BOUND_NS;
+
+            // Ensure that there's an FPS range that's fast enough to capture at above
+            // minFrameDuration, for full-auto bursts
+            Range[] fpsRanges = c.get(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
+            float minYuvFps = 1.f / maxYuvRate;
+
+            boolean haveFastAeTargetFps = false;
+            for (Range<Integer> r : fpsRanges) {
+                if (r.getLower() >= minYuvFps) {
+                    haveFastAeTargetFps = true;
+                    break;
+                }
+            }
+
+            // Ensure that maximum sync latency is small enough for fast setting changes, even if
+            // it's not quite per-frame
+
+            Integer maxSyncLatencyValue = c.get(CameraCharacteristics.SYNC_MAX_LATENCY);
+            assertNotNull(String.format("No sync latency declared for ID %s", mIds[counter]),
+                    maxSyncLatencyValue);
+
+            int maxSyncLatency = maxSyncLatencyValue;
+            final long MAX_LATENCY_BOUND = 4;
+            boolean haveFastSyncLatency =
+                (maxSyncLatency <= MAX_LATENCY_BOUND) && (maxSyncLatency >= 0);
+
+            if (haveBurstCapability) {
+                assertTrue(
+                        String.format("BURST-capable camera device %s does not have maximum YUV " +
+                                "size that is at least max JPEG size",
+                                mIds[counter]),
+                        haveMaxYuv);
+                assertTrue(
+                        String.format("BURST-capable camera device %s YUV frame rate is too slow" +
+                                "(%d ns min frame duration reported, less than %d ns expected)",
+                                mIds[counter], maxYuvRate, MIN_DURATION_BOUND_NS),
+                        haveMaxYuvRate);
+                assertTrue(
+                        String.format("BURST-capable camera device %s does not list an AE target " +
+                                " FPS range with min FPS >= %f, for full-AUTO bursts",
+                                mIds[counter], minYuvFps),
+                        haveFastAeTargetFps);
+                assertTrue(
+                        String.format("BURST-capable camera device %s YUV sync latency is too long" +
+                                "(%d frames reported, [0, %d] frames expected)",
+                                mIds[counter], maxSyncLatency, MAX_LATENCY_BOUND),
+                        haveFastSyncLatency);
+            } else {
+                assertTrue(
+                        String.format("Camera device %s has all the requirements for BURST" +
+                                " capability but does not report it!", mIds[counter]),
+                        !(haveMaxYuv && haveMaxYuvRate &&
+                                haveFastAeTargetFps && haveFastSyncLatency));
+            }
+
+            counter++;
+        }
+    }
+
+    /**
+     * Cross-check StreamConfigurationMap output
+     */
+    public void testStreamConfigurationMap() {
+        int counter = 0;
+        for (CameraCharacteristics c : mCharacteristics) {
+            Log.i(TAG, "testStreamConfigurationMap: Testing camera ID " + mIds[counter]);
+            StreamConfigurationMap config =
+                    c.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
+            assertNotNull(String.format("No stream configuration map found for: ID %s",
+                            mIds[counter]), config);
+
+            assertTrue("ImageReader must be supported",
+                    config.isOutputSupportedFor(android.media.ImageReader.class));
+            assertTrue("MediaRecorder must be supported",
+                    config.isOutputSupportedFor(android.media.MediaRecorder.class));
+            assertTrue("MediaCodec must be supported",
+                    config.isOutputSupportedFor(android.media.MediaCodec.class));
+            assertTrue("Allocation must be supported",
+                    config.isOutputSupportedFor(android.renderscript.Allocation.class));
+            assertTrue("SurfaceHolder must be supported",
+                    config.isOutputSupportedFor(android.view.SurfaceHolder.class));
+            assertTrue("SurfaceTexture must be supported",
+                    config.isOutputSupportedFor(android.graphics.SurfaceTexture.class));
+
+            assertTrue("YUV_420_888 must be supported",
+                    config.isOutputSupportedFor(ImageFormat.YUV_420_888));
+            assertTrue("JPEG must be supported",
+                    config.isOutputSupportedFor(ImageFormat.JPEG));
+
+            // Legacy YUV formats should not be listed
+            assertTrue("NV21 must not be supported",
+                    !config.isOutputSupportedFor(ImageFormat.NV21));
+
+            int[] actualCapabilities = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+            assertNotNull("android.request.availableCapabilities must never be null",
+                    actualCapabilities);
+            if (arrayContains(actualCapabilities,
+                    CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW)) {
+                assertTrue("RAW_SENSOR must be supported if RAW capability is advertised",
+                    config.isOutputSupportedFor(ImageFormat.RAW_SENSOR));
+            }
+
+            // Cross check public formats and sizes
+
+            int[] supportedFormats = config.getOutputFormats();
+            for (int format : supportedFormats) {
+                assertTrue("Format " + format + " fails cross check",
+                        config.isOutputSupportedFor(format));
+                Size[] supportedSizes = config.getOutputSizes(format);
+                assertTrue("Supported format " + format + " has no sizes listed",
+                        supportedSizes.length > 0);
+                for (Size size : supportedSizes) {
+                    if (VERBOSE) {
+                        Log.v(TAG,
+                                String.format("Testing camera %s, format %d, size %s",
+                                        mIds[counter], format, size.toString()));
+                    }
+
+                    long stallDuration = config.getOutputStallDuration(format, size);
+                    switch(format) {
+                        case ImageFormat.YUV_420_888:
+                            assertTrue("YUV_420_888 may not have a non-zero stall duration",
+                                    stallDuration == 0);
+                            break;
+                        default:
+                            assertTrue("Negative stall duration for format " + format,
+                                    stallDuration >= 0);
+                            break;
+                    }
+                    long minDuration = config.getOutputMinFrameDuration(format, size);
+                    if (arrayContains(actualCapabilities,
+                            CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
+                        assertTrue("MANUAL_SENSOR capability, need positive min frame duration for"
+                                + "format " + format + " for size " + size + " minDuration " +
+                                minDuration,
+                                minDuration > 0);
+                    } else {
+                        assertTrue("Need non-negative min frame duration for format " + format,
+                                minDuration >= 0);
+                    }
+
+                    ImageReader testReader = ImageReader.newInstance(
+                        size.getWidth(),
+                        size.getHeight(),
+                        format,
+                        1);
+                    Surface testSurface = testReader.getSurface();
+
+                    assertTrue(
+                        String.format("isOutputSupportedFor fails for config %s, format %d",
+                                size.toString(), format),
+                        config.isOutputSupportedFor(testSurface));
+
+                    testReader.close();
+
+                } // sizes
+
+                // Try an invalid size in this format, should round
+                Size invalidSize = findInvalidSize(supportedSizes);
+                int MAX_ROUNDING_WIDTH = 1920;
+                if (invalidSize.getWidth() <= MAX_ROUNDING_WIDTH) {
+                    ImageReader testReader = ImageReader.newInstance(
+                                                                     invalidSize.getWidth(),
+                                                                     invalidSize.getHeight(),
+                                                                     format,
+                                                                     1);
+                    Surface testSurface = testReader.getSurface();
+
+                    assertTrue(
+                               String.format("isOutputSupportedFor fails for config %s, %d",
+                                       invalidSize.toString(), format),
+                               config.isOutputSupportedFor(testSurface));
+
+                    testReader.close();
+                }
+            } // formats
+
+            // Cross-check opaque format and sizes
+
+            SurfaceTexture st = new SurfaceTexture(1);
+            Surface surf = new Surface(st);
+
+            Size[] opaqueSizes = config.getOutputSizes(SurfaceTexture.class);
+            assertTrue("Opaque format has no sizes listed",
+                    opaqueSizes.length > 0);
+            for (Size size : opaqueSizes) {
+                long stallDuration = config.getOutputStallDuration(SurfaceTexture.class, size);
+                assertTrue("Opaque output may not have a non-zero stall duration",
+                        stallDuration == 0);
+
+                long minDuration = config.getOutputMinFrameDuration(SurfaceTexture.class, size);
+                if (arrayContains(actualCapabilities,
+                                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
+                    assertTrue("MANUAL_SENSOR capability, need positive min frame duration for"
+                            + "opaque format",
+                            minDuration > 0);
+                } else {
+                    assertTrue("Need non-negative min frame duration for opaque format ",
+                            minDuration >= 0);
+                }
+                st.setDefaultBufferSize(size.getWidth(), size.getHeight());
+
+                assertTrue(
+                    String.format("isOutputSupportedFor fails for SurfaceTexture config %s",
+                            size.toString()),
+                    config.isOutputSupportedFor(surf));
+
+            } // opaque sizes
+
+            // Try invalid opaque size, should get rounded
+            Size invalidSize = findInvalidSize(opaqueSizes);
+            st.setDefaultBufferSize(invalidSize.getWidth(), invalidSize.getHeight());
+            assertTrue(
+                String.format("isOutputSupportedFor fails for SurfaceTexture config %s",
+                        invalidSize.toString()),
+                config.isOutputSupportedFor(surf));
+
+            counter++;
+        } // mCharacteristics
+
+    }
+
+    /**
+     * Create an invalid size that's close to one of the good sizes in the list, but not one of them
+     */
+    private Size findInvalidSize(Size[] goodSizes) {
+        Size invalidSize = new Size(goodSizes[0].getWidth() + 1, goodSizes[0].getHeight());
+        while(arrayContains(goodSizes, invalidSize)) {
+            invalidSize = new Size(invalidSize.getWidth() + 1, invalidSize.getHeight());
+        }
+        return invalidSize;
+    }
+
+    /**
      * Check key is present in characteristics if the hardware level is at least {@code hwLevel};
      * check that the key is present if the actual capabilities are one of {@code capabilities}.
      *
@@ -351,7 +626,8 @@
         assertNotNull("android.info.supportedHardwareLevel must never be null", actualHwLevel);
 
         int[] actualCapabilities = c.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
-        assertNotNull("android.request.availableCapabilities must never be null");
+        assertNotNull("android.request.availableCapabilities must never be null",
+                actualCapabilities);
 
         List<Key<?>> allKeys = c.getKeys();
 
@@ -410,6 +686,20 @@
         return false;
     }
 
+    private static <T> boolean arrayContains(T[] arr, T needle) {
+        if (arr == null) {
+            return false;
+        }
+
+        for (T elem : arr) {
+            if (elem.equals(needle)) {
+                return true;
+            }
+        }
+
+        return false;
+    }
+
     private static boolean arrayContainsAnyOf(int[] arr, int[] needles) {
         for (int needle : needles) {
             if (arrayContains(arr, needle)) {
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java
index 61f25fb..a410775 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/ImageReaderTest.java
@@ -16,26 +16,40 @@
 
 package android.hardware.camera2.cts;
 
-import static android.hardware.camera2.cts.CameraTestUtils.*;
-
 import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.BitmapRegionDecoder;
+import android.graphics.Color;
 import android.graphics.ImageFormat;
-import android.hardware.camera2.CameraDevice;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
-import android.util.Size;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
+import android.hardware.camera2.cts.rs.BitmapUtils;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
+import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.Image;
 import android.media.ImageReader;
 import android.os.ConditionVariable;
 import android.util.Log;
+import android.util.Size;
 import android.view.Surface;
 
+import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.List;
 
+import static android.hardware.camera2.cts.CameraTestUtils.CAPTURE_RESULT_TIMEOUT_MS;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleCaptureCallback;
+import static android.hardware.camera2.cts.CameraTestUtils.SimpleImageReaderListener;
+import static android.hardware.camera2.cts.CameraTestUtils.dumpFile;
+import static android.hardware.camera2.cts.CameraTestUtils.getValueNotNull;
+
 /**
  * <p>Basic test for ImageReader APIs. It uses CameraDevice as producer, camera
  * sends the data to the surface provided by imageReader. Below image formats
@@ -49,10 +63,16 @@
 public class ImageReaderTest extends Camera2AndroidTestCase {
     private static final String TAG = "ImageReaderTest";
     private static final boolean VERBOSE = Log.isLoggable(TAG, Log.VERBOSE);
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
     // number of frame (for streaming requests) to be verified.
     private static final int NUM_FRAME_VERIFIED = 2;
     // Max number of images can be accessed simultaneously from ImageReader.
     private static final int MAX_NUM_IMAGES = 5;
+    // Max difference allowed between YUV and JPEG patches. This tolerance is intentionally very
+    // generous to avoid false positives due to punch/saturation operations vendors apply to the
+    // JPEG outputs.
+    private static final double IMAGE_DIFFERENCE_TOLERANCE = 30;
 
     private SimpleImageListener mListener;
 
@@ -88,10 +108,6 @@
             try {
                 Log.v(TAG, "Testing jpeg capture for Camera " + id);
                 openDevice(id);
-                if (mStaticInfo.isHardwareLevelLegacy()) {
-                    Log.i(TAG, "Skipping test on legacy devices");
-                    continue;
-                }
                 bufferFormatTestByCamera(ImageFormat.JPEG, /*repeating*/false);
             } finally {
                 closeDevice(id);
@@ -117,10 +133,6 @@
             try {
                 Log.v(TAG, "Testing repeating jpeg capture for Camera " + id);
                 openDevice(id);
-                if (mStaticInfo.isHardwareLevelLegacy()) {
-                    Log.i(TAG, "Skipping test on legacy devices");
-                    continue;
-                }
                 bufferFormatTestByCamera(ImageFormat.JPEG, /*repeating*/true);
             } finally {
                 closeDevice(id);
@@ -141,9 +153,29 @@
         }
     }
 
-    public void testInvalidAccessTest() {
-        // TODO: test invalid access case, see if we can receive expected
-        // exceptions
+    /**
+     * Test invalid access of image byte buffers: when an image is closed, further access
+     * of the image byte buffers will get an IllegalStateException. The basic assumption of
+     * this test is that the ImageReader always gives direct byte buffer, which is always true
+     * for camera case. For if the produced image byte buffer is not direct byte buffer, there
+     * is no guarantee to get an ISE for this invalid access case.
+     */
+    public void testInvalidAccessTest() throws Exception {
+        // Test byte buffer access after an image is released, it should throw ISE.
+        for (String id : mCameraIds) {
+            try {
+                Log.v(TAG, "Testing invalid image access for Camera " + id);
+                openDevice(id);
+                bufferAccessAfterRelease();
+                fail("ImageReader should throw IllegalStateException when accessing a byte buffer"
+                        + " after the image is closed");
+            } catch (IllegalStateException e) {
+                // Expected.
+            } finally {
+                closeDevice(id);
+            }
+        }
+
     }
 
     /**
@@ -156,10 +188,7 @@
             try {
                 Log.v(TAG, "YUV and JPEG testing for camera " + id);
                 openDevice(id);
-                if (mStaticInfo.isHardwareLevelLegacy()) {
-                    Log.i(TAG, "Skipping test on legacy devices");
-                    continue;
-                }
+
                 bufferFormatWithYuvTestByCamera(ImageFormat.JPEG);
             } finally {
                 closeDevice(id);
@@ -184,6 +213,302 @@
         }
     }
 
+    /**
+     * Check that the center patches for YUV and JPEG outputs for the same frame match for each YUV
+     * resolution and format supported.
+     */
+    public void testAllOutputYUVResolutions() throws Exception {
+        for (String id : mCameraIds) {
+            try {
+                Log.v(TAG, "Testing all YUV image resolutions for camera " + id);
+                openDevice(id);
+
+                // Skip warmup on FULL mode devices.
+                int warmupCaptureNumber = (mStaticInfo.isHardwareLevelLegacy()) ?
+                        MAX_NUM_IMAGES - 1 : 0;
+
+                // NV21 isn't supported by ImageReader.
+                final int[] YUVFormats = new int[] {ImageFormat.YUV_420_888, ImageFormat.YV12};
+
+                CameraCharacteristics.Key<StreamConfigurationMap> key =
+                        CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP;
+                StreamConfigurationMap config = mStaticInfo.getValueFromKeyNonNull(key);
+                int[] supportedFormats = config.getOutputFormats();
+                List<Integer> supportedYUVFormats = new ArrayList<>();
+                for (int format : YUVFormats) {
+                    if (CameraTestUtils.contains(supportedFormats, format)) {
+                        supportedYUVFormats.add(format);
+                    }
+                }
+
+                Size[] jpegSizes = mStaticInfo.getAvailableSizesForFormatChecked(ImageFormat.JPEG,
+                        StaticMetadata.StreamDirection.Output);
+                assertFalse("JPEG output not supported for camera " + id +
+                        ", at least one JPEG output is required.", jpegSizes.length == 0);
+
+                Size maxJpegSize = CameraTestUtils.getMaxSize(jpegSizes);
+
+                for (int format : supportedYUVFormats) {
+                    Size[] targetCaptureSizes =
+                            mStaticInfo.getAvailableSizesForFormatChecked(format,
+                            StaticMetadata.StreamDirection.Output);
+
+                    for (Size captureSz : targetCaptureSizes) {
+                        if (VERBOSE) {
+                            Log.v(TAG, "Testing yuv size " + captureSz + " and jpeg size "
+                                    + maxJpegSize + " for camera " + mCamera.getId());
+                        }
+
+                        ImageReader jpegReader = null;
+                        ImageReader yuvReader = null;
+                        try {
+                            // Create YUV image reader
+                            SimpleImageReaderListener yuvListener = new SimpleImageReaderListener();
+                            yuvReader = createImageReader(captureSz, format, MAX_NUM_IMAGES,
+                                    yuvListener);
+                            Surface yuvSurface = yuvReader.getSurface();
+
+                            // Create JPEG image reader
+                            SimpleImageReaderListener jpegListener =
+                                    new SimpleImageReaderListener();
+                            jpegReader = createImageReader(maxJpegSize,
+                                    ImageFormat.JPEG, MAX_NUM_IMAGES, jpegListener);
+                            Surface jpegSurface = jpegReader.getSurface();
+
+                            // Setup session
+                            List<Surface> outputSurfaces = new ArrayList<Surface>();
+                            outputSurfaces.add(yuvSurface);
+                            outputSurfaces.add(jpegSurface);
+                            createSession(outputSurfaces);
+
+                            // Warm up camera preview (mainly to give legacy devices time to do 3A).
+                            CaptureRequest.Builder warmupRequest =
+                                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+                            warmupRequest.addTarget(yuvSurface);
+                            assertNotNull("Fail to get CaptureRequest.Builder", warmupRequest);
+                            SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+
+                            for (int i = 0; i < warmupCaptureNumber; i++) {
+                                startCapture(warmupRequest.build(), /*repeating*/false,
+                                        resultListener, mHandler);
+                            }
+                            for (int i = 0; i < warmupCaptureNumber; i++) {
+                                resultListener.getCaptureResult(CAPTURE_WAIT_TIMEOUT_MS);
+                                Image image = yuvListener.getImage(CAPTURE_WAIT_TIMEOUT_MS);
+                                image.close();
+                            }
+
+                            // Capture image.
+                            CaptureRequest.Builder mainRequest =
+                                    mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+                            for (Surface s : outputSurfaces) {
+                                mainRequest.addTarget(s);
+                            }
+
+                            startCapture(mainRequest.build(), /*repeating*/false, resultListener,
+                                    mHandler);
+
+                            // Verify capture result and images
+                            resultListener.getCaptureResult(CAPTURE_WAIT_TIMEOUT_MS);
+
+                            Image yuvImage = yuvListener.getImage(CAPTURE_WAIT_TIMEOUT_MS);
+                            Image jpegImage = jpegListener.getImage(CAPTURE_WAIT_TIMEOUT_MS);
+
+                            //Validate captured images.
+                            CameraTestUtils.validateImage(yuvImage, captureSz.getWidth(),
+                                    captureSz.getHeight(), format, /*filePath*/null);
+                            CameraTestUtils.validateImage(jpegImage, maxJpegSize.getWidth(),
+                                    maxJpegSize.getHeight(), ImageFormat.JPEG, /*filePath*/null);
+
+                            // Compare the image centers.
+                            RectF jpegDimens = new RectF(0, 0, jpegImage.getWidth(),
+                                    jpegImage.getHeight());
+                            RectF yuvDimens = new RectF(0, 0, yuvImage.getWidth(),
+                                    yuvImage.getHeight());
+
+                            // Find scale difference between YUV and JPEG output
+                            Matrix m = new Matrix();
+                            m.setRectToRect(yuvDimens, jpegDimens, Matrix.ScaleToFit.START);
+                            RectF scaledYuv = new RectF();
+                            m.mapRect(scaledYuv, yuvDimens);
+                            float scale = scaledYuv.width() / yuvDimens.width();
+
+                            final int PATCH_DIMEN = 40; // pixels in YUV
+
+                            // Find matching square patch of pixels in YUV and JPEG output
+                            RectF tempPatch = new RectF(0, 0, PATCH_DIMEN, PATCH_DIMEN);
+                            tempPatch.offset(yuvDimens.centerX() - tempPatch.centerX(),
+                                    yuvDimens.centerY() - tempPatch.centerY());
+                            Rect yuvPatch = new Rect();
+                            tempPatch.roundOut(yuvPatch);
+
+                            tempPatch.set(0, 0, PATCH_DIMEN * scale, PATCH_DIMEN * scale);
+                            tempPatch.offset(jpegDimens.centerX() - tempPatch.centerX(),
+                                    jpegDimens.centerY() - tempPatch.centerY());
+                            Rect jpegPatch = new Rect();
+                            tempPatch.roundOut(jpegPatch);
+
+                            // Decode center patches
+                            int[] yuvColors = convertPixelYuvToRgba(yuvPatch.width(),
+                                    yuvPatch.height(), yuvPatch.left, yuvPatch.top, yuvImage);
+                            Bitmap yuvBmap = Bitmap.createBitmap(yuvColors, yuvPatch.width(),
+                                    yuvPatch.height(), Bitmap.Config.ARGB_8888);
+
+                            byte[] compressedJpegData = CameraTestUtils.getDataFromImage(jpegImage);
+                            BitmapRegionDecoder decoder = BitmapRegionDecoder.newInstance(
+                                    compressedJpegData, /*offset*/0, compressedJpegData.length,
+                                    /*isShareable*/true);
+                            BitmapFactory.Options opt = new BitmapFactory.Options();
+                            opt.inPreferredConfig = Bitmap.Config.ARGB_8888;
+                            Bitmap fullSizeJpegBmap = decoder.decodeRegion(jpegPatch, opt);
+                            Bitmap jpegBmap = Bitmap.createScaledBitmap(fullSizeJpegBmap,
+                                    yuvPatch.width(), yuvPatch.height(), /*filter*/true);
+
+                            // Compare two patches using average of per-pixel differences
+                            double difference = BitmapUtils.calcDifferenceMetric(yuvBmap, jpegBmap);
+
+                            Log.i(TAG, "Difference for resolution " + captureSz + " is: " +
+                                    difference);
+                            if (difference > IMAGE_DIFFERENCE_TOLERANCE) {
+                                // Dump files if running in verbose mode
+                                if (DEBUG) {
+                                    String jpegFileName = DEBUG_FILE_NAME_BASE + "/" + captureSz +
+                                            "_jpeg.jpg";
+                                    dumpFile(jpegFileName, jpegBmap);
+                                    String fullSizeJpegFileName = DEBUG_FILE_NAME_BASE + "/" +
+                                            captureSz + "_full_jpeg.jpg";
+                                    dumpFile(fullSizeJpegFileName, compressedJpegData);
+                                    String yuvFileName = DEBUG_FILE_NAME_BASE + "/" + captureSz +
+                                            "_yuv.jpg";
+                                    dumpFile(yuvFileName, yuvBmap);
+                                    String fullSizeYuvFileName = DEBUG_FILE_NAME_BASE + "/" +
+                                            captureSz + "_full_yuv.jpg";
+                                    int[] fullYUVColors = convertPixelYuvToRgba(yuvImage.getWidth(),
+                                            yuvImage.getHeight(), 0, 0, yuvImage);
+                                    Bitmap fullYUVBmap = Bitmap.createBitmap(fullYUVColors,
+                                            yuvImage.getWidth(), yuvImage.getHeight(),
+                                            Bitmap.Config.ARGB_8888);
+                                    dumpFile(fullSizeYuvFileName, fullYUVBmap);
+                                }
+                                fail("Camera " + mCamera.getId() + ": YUV and JPEG image at " +
+                                        "capture size " + captureSz + " for the same frame are " +
+                                        "not similar, center patches have difference metric of " +
+                                        difference);
+                            }
+
+                            // Stop capture, delete the streams.
+                            stopCapture(/*fast*/false);
+                        } finally {
+                            closeImageReader(jpegReader);
+                            jpegReader = null;
+                            closeImageReader(yuvReader);
+                            yuvReader = null;
+                        }
+                    }
+                }
+
+            } finally {
+                closeDevice(id);
+            }
+        }
+    }
+
+    /**
+     * Convert a rectangular patch in a YUV image to an ARGB color array.
+     *
+     * @param w width of the patch.
+     * @param h height of the patch.
+     * @param wOffset offset of the left side of the patch.
+     * @param hOffset offset of the top of the patch.
+     * @param yuvImage a YUV image to select a patch from.
+     * @return the image patch converted to RGB as an ARGB color array.
+     */
+    private static int[] convertPixelYuvToRgba(int w, int h, int wOffset, int hOffset,
+                                               Image yuvImage) {
+        final int CHANNELS = 3; // yuv
+        final float COLOR_RANGE = 255f;
+
+        assertTrue("Invalid argument to convertPixelYuvToRgba",
+                w > 0 && h > 0 && wOffset >= 0 && hOffset >= 0);
+        assertNotNull(yuvImage);
+
+        int imageFormat = yuvImage.getFormat();
+        assertTrue("YUV image must have YUV-type format",
+                imageFormat == ImageFormat.YUV_420_888 || imageFormat == ImageFormat.YV12 ||
+                        imageFormat == ImageFormat.NV21);
+
+        int height = yuvImage.getHeight();
+        int width = yuvImage.getWidth();
+
+        Rect imageBounds = new Rect(/*left*/0, /*top*/0, /*right*/width, /*bottom*/height);
+        Rect crop = new Rect(/*left*/wOffset, /*top*/hOffset, /*right*/wOffset + w,
+                /*bottom*/hOffset + h);
+        assertTrue("Output rectangle" + crop + " must lie within image bounds " + imageBounds,
+                imageBounds.contains(crop));
+        Image.Plane[] planes = yuvImage.getPlanes();
+
+        Image.Plane yPlane = planes[0];
+        Image.Plane cbPlane = planes[1];
+        Image.Plane crPlane = planes[2];
+
+        ByteBuffer yBuf = yPlane.getBuffer();
+        int yPixStride = yPlane.getPixelStride();
+        int yRowStride = yPlane.getRowStride();
+        ByteBuffer cbBuf = cbPlane.getBuffer();
+        int cbPixStride = cbPlane.getPixelStride();
+        int cbRowStride = cbPlane.getRowStride();
+        ByteBuffer crBuf = crPlane.getBuffer();
+        int crPixStride = crPlane.getPixelStride();
+        int crRowStride = crPlane.getRowStride();
+
+        int[] output = new int[w * h];
+
+        // TODO: Optimize this with renderscript intrinsics
+        byte[] yRow = new byte[yPixStride * w];
+        byte[] cbRow = new byte[cbPixStride * w / 2];
+        byte[] crRow = new byte[crPixStride * w / 2];
+        yBuf.mark();
+        cbBuf.mark();
+        crBuf.mark();
+        int initialYPos = yBuf.position();
+        int initialCbPos = cbBuf.position();
+        int initialCrPos = crBuf.position();
+        int outputPos = 0;
+        for (int i = hOffset; i < hOffset + h; i++) {
+            yBuf.position(initialYPos + i * yRowStride + wOffset * yPixStride);
+            yBuf.get(yRow);
+            if ((i & 1) == (hOffset & 1)) {
+                cbBuf.position(initialCbPos + (i / 2) * cbRowStride + wOffset * cbPixStride / 2);
+                cbBuf.get(cbRow);
+                crBuf.position(initialCrPos + (i / 2) * crRowStride + wOffset * crPixStride / 2);
+                crBuf.get(crRow);
+            }
+            for (int j = 0, yPix = 0, crPix = 0, cbPix = 0; j < w; j++, yPix += yPixStride) {
+                float y = yRow[yPix] & 0xFF;
+                float cb = cbRow[cbPix] & 0xFF;
+                float cr = crRow[crPix] & 0xFF;
+
+                // convert YUV -> RGB (from JFIF's "Conversion to and from RGB" section)
+                int r = (int) Math.max(0.0f, Math.min(COLOR_RANGE, y + 1.402f * (cr - 128)));
+                int g = (int) Math.max(0.0f,
+                        Math.min(COLOR_RANGE, y - 0.34414f * (cb - 128) - 0.71414f * (cr - 128)));
+                int b = (int) Math.max(0.0f, Math.min(COLOR_RANGE, y + 1.772f * (cb - 128)));
+
+                // Convert to ARGB pixel color (use opaque alpha)
+                output[outputPos++] = Color.rgb(r, g, b);
+
+                if ((j & 1) == 1) {
+                    crPix += crPixStride;
+                    cbPix += cbPixStride;
+                }
+            }
+        }
+        yBuf.rewind();
+        cbBuf.rewind();
+        crBuf.rewind();
+
+        return output;
+    }
 
     /**
      * Test capture a given format stream with yuv stream simultaneously.
@@ -270,6 +595,36 @@
         }
     }
 
+    /**
+     * Test buffer access after release, YUV420_888 single capture is tested. This method
+     * should throw ISE.
+     */
+    private void bufferAccessAfterRelease() throws Exception {
+        final int FORMAT = ImageFormat.YUV_420_888;
+        Size[] availableSizes = mStaticInfo.getAvailableSizesForFormatChecked(FORMAT,
+                StaticMetadata.StreamDirection.Output);
+
+        try {
+            // Create ImageReader.
+            mListener = new SimpleImageListener();
+            createDefaultImageReader(availableSizes[0], FORMAT, MAX_NUM_IMAGES, mListener);
+
+            // Start capture.
+            CaptureRequest request = prepareCaptureRequest();
+            SimpleCaptureCallback listener = new SimpleCaptureCallback();
+            startCapture(request, /* repeating */false, listener, mHandler);
+
+            mListener.waitForAnyImageAvailable(CAPTURE_WAIT_TIMEOUT_MS);
+            Image img = mReader.acquireNextImage();
+            ByteBuffer buffer = img.getPlanes()[0].getBuffer();
+            img.close();
+
+            byte data = buffer.get(); // An ISE should be thrown here.
+        } finally {
+            closeDefaultImageReader();
+        }
+    }
+
     private void bufferFormatTestByCamera(int format, boolean repeating) throws Exception {
 
         Size[] availableSizes = mStaticInfo.getAvailableSizesForFormatChecked(format,
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/PerformanceTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/PerformanceTest.java
index 8b8f2f6..d4a0e73 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/PerformanceTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/PerformanceTest.java
@@ -195,11 +195,6 @@
             try {
                 openDevice(id);
 
-                if (mStaticInfo.isHardwareLevelLegacy()) {
-                    Log.i(TAG, "Skipping test on legacy devices");
-                    continue;
-                }
-
                 boolean partialsExpected = mStaticInfo.getPartialResultCount() > 1;
                 long startTimeMs;
                 boolean isPartialTimingValid = partialsExpected;
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
index 90cb18a..f8b3bc3 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
@@ -30,7 +30,8 @@
 import android.media.Image;
 import android.media.ImageReader;
 import android.media.MediaCodecList;
-import android.media.MediaPlayer;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
 import android.media.MediaRecorder;
 import android.os.Environment;
 import android.os.SystemClock;
@@ -65,25 +66,27 @@
     private static final int VIDEO_FRAME_RATE = 30;
     private final String VIDEO_FILE_PATH = Environment.getExternalStorageDirectory().getPath();
     private static final int[] mCamcorderProfileList = {
+            CamcorderProfile.QUALITY_HIGH,
             CamcorderProfile.QUALITY_2160P,
             CamcorderProfile.QUALITY_1080P,
-            CamcorderProfile.QUALITY_480P,
             CamcorderProfile.QUALITY_720P,
+            CamcorderProfile.QUALITY_480P,
             CamcorderProfile.QUALITY_CIF,
-            CamcorderProfile.QUALITY_LOW,
-            CamcorderProfile.QUALITY_HIGH,
             CamcorderProfile.QUALITY_QCIF,
             CamcorderProfile.QUALITY_QVGA,
+            CamcorderProfile.QUALITY_LOW,
     };
     private static final int MAX_VIDEO_SNAPSHOT_IMAGES = 5;
     private static final int BURST_VIDEO_SNAPSHOT_NUM = 3;
     private static final int SLOWMO_SLOW_FACTOR = 4;
+    private static final int MAX_NUM_FRAME_DROP_ALLOWED = 4;
     private List<Size> mSupportedVideoSizes;
     private Surface mRecordingSurface;
     private MediaRecorder mMediaRecorder;
     private String mOutMediaFileName;
     private int mVideoFrameRate;
     private Size mVideoSize;
+    private long mRecordingStartTime;
 
     @Override
     protected void setUp() throws Exception {
@@ -117,7 +120,7 @@
 
                 initSupportedVideoSize(mCameraIds[i]);
 
-                basicRecordingTestByCamera();
+                basicRecordingTestByCamera(mCamcorderProfileList);
             } finally {
                 closeDevice();
                 releaseRecorder();
@@ -220,6 +223,53 @@
     }
 
     /**
+     * <p>
+     * Test recording framerate accuracy when switching from low FPS to high FPS.
+     * </p>
+     * <p>
+     * This test first record a video with profile of lowest framerate then record a video with
+     * profile of highest framerate. Make sure that the video framerate are still accurate.
+     * </p>
+     */
+    public void testRecordingFramerateLowToHigh() throws Exception {
+        for (int i = 0; i < mCameraIds.length; i++) {
+            try {
+                Log.i(TAG, "Testing basic recording for camera " + mCameraIds[i]);
+                // Re-use the MediaRecorder object for the same camera device.
+                mMediaRecorder = new MediaRecorder();
+                openDevice(mCameraIds[i]);
+
+                initSupportedVideoSize(mCameraIds[i]);
+
+                int minFpsProfileId = -1, minFps = 1000;
+                int maxFpsProfileId = -1, maxFps = 0;
+                int cameraId = Integer.valueOf(mCamera.getId());
+
+                for (int profileId : mCamcorderProfileList) {
+                    if (!CamcorderProfile.hasProfile(cameraId, profileId)) {
+                        continue;
+                    }
+                    CamcorderProfile profile = CamcorderProfile.get(cameraId, profileId);
+                    if (profile.videoFrameRate < minFps) {
+                        minFpsProfileId = profileId;
+                        minFps = profile.videoFrameRate;
+                    }
+                    if (profile.videoFrameRate > maxFps) {
+                        maxFpsProfileId = profileId;
+                        maxFps = profile.videoFrameRate;
+                    }
+                }
+
+                int camcorderProfileList[] = new int[] {minFpsProfileId, maxFpsProfileId};
+                basicRecordingTestByCamera(camcorderProfileList);
+            } finally {
+                closeDevice();
+                releaseRecorder();
+            }
+        }
+    }
+
+    /**
      * Test slow motion recording where capture rate (camera output) is different with
      * video (playback) frame rate for each camera if high speed recording is supported
      * by both camera and encoder.
@@ -286,8 +336,9 @@
                     updatePreviewSurfaceWithVideoSize(size);
 
                     // Start recording
+                    SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
                     startSlowMotionRecording(/*useMediaRecorder*/true, videoFramerate, captureRate,
-                            fpsRange);
+                            fpsRange, resultListener);
                     long startTime = SystemClock.elapsedRealtime();
 
                     // Record certain duration.
@@ -295,10 +346,12 @@
 
                     // Stop recording and preview
                     stopRecording(/*useMediaRecorder*/true);
-                    int duration = (int) (SystemClock.elapsedRealtime() - startTime);
+                    // Convert number of frames camera produced into the duration in unit of ms.
+                    int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
+                                    videoFramerate);
 
                     // Validation.
-                    validateRecording(size, duration * SLOWMO_SLOW_FACTOR);
+                    validateRecording(size, durationMs);
 
                 }
 
@@ -328,7 +381,8 @@
     }
 
     private void startSlowMotionRecording(boolean useMediaRecorder, int videoFrameRate,
-            int captureRate, Range<Integer> fpsRange) throws Exception {
+            int captureRate, Range<Integer> fpsRange,
+            CameraCaptureSession.CaptureCallback listener) throws Exception {
         List<Surface> outputSurfaces = new ArrayList<Surface>(2);
         assertTrue("Both preview and recording surfaces should be valid",
                 mPreviewSurface.isValid() && mRecordingSurface.isValid());
@@ -369,7 +423,7 @@
         for (int i = 0; i < slowMotionFactor - 1; i++) {
             slowMoRequests.add(recordingOnlyBuilder.build()); // Recording only.
         }
-        mSession.setRepeatingBurst(slowMoRequests, null, null);
+        mSession.setRepeatingBurst(slowMoRequests, listener, mHandler);
 
         if (useMediaRecorder) {
             mMediaRecorder.start();
@@ -383,8 +437,8 @@
      * Test camera recording by using each available CamcorderProfile for a
      * given camera. preview size is set to the video size.
      */
-    private void basicRecordingTestByCamera() throws Exception {
-        for (int profileId : mCamcorderProfileList) {
+    private void basicRecordingTestByCamera(int[] camcorderProfileList) throws Exception {
+        for (int profileId : camcorderProfileList) {
             int cameraId = Integer.valueOf(mCamera.getId());
             if (!CamcorderProfile.hasProfile(cameraId, profileId) ||
                     allowedUnsupported(cameraId, profileId)) {
@@ -414,18 +468,25 @@
             updatePreviewSurfaceWithVideoSize(videoSz);
 
             // Start recording
-            startRecording(/* useMediaRecorder */true);
-            long startTime = SystemClock.elapsedRealtime();
+            SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+            startRecording(/* useMediaRecorder */true, resultListener);
 
             // Record certain duration.
             SystemClock.sleep(RECORDING_DURATION_MS);
 
             // Stop recording and preview
             stopRecording(/* useMediaRecorder */true);
-            int duration = (int) (SystemClock.elapsedRealtime() - startTime);
+            // Convert number of frames camera produced into the duration in unit of ms.
+            int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
+                            profile.videoFrameRate);
+
+            if (VERBOSE) {
+                Log.v(TAG, "video frame rate: " + profile.videoFrameRate +
+                                ", num of frames produced: " + resultListener.getTotalNumFrames());
+            }
 
             // Validation.
-            validateRecording(videoSz, duration);
+            validateRecording(videoSz, durationMs);
         }
     }
 
@@ -457,18 +518,20 @@
             updatePreviewSurfaceWithVideoSize(sz);
 
             // Start recording
-            startRecording(/* useMediaRecorder */true);
-            long startTime = SystemClock.elapsedRealtime();
+            SimpleCaptureCallback resultListener = new SimpleCaptureCallback();
+            startRecording(/* useMediaRecorder */true, resultListener);
 
             // Record certain duration.
             SystemClock.sleep(RECORDING_DURATION_MS);
 
             // Stop recording and preview
             stopRecording(/* useMediaRecorder */true);
-            int duration = (int) (SystemClock.elapsedRealtime() - startTime);
+            // Convert number of frames camera produced into the duration in unit of ms.
+            int durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
+                            VIDEO_FRAME_RATE);
 
             // Validation.
-            validateRecording(sz, duration);
+            validateRecording(sz, durationMs);
         }
     }
 
@@ -496,11 +559,6 @@
 
                     openDevice(id);
 
-                    if (mStaticInfo.isHardwareLevelLegacy()) {
-                        Log.i(TAG, "Skipping test on legacy devices");
-                        continue;
-                    }
-
                     initSupportedVideoSize(id);
 
                     videoSnapshotTestByCamera(burstTest);
@@ -647,11 +705,17 @@
                 SystemClock.sleep(RECORDING_DURATION_MS / 2);
 
                 // Stop recording and preview
-                stopRecording(/* useMediaRecorder */true);
-                int duration = (int) (SystemClock.elapsedRealtime() - startTime);
+                int durationMs = stopRecording(/* useMediaRecorder */true);
+                // For non-burst test, use number of frames to also double check video frame rate.
+                // Burst video snapshot is allowed to cause frame rate drop, so do not use number
+                // of frames to estimate duration
+                if (!burstTest) {
+                    durationMs = (int) (resultListener.getTotalNumFrames() * 1000.0f /
+                        profile.videoFrameRate);
+                }
 
                 // Validation recorded video
-                validateRecording(videoSz, duration);
+                validateRecording(videoSz, durationMs);
 
                 if (burstTest) {
                     for (int i = 0; i < BURST_VIDEO_SNAPSHOT_NUM; i++) {
@@ -801,6 +865,7 @@
         } else {
             // TODO: need implement MediaCodec path.
         }
+        mRecordingStartTime = SystemClock.elapsedRealtime();
     }
 
     private void startRecording(boolean useMediaRecorder)  throws Exception {
@@ -817,7 +882,9 @@
         mSessionListener.getStateWaiter().waitForState(SESSION_CLOSED, SESSION_CLOSE_TIMEOUT_MS);
     }
 
-    private void stopRecording(boolean useMediaRecorder) throws Exception {
+    // Stop recording and return the estimated video duration in milliseconds.
+    private int stopRecording(boolean useMediaRecorder) throws Exception {
+        long stopRecordingTime = SystemClock.elapsedRealtime();
         if (useMediaRecorder) {
             stopCameraStreaming();
 
@@ -831,6 +898,7 @@
             mRecordingSurface.release();
             mRecordingSurface = null;
         }
+        return (int) (stopRecordingTime - mRecordingStartTime);
     }
 
     private void releaseRecorder() {
@@ -844,14 +912,28 @@
         File outFile = new File(mOutMediaFileName);
         assertTrue("No video is recorded", outFile.exists());
 
-        MediaPlayer mediaPlayer = new MediaPlayer();
+        MediaExtractor extractor = new MediaExtractor();
         try {
-            mediaPlayer.setDataSource(mOutMediaFileName);
-            mediaPlayer.prepare();
-            Size videoSz = new Size(mediaPlayer.getVideoWidth(), mediaPlayer.getVideoHeight());
+            extractor.setDataSource(mOutMediaFileName);
+            long durationUs = 0;
+            int width = -1, height = -1;
+            int numTracks = extractor.getTrackCount();
+            final String VIDEO_MIME_TYPE = "video";
+            for (int i = 0; i < numTracks; i++) {
+                MediaFormat format = extractor.getTrackFormat(i);
+                String mime = format.getString(MediaFormat.KEY_MIME);
+                if (mime.contains(VIDEO_MIME_TYPE)) {
+                    Log.i(TAG, "video format is: " + format.toString());
+                    durationUs = format.getLong(MediaFormat.KEY_DURATION);
+                    width = format.getInteger(MediaFormat.KEY_WIDTH);
+                    height = format.getInteger(MediaFormat.KEY_HEIGHT);
+                    break;
+                }
+            }
+            Size videoSz = new Size(width, height);
             assertTrue("Video size doesn't match, expected " + sz.toString() +
                     " got " + videoSz.toString(), videoSz.equals(sz));
-            int duration = mediaPlayer.getDuration();
+            int duration = (int) (durationUs / 1000);
             if (VERBOSE) {
                 Log.v(TAG, String.format("Video duration: recorded %dms, expected %dms",
                                          duration, durationMs));
@@ -860,11 +942,12 @@
             // TODO: Don't skip this for video snapshot
             if (!mStaticInfo.isHardwareLevelLegacy()) {
                 assertTrue(String.format(
-                        "Video duration doesn't match: recorded %dms, expected %dms", duration,
-                        durationMs), Math.abs(duration - durationMs) < DURATION_MARGIN_MS);
+                        "Camera %s: Video duration doesn't match: recorded %dms, expected %dms.",
+                        mCamera.getId(), duration, durationMs),
+                        Math.abs(duration - durationMs) < DURATION_MARGIN_MS);
             }
         } finally {
-            mediaPlayer.release();
+            extractor.release();
             if (!DEBUG_DUMP) {
                 outFile.delete();
             }
@@ -909,6 +992,15 @@
                 // Snapshots in legacy mode pause the preview briefly.  Skip the duration
                 // requirements for legacy mode unless this is fixed.
                 if (!mStaticInfo.isHardwareLevelLegacy()) {
+                    mCollector.expectTrue(
+                            String.format(
+                                    "Video %dx%d Frame drop detected before video snapshot: " +
+                                            "duration %dms (expected %dms)",
+                                    mVideoSize.getWidth(), mVideoSize.getHeight(),
+                                    durationMs, expectedDurationMs
+                            ),
+                            durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_ALLOWED)
+                    );
                     // Log a warning is there is any frame drop detected.
                     if (durationMs >= expectedDurationMs * 2) {
                         Log.w(TAG, String.format(
@@ -920,6 +1012,15 @@
                     }
 
                     durationMs = (int) (nextTS - currentTS) / 1000000;
+                    mCollector.expectTrue(
+                            String.format(
+                                    "Video %dx%d Frame drop detected after video snapshot: " +
+                                            "duration %dms (expected %dms)",
+                                    mVideoSize.getWidth(), mVideoSize.getHeight(),
+                                    durationMs, expectedDurationMs
+                            ),
+                            durationMs <= (expectedDurationMs * MAX_NUM_FRAME_DROP_ALLOWED)
+                    );
                     // Log a warning is there is any frame drop detected.
                     if (durationMs >= expectedDurationMs * 2) {
                         Log.w(TAG, String.format(
@@ -1031,133 +1132,10 @@
      * by AVC specification for certain level.
      */
     private static boolean isSupportedByAVCEncoder(Size sz, int frameRate) {
-        String mimeType = "video/avc";
-        MediaCodecInfo codecInfo = getEncoderInfo(mimeType);
-        if (codecInfo == null) {
-            return false;
-        }
-        CodecCapabilities cap = codecInfo.getCapabilitiesForType(mimeType);
-        if (cap == null) {
-            return false;
-        }
-
-        int highestLevel = 0;
-        for (CodecProfileLevel lvl : cap.profileLevels) {
-            if (lvl.level > highestLevel) {
-                highestLevel = lvl.level;
-            }
-        }
-        // Don't support anything meaningful for level 1 or 2.
-        if (highestLevel <= CodecProfileLevel.AVCLevel2) {
-            return false;
-        }
-
-        if(VERBOSE) {
-            Log.v(TAG, "The highest level supported by encoder is: " + highestLevel);
-        }
-
-        // Put bitRate here for future use.
-        int maxW, maxH, bitRate;
-        // Max encoding speed.
-        int maxMacroblocksPerSecond = 0;
-        switch(highestLevel) {
-            case CodecProfileLevel.AVCLevel21:
-                maxW = 352;
-                maxH = 576;
-                bitRate = 4000000;
-                maxMacroblocksPerSecond = 19800;
-                break;
-            case CodecProfileLevel.AVCLevel22:
-                maxW = 720;
-                maxH = 480;
-                bitRate = 4000000;
-                maxMacroblocksPerSecond = 20250;
-                break;
-            case CodecProfileLevel.AVCLevel3:
-                maxW = 720;
-                maxH = 480;
-                bitRate = 10000000;
-                maxMacroblocksPerSecond = 40500;
-                break;
-            case CodecProfileLevel.AVCLevel31:
-                maxW = 1280;
-                maxH = 720;
-                bitRate = 14000000;
-                maxMacroblocksPerSecond = 108000;
-                break;
-            case CodecProfileLevel.AVCLevel32:
-                maxW = 1280;
-                maxH = 720;
-                bitRate = 20000000;
-                maxMacroblocksPerSecond = 216000;
-                break;
-            case CodecProfileLevel.AVCLevel4:
-                maxW = 1920;
-                maxH = 1088; // It should be 1088 in terms of AVC capability.
-                bitRate = 20000000;
-                maxMacroblocksPerSecond = 245760;
-                break;
-            case CodecProfileLevel.AVCLevel41:
-                maxW = 1920;
-                maxH = 1088; // It should be 1088 in terms of AVC capability.
-                bitRate = 50000000;
-                maxMacroblocksPerSecond = 245760;
-                break;
-            case CodecProfileLevel.AVCLevel42:
-                maxW = 2048;
-                maxH = 1088; // It should be 1088 in terms of AVC capability.
-                bitRate = 50000000;
-                maxMacroblocksPerSecond = 522240;
-                break;
-            case CodecProfileLevel.AVCLevel5:
-                maxW = 3672;
-                maxH = 1536;
-                bitRate = 135000000;
-                maxMacroblocksPerSecond = 589824;
-                break;
-            case CodecProfileLevel.AVCLevel51:
-            default:
-                maxW = 4096;
-                maxH = 2304;
-                bitRate = 240000000;
-                maxMacroblocksPerSecond = 983040;
-                break;
-        }
-
-        // Check size limit.
-        if (sz.getWidth() > maxW || sz.getHeight() > maxH) {
-            Log.i(TAG, "Requested resolution " + sz.toString() + " exceeds (" +
-                    maxW + "," + maxH + ")");
-            return false;
-        }
-
-        // Check frame rate limit.
-        Size sizeInMb = new Size((sz.getWidth() + 15) / 16, (sz.getHeight() + 15) / 16);
-        int maxFps = maxMacroblocksPerSecond / (sizeInMb.getWidth() * sizeInMb.getHeight());
-        if (frameRate > maxFps) {
-            Log.i(TAG, "Requested frame rate " + frameRate + " exceeds " + maxFps);
-            return false;
-        }
-
-        return true;
-    }
-
-    private static MediaCodecInfo getEncoderInfo(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return codecInfo;
-                }
-            }
-        }
-        return null;
+        MediaFormat format = MediaFormat.createVideoFormat(
+                MediaFormat.MIMETYPE_VIDEO_AVC, sz.getWidth(), sz.getHeight());
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        return mcl.findEncoderForFormat(format) != null;
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java
index 7960200..61860a7 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -21,7 +21,6 @@
 
 import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
-import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraDevice;
@@ -29,21 +28,20 @@
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.params.StreamConfigurationMap;
-import android.hardware.camera2.cts.CameraTestUtils;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
 import android.media.CamcorderProfile;
+import android.media.Image;
 import android.media.ImageReader;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
 
-import com.android.ex.camera2.blocking.BlockingSessionCallback;
-
 import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.List;
 
+import static junit.framework.Assert.assertTrue;
 import static org.mockito.Mockito.*;
 
 /**
@@ -52,11 +50,14 @@
 public class RobustnessTest extends Camera2AndroidTestCase {
     private static final String TAG = "RobustnessTest";
 
-    private static final int FAILED_CONFIGURE_TIMEOUT = 5000; //ms
+    private static final int CONFIGURE_TIMEOUT = 5000; //ms
+    private static final int CAPTURE_TIMEOUT = 1000; //ms
 
     /**
-     * Test that a {@link CameraCaptureSession} configured with a {@link Surface} with invalid
-     * dimensions fails gracefully.
+     * Test that a {@link CameraCaptureSession} can be configured with a {@link Surface} containing
+     * a dimension other than one of the supported output dimensions.  The buffers produced into
+     * this surface are expected have the dimensions of the closest possible buffer size in the
+     * available stream configurations for a surface with this format.
      */
     public void testBadSurfaceDimensions() throws Exception {
         for (String id : mCameraIds) {
@@ -64,9 +65,25 @@
                 Log.i(TAG, "Testing Camera " + id);
                 openDevice(id);
 
-                // Setup Surface with unconfigured dimensions.
-                SurfaceTexture surfaceTexture = new SurfaceTexture(0);
-                Surface surface = new Surface(surfaceTexture);
+                // Find some size not supported by the camera
+                Size weirdSize = new Size(643, 577);
+                int count = 0;
+                while(mOrderedPreviewSizes.contains(weirdSize)) {
+                    // Really, they can't all be supported...
+                    weirdSize = new Size(weirdSize.getWidth() + 1, weirdSize.getHeight() + 1);
+                    count++;
+                    assertTrue("Too many exotic YUV_420_888 resolutions supported.", count < 100);
+                }
+
+                // Setup imageReader with invalid dimension
+                ImageReader imageReader = ImageReader.newInstance(weirdSize.getWidth(),
+                        weirdSize.getHeight(), ImageFormat.YUV_420_888, 3);
+
+                // Setup ImageReaderListener
+                SimpleImageReaderListener imageListener = new SimpleImageReaderListener();
+                imageReader.setOnImageAvailableListener(imageListener, mHandler);
+
+                Surface surface = imageReader.getSurface();
                 List<Surface> surfaces = new ArrayList<>();
                 surfaces.add(surface);
 
@@ -74,19 +91,38 @@
                 CaptureRequest.Builder request =
                         mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
                 request.addTarget(surface);
-                CameraCaptureSession.CaptureCallback mockCaptureListener =
-                        mock(CameraCaptureSession.CaptureCallback.class);
 
                 // Check that correct session callback is hit.
                 CameraCaptureSession.StateCallback sessionListener =
                         mock(CameraCaptureSession.StateCallback.class);
-                mCamera.createCaptureSession(surfaces, sessionListener, mHandler);
-                verify(sessionListener, timeout(FAILED_CONFIGURE_TIMEOUT).atLeastOnce()).
-                        onConfigureFailed(any(CameraCaptureSession.class));
-                verify(sessionListener, never()).onConfigured(any(CameraCaptureSession.class));
+                CameraCaptureSession session = CameraTestUtils.configureCameraSession(mCamera,
+                        surfaces, sessionListener, mHandler);
+
+                verify(sessionListener, timeout(CONFIGURE_TIMEOUT).atLeastOnce()).
+                        onConfigured(any(CameraCaptureSession.class));
+                verify(sessionListener, timeout(CONFIGURE_TIMEOUT).atLeastOnce()).
+                        onReady(any(CameraCaptureSession.class));
+                verify(sessionListener, never()).onConfigureFailed(any(CameraCaptureSession.class));
                 verify(sessionListener, never()).onActive(any(CameraCaptureSession.class));
-                verify(sessionListener, never()).onReady(any(CameraCaptureSession.class));
                 verify(sessionListener, never()).onClosed(any(CameraCaptureSession.class));
+
+                CameraCaptureSession.CaptureCallback captureListener =
+                        mock(CameraCaptureSession.CaptureCallback.class);
+                session.capture(request.build(), captureListener, mHandler);
+
+                verify(captureListener, timeout(CAPTURE_TIMEOUT).atLeastOnce()).
+                        onCaptureCompleted(any(CameraCaptureSession.class),
+                                any(CaptureRequest.class), any(TotalCaptureResult.class));
+                verify(captureListener, never()).onCaptureFailed(any(CameraCaptureSession.class),
+                        any(CaptureRequest.class), any(CaptureFailure.class));
+
+                Image image = imageListener.getImage(CAPTURE_TIMEOUT);
+                int imageWidth = image.getWidth();
+                int imageHeight = image.getHeight();
+                Size actualSize = new Size(imageWidth, imageHeight);
+
+                assertTrue("Camera does not contain outputted image resolution " + actualSize,
+                        mOrderedPreviewSizes.contains(actualSize));
             } finally {
                 closeDevice(id);
             }
@@ -128,10 +164,13 @@
             {YUV , PREVIEW,  YUV,  PREVIEW,  JPEG, MAXIMUM }  // Two-input in-app processing with still capture.
         };
 
-        final int[][] FULL_COMBINATIONS = {
+        final int[][] BURST_COMBINATIONS = {
             {PRIV, PREVIEW,  PRIV, MAXIMUM }, // Maximum-resolution GPU processing with preview.
             {PRIV, PREVIEW,  YUV,  MAXIMUM }, // Maximum-resolution in-app processing with preview.
             {YUV,  PREVIEW,  YUV,  MAXIMUM }, // Maximum-resolution two-input in-app processsing.
+        };
+
+        final int[][] FULL_COMBINATIONS = {
             {PRIV, PREVIEW,  PRIV, PREVIEW,  JPEG, MAXIMUM }, //Video recording with maximum-size video snapshot.
             {YUV,  VGA,      PRIV, PREVIEW,  YUV,  MAXIMUM }, // Standard video recording plus maximum-resolution in-app processing.
             {YUV,  VGA,      YUV,  PREVIEW,  YUV,  MAXIMUM } // Preview plus two-input maximum-resolution in-app processing.
@@ -149,7 +188,7 @@
         };
 
         final int[][][] TABLES =
-                { LEGACY_COMBINATIONS, LIMITED_COMBINATIONS, FULL_COMBINATIONS, RAW_COMBINATIONS };
+            { LEGACY_COMBINATIONS, LIMITED_COMBINATIONS, BURST_COMBINATIONS, FULL_COMBINATIONS, RAW_COMBINATIONS };
 
         // Sanity check the tables
         int tableIdx = 0;
@@ -185,11 +224,6 @@
 
             final StaticMetadata staticInfo = new StaticMetadata(cc);
 
-            if (staticInfo.isHardwareLevelLegacy()) {
-                Log.i(TAG, "Skipping test on legacy devices");
-                continue;
-            }
-
             openDevice(id);
 
             // Always run legacy-level tests
@@ -208,7 +242,14 @@
                     testOutputCombination(id, config, maxSizes);
                 }
 
-                // Check for FULL and RAW and run those if appropriate
+                // Check for BURST_CAPTURE, FULL and RAW and run those if appropriate
+
+                if (staticInfo.isCapabilitySupported(
+                        CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE)) {
+                    for (int[] config : BURST_COMBINATIONS) {
+                        testOutputCombination(id, config, maxSizes);
+                    }
+                }
 
                 if (staticInfo.isHardwareLevelFull()) {
                     for (int[] config : FULL_COMBINATIONS) {
@@ -330,9 +371,11 @@
         Log.i(TAG, String.format("Testing Camera %s, config %s",
                         cameraId, MaxOutputSizes.configToString(config)));
 
-        final int TIMEOUT_FOR_RESULT_MS = 1000;
+        // Timeout is relaxed by 500ms for LEGACY devices to reduce false positive rate in CTS
+        final int TIMEOUT_FOR_RESULT_MS = (mStaticInfo.isHardwareLevelLegacy()) ? 1500 : 1000;
         final int MIN_RESULT_COUNT = 3;
 
+        ImageDropperListener imageDropperListener = new ImageDropperListener();
         // Set up outputs
         List<Object> outputTargets = new ArrayList<>();
         List<Surface> outputSurfaces = new ArrayList<>();
@@ -358,6 +401,7 @@
                     Size targetSize = maxSizes.maxJpegSizes[sizeLimit];
                     ImageReader target = ImageReader.newInstance(
                         targetSize.getWidth(), targetSize.getHeight(), JPEG, MIN_RESULT_COUNT);
+                    target.setOnImageAvailableListener(imageDropperListener, mHandler);
                     outputTargets.add(target);
                     outputSurfaces.add(target.getSurface());
                     jpegTargets.add(target);
@@ -367,6 +411,7 @@
                     Size targetSize = maxSizes.maxYuvSizes[sizeLimit];
                     ImageReader target = ImageReader.newInstance(
                         targetSize.getWidth(), targetSize.getHeight(), YUV, MIN_RESULT_COUNT);
+                    target.setOnImageAvailableListener(imageDropperListener, mHandler);
                     outputTargets.add(target);
                     outputSurfaces.add(target.getSurface());
                     yuvTargets.add(target);
@@ -376,6 +421,7 @@
                     Size targetSize = maxSizes.maxRawSize;
                     ImageReader target = ImageReader.newInstance(
                         targetSize.getWidth(), targetSize.getHeight(), RAW, MIN_RESULT_COUNT);
+                    target.setOnImageAvailableListener(imageDropperListener, mHandler);
                     outputTargets.add(target);
                     outputSurfaces.add(target.getSurface());
                     rawTargets.add(target);
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java
new file mode 100644
index 0000000..283f09b
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataCollectionTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.hardware.camera2.cts;
+
+import android.content.pm.PackageManager;
+import android.cts.util.DeviceReportLog;
+import android.hardware.camera2.cts.testcases.Camera2SurfaceViewTestCase;
+import android.hardware.camera2.cts.helpers.CameraMetadataGetter;
+import android.util.Log;
+
+import com.android.cts.util.ResultType;
+import com.android.cts.util.ResultUnit;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.util.Iterator;
+
+/**
+ * This test collects camera2 API static metadata and reports to device report.
+ *
+ */
+public class StaticMetadataCollectionTest extends Camera2SurfaceViewTestCase {
+    private static final String TAG = "StaticMetadataCollectionTest";
+
+    private DeviceReportLog mReportLog;
+
+    @Override
+    protected void setUp() throws Exception {
+        mReportLog = new DeviceReportLog();
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Deliver the report to host will automatically clear the report log.
+        mReportLog.deliverReportToHost(getInstrumentation());
+        super.tearDown();
+    }
+
+    public void testDataCollection() {
+        if (hasCameraFeature()) {
+            CameraMetadataGetter cameraInfoGetter = new CameraMetadataGetter(mCameraManager);
+            for (String id : mCameraIds) {
+                // Gather camera info
+                JSONObject cameraInfo = cameraInfoGetter.getCameraInfo(id);
+                dumpJsonObjectAsCtsResult(String.format("camera2_id%s_static_info", id), cameraInfo);
+                dumpDoubleAsCtsResult(String.format("camera2_id%s_static_info:", id)
+                        + cameraInfo.toString(), 0);
+
+                JSONObject[] templates = cameraInfoGetter.getCaptureRequestTemplates(id);
+                for (int i = 0; i < templates.length; i++) {
+                    dumpJsonObjectAsCtsResult(String.format("camera2_id%s_capture_template%d",
+                            id, CameraMetadataGetter.TEMPLATE_IDS[i]), templates[i]);
+                    if (templates[i] != null) {
+                        dumpDoubleAsCtsResult(String.format("camera2_id%s_capture_template%d:",
+                                id, CameraMetadataGetter.TEMPLATE_IDS[i])
+                                + templates[i].toString(), 0);
+                    }
+                }
+            }
+
+            try {
+                cameraInfoGetter.close();
+            } catch (Exception e) {
+                Log.e(TAG, "Unable to close camera info getter " + e.getMessage());
+            }
+
+            mReportLog.printSummary("Camera data collection for static info and capture request"
+                    + " templates",
+                    0.0, ResultType.NEUTRAL, ResultUnit.NONE);
+        }
+
+    }
+
+    private void dumpDoubleAsCtsResult(String name, double value) {
+        mReportLog.printValue(name, value, ResultType.NEUTRAL, ResultUnit.NONE);
+    }
+
+    public void dumpDoubleArrayAsCtsResult(String name, double[] values) {
+        mReportLog.printArray(name, values, ResultType.NEUTRAL, ResultUnit.NONE);
+    }
+
+    private double getJsonValueAsDouble(String name, Object obj) throws Exception {
+        if (obj == null) {
+            Log.e(TAG, "Null value: " + name);
+            throw new Exception();
+        } else if (obj instanceof Double) {
+            return ((Double)obj).doubleValue();
+        } else if (obj instanceof Float) {
+            return ((Float)obj).floatValue();
+        } else if (obj instanceof Long) {
+            return ((Long)obj).longValue();
+        } else if (obj instanceof Integer) {
+            return ((Integer)obj).intValue();
+        } else if (obj instanceof Byte) {
+            return ((Byte)obj).intValue();
+        } else if (obj instanceof Short) {
+            return ((Short)obj).intValue();
+        } else if (obj instanceof Boolean) {
+            return ((Boolean)obj) ? 1 : 0;
+        } else {
+            Log.e(TAG, "Unsupported value type: " + name);
+            throw new Exception();
+        }
+    }
+
+    private void dumpJsonArrayAsCtsResult(String name, JSONArray arr) throws Exception {
+        if (arr == null || arr.length() == 0) {
+            dumpDoubleAsCtsResult(name + "[]", 0);
+        } else if (arr.get(0) instanceof JSONObject) {
+            for (int i = 0; i < arr.length(); i++) {
+                dumpJsonObjectAsCtsResult(name+String.format("[%04d]",i),(JSONObject)arr.get(i));
+            }
+        } else if (arr.get(0) instanceof JSONArray) {
+            for (int i = 0; i < arr.length(); i++) {
+                dumpJsonArrayAsCtsResult(name+String.format("[%04d]",i),(JSONArray)arr.get(i));
+            }
+        } else if (!(arr.get(0) instanceof String)) {
+            double[] values = new double[arr.length()];
+            for (int i = 0; i < arr.length(); i++) {
+                values[i] = getJsonValueAsDouble(name + "[]", arr.get(i));
+            }
+            dumpDoubleArrayAsCtsResult(name + "[]", values);
+        } else if (arr.get(0) instanceof String) {
+            for (int i = 0; i < arr.length(); i++) {
+                dumpDoubleAsCtsResult(
+                        name+String.format("[%04d]",i)+" = "+(String)arr.get(i), 0);
+            }
+        } else {
+            Log.e(TAG, "Unsupported array value type: " + name);
+            throw new Exception();
+        }
+    }
+
+    private void dumpJsonObjectAsCtsResult(String name, JSONObject obj) {
+        if (obj == null) {
+            dumpDoubleAsCtsResult(name + "{}", 0);
+            return;
+        }
+        Iterator<?> keys = obj.keys();
+        while (keys.hasNext()) {
+            try {
+                String key = (String)keys.next();
+                if (obj.get(key) instanceof JSONObject) {
+                    dumpJsonObjectAsCtsResult(name+"."+key, (JSONObject)obj.get(key));
+                } else if (obj.get(key) instanceof JSONArray) {
+                    dumpJsonArrayAsCtsResult(name+"."+key, (JSONArray)obj.get(key));
+                } else if (!(obj.get(key) instanceof String)) {
+                    dumpDoubleAsCtsResult(name+"."+key,
+                            getJsonValueAsDouble(name+"."+key, obj.get(key)));
+                } else if (obj.get(key) instanceof String) {
+                    dumpDoubleAsCtsResult(name+"."+key + " = " + (String)obj.get(key), 0);
+                } else {
+                    Log.e(TAG, "Unsupported object field type: " + name + "." + key);
+                }
+            } catch (Exception e) {
+                // Swallow
+            }
+        }
+    }
+
+    private boolean hasCameraFeature() {
+        PackageManager packageManager = getActivity().getPackageManager();
+        return packageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
index ec7ecf8..a5ed0e1 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StaticMetadataTest.java
@@ -20,17 +20,20 @@
 
 import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.cts.helpers.StaticMetadata;
 import android.hardware.camera2.cts.helpers.StaticMetadata.CheckLevel;
 import android.hardware.camera2.cts.testcases.Camera2AndroidTestCase;
 import android.util.Log;
+import android.util.Pair;
 import android.util.Size;
 
 import junit.framework.Assert;
 
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -52,6 +55,9 @@
     private static final float MIN_FPS_FOR_FULL_DEVICE = 20.0f;
     private String mCameraId;
 
+    // Last defined capability enum, for iterating over all of them
+    private static final int LAST_CAPABILITY_ENUM = REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE;
+
     /**
      * Test the available capability for different hardware support level devices.
      */
@@ -65,11 +71,13 @@
 
             if (mStaticInfo.isHardwareLevelFull()) {
                 // Capability advertisement must be right.
-                mCollector.expectTrue("Full device must contains MANUAL_SENSOR capability",
+                mCollector.expectTrue("Full device must contain MANUAL_SENSOR capability",
                         availableCaps.contains(REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR));
-                mCollector.expectTrue("Full device must contains MANUAL_POST_PROCESSING capability",
+                mCollector.expectTrue("Full device must contain MANUAL_POST_PROCESSING capability",
                         availableCaps.contains(
                                 REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING));
+                mCollector.expectTrue("Full device must contain BURST_CAPTURE capability",
+                        availableCaps.contains(REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE));
 
                 // Max resolution fps must be >= 20.
                 mCollector.expectTrue("Full device must support at least 20fps for max resolution",
@@ -80,6 +88,12 @@
                         mStaticInfo.isPerFrameControlSupported());
             }
 
+            if (availableCaps.contains(REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
+                mCollector.expectTrue("MANUAL_SENSOR capability always requires " +
+                        "READ_SENSOR_SETTINGS capability as well",
+                        availableCaps.contains(REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS));
+            }
+
             // TODO: test all the keys mandatory for all capability devices.
         }
     }
@@ -121,7 +135,7 @@
             List<Integer> availableCaps = mStaticInfo.getAvailableCapabilitiesChecked();
 
             for (Integer capability = REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE;
-                    capability <= REQUEST_AVAILABLE_CAPABILITIES_RAW; capability++) {
+                    capability <= LAST_CAPABILITY_ENUM; capability++) {
                 boolean isCapabilityAvailable = availableCaps.contains(capability);
                 validateCapability(capability, isCapabilityAvailable);
             }
@@ -227,6 +241,10 @@
 
     private void validateCapability(Integer capability, boolean isCapabilityAvailable) {
         List<CaptureRequest.Key<?>> requestKeys = new ArrayList<>();
+        Set<CaptureResult.Key<?>> resultKeys = new HashSet<>();
+        // Capability requirements other than key presences
+        List<Pair<String, Boolean>> additionalRequirements = new ArrayList<>();
+
         /* For available capabilities, only check request keys in this test
            Characteristics keys are tested in ExtendedCameraCharacteristicsTest
            Result keys are tested in CaptureResultTest */
@@ -285,11 +303,31 @@
                 requestKeys.add(CaptureRequest.COLOR_CORRECTION_TRANSFORM);
                 requestKeys.add(CaptureRequest.SHADING_MODE);
                 requestKeys.add(CaptureRequest.STATISTICS_LENS_SHADING_MAP_MODE);
-                if (mStaticInfo.isHardwareLevelFull()) {
-                    requestKeys.add(CaptureRequest.TONEMAP_CURVE);
-                    requestKeys.add(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE);
-                }
+                requestKeys.add(CaptureRequest.TONEMAP_CURVE);
+                requestKeys.add(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE);
 
+                // Legacy mode always doesn't support these requirements
+                Boolean contrastCurveModeSupported = false;
+                Boolean offColorAberrationModeSupported = false;
+                if (mStaticInfo.isHardwareLevelLimitedOrBetter()) {
+                    int[] tonemapModes = mStaticInfo.getAvailableToneMapModesChecked();
+                    List<Integer> modeList = (tonemapModes.length == 0) ?
+                            new ArrayList<Integer>() :
+                            Arrays.asList(CameraTestUtils.toObject(tonemapModes));
+                    contrastCurveModeSupported =
+                            modeList.contains(CameraMetadata.TONEMAP_MODE_CONTRAST_CURVE);
+                    int[] colorAberrationModes =
+                            mStaticInfo.getAvailableColorAberrationModesChecked();
+                    modeList = (colorAberrationModes.length == 0) ?
+                            new ArrayList<Integer>() :
+                            Arrays.asList(CameraTestUtils.toObject(colorAberrationModes));
+                    offColorAberrationModeSupported =
+                            modeList.contains(CameraMetadata.COLOR_CORRECTION_ABERRATION_MODE_OFF);
+                }
+                additionalRequirements.add(new Pair<String, Boolean>(
+                        "Tonemap mode must include CONTRAST_CURVE", contrastCurveModeSupported));
+                additionalRequirements.add(new Pair<String, Boolean>(
+                        "Color aberration mode must include OFF", offColorAberrationModeSupported));
                 break;
             case REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR:
                 capabilityName = "REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR";
@@ -298,6 +336,7 @@
                 requestKeys.add(CaptureRequest.SENSOR_SENSITIVITY);
                 if (mStaticInfo.hasFocuser()) {
                     requestKeys.add(CaptureRequest.LENS_APERTURE);
+                    requestKeys.add(CaptureRequest.LENS_FOCUS_DISTANCE);
                     requestKeys.add(CaptureRequest.LENS_FILTER_DENSITY);
                     requestKeys.add(CaptureRequest.LENS_OPTICAL_STABILIZATION_MODE);
                 }
@@ -307,18 +346,68 @@
                 // RAW_CAPABILITY needs to check for not just capture request keys
                 validateRawCapability(isCapabilityAvailable);
                 return;
+            case REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE:
+                // Tested in ExtendedCameraCharacteristicsTest
+                return;
+            case REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS:
+                capabilityName = "REQUEST_AVAILABLE_CAPABILITIES_READ_SENSOR_SETTINGS";
+                resultKeys.add(CaptureResult.SENSOR_FRAME_DURATION);
+                resultKeys.add(CaptureResult.SENSOR_EXPOSURE_TIME);
+                resultKeys.add(CaptureResult.SENSOR_SENSITIVITY);
+                if (mStaticInfo.hasFocuser()) {
+                    resultKeys.add(CaptureResult.LENS_APERTURE);
+                    resultKeys.add(CaptureResult.LENS_FOCUS_DISTANCE);
+                    resultKeys.add(CaptureResult.LENS_FILTER_DENSITY);
+                }
+                break;
             default:
                 capabilityName = "Unknown";
-                Assert.fail(String.format("Unknown capability: %d", capability));
+                assertTrue(String.format("Unknown capability set: %d", capability),
+                           !isCapabilityAvailable);
+                return;
         }
 
-        boolean matchExpectation =
-                validateRequestKeysPresence(capabilityName, requestKeys, isCapabilityAvailable);
+        // Check additional requirements and exit early if possible
+        if (!isCapabilityAvailable) {
+            for (Pair<String, Boolean> p : additionalRequirements) {
+                String requirement = p.first;
+                Boolean meetRequirement = p.second;
+                // No further check is needed if we've found why capability cannot be advertised
+                if (!meetRequirement) {
+                    Log.v(TAG, String.format(
+                            "Camera %s doesn't list capability %s because of requirement: %s",
+                            mCameraId, capabilityName, requirement));
+                    return;
+                }
+            }
+        }
+
+        boolean matchExpectation = true;
+        if (!requestKeys.isEmpty()) {
+            matchExpectation &= validateRequestKeysPresence(
+                    capabilityName, requestKeys, isCapabilityAvailable);
+        }
+        if(!resultKeys.isEmpty()) {
+            matchExpectation &= validateResultKeysPresence(
+                    capabilityName, resultKeys, isCapabilityAvailable);
+        }
+
+        // Check additional requirements
+        for (Pair<String, Boolean> p : additionalRequirements) {
+            String requirement = p.first;
+            Boolean meetRequirement = p.second;
+            if (isCapabilityAvailable && !meetRequirement) {
+                mCollector.addMessage(String.format(
+                        "Camera %s list capability %s but does not meet the requirement: %s",
+                        mCameraId, capabilityName, requirement));
+            }
+        }
+
         // In case of isCapabilityAvailable == true, error has been filed in
-        // validateRequestKeysPresence
+        // validateRequest/ResultKeysPresence
         if (!matchExpectation && !isCapabilityAvailable) {
             mCollector.addMessage(String.format(
-                    "Camera %s doesn't list capability %s but contain all required keys",
+                    "Camera %s doesn't list capability %s but meets all requirements",
                     mCameraId, capabilityName));
         }
     }
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
index f18a1cf..2554b17 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/StillCaptureTest.java
@@ -71,6 +71,7 @@
     private static final Location sTestLocation0 = new Location(LocationManager.GPS_PROVIDER);
     private static final Location sTestLocation1 = new Location(LocationManager.GPS_PROVIDER);
     private static final Location sTestLocation2 = new Location(LocationManager.NETWORK_PROVIDER);
+    private static final int RELAXED_CAPTURE_IMAGE_TIMEOUT_MS = CAPTURE_IMAGE_TIMEOUT_MS + 1000;
     static {
         sTestLocation0.setTime(1199145600L);
         sTestLocation0.setLatitude(37.736071);
@@ -263,11 +264,6 @@
                 Log.i(TAG, "Testing Still preview capture combination for Camera " + id);
                 openDevice(id);
 
-                if (mStaticInfo.isHardwareLevelLegacy()) {
-                    Log.i(TAG, "Skipping test for legacy devices");
-                    continue;
-                }
-
                 previewStillCombinationTestByCamera();
             } finally {
                 closeDevice();
@@ -291,6 +287,12 @@
             try {
                 Log.i(TAG, "Testing AE compensation for Camera " + id);
                 openDevice(id);
+
+                if (mStaticInfo.isHardwareLevelLegacy()) {
+                    Log.i(TAG, "Skipping test on legacy devices");
+                    continue;
+                }
+
                 aeCompensationTestByCamera();
             } finally {
                 closeDevice();
@@ -308,11 +310,6 @@
                 Log.i(TAG, "Testing AE regions for Camera " + id);
                 openDevice(id);
 
-                if (mStaticInfo.isHardwareLevelLegacy()) {
-                    Log.i(TAG, "Skipping test on legacy devices");
-                    continue;
-                }
-
                 boolean aeRegionsSupported = isRegionsSupportedFor3A(MAX_REGIONS_AE_INDEX);
                 if (!aeRegionsSupported) {
                     continue;
@@ -363,11 +360,6 @@
                 Log.i(TAG, "Testing AF regions for Camera " + id);
                 openDevice(id);
 
-                if (mStaticInfo.isHardwareLevelLegacy()) {
-                    Log.i(TAG, "Skipping test on legacy devices");
-                    continue;
-                }
-
                 boolean afRegionsSupported = isRegionsSupportedFor3A(MAX_REGIONS_AF_INDEX);
                 if (!afRegionsSupported) {
                     continue;
@@ -646,7 +638,8 @@
                 prepareStillCaptureAndStartPreview(previewRequest, stillRequest, previewSz,
                         stillSz, resultListener, imageListener);
                 mSession.capture(stillRequest.build(), resultListener, mHandler);
-                Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
+                Image image = imageListener.getImage((mStaticInfo.isHardwareLevelLegacy()) ?
+                        RELAXED_CAPTURE_IMAGE_TIMEOUT_MS : CAPTURE_IMAGE_TIMEOUT_MS);
                 validateJpegCapture(image, stillSz);
                 // stopPreview must be called here to make sure next time a preview stream
                 // is created with new size.
@@ -1239,6 +1232,11 @@
 
     private void aeCompensationTestByCamera() throws Exception {
         Range<Integer> compensationRange = mStaticInfo.getAeCompensationRangeChecked();
+        // Skip the test if exposure compensation is not supported.
+        if (compensationRange.equals(Range.create(0, 0))) {
+            return;
+        }
+
         Rational step = mStaticInfo.getAeCompensationStepChecked();
         float stepF = (float) step.getNumerator() / step.getDenominator();
         int stepsPerEv = (int) Math.round(1.0 / stepF);
@@ -1253,6 +1251,8 @@
         CaptureRequest.Builder stillRequest =
                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
         stillRequest.set(CaptureRequest.CONTROL_AE_LOCK, true);
+        CaptureResult normalResult;
+        CaptureResult compensatedResult;
 
         // The following variables should only be read under the MANUAL_SENSOR capability guard:
         long minExposureValue = -1;
@@ -1284,13 +1284,13 @@
 
             // Wait for AE to be stabilized before capture: CONVERGED or FLASH_REQUIRED.
             waitForAeStable(resultListener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
-            CaptureResult result = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
+            normalResult = resultListener.getCaptureResult(WAIT_FOR_RESULT_TIMEOUT_MS);
 
             long normalExposureValue = -1;
             if (mStaticInfo.isCapabilitySupported(
                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
                 // get and check if current exposure value is valid
-                normalExposureValue = getExposureValue(result);
+                normalExposureValue = getExposureValue(normalResult);
                 mCollector.expectInRange("Exposure setting out of bound", normalExposureValue,
                         minExposureValue, maxExposureValuePreview);
 
@@ -1300,6 +1300,12 @@
                     expectedExposureValue > maxExposureValueStill) {
                     continue;
                 }
+                Log.v(TAG, "Expect ratio: " + expectedRatio +
+                        " normalExposureValue: " + normalExposureValue +
+                        " expectedExposureValue: " + expectedExposureValue +
+                        " minExposureValue: " + minExposureValue +
+                        " maxExposureValuePreview: " + maxExposureValuePreview +
+                        " maxExposureValueStill: " + maxExposureValueStill);
             }
 
             // Now issue exposure compensation and wait for AE locked. AE could take a few
@@ -1320,29 +1326,39 @@
             CaptureRequest request = stillRequest.build();
             mSession.capture(request, resultListener, mHandler);
 
-            result = resultListener.getCaptureResultForRequest(request, WAIT_FOR_RESULT_TIMEOUT_MS);
+            compensatedResult = resultListener.getCaptureResultForRequest(
+                    request, WAIT_FOR_RESULT_TIMEOUT_MS);
 
             if (mStaticInfo.isCapabilitySupported(
                     CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_SENSOR)) {
                 // Verify the exposure value compensates as requested
-                long compensatedExposureValue = getExposureValue(result);
+                long compensatedExposureValue = getExposureValue(compensatedResult);
                 mCollector.expectInRange("Exposure setting out of bound", compensatedExposureValue,
                         minExposureValue, maxExposureValueStill);
                 double observedRatio = (double) compensatedExposureValue / normalExposureValue;
                 double error = observedRatio / expectedRatio;
-                mCollector.expectInRange(String.format(
-                        "Exposure compensation ratio exceeds error tolerence:"
-                        + " expected(%f) observed(%f) ", expectedRatio, observedRatio),
-                        error,
+                String errorString = String.format(
+                        "Exposure compensation ratio exceeds error tolerence:" +
+                        " expected(%f) observed(%f)." +
+                        " Normal exposure time %d us, sensitivity %d." +
+                        " Compensated exposure time %d us, sensitivity %d",
+                        expectedRatio, observedRatio,
+                        (int) (getValueNotNull(
+                                normalResult, CaptureResult.SENSOR_EXPOSURE_TIME) / 1000),
+                        getValueNotNull(normalResult, CaptureResult.SENSOR_SENSITIVITY),
+                        (int) (getValueNotNull(
+                                compensatedResult, CaptureResult.SENSOR_EXPOSURE_TIME) / 1000),
+                        getValueNotNull(compensatedResult, CaptureResult.SENSOR_SENSITIVITY));
+                mCollector.expectInRange(errorString, error,
                         1.0 - AE_COMPENSATION_ERROR_TOLERANCE,
                         1.0 + AE_COMPENSATION_ERROR_TOLERANCE);
             }
 
             mCollector.expectEquals("Exposure compensation result should match requested value.",
                     exposureCompensation,
-                    result.get(CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION));
+                    compensatedResult.get(CaptureResult.CONTROL_AE_EXPOSURE_COMPENSATION));
             mCollector.expectTrue("Exposure lock should be set",
-                    result.get(CaptureResult.CONTROL_AE_LOCK));
+                    compensatedResult.get(CaptureResult.CONTROL_AE_LOCK));
 
             Image image = imageListener.getImage(CAPTURE_IMAGE_TIMEOUT_MS);
             validateJpegCapture(image, maxStillSz);
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
index b3d4cf9..01da4c8 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/SurfaceViewPreviewTest.java
@@ -74,11 +74,6 @@
                 Log.i(TAG, "Testing preview for Camera " + mCameraIds[i]);
                 openDevice(mCameraIds[i]);
 
-                if (mStaticInfo.isHardwareLevelLegacy()) {
-                    Log.i(TAG, "Skipping test on legacy devices");
-                    continue;
-                }
-
                 previewTestByCamera();
             } finally {
                 closeDevice();
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
index 7cf4089..f0e7e57 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraErrorCollector.java
@@ -828,8 +828,25 @@
      */
     public <T> void expectKeyValueIsIn(CameraCharacteristics characteristics,
                                        CameraCharacteristics.Key<T> key, T... expected) {
-        T value;
-        if ((value = expectKeyValueNotNull(characteristics, key)) == null) {
+        T value = expectKeyValueNotNull(characteristics, 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 is one of the expected values.
+     *
+     * @param request The The {@link CaptureRequest#Builder} to get the key from.
+     * @param key The {@link CaptureRequest} key to be checked.
+     * @param expected The expected values of the CaptureRequest key.
+     */
+    public <T> void expectKeyValueIsIn(Builder request, CaptureRequest.Key<T> key, T... expected) {
+        T value = expectKeyValueNotNull(request, key);
+        if (value == null) {
             return;
         }
         String reason = "Key " + key.getName() + " value " + value
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java
new file mode 100755
index 0000000..db75cdd
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/CameraMetadataGetter.java
@@ -0,0 +1,690 @@
+/*
+ * 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.hardware.camera2.cts.helpers;
+
+import static com.android.ex.camera2.blocking.BlockingStateCallback.*;
+
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.TotalCaptureResult;
+import android.hardware.camera2.params.BlackLevelPattern;
+import android.hardware.camera2.params.ColorSpaceTransform;
+import android.hardware.camera2.params.Face;
+import android.hardware.camera2.params.LensShadingMap;
+import android.hardware.camera2.params.MeteringRectangle;
+import android.hardware.camera2.params.RggbChannelVector;
+import android.hardware.camera2.params.StreamConfigurationMap;
+import android.hardware.camera2.params.TonemapCurve;
+import android.location.Location;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Rational;
+import android.util.Size;
+import android.util.SizeF;
+import android.util.Range;
+
+import com.android.ex.camera2.blocking.BlockingCameraManager;
+import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
+import com.android.ex.camera2.blocking.BlockingStateCallback;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Field;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.Modifier;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+
+/**
+ * Utility class to dump the camera metadata.
+ */
+public final class CameraMetadataGetter implements AutoCloseable {
+    private static final String TAG = CameraMetadataGetter.class.getSimpleName();
+    private static final int CAMERA_CLOSE_TIMEOUT_MS = 5000;
+    public static final int[] TEMPLATE_IDS = {
+        CameraDevice.TEMPLATE_PREVIEW,
+        CameraDevice.TEMPLATE_STILL_CAPTURE,
+        CameraDevice.TEMPLATE_RECORD,
+        CameraDevice.TEMPLATE_VIDEO_SNAPSHOT,
+        CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG,
+        CameraDevice.TEMPLATE_MANUAL,
+    };
+    private CameraManager mCameraManager;
+    private BlockingStateCallback mCameraListener;
+    private HandlerThread mHandlerThread;
+    private Handler mHandler;
+
+    private static class MetadataEntry {
+        public MetadataEntry(String k, Object v) {
+            key = k;
+            value = v;
+        }
+
+        public String key;
+        public Object value;
+    }
+
+    public CameraMetadataGetter(CameraManager cameraManager) {
+        if (cameraManager == null) {
+            throw new IllegalArgumentException("can not create an CameraMetadataGetter object"
+                    + " with null CameraManager");
+        }
+
+        mCameraManager = cameraManager;
+
+        mCameraListener = new BlockingStateCallback();
+        mHandlerThread = new HandlerThread(TAG);
+        mHandlerThread.start();
+        mHandler = new Handler(mHandlerThread.getLooper());
+    }
+
+    public String getCameraInfo() {
+        StringBuffer cameraInfo = new StringBuffer("{\"CameraStaticMetadata\":{");
+        CameraCharacteristics staticMetadata;
+        String[] cameraIds;
+        try {
+            cameraIds = mCameraManager.getCameraIdList();
+        } catch (CameraAccessException e) {
+            Log.e(TAG, "Unable to get camera ids, skip this info, error: " + e.getMessage());
+            return "";
+        }
+        for (String id : cameraIds) {
+            String value = null;
+            try {
+                staticMetadata = mCameraManager.getCameraCharacteristics(id);
+                value = serialize(staticMetadata).toString();
+            } catch (CameraAccessException e) {
+                Log.e(TAG,
+                        "Unable to get camera camera static info, skip this camera, error: "
+                                + e.getMessage());
+            }
+            cameraInfo.append("\"camera" + id + "\":"); // Key
+            cameraInfo.append(value); // Value
+            // If not last, print "," // Separator
+            if (!id.equals(cameraIds[cameraIds.length - 1])) {
+                cameraInfo.append(",");
+            }
+        }
+        cameraInfo.append("}}");
+
+        return cameraInfo.toString();
+    }
+
+    public JSONObject getCameraInfo(String cameraId) {
+        JSONObject staticMetadata = null;
+        try {
+            staticMetadata = serialize(mCameraManager.getCameraCharacteristics(cameraId));
+        } catch (CameraAccessException e) {
+            Log.e(TAG,
+                    "Unable to get camera camera static info, skip this camera, error: "
+                            + e.getMessage());
+        }
+        return staticMetadata;
+    }
+
+    public JSONObject[] getCaptureRequestTemplates(String cameraId) {
+        JSONObject[] templates = new JSONObject[TEMPLATE_IDS.length];
+        CameraDevice camera = null;
+        try {
+            camera = (new BlockingCameraManager(mCameraManager)).openCamera(cameraId,
+                            mCameraListener, mHandler);
+            for (int i = 0; i < TEMPLATE_IDS.length; i++) {
+                CaptureRequest.Builder request;
+                try {
+                    request = camera.createCaptureRequest(TEMPLATE_IDS[i]);
+                    templates[i] = serialize(request.build());
+                } catch (Exception e) {
+                    Log.e(TAG, "Unable to create template " + TEMPLATE_IDS[i]
+                                    + " because of error " + e.getMessage());
+                    templates[i] = null;
+                }
+            }
+            return templates;
+        } catch (CameraAccessException | BlockingOpenException e) {
+            Log.e(TAG, "Unable to open camera " + cameraId + " because of error "
+                            + e.getMessage());
+            return new JSONObject[0];
+        } finally {
+            if (camera != null) {
+                camera.close();
+            }
+        }
+    }
+
+    public String getCaptureRequestTemplates() {
+        StringBuffer templates = new StringBuffer("{\"CameraRequestTemplates\":{");
+        String[] cameraIds;
+        try {
+            cameraIds = mCameraManager.getCameraIdList();
+        } catch (CameraAccessException e) {
+            Log.e(TAG, "Unable to get camera ids, skip this info, error: " + e.getMessage());
+            return "";
+        }
+        CameraDevice camera = null;
+        for (String id : cameraIds) {
+            try {
+                try {
+                    camera = (new BlockingCameraManager(mCameraManager)).openCamera(id,
+                                    mCameraListener, mHandler);
+                } catch (CameraAccessException | BlockingOpenException e) {
+                    Log.e(TAG, "Unable to open camera " + id + " because of error "
+                                    + e.getMessage());
+                    continue;
+                }
+
+                for (int i = 0; i < TEMPLATE_IDS.length; i++) {
+                    String value = null;
+                    CaptureRequest.Builder request;
+                    try {
+                        request = camera.createCaptureRequest(TEMPLATE_IDS[i]);
+                        value = serialize(request.build()).toString();
+                    } catch (Exception e) {
+                        Log.e(TAG, "Unable to create template " + TEMPLATE_IDS[i]
+                                        + " because of error " + e.getMessage());
+                    }
+                    templates.append("\"Camera" + id + "CaptureTemplate" +
+                                    TEMPLATE_IDS[i] + "\":");
+                    templates.append(value);
+                    if (!id.equals(cameraIds[cameraIds.length - 1]) ||
+                                    i < (TEMPLATE_IDS.length - 1)) {
+                        templates.append(",");
+                    }
+                }
+            } finally {
+                if (camera != null) {
+                    camera.close();
+                    mCameraListener.waitForState(STATE_CLOSED, CAMERA_CLOSE_TIMEOUT_MS);
+                }
+            }
+        }
+
+        templates.append("}}");
+        return templates.toString();
+    }
+
+    /*
+     * Cleanup the resources.
+     */
+    @Override
+    public void close() throws Exception {
+        mHandlerThread.quitSafely();
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeRational(Rational rat) throws org.json.JSONException {
+        JSONObject ratObj = new JSONObject();
+        ratObj.put("numerator", rat.getNumerator());
+        ratObj.put("denominator", rat.getDenominator());
+        return ratObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeSize(Size size) throws org.json.JSONException {
+        JSONObject sizeObj = new JSONObject();
+        sizeObj.put("width", size.getWidth());
+        sizeObj.put("height", size.getHeight());
+        return sizeObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeSizeF(SizeF size) throws org.json.JSONException {
+        JSONObject sizeObj = new JSONObject();
+        sizeObj.put("width", size.getWidth());
+        sizeObj.put("height", size.getHeight());
+        return sizeObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeRect(Rect rect) throws org.json.JSONException {
+        JSONObject rectObj = new JSONObject();
+        rectObj.put("left", rect.left);
+        rectObj.put("right", rect.right);
+        rectObj.put("top", rect.top);
+        rectObj.put("bottom", rect.bottom);
+        return rectObj;
+    }
+
+    private static Object serializePoint(Point point) throws org.json.JSONException {
+        JSONObject pointObj = new JSONObject();
+        pointObj.put("x", point.x);
+        pointObj.put("y", point.y);
+        return pointObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeFace(Face face)
+                    throws org.json.JSONException {
+        JSONObject faceObj = new JSONObject();
+        faceObj.put("bounds", serializeRect(face.getBounds()));
+        faceObj.put("score", face.getScore());
+        faceObj.put("id", face.getId());
+        faceObj.put("leftEye", serializePoint(face.getLeftEyePosition()));
+        faceObj.put("rightEye", serializePoint(face.getRightEyePosition()));
+        faceObj.put("mouth", serializePoint(face.getMouthPosition()));
+        return faceObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeStreamConfigurationMap(
+                    StreamConfigurationMap map)
+                    throws org.json.JSONException {
+        // TODO: Serialize the rest of the StreamConfigurationMap fields.
+        JSONObject mapObj = new JSONObject();
+        JSONArray cfgArray = new JSONArray();
+        int fmts[] = map.getOutputFormats();
+        if (fmts != null) {
+            for (int fi = 0; fi < Array.getLength(fmts); fi++) {
+                Size sizes[] = map.getOutputSizes(fmts[fi]);
+                if (sizes != null) {
+                    for (int si = 0; si < Array.getLength(sizes); si++) {
+                        JSONObject obj = new JSONObject();
+                        obj.put("format", fmts[fi]);
+                        obj.put("width", sizes[si].getWidth());
+                        obj.put("height", sizes[si].getHeight());
+                        obj.put("input", false);
+                        obj.put("minFrameDuration",
+                                        map.getOutputMinFrameDuration(fmts[fi], sizes[si]));
+                        cfgArray.put(obj);
+                    }
+                }
+            }
+        }
+        mapObj.put("availableStreamConfigurations", cfgArray);
+        return mapObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeMeteringRectangle(MeteringRectangle rect)
+                    throws org.json.JSONException {
+        JSONObject rectObj = new JSONObject();
+        rectObj.put("x", rect.getX());
+        rectObj.put("y", rect.getY());
+        rectObj.put("width", rect.getWidth());
+        rectObj.put("height", rect.getHeight());
+        rectObj.put("weight", rect.getMeteringWeight());
+        return rectObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializePair(Pair pair)
+                    throws org.json.JSONException {
+        JSONArray pairObj = new JSONArray();
+        pairObj.put(pair.first);
+        pairObj.put(pair.second);
+        return pairObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeRange(Range range)
+                    throws org.json.JSONException {
+        JSONArray rangeObj = new JSONArray();
+        rangeObj.put(range.getLower());
+        rangeObj.put(range.getUpper());
+        return rangeObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeColorSpaceTransform(ColorSpaceTransform xform)
+                    throws org.json.JSONException {
+        JSONArray xformObj = new JSONArray();
+        for (int row = 0; row < 3; row++) {
+            for (int col = 0; col < 3; col++) {
+                xformObj.put(serializeRational(xform.getElement(col, row)));
+            }
+        }
+        return xformObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeTonemapCurve(TonemapCurve curve)
+                    throws org.json.JSONException {
+        JSONObject curveObj = new JSONObject();
+        String names[] = {
+                        "red", "green", "blue" };
+        for (int ch = 0; ch < 3; ch++) {
+            JSONArray curveArr = new JSONArray();
+            int len = curve.getPointCount(ch);
+            for (int i = 0; i < len; i++) {
+                curveArr.put(curve.getPoint(ch, i).x);
+                curveArr.put(curve.getPoint(ch, i).y);
+            }
+            curveObj.put(names[ch], curveArr);
+        }
+        return curveObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeRggbChannelVector(RggbChannelVector vec)
+                    throws org.json.JSONException {
+        JSONArray vecObj = new JSONArray();
+        vecObj.put(vec.getRed());
+        vecObj.put(vec.getGreenEven());
+        vecObj.put(vec.getGreenOdd());
+        vecObj.put(vec.getBlue());
+        return vecObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeBlackLevelPattern(BlackLevelPattern pat)
+                    throws org.json.JSONException {
+        int patVals[] = new int[4];
+        pat.copyTo(patVals, 0);
+        JSONArray patObj = new JSONArray();
+        patObj.put(patVals[0]);
+        patObj.put(patVals[1]);
+        patObj.put(patVals[2]);
+        patObj.put(patVals[3]);
+        return patObj;
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeLocation(Location loc)
+                    throws org.json.JSONException {
+        return loc.toString();
+    }
+
+    @SuppressWarnings("unchecked")
+    private static Object serializeLensShadingMap(LensShadingMap map)
+            throws org.json.JSONException {
+        JSONArray mapObj = new JSONArray();
+        for (int row = 0; row < map.getRowCount(); row++) {
+            for (int col = 0; col < map.getColumnCount(); col++) {
+                for (int ch = 0; ch < 4; ch++) {
+                    mapObj.put(map.getGainFactor(ch, col, row));
+                }
+            }
+        }
+        return mapObj;
+    }
+
+    private static String getKeyName(Object keyObj) {
+        if (keyObj.getClass() == CaptureResult.Key.class
+                || keyObj.getClass() == TotalCaptureResult.class) {
+            return ((CaptureResult.Key) keyObj).getName();
+        } else if (keyObj.getClass() == CaptureRequest.Key.class) {
+            return ((CaptureRequest.Key) keyObj).getName();
+        } else if (keyObj.getClass() == CameraCharacteristics.Key.class) {
+            return ((CameraCharacteristics.Key) keyObj).getName();
+        }
+
+        throw new IllegalArgumentException("Invalid key object");
+    }
+
+    private static Object getKeyValue(CameraMetadata md, Object keyObj) {
+        if (md.getClass() == CaptureResult.class || md.getClass() == TotalCaptureResult.class) {
+            return ((CaptureResult) md).get((CaptureResult.Key) keyObj);
+        } else if (md.getClass() == CaptureRequest.class) {
+            return ((CaptureRequest) md).get((CaptureRequest.Key) keyObj);
+        } else if (md.getClass() == CameraCharacteristics.class) {
+            return ((CameraCharacteristics) md).get((CameraCharacteristics.Key) keyObj);
+        }
+
+        throw new IllegalArgumentException("Invalid key object");
+    }
+
+    @SuppressWarnings("unchecked")
+    private static MetadataEntry serializeEntry(Type keyType, Object keyObj, CameraMetadata md) {
+        String keyName = getKeyName(keyObj);
+
+        try {
+            Object keyValue = getKeyValue(md, keyObj);
+            if (keyValue == null) {
+                return new MetadataEntry(keyName, JSONObject.NULL);
+            } else if (keyType == Float.class) {
+                // The JSON serializer doesn't handle floating point NaN or Inf.
+                if (((Float) keyValue).isInfinite() || ((Float) keyValue).isNaN()) {
+                    Log.w(TAG, "Inf/NaN floating point value serialized: " + keyName);
+                    return null;
+                }
+                return new MetadataEntry(keyName, keyValue);
+            } else if (keyType == Integer.class || keyType == Long.class || keyType == Byte.class ||
+                    keyType == Boolean.class || keyType == String.class) {
+                return new MetadataEntry(keyName, keyValue);
+            } else if (keyType == Rational.class) {
+                return new MetadataEntry(keyName, serializeRational((Rational) keyValue));
+            } else if (keyType == Size.class) {
+                return new MetadataEntry(keyName, serializeSize((Size) keyValue));
+            } else if (keyType == SizeF.class) {
+                return new MetadataEntry(keyName, serializeSizeF((SizeF) keyValue));
+            } else if (keyType == Rect.class) {
+                return new MetadataEntry(keyName, serializeRect((Rect) keyValue));
+            } else if (keyType == Face.class) {
+                return new MetadataEntry(keyName, serializeFace((Face) keyValue));
+            } else if (keyType == StreamConfigurationMap.class) {
+                return new MetadataEntry(keyName,
+                        serializeStreamConfigurationMap((StreamConfigurationMap) keyValue));
+            } else if (keyType instanceof ParameterizedType &&
+                    ((ParameterizedType) keyType).getRawType() == Range.class) {
+                return new MetadataEntry(keyName, serializeRange((Range) keyValue));
+            } else if (keyType == ColorSpaceTransform.class) {
+                return new MetadataEntry(keyName,
+                        serializeColorSpaceTransform((ColorSpaceTransform) keyValue));
+            } else if (keyType == MeteringRectangle.class) {
+                return new MetadataEntry(keyName,
+                        serializeMeteringRectangle((MeteringRectangle) keyValue));
+            } else if (keyType == Location.class) {
+                return new MetadataEntry(keyName,
+                        serializeLocation((Location) keyValue));
+            } else if (keyType == RggbChannelVector.class) {
+                return new MetadataEntry(keyName,
+                        serializeRggbChannelVector((RggbChannelVector) keyValue));
+            } else if (keyType == BlackLevelPattern.class) {
+                return new MetadataEntry(keyName,
+                        serializeBlackLevelPattern((BlackLevelPattern) keyValue));
+            } else if (keyType == TonemapCurve.class) {
+                return new MetadataEntry(keyName,
+                        serializeTonemapCurve((TonemapCurve) keyValue));
+            } else if (keyType == Point.class) {
+                return new MetadataEntry(keyName,
+                        serializePoint((Point) keyValue));
+            } else if (keyType == LensShadingMap.class) {
+                return new MetadataEntry(keyName,
+                        serializeLensShadingMap((LensShadingMap) keyValue));
+            } else {
+                Log.w(TAG, String.format("Serializing unsupported key type: " + keyType));
+                return null;
+            }
+        } catch (org.json.JSONException e) {
+            throw new IllegalStateException("JSON error for key: " + keyName + ": ", e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static MetadataEntry serializeArrayEntry(Type keyType, Object keyObj,
+            CameraMetadata md) {
+        String keyName = getKeyName(keyObj);
+        try {
+            Object keyValue = getKeyValue(md, keyObj);
+            if (keyValue == null) {
+                return new MetadataEntry(keyName, JSONObject.NULL);
+            }
+            int arrayLen = Array.getLength(keyValue);
+            Type elmtType = ((GenericArrayType) keyType).getGenericComponentType();
+            if (elmtType == int.class || elmtType == float.class || elmtType == byte.class ||
+                    elmtType == long.class || elmtType == double.class
+                    || elmtType == boolean.class) {
+                return new MetadataEntry(keyName, new JSONArray(keyValue));
+            } else if (elmtType == Rational.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeRational((Rational) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Size.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeSize((Size) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Rect.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeRect((Rect) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Face.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeFace((Face) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == StreamConfigurationMap.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeStreamConfigurationMap(
+                            (StreamConfigurationMap) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType instanceof ParameterizedType &&
+                    ((ParameterizedType) elmtType).getRawType() == Range.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeRange((Range) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType instanceof ParameterizedType &&
+                    ((ParameterizedType) elmtType).getRawType() == Pair.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializePair((Pair) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == MeteringRectangle.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeMeteringRectangle(
+                            (MeteringRectangle) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Location.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeLocation((Location) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == RggbChannelVector.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeRggbChannelVector(
+                            (RggbChannelVector) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == BlackLevelPattern.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializeBlackLevelPattern(
+                            (BlackLevelPattern) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else if (elmtType == Point.class) {
+                JSONArray jsonArray = new JSONArray();
+                for (int i = 0; i < arrayLen; i++) {
+                    jsonArray.put(serializePoint((Point) Array.get(keyValue, i)));
+                }
+                return new MetadataEntry(keyName, jsonArray);
+            } else {
+                Log.w(TAG, String.format("Serializing unsupported array type: " + elmtType));
+                return null;
+            }
+        } catch (org.json.JSONException e) {
+            throw new IllegalStateException("JSON error for key: " + keyName + ": ", e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    private static JSONObject serialize(CameraMetadata md) {
+        JSONObject jsonObj = new JSONObject();
+        Field[] allFields = md.getClass().getDeclaredFields();
+        if (md.getClass() == TotalCaptureResult.class) {
+            allFields = CaptureResult.class.getDeclaredFields();
+        }
+        for (Field field : allFields) {
+            if (Modifier.isPublic(field.getModifiers()) &&
+                    Modifier.isStatic(field.getModifiers()) &&
+                            (field.getType() == CaptureRequest.Key.class
+                            || field.getType() == CaptureResult.Key.class
+                            || field.getType() == TotalCaptureResult.Key.class
+                            || field.getType() == CameraCharacteristics.Key.class)
+                    &&
+                    field.getGenericType() instanceof ParameterizedType) {
+                ParameterizedType paramType = (ParameterizedType) field.getGenericType();
+                Type[] argTypes = paramType.getActualTypeArguments();
+                if (argTypes.length > 0) {
+                    try {
+                        Type keyType = argTypes[0];
+                        Object keyObj = field.get(md);
+                        MetadataEntry entry;
+                        if (keyType instanceof GenericArrayType) {
+                            entry = serializeArrayEntry(keyType, keyObj, md);
+                        } else {
+                            entry = serializeEntry(keyType, keyObj, md);
+                        }
+
+                        // TODO: Figure this weird case out.
+                        // There is a weird case where the entry is non-null but
+                        // the toString
+                        // of the entry is null, and if this happens, the
+                        // null-ness spreads like
+                        // a virus and makes the whole JSON object null from the
+                        // top level down.
+                        // Not sure if it's a bug in the library or I'm just not
+                        // using it right.
+                        // Workaround by checking for this case explicitly and
+                        // not adding the
+                        // value to the jsonObj when it is detected.
+                        if (entry != null && entry.key != null && entry.value != null
+                                && entry.value.toString() == null) {
+                            Log.w(TAG, "Error encountered serializing value for key: "
+                                    + entry.key);
+                        } else if (entry != null) {
+                            jsonObj.put(entry.key, entry.value);
+                        } else {
+                            // Ignore.
+                        }
+                    } catch (IllegalAccessException e) {
+                        throw new IllegalStateException(
+                                "Access error for field: " + field + ": ", e);
+                    } catch (org.json.JSONException e) {
+                        throw new IllegalStateException(
+                                "JSON error for field: " + field + ": ", e);
+                    }
+                }
+            }
+        }
+        return jsonObj;
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
index a5c7083..2a654db 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/helpers/StaticMetadata.java
@@ -436,17 +436,23 @@
         int[] modes = getValueFromKeyNonNull(key);
 
         boolean foundAuto = false;
+        boolean found50Hz = false;
+        boolean found60Hz = false;
         for (int mode : modes) {
             checkTrueForKey(key, "mode value " + mode + " is out if range",
                     mode >= CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_OFF ||
                     mode <= CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_AUTO);
             if (mode == CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_AUTO) {
                 foundAuto = true;
-                return modes;
+            } else if (mode == CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_50HZ) {
+                found50Hz = true;
+            } else if (mode == CameraMetadata.CONTROL_AE_ANTIBANDING_MODE_60HZ) {
+                found60Hz = true;
             }
         }
-        // Must contain AUTO mode.
-        checkTrueForKey(key, "AUTO mode is missing", foundAuto);
+        // Must contain AUTO mode or one of 50/60Hz mode.
+        checkTrueForKey(key, "Either AUTO mode or both 50HZ/60HZ mode should present",
+                foundAuto || (found50Hz && found60Hz));
 
         return modes;
     }
@@ -660,8 +666,9 @@
         List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
         checkTrueForKey(key, " Camera devices must always support FAST mode",
                 modeList.contains(CameraMetadata.TONEMAP_MODE_FAST));
-        if (isHardwareLevelFull()) {
-            checkTrueForKey(key, "Full-capability camera devices must support"
+        if (isCapabilitySupported(
+                CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_MANUAL_POST_PROCESSING)) {
+            checkTrueForKey(key, "MANUAL_POST_PROCESSING supported camera devices must support"
                     + "CONTRAST_CURVE mode",
                     modeList.contains(CameraMetadata.TONEMAP_MODE_CONTRAST_CURVE) &&
                     modeList.contains(CameraMetadata.TONEMAP_MODE_FAST));
@@ -1302,12 +1309,13 @@
         final Range<Integer> DEFAULT_RANGE = Range.create(
                 (int)(CONTROL_AE_COMPENSATION_RANGE_DEFAULT_MIN / compensationStepF),
                 (int)(CONTROL_AE_COMPENSATION_RANGE_DEFAULT_MAX / compensationStepF));
+        final Range<Integer> ZERO_RANGE = Range.create(0, 0);
         if (compensationRange == null) {
-            return DEFAULT_RANGE;
+            return ZERO_RANGE;
         }
 
         // Legacy devices don't have a minimum range requirement
-        if (isHardwareLevelLimitedOrBetter()) {
+        if (isHardwareLevelLimitedOrBetter() && !compensationRange.equals(ZERO_RANGE)) {
             checkTrueForKey(key, " range value must be at least " + DEFAULT_RANGE
                     + ", actual " + compensationRange + ", compensation step " + compensationStep,
                    compensationRange.getLower() <= DEFAULT_RANGE.getLower() &&
@@ -1433,8 +1441,9 @@
         }
 
         List<Integer> modeList = Arrays.asList(CameraTestUtils.toObject(modes));
-        checkTrueForKey(key, " Camera devices must always support OFF mode",
-                modeList.contains(CameraMetadata.COLOR_CORRECTION_ABERRATION_MODE_OFF));
+        checkTrueForKey(key, " Camera devices must always support either OFF or FAST mode",
+                modeList.contains(CameraMetadata.COLOR_CORRECTION_ABERRATION_MODE_OFF) ||
+                modeList.contains(CameraMetadata.COLOR_CORRECTION_ABERRATION_MODE_FAST));
         checkElementDistinct(key, modeList);
         checkArrayValuesInRange(key, modes,
                 CameraMetadata.COLOR_CORRECTION_ABERRATION_MODE_OFF,
@@ -1480,7 +1489,7 @@
 
         checkArrayValuesInRange(key, availableCaps,
                 CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BACKWARD_COMPATIBLE,
-                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW);
+                CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE);
         capList = Arrays.asList(CameraTestUtils.toObject(availableCaps));
         return capList;
     }
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/BitmapUtils.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/BitmapUtils.java
new file mode 100644
index 0000000..744d2c7
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/BitmapUtils.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.camera2.cts.rs;
+
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.ScriptIntrinsicHistogram;
+
+/**
+ * Utility class providing methods for various pixel-wise ARGB bitmap operations.
+ */
+public class BitmapUtils {
+    private static final String TAG = "BitmapUtils";
+    private static final int COLOR_BIT_DEPTH = 256;
+
+    public static int A = 3;
+    public static int R = 0;
+    public static int G = 1;
+    public static int B = 2;
+    public static int NUM_CHANNELS = 4;
+
+    /**
+     * Return the histograms for each color channel (interleaved).
+     *
+     * @param rs a {@link RenderScript} context to use.
+     * @param bmap a {@link Bitmap} to generate the histograms for.
+     * @return an array containing NUM_CHANNELS * COLOR_BIT_DEPTH histogram bucket values, with
+     * the color channels interleaved.
+     */
+    public static int[] calcHistograms(RenderScript rs, Bitmap bmap) {
+        ScriptIntrinsicHistogram hist = ScriptIntrinsicHistogram.create(rs, Element.U8_4(rs));
+        Allocation sums = Allocation.createSized(rs, Element.I32_4(rs), COLOR_BIT_DEPTH);
+
+        // Setup input allocation (ARGB 8888 bitmap).
+        Allocation input = Allocation.createFromBitmap(rs, bmap);
+
+        hist.setOutput(sums);
+        hist.forEach(input);
+        int[] output = new int[COLOR_BIT_DEPTH * NUM_CHANNELS];
+        sums.copyTo(output);
+        return output;
+    }
+
+    /**
+     * Find the difference between two bitmaps using average of per-pixel differences.
+     *
+     * @param a first {@link android.graphics.Bitmap}.
+     * @param b second {@link android.graphics.Bitmap}.
+     * @return the difference.
+     */
+    public static double calcDifferenceMetric(Bitmap a, Bitmap b) {
+        if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) {
+            throw new IllegalArgumentException("Bitmap dimensions for arguments do not match a=" +
+                    a.getWidth() + "x" + a.getHeight() + ", b=" + b.getWidth() + "x" +
+                    b.getHeight());
+        }
+        // TODO: Optimize this in renderscript to avoid copy.
+        int[] aPixels = new int[a.getHeight() * a.getWidth()];
+        int[] bPixels = new int[aPixels.length];
+        a.getPixels(aPixels, /*offset*/0, /*stride*/a.getWidth(), /*x*/0, /*y*/0, a.getWidth(),
+                a.getHeight());
+        b.getPixels(bPixels, /*offset*/0, /*stride*/b.getWidth(), /*x*/0, /*y*/0, b.getWidth(),
+                b.getHeight());
+        double diff = 0;
+        for (int i = 0; i < aPixels.length; i++) {
+            int aPix = aPixels[i];
+            int bPix = bPixels[i];
+
+            diff += Math.abs(Color.red(aPix) - Color.red(bPix)); // red
+            diff += Math.abs(Color.green(aPix) - Color.green(bPix)); // green
+            diff += Math.abs(Color.blue(aPix) - Color.blue(bPix)); // blue
+        }
+        diff /= (aPixels.length * 3);
+        return diff;
+    }
+
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/RawConverter.java b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/RawConverter.java
new file mode 100644
index 0000000..2cd2469
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/RawConverter.java
@@ -0,0 +1,786 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.cts.rs;
+
+import android.graphics.Bitmap;
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.params.ColorSpaceTransform;
+import android.hardware.camera2.params.LensShadingMap;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.Float3;
+import android.renderscript.Float4;
+import android.renderscript.Int4;
+import android.renderscript.Matrix3f;
+import android.renderscript.RenderScript;
+import android.renderscript.Type;
+
+import android.hardware.camera2.cts.ScriptC_raw_converter;
+import android.util.Log;
+import android.util.Rational;
+import android.util.SparseIntArray;
+
+import java.util.Arrays;
+
+/**
+ * Utility class providing methods for rendering RAW16 images into other colorspaces.
+ */
+public class RawConverter {
+    private static final String TAG = "RawConverter";
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    /**
+     * Matrix to convert from CIE XYZ colorspace to sRGB, Bradford-adapted to D65.
+     */
+    private static final float[] sXYZtoRGBBradford = new float[] {
+            3.1338561f, -1.6168667f, -0.4906146f,
+            -0.9787684f, 1.9161415f, 0.0334540f,
+            0.0719453f, -0.2289914f, 1.4052427f
+    };
+
+    /**
+     * Matrix to convert from the ProPhoto RGB colorspace to CIE XYZ colorspace.
+     */
+    private static final float[] sProPhotoToXYZ = new float[] {
+            0.797779f, 0.135213f, 0.031303f,
+            0.288000f, 0.711900f, 0.000100f,
+            0.000000f, 0.000000f, 0.825105f
+    };
+
+    /**
+     * Matrix to convert from CIE XYZ colorspace to ProPhoto RGB colorspace.
+     */
+    private static final float[] sXYZtoProPhoto = new float[] {
+            1.345753f, -0.255603f, -0.051025f,
+            -0.544426f, 1.508096f, 0.020472f,
+            0.000000f, 0.000000f, 1.211968f
+    };
+
+    /**
+     * Coefficients for a 3rd order polynomial, ordered from highest to lowest power.  This
+     * polynomial approximates the default tonemapping curve used for ACR3.
+     */
+    private static final float[] DEFAULT_ACR3_TONEMAP_CURVE_COEFFS = new float[] {
+            1.041f, -2.973f, 2.932f, 0f
+    };
+
+    /**
+     * The D50 whitepoint coordinates in CIE XYZ colorspace.
+     */
+    private static final float[] D50_XYZ = new float[] { 0.9642f, 1, 0.8249f };
+
+    /**
+     * An array containing the color temperatures for standard reference illuminants.
+     */
+    private static final SparseIntArray sStandardIlluminants = new SparseIntArray();
+    private static final int NO_ILLUMINANT = -1;
+    static {
+        sStandardIlluminants.append(CameraMetadata.SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT, 6504);
+        sStandardIlluminants.append(CameraMetadata.SENSOR_REFERENCE_ILLUMINANT1_D65, 6504);
+        sStandardIlluminants.append(CameraMetadata.SENSOR_REFERENCE_ILLUMINANT1_D50, 5003);
+        sStandardIlluminants.append(CameraMetadata.SENSOR_REFERENCE_ILLUMINANT1_D55, 5503);
+        sStandardIlluminants.append(CameraMetadata.SENSOR_REFERENCE_ILLUMINANT1_D75, 7504);
+        sStandardIlluminants.append(CameraMetadata.SENSOR_REFERENCE_ILLUMINANT1_STANDARD_A, 2856);
+        sStandardIlluminants.append(CameraMetadata.SENSOR_REFERENCE_ILLUMINANT1_STANDARD_B, 4874);
+        sStandardIlluminants.append(CameraMetadata.SENSOR_REFERENCE_ILLUMINANT1_STANDARD_C, 6774);
+        sStandardIlluminants.append(
+                CameraMetadata.SENSOR_REFERENCE_ILLUMINANT1_DAYLIGHT_FLUORESCENT, 6430);
+        sStandardIlluminants.append(
+                CameraMetadata.SENSOR_REFERENCE_ILLUMINANT1_COOL_WHITE_FLUORESCENT, 4230);
+        sStandardIlluminants.append(
+                CameraMetadata.SENSOR_REFERENCE_ILLUMINANT1_WHITE_FLUORESCENT, 3450);
+        // TODO: Add the rest of the illuminants included in the LightSource EXIF tag.
+    }
+
+    /**
+     * Convert a RAW16 buffer into an sRGB buffer, and write the result into a bitmap.
+     *
+     * <p> This function applies the operations roughly outlined in the Adobe DNG specification
+     * using the provided metadata about the image sensor.  Sensor data for Android devices is
+     * assumed to be relatively linear, and no extra linearization step is applied here.  The
+     * following operations are applied in the given order:</p>
+     *
+     * <ul>
+     *     <li>
+     *         Black level subtraction - the black levels given in the SENSOR_BLACK_LEVEL_PATTERN
+     *         tag are subtracted from the corresponding raw pixels.
+     *     </li>
+     *     <li>
+     *         Rescaling - each raw pixel is scaled by 1/(white level - black level).
+     *     </li>
+     *     <li>
+     *         Lens shading correction - the interpolated gains from the gain map defined in the
+     *         STATISTICS_LENS_SHADING_CORRECTION_MAP are applied to each raw pixel.
+     *     </li>
+     *     <li>
+     *         Clipping - each raw pixel is clipped to a range of [0.0, 1.0].
+     *     </li>
+     *     <li>
+     *         Demosaic - the RGB channels for each pixel are retrieved from the Bayer mosaic
+     *         of raw pixels using a simple bilinear-interpolation demosaicing algorithm.
+     *     </li>
+     *     <li>
+     *         Colorspace transform to wide-gamut RGB - each pixel is mapped into a
+     *         wide-gamut colorspace (in this case ProPhoto RGB is used) from the sensor
+     *         colorspace.
+     *     </li>
+     *     <li>
+     *         Tonemapping - A basic tonemapping curve using the default from ACR3 is applied
+     *         (no further exposure compensation is applied here, though this could be improved).
+     *     </li>
+     *     <li>
+     *         Colorspace transform to final RGB - each pixel is mapped into linear sRGB colorspace.
+     *     </li>
+     *     <li>
+     *         Gamma correction - each pixel is gamma corrected using γ=2.2 to map into sRGB
+     *         colorspace for viewing.
+     *     </li>
+     *     <li>
+     *         Packing - each pixel is scaled so that each color channel has a range of [0, 255],
+     *         and is packed into an Android bitmap.
+     *     </li>
+     * </ul>
+     *
+     * <p> Arguments given here are assumed to come from the values for the corresponding
+     * {@link CameraCharacteristics.Key}s defined for the camera that produced this RAW16 buffer.
+     * </p>
+     * @param rs a {@link RenderScript} context to use.
+     * @param inputWidth width of the input RAW16 image in pixels.
+     * @param inputHeight height of the input RAW16 image in pixels.
+     * @param rawImageInput a byte array containing a RAW16 image.
+     * @param staticMetadata the {@link CameraCharacteristics} for this RAW capture.
+     * @param dynamicMetadata the {@link CaptureResult} for this RAW capture.
+     * @param outputOffsetX the offset width into the raw image of the left side of the output
+     *                      rectangle.
+     * @param outputOffsetY the offset height into the raw image of the top side of the output
+     *                      rectangle.
+     * @param argbOutput a {@link Bitmap} to output the rendered RAW image into.  The height and
+     *                   width of this bitmap along with the output offsets are used to determine
+     *                   the dimensions and offset of the output rectangle contained in the RAW
+     *                   image to be rendered.
+     */
+    public static void convertToSRGB(RenderScript rs, int inputWidth, int inputHeight,
+            byte[] rawImageInput, CameraCharacteristics staticMetadata,
+            CaptureResult dynamicMetadata, int outputOffsetX, int outputOffsetY,
+            /*out*/Bitmap argbOutput) {
+        int cfa = staticMetadata.get(CameraCharacteristics.SENSOR_INFO_COLOR_FILTER_ARRANGEMENT);
+        int[] blackLevelPattern = new int[4];
+        staticMetadata.get(CameraCharacteristics.SENSOR_BLACK_LEVEL_PATTERN).
+                copyTo(blackLevelPattern, /*offset*/0);
+        int whiteLevel = staticMetadata.get(CameraCharacteristics.SENSOR_INFO_WHITE_LEVEL);
+        int ref1 = staticMetadata.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT1);
+        int ref2 = staticMetadata.get(CameraCharacteristics.SENSOR_REFERENCE_ILLUMINANT2);
+        float[] calib1 = new float[9];
+        float[] calib2 = new float[9];
+        convertColorspaceTransform(
+                staticMetadata.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM1), calib1);
+        convertColorspaceTransform(
+                staticMetadata.get(CameraCharacteristics.SENSOR_CALIBRATION_TRANSFORM2), calib2);
+        float[] color1 = new float[9];
+        float[] color2 = new float[9];
+        convertColorspaceTransform(
+                staticMetadata.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM1), color1);
+        convertColorspaceTransform(
+                staticMetadata.get(CameraCharacteristics.SENSOR_COLOR_TRANSFORM2), color2);
+        float[] forward1 = new float[9];
+        float[] forward2 = new float[9];
+        convertColorspaceTransform(
+                staticMetadata.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX1), forward1);
+        convertColorspaceTransform(
+                staticMetadata.get(CameraCharacteristics.SENSOR_FORWARD_MATRIX2), forward2);
+
+        Rational[] neutral = dynamicMetadata.get(CaptureResult.SENSOR_NEUTRAL_COLOR_POINT);
+
+        LensShadingMap shadingMap = dynamicMetadata.get(CaptureResult.STATISTICS_LENS_SHADING_CORRECTION_MAP);
+
+        convertToSRGB(rs, inputWidth, inputHeight, cfa, blackLevelPattern, whiteLevel,
+                rawImageInput, ref1, ref2, calib1, calib2, color1, color2,
+                forward1, forward2, neutral, shadingMap, outputOffsetX, outputOffsetY, argbOutput);
+    }
+
+    /**
+     * Convert a RAW16 buffer into an sRGB buffer, and write the result into a bitmap.
+     *
+     * @see #convertToSRGB
+     */
+    private static void convertToSRGB(RenderScript rs, int inputWidth, int inputHeight, int cfa,
+            int[] blackLevelPattern, int whiteLevel, byte[] rawImageInput,
+            int referenceIlluminant1, int referenceIlluminant2, float[] calibrationTransform1,
+            float[] calibrationTransform2, float[] colorMatrix1, float[] colorMatrix2,
+            float[] forwardTransform1, float[] forwardTransform2, Rational[/*3*/] neutralColorPoint,
+            LensShadingMap lensShadingMap, int outputOffsetX, int outputOffsetY,
+            /*out*/Bitmap argbOutput) {
+
+        // Validate arguments
+        if (argbOutput == null || rs == null || rawImageInput == null) {
+            throw new IllegalArgumentException("Null argument to convertToSRGB");
+        }
+        if (argbOutput.getConfig() != Bitmap.Config.ARGB_8888) {
+            throw new IllegalArgumentException(
+                    "Output bitmap passed to convertToSRGB is not ARGB_8888 format");
+        }
+        if (outputOffsetX < 0 || outputOffsetY < 0) {
+            throw new IllegalArgumentException("Negative offset passed to convertToSRGB");
+        }
+        int outWidth = argbOutput.getWidth();
+        int outHeight = argbOutput.getHeight();
+        if (outWidth + outputOffsetX > inputWidth || outHeight + outputOffsetY > inputHeight) {
+            throw new IllegalArgumentException("Raw image with dimensions (w=" + inputWidth +
+                    ", h=" + inputHeight + "), cannot converted into sRGB image with dimensions (w="
+                    + outWidth + ", h=" + outHeight + ").");
+        }
+        if (cfa < 0 || cfa > 3) {
+            throw new IllegalArgumentException("Unsupported cfa pattern " + cfa + " used.");
+        }
+        if (DEBUG) {
+            Log.d(TAG, "Metadata Used:");
+            Log.d(TAG, "Input width,height: " + inputWidth + "," + inputHeight);
+            Log.d(TAG, "Output offset x,y: " + outputOffsetX + "," + outputOffsetY);
+            Log.d(TAG, "Output width,height: " + outWidth + "," + outHeight);
+            Log.d(TAG, "CFA: " + cfa);
+            Log.d(TAG, "BlackLevelPattern: " + Arrays.toString(blackLevelPattern));
+            Log.d(TAG, "WhiteLevel: " + whiteLevel);
+            Log.d(TAG, "ReferenceIlluminant1: " + referenceIlluminant1);
+            Log.d(TAG, "ReferenceIlluminant2: " + referenceIlluminant2);
+            Log.d(TAG, "CalibrationTransform1: " + Arrays.toString(calibrationTransform1));
+            Log.d(TAG, "CalibrationTransform2: " + Arrays.toString(calibrationTransform2));
+            Log.d(TAG, "ColorMatrix1: " + Arrays.toString(colorMatrix1));
+            Log.d(TAG, "ColorMatrix2: " + Arrays.toString(colorMatrix2));
+            Log.d(TAG, "ForwardTransform1: " + Arrays.toString(forwardTransform1));
+            Log.d(TAG, "ForwardTransform2: " + Arrays.toString(forwardTransform2));
+            Log.d(TAG, "NeutralColorPoint: " + Arrays.toString(neutralColorPoint));
+        }
+
+        Allocation gainMap = null;
+        if (lensShadingMap != null) {
+            float[] lsm = new float[lensShadingMap.getGainFactorCount()];
+            lensShadingMap.copyGainFactors(/*inout*/lsm, /*offset*/0);
+            gainMap = createFloat4Allocation(rs, lsm, lensShadingMap.getColumnCount(),
+                    lensShadingMap.getRowCount());
+        }
+
+        float[] normalizedForwardTransform1 = Arrays.copyOf(forwardTransform1,
+                forwardTransform1.length);
+        normalizeFM(normalizedForwardTransform1);
+        float[] normalizedForwardTransform2 = Arrays.copyOf(forwardTransform2,
+                forwardTransform2.length);
+        normalizeFM(normalizedForwardTransform2);
+
+        float[] normalizedColorMatrix1 = Arrays.copyOf(colorMatrix1, colorMatrix1.length);
+        normalizeCM(normalizedColorMatrix1);
+        float[] normalizedColorMatrix2 = Arrays.copyOf(colorMatrix2, colorMatrix2.length);
+        normalizeCM(normalizedColorMatrix2);
+
+        if (DEBUG) {
+            Log.d(TAG, "Normalized ForwardTransform1: " + Arrays.toString(normalizedForwardTransform1));
+            Log.d(TAG, "Normalized ForwardTransform2: " + Arrays.toString(normalizedForwardTransform2));
+            Log.d(TAG, "Normalized ColorMatrix1: " + Arrays.toString(normalizedColorMatrix1));
+            Log.d(TAG, "Normalized ColorMatrix2: " + Arrays.toString(normalizedColorMatrix2));
+        }
+
+        // Calculate full sensor colorspace to sRGB colorspace transform.
+        double interpolationFactor = findDngInterpolationFactor(referenceIlluminant1,
+                referenceIlluminant2, calibrationTransform1, calibrationTransform2,
+                normalizedColorMatrix1, normalizedColorMatrix2, neutralColorPoint);
+        if (DEBUG) Log.d(TAG, "Interpolation factor used: " + interpolationFactor);
+        float[] sensorToXYZ = new float[9];
+        calculateCameraToXYZD50Transform(normalizedForwardTransform1, normalizedForwardTransform2,
+                calibrationTransform1, calibrationTransform2, neutralColorPoint,
+                interpolationFactor, /*out*/sensorToXYZ);
+        if (DEBUG) Log.d(TAG, "CameraToXYZ xform used: " + Arrays.toString(sensorToXYZ));
+        float[] sensorToProPhoto = new float[9];
+        multiply(sXYZtoProPhoto, sensorToXYZ, /*out*/sensorToProPhoto);
+        if (DEBUG) Log.d(TAG, "CameraToIntemediate xform used: " + Arrays.toString(sensorToProPhoto));
+        Allocation output = Allocation.createFromBitmap(rs, argbOutput);
+
+        float[] proPhotoToSRGB = new float[9];
+        multiply(sXYZtoRGBBradford, sProPhotoToXYZ, /*out*/proPhotoToSRGB);
+
+        // Setup input allocation (16-bit raw pixels)
+        Type.Builder typeBuilder = new Type.Builder(rs, Element.U16(rs));
+        typeBuilder.setX(inputWidth);
+        typeBuilder.setY(inputHeight);
+        Type inputType = typeBuilder.create();
+        Allocation input = Allocation.createTyped(rs, inputType);
+        input.copyFromUnchecked(rawImageInput);
+
+        // Setup RS kernel globals
+        ScriptC_raw_converter converterKernel = new ScriptC_raw_converter(rs);
+        converterKernel.set_inputRawBuffer(input);
+        converterKernel.set_whiteLevel(whiteLevel);
+        converterKernel.set_sensorToIntermediate(new Matrix3f(transpose(sensorToProPhoto)));
+        converterKernel.set_intermediateToSRGB(new Matrix3f(transpose(proPhotoToSRGB)));
+        converterKernel.set_offsetX(outputOffsetX);
+        converterKernel.set_offsetY(outputOffsetY);
+        converterKernel.set_rawHeight(inputHeight);
+        converterKernel.set_rawWidth(inputWidth);
+        converterKernel.set_neutralPoint(new Float3(neutralColorPoint[0].floatValue(),
+                neutralColorPoint[1].floatValue(), neutralColorPoint[2].floatValue()));
+        converterKernel.set_toneMapCoeffs(new Float4(DEFAULT_ACR3_TONEMAP_CURVE_COEFFS[0],
+                DEFAULT_ACR3_TONEMAP_CURVE_COEFFS[1], DEFAULT_ACR3_TONEMAP_CURVE_COEFFS[2],
+                DEFAULT_ACR3_TONEMAP_CURVE_COEFFS[3]));
+        converterKernel.set_hasGainMap(gainMap != null);
+        if (gainMap != null) {
+            converterKernel.set_gainMap(gainMap);
+            converterKernel.set_gainMapWidth(lensShadingMap.getColumnCount());
+            converterKernel.set_gainMapHeight(lensShadingMap.getRowCount());
+        }
+
+        converterKernel.set_cfaPattern(cfa);
+        converterKernel.set_blackLevelPattern(new Int4(blackLevelPattern[0],
+                blackLevelPattern[1], blackLevelPattern[2], blackLevelPattern[3]));
+        converterKernel.forEach_convert_RAW_To_ARGB(output);
+        output.copyTo(argbOutput);  // Force RS sync with bitmap (does not do an extra copy).
+    }
+
+    /**
+     * Create a float-backed renderscript {@link Allocation} with the given dimensions, containing
+     * the contents of the given float array.
+     *
+     * @param rs a {@link RenderScript} context to use.
+     * @param fArray the float array to copy into the {@link Allocation}.
+     * @param width the width of the {@link Allocation}.
+     * @param height the height of the {@link Allocation}.
+     * @return an {@link Allocation} containing the given floats.
+     */
+    private static Allocation createFloat4Allocation(RenderScript rs, float[] fArray,
+                                                    int width, int height) {
+        if (fArray.length != width * height * 4) {
+            throw new IllegalArgumentException("Invalid float array of length " + fArray.length +
+                    ", must be correct size for Allocation of dimensions " + width + "x" + height);
+        }
+        Type.Builder builder = new Type.Builder(rs, Element.F32_4(rs));
+        builder.setX(width);
+        builder.setY(height);
+        Allocation fAlloc = Allocation.createTyped(rs, builder.create());
+        fAlloc.copyFrom(fArray);
+        return fAlloc;
+    }
+
+    /**
+     * Calculate the correlated color temperature (CCT) for a given x,y chromaticity in CIE 1931 x,y
+     * chromaticity space using McCamy's cubic approximation algorithm given in:
+     *
+     * McCamy, Calvin S. (April 1992).
+     * "Correlated color temperature as an explicit function of chromaticity coordinates".
+     * Color Research & Application 17 (2): 142–144
+     *
+     * @param x x chromaticity component.
+     * @param y y chromaticity component.
+     *
+     * @return the CCT associated with this chromaticity coordinate.
+     */
+    private static double calculateColorTemperature(double x, double y) {
+        double n = (x - 0.332) / (y - 0.1858);
+        return -449 * Math.pow(n, 3) + 3525 * Math.pow(n, 2) - 6823.3 * n + 5520.33;
+    }
+
+    /**
+     * Calculate the x,y chromaticity coordinates in CIE 1931 x,y chromaticity space from the given
+     * CIE XYZ coordinates.
+     *
+     * @param X the CIE XYZ X coordinate.
+     * @param Y the CIE XYZ Y coordinate.
+     * @param Z the CIE XYZ Z coordinate.
+     *
+     * @return the [x, y] chromaticity coordinates as doubles.
+     */
+    private static double[] calculateCIExyCoordinates(double X, double Y, double Z) {
+        double[] ret = new double[] { 0, 0 };
+        ret[0] = X / (X + Y + Z);
+        ret[1] = Y / (X + Y + Z);
+        return ret;
+    }
+
+    /**
+     * Linearly interpolate between a and b given fraction f.
+     *
+     * @param a first term to interpolate between, a will be returned when f == 0.
+     * @param b second term to interpolate between, b will be returned when f == 1.
+     * @param f the fraction to interpolate by.
+     *
+     * @return interpolated result as double.
+     */
+    private static double lerp(double a, double b, double f) {
+        return (a * (1.0f - f)) + (b * f);
+    }
+
+    /**
+     * Linearly interpolate between 3x3 matrices a and b given fraction f.
+     *
+     * @param a first 3x3 matrix to interpolate between, a will be returned when f == 0.
+     * @param b second 3x3 matrix to interpolate between, b will be returned when f == 1.
+     * @param f the fraction to interpolate by.
+     * @param result will be set to contain the interpolated matrix.
+     */
+    private static void lerp(float[] a, float[] b, double f, /*out*/float[] result) {
+        for (int i = 0; i < 9; i++) {
+            result[i] = (float) lerp(a[i], b[i], f);
+        }
+    }
+
+    /**
+     * Convert a 9x9 {@link ColorSpaceTransform} to a matrix and write the matrix into the
+     * output.
+     *
+     * @param xform a {@link ColorSpaceTransform} to transform.
+     * @param output the 3x3 matrix to overwrite.
+     */
+    private static void convertColorspaceTransform(ColorSpaceTransform xform, /*out*/float[] output) {
+        for (int i = 0; i < 3; i++) {
+            for (int j = 0; j < 3; j++) {
+                output[i * 3 + j] = xform.getElement(j, i).floatValue();
+            }
+        }
+    }
+
+    /**
+     * Find the interpolation factor to use with the RAW matrices given a neutral color point.
+     *
+     * @param referenceIlluminant1 first reference illuminant.
+     * @param referenceIlluminant2 second reference illuminant.
+     * @param calibrationTransform1 calibration matrix corresponding to the first reference
+     *                              illuminant.
+     * @param calibrationTransform2 calibration matrix corresponding to the second reference
+     *                              illuminant.
+     * @param colorMatrix1 color matrix corresponding to the first reference illuminant.
+     * @param colorMatrix2 color matrix corresponding to the second reference illuminant.
+     * @param neutralColorPoint the neutral color point used to calculate the interpolation factor.
+     *
+     * @return the interpolation factor corresponding to the given neutral color point.
+     */
+    private static double findDngInterpolationFactor(int referenceIlluminant1,
+            int referenceIlluminant2, float[] calibrationTransform1, float[] calibrationTransform2,
+            float[] colorMatrix1, float[] colorMatrix2, Rational[/*3*/] neutralColorPoint) {
+
+        int colorTemperature1 = sStandardIlluminants.get(referenceIlluminant1, NO_ILLUMINANT);
+        if (colorTemperature1 == NO_ILLUMINANT) {
+            throw new IllegalArgumentException("No such illuminant for reference illuminant 1: " +
+                    referenceIlluminant1);
+        }
+
+        int colorTemperature2 = sStandardIlluminants.get(referenceIlluminant2, NO_ILLUMINANT);
+        if (colorTemperature2 == NO_ILLUMINANT) {
+            throw new IllegalArgumentException("No such illuminant for reference illuminant 2: " +
+                    referenceIlluminant2);
+        }
+
+        if (DEBUG) Log.d(TAG, "ColorTemperature1: " + colorTemperature1);
+        if (DEBUG) Log.d(TAG, "ColorTemperature2: " + colorTemperature2);
+
+        double interpFactor = 0.5; // Initial guess for interpolation factor
+        double oldInterpFactor = interpFactor;
+
+        double lastDiff = Double.MAX_VALUE;
+        double tolerance = 0.0001;
+        float[] XYZToCamera1 = new float[9];
+        float[] XYZToCamera2 = new float[9];
+        multiply(calibrationTransform1, colorMatrix1, /*out*/XYZToCamera1);
+        multiply(calibrationTransform2, colorMatrix2, /*out*/XYZToCamera2);
+
+        float[] cameraNeutral = new float[] { neutralColorPoint[0].floatValue(),
+                neutralColorPoint[1].floatValue(), neutralColorPoint[2].floatValue()};
+
+        float[] neutralGuess = new float[3];
+        float[] interpXYZToCamera = new float[9];
+        float[] interpXYZToCameraInverse = new float[9];
+
+
+        double lower = Math.min(colorTemperature1, colorTemperature2);
+        double upper = Math.max(colorTemperature1, colorTemperature2);
+
+        if(DEBUG) {
+            Log.d(TAG, "XYZtoCamera1: " + Arrays.toString(XYZToCamera1));
+            Log.d(TAG, "XYZtoCamera2: " + Arrays.toString(XYZToCamera2));
+            Log.d(TAG, "Finding interpolation factor, initial guess 0.5...");
+        }
+        // Iteratively guess xy value, find new CCT, and update interpolation factor.
+        int loopLimit = 30;
+        int count = 0;
+        while (lastDiff > tolerance && loopLimit > 0) {
+            if (DEBUG) Log.d(TAG, "Loop count " + count);
+            lerp(XYZToCamera1, XYZToCamera2, interpFactor, interpXYZToCamera);
+            if (!invert(interpXYZToCamera, /*out*/interpXYZToCameraInverse)) {
+                throw new IllegalArgumentException(
+                        "Cannot invert XYZ to Camera matrix, input matrices are invalid.");
+            }
+
+            map(interpXYZToCameraInverse, cameraNeutral, /*out*/neutralGuess);
+            double[] xy = calculateCIExyCoordinates(neutralGuess[0], neutralGuess[1],
+                    neutralGuess[2]);
+
+            double colorTemperature = calculateColorTemperature(xy[0], xy[1]);
+
+            if (colorTemperature <= lower) {
+                interpFactor = 1;
+            } else if (colorTemperature >= upper) {
+                interpFactor = 0;
+            } else {
+                double invCT = 1.0 / colorTemperature;
+                interpFactor = (invCT - 1.0 / upper) / ( 1.0 / lower - 1.0 / upper);
+            }
+
+            if (lower == colorTemperature1) {
+                interpFactor = 1.0 - interpFactor;
+            }
+
+            interpFactor = (interpFactor + oldInterpFactor) / 2;
+            lastDiff = Math.abs(oldInterpFactor - interpFactor);
+            oldInterpFactor = interpFactor;
+            loopLimit--;
+            count++;
+
+            if (DEBUG) {
+                Log.d(TAG, "CameraToXYZ chosen: " + Arrays.toString(interpXYZToCameraInverse));
+                Log.d(TAG, "XYZ neutral color guess: " + Arrays.toString(neutralGuess));
+                Log.d(TAG, "xy coordinate: " + Arrays.toString(xy));
+                Log.d(TAG, "xy color temperature: " + colorTemperature);
+                Log.d(TAG, "New interpolation factor: " + interpFactor);
+            }
+        }
+
+        if (loopLimit == 0) {
+            Log.w(TAG, "Could not converge on interpolation factor, using factor " + interpFactor +
+                    " with remaining error factor of " + lastDiff);
+        }
+        return interpFactor;
+    }
+
+    /**
+     * Calculate the transform from the raw camera sensor colorspace to CIE XYZ colorspace with a
+     * D50 whitepoint.
+     *
+     * @param forwardTransform1 forward transform matrix corresponding to the first reference
+     *                          illuminant.
+     * @param forwardTransform2 forward transform matrix corresponding to the second reference
+     *                          illuminant.
+     * @param calibrationTransform1 calibration transform matrix corresponding to the first
+     *                              reference illuminant.
+     * @param calibrationTransform2 calibration transform matrix corresponding to the second
+     *                              reference illuminant.
+     * @param neutralColorPoint the neutral color point used to calculate the interpolation factor.
+     * @param interpolationFactor the interpolation factor to use for the forward and
+     *                            calibration transforms.
+     * @param outputTransform set to the full sensor to XYZ colorspace transform.
+     */
+    private static void calculateCameraToXYZD50Transform(float[] forwardTransform1,
+            float[] forwardTransform2, float[] calibrationTransform1, float[] calibrationTransform2,
+            Rational[/*3*/] neutralColorPoint, double interpolationFactor,
+            /*out*/float[] outputTransform) {
+        float[] cameraNeutral = new float[] { neutralColorPoint[0].floatValue(),
+                neutralColorPoint[1].floatValue(), neutralColorPoint[2].floatValue()};
+        if (DEBUG) Log.d(TAG, "Camera neutral: " + Arrays.toString(cameraNeutral));
+
+        float[] interpolatedCC = new float[9];
+        lerp(calibrationTransform1, calibrationTransform2, interpolationFactor,
+                interpolatedCC);
+        float[] inverseInterpolatedCC = new float[9];
+        if (!invert(interpolatedCC, /*out*/inverseInterpolatedCC)) {
+            throw new IllegalArgumentException( "Cannot invert interpolated calibration transform" +
+                    ", input matrices are invalid.");
+        }
+        if (DEBUG) Log.d(TAG, "Inverted interpolated CalibrationTransform: " +
+                Arrays.toString(inverseInterpolatedCC));
+
+        float[] referenceNeutral = new float[3];
+        map(inverseInterpolatedCC, cameraNeutral, /*out*/referenceNeutral);
+        if (DEBUG) Log.d(TAG, "Reference neutral: " + Arrays.toString(referenceNeutral));
+        float[] D = new float[] { 1/referenceNeutral[0], 0, 0,  0, 1/referenceNeutral[1], 0, 0, 0,
+                1/referenceNeutral[2] };
+        if (DEBUG) Log.d(TAG, "Reference Neutral Diagonal: " + Arrays.toString(D));
+
+        float[] intermediate = new float[9];
+        float[] intermediate2 = new float[9];
+
+        lerp(forwardTransform1, forwardTransform2, interpolationFactor, /*out*/intermediate);
+        if (DEBUG) Log.d(TAG, "Interpolated ForwardTransform: " + Arrays.toString(intermediate));
+
+        multiply(D, inverseInterpolatedCC, /*out*/intermediate2);
+        multiply(intermediate, intermediate2, /*out*/outputTransform);
+    }
+
+    /**
+     * Map a 3d column vector using the given matrix.
+     *
+     * @param matrix float array containing 3x3 matrix to map vector by.
+     * @param input 3 dimensional vector to map.
+     * @param output 3 dimensional vector result.
+     */
+    private static void map(float[] matrix, float[] input, /*out*/float[] output) {
+        output[0] = input[0] * matrix[0] + input[1] * matrix[1] + input[2] * matrix[2];
+        output[1] = input[0] * matrix[3] + input[1] * matrix[4] + input[2] * matrix[5];
+        output[2] = input[0] * matrix[6] + input[1] * matrix[7] + input[2] * matrix[8];
+    }
+
+    /**
+     * Multiply two 3x3 matrices together: A * B
+     *
+     * @param a left matrix.
+     * @param b right matrix.
+     */
+    private static void multiply(float[] a, float[] b, /*out*/float[] output) {
+        output[0] = a[0] * b[0] + a[1] * b[3] + a[2] * b[6];
+        output[3] = a[3] * b[0] + a[4] * b[3] + a[5] * b[6];
+        output[6] = a[6] * b[0] + a[7] * b[3] + a[8] * b[6];
+        output[1] = a[0] * b[1] + a[1] * b[4] + a[2] * b[7];
+        output[4] = a[3] * b[1] + a[4] * b[4] + a[5] * b[7];
+        output[7] = a[6] * b[1] + a[7] * b[4] + a[8] * b[7];
+        output[2] = a[0] * b[2] + a[1] * b[5] + a[2] * b[8];
+        output[5] = a[3] * b[2] + a[4] * b[5] + a[5] * b[8];
+        output[8] = a[6] * b[2] + a[7] * b[5] + a[8] * b[8];
+    }
+
+    /**
+     * Transpose a 3x3 matrix in-place.
+     *
+     * @param m the matrix to transpose.
+     * @return the transposed matrix.
+     */
+    private static float[] transpose(/*inout*/float[/*9*/] m) {
+        float t = m[1];
+        m[1] = m[3];
+        m[3] = t;
+        t = m[2];
+        m[2] = m[6];
+        m[6] = t;
+        t = m[5];
+        m[5] = m[7];
+        m[7] = t;
+        return m;
+    }
+
+    /**
+     * Invert a 3x3 matrix, or return false if the matrix is singular.
+     *
+     * @param m matrix to invert.
+     * @param output set the output to be the inverse of m.
+     */
+    private static boolean invert(float[] m, /*out*/float[] output) {
+        double a00 = m[0];
+        double a01 = m[1];
+        double a02 = m[2];
+        double a10 = m[3];
+        double a11 = m[4];
+        double a12 = m[5];
+        double a20 = m[6];
+        double a21 = m[7];
+        double a22 = m[8];
+
+        double t00 = a11 * a22 - a21 * a12;
+        double t01 = a21 * a02 - a01 * a22;
+        double t02 = a01 * a12 - a11 * a02;
+        double t10 = a20 * a12 - a10 * a22;
+        double t11 = a00 * a22 - a20 * a02;
+        double t12 = a10 * a02 - a00 * a12;
+        double t20 = a10 * a21 - a20 * a11;
+        double t21 = a20 * a01 - a00 * a21;
+        double t22 = a00 * a11 - a10 * a01;
+
+        double det = a00 * t00 + a01 * t10 + a02 * t20;
+        if (Math.abs(det) < 1e-9) {
+            return false; // Inverse too close to zero, not invertible.
+        }
+
+        output[0] = (float) (t00 / det);
+        output[1] = (float) (t01 / det);
+        output[2] = (float) (t02 / det);
+        output[3] = (float) (t10 / det);
+        output[4] = (float) (t11 / det);
+        output[5] = (float) (t12 / det);
+        output[6] = (float) (t20 / det);
+        output[7] = (float) (t21 / det);
+        output[8] = (float) (t22 / det);
+        return true;
+    }
+
+    /**
+     * Scale each element in a matrix by the given scaling factor.
+     *
+     * @param factor factor to scale by.
+     * @param matrix the float array containing a 3x3 matrix to scale.
+     */
+    private static void scale(float factor, /*inout*/float[] matrix) {
+        for (int i = 0; i < 9; i++) {
+            matrix[i] *= factor;
+        }
+    }
+
+    /**
+     * Clamp a value to a given range.
+     *
+     * @param low lower bound to clamp to.
+     * @param high higher bound to clamp to.
+     * @param value the value to clamp.
+     * @return the clamped value.
+     */
+    private static double clamp(double low, double high, double value) {
+        return Math.max(low, Math.min(high, value));
+    }
+
+    /**
+     * Return the max float in the array.
+     *
+     * @param array array of floats to search.
+     * @return max float in the array.
+     */
+    private static float max(float[] array) {
+        float val = array[0];
+        for (float f : array) {
+            val = (f > val) ? f : val;
+        }
+        return val;
+    }
+
+    /**
+     * Normalize ColorMatrix to eliminate headroom for input space scaled to [0, 1] using
+     * the D50 whitepoint.  This maps the D50 whitepoint into the colorspace used by the
+     * ColorMatrix, then uses the resulting whitepoint to renormalize the ColorMatrix so
+     * that the channel values in the resulting whitepoint for this operation are clamped
+     * to the range [0, 1].
+     *
+     * @param colorMatrix a 3x3 matrix containing a DNG ColorMatrix to be normalized.
+     */
+    private static void normalizeCM(/*inout*/float[] colorMatrix) {
+        float[] tmp = new float[3];
+        map(colorMatrix, D50_XYZ, /*out*/tmp);
+        float maxVal = max(tmp);
+        if (maxVal > 0) {
+            scale(1.0f / maxVal, colorMatrix);
+        }
+    }
+
+    /**
+     * Normalize ForwardMatrix to ensure that sensor whitepoint [1, 1, 1] maps to D50 in CIE XYZ
+     * colorspace.
+     *
+     * @param forwardMatrix a 3x3 matrix containing a DNG ForwardTransform to be normalized.
+     */
+    private static void normalizeFM(/*inout*/float[] forwardMatrix) {
+        float[] tmp = new float[] {1, 1, 1};
+        float[] xyz = new float[3];
+        map(forwardMatrix, tmp, /*out*/xyz);
+
+        float[] intermediate = new float[9];
+        float[] m = new float[] {1.0f / xyz[0], 0, 0, 0, 1.0f / xyz[1], 0, 0, 0, 1.0f / xyz[2]};
+
+        multiply(m, forwardMatrix, /*out*/ intermediate);
+        float[] m2 = new float[] {D50_XYZ[0], 0, 0, 0, D50_XYZ[1], 0, 0, 0, D50_XYZ[2]};
+        multiply(m2, intermediate, /*out*/forwardMatrix);
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/rs/raw_converter.rs b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/raw_converter.rs
new file mode 100644
index 0000000..c8b353e
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/rs/raw_converter.rs
@@ -0,0 +1,369 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "../common.rs"
+
+// This file includes a conversion kernel for RGGB, GRBG, GBRG, and BGGR Bayer patterns.
+// Applying this script also will apply black-level subtraction, rescaling, clipping, tonemapping,
+// and color space transforms along with the Bayer demosaic.  See RawConverter.java
+// for more information.
+
+// Input globals
+
+rs_allocation inputRawBuffer; // RAW16 buffer of dimensions (raw image stride) * (raw image height)
+rs_allocation gainMap; // Gainmap to apply to linearized raw sensor data.
+uint cfaPattern; // The Color Filter Arrangement pattern used
+uint gainMapWidth;  // The width of the gain map
+uint gainMapHeight;  // The height of the gain map
+bool hasGainMap; // Does gainmap exist?
+rs_matrix3x3 sensorToIntermediate; // Color transform from sensor to a wide-gamut colorspace
+rs_matrix3x3 intermediateToSRGB; // Color transform from wide-gamut colorspace to sRGB
+ushort4 blackLevelPattern; // Blacklevel to subtract for each channel, given in CFA order
+int whiteLevel;  // Whitelevel of sensor
+uint offsetX; // X offset into inputRawBuffer
+uint offsetY; // Y offset into inputRawBuffer
+uint rawWidth; // Width of raw buffer
+uint rawHeight; // Height of raw buffer
+float3 neutralPoint; // The camera neutral
+float4 toneMapCoeffs; // Coefficients for a polynomial tonemapping curve
+
+// Interpolate gain map to find per-channel gains at a given pixel
+static float4 getGain(uint x, uint y) {
+    float interpX = (((float) x) / rawWidth) * gainMapWidth;
+    float interpY = (((float) y) / rawHeight) * gainMapHeight;
+    uint gX = (uint) interpX;
+    uint gY = (uint) interpY;
+    uint gXNext = (gX + 1 < gainMapWidth) ? gX + 1 : gX;
+    uint gYNext = (gY + 1 < gainMapHeight) ? gY + 1 : gY;
+
+    float4 tl = *((float4 *) rsGetElementAt(gainMap, gX, gY));
+    float4 tr = *((float4 *) rsGetElementAt(gainMap, gXNext, gY));
+    float4 bl = *((float4 *) rsGetElementAt(gainMap, gX, gYNext));
+    float4 br = *((float4 *) rsGetElementAt(gainMap, gXNext, gYNext));
+
+    float fracX = interpX - (float) gX;
+    float fracY = interpY - (float) gY;
+    float invFracX = 1.f - fracX;
+    float invFracY = 1.f - fracY;
+
+    return tl * invFracX * invFracY + tr * fracX * invFracY +
+            bl * invFracX * fracY + br * fracX * fracY;
+}
+
+// Apply gamma correction using sRGB gamma curve
+static float gammaEncode(float x) {
+    return (x <= 0.0031308f) ? x * 12.92f : 1.055f * pow(x, 0.4166667f) - 0.055f;
+}
+
+// Apply gamma correction to each color channel in RGB pixel
+static float3 gammaCorrectPixel(float3 rgb) {
+    float3 ret;
+    ret.x = gammaEncode(rgb.x);
+    ret.y = gammaEncode(rgb.y);
+    ret.z = gammaEncode(rgb.z);
+    return ret;
+}
+
+// Apply polynomial tonemapping curve to each color channel in RGB pixel.
+// This attempts to apply tonemapping without changing the hue of each pixel,
+// i.e.:
+//
+// For some RGB values:
+// M = max(R, G, B)
+// m = min(R, G, B)
+// m' = mid(R, G, B)
+// chroma = M - m
+// H = m' - m / chroma
+//
+// The relationship H=H' should be preserved, where H and H' are calculated from
+// the RGB and RGB' value at this pixel before and after this tonemapping
+// operation has been applied, respectively.
+static float3 tonemap(float3 rgb) {
+    float3 sorted = clamp(rgb, 0.f, 1.f);
+    float tmp;
+    int permutation = 0;
+
+    // Sort the RGB channels by value
+    if (sorted.z < sorted.y) {
+        tmp = sorted.z;
+        sorted.z = sorted.y;
+        sorted.y = tmp;
+        permutation |= 1;
+    }
+    if (sorted.y < sorted.x) {
+        tmp = sorted.y;
+        sorted.y = sorted.x;
+        sorted.x = tmp;
+        permutation |= 2;
+    }
+    if (sorted.z < sorted.y) {
+        tmp = sorted.z;
+        sorted.z = sorted.y;
+        sorted.y = tmp;
+        permutation |= 4;
+    }
+
+    float2 minmax;
+    minmax.x = sorted.x;
+    minmax.y = sorted.z;
+
+    // Apply tonemapping curve to min, max RGB channel values
+    minmax = native_powr(minmax, 3.f) * toneMapCoeffs.x +
+            native_powr(minmax, 2.f) * toneMapCoeffs.y +
+            minmax * toneMapCoeffs.z + toneMapCoeffs.w;
+
+    // Rescale middle value
+    float newMid;
+    if (sorted.z == sorted.x) {
+        newMid = minmax.y;
+    } else {
+        newMid = minmax.x + ((minmax.y - minmax.x) * (sorted.y - sorted.x) /
+                (sorted.z - sorted.x));
+    }
+
+    float3 finalRGB;
+    switch (permutation) {
+        case 0: // b >= g >= r
+            finalRGB.x = minmax.x;
+            finalRGB.y = newMid;
+            finalRGB.z = minmax.y;
+            break;
+        case 1: // g >= b >= r
+            finalRGB.x = minmax.x;
+            finalRGB.z = newMid;
+            finalRGB.y = minmax.y;
+            break;
+        case 2: // b >= r >= g
+            finalRGB.y = minmax.x;
+            finalRGB.x = newMid;
+            finalRGB.z = minmax.y;
+            break;
+        case 3: // g >= r >= b
+            finalRGB.z = minmax.x;
+            finalRGB.x = newMid;
+            finalRGB.y = minmax.y;
+            break;
+        case 6: // r >= b >= g
+            finalRGB.y = minmax.x;
+            finalRGB.z = newMid;
+            finalRGB.x = minmax.y;
+            break;
+        case 7: // r >= g >= b
+            finalRGB.z = minmax.x;
+            finalRGB.y = newMid;
+            finalRGB.x = minmax.y;
+            break;
+        case 4: // impossible
+        case 5: // impossible
+        default:
+            LOGD("raw_converter.rs: Logic error in tonemap.", 0);
+            break;
+    }
+    return clamp(finalRGB, 0.f, 1.f);
+}
+
+// Apply a colorspace transform to the intermediate colorspace, apply
+// a tonemapping curve, apply a colorspace transform to a final colorspace,
+// and apply a gamma correction curve.
+static float3 applyColorspace(float3 pRGB) {
+    pRGB.x = clamp(pRGB.x, 0.f, neutralPoint.x);
+    pRGB.y = clamp(pRGB.y, 0.f, neutralPoint.y);
+    pRGB.z = clamp(pRGB.z, 0.f, neutralPoint.z);
+
+    float3 intermediate = rsMatrixMultiply(&sensorToIntermediate, pRGB);
+    intermediate = tonemap(intermediate);
+    return gammaCorrectPixel(clamp(rsMatrixMultiply(&intermediateToSRGB, intermediate), 0.f, 1.f));
+}
+
+// Load a 3x3 patch of pixels into the output.
+static void load3x3(uint x, uint y, rs_allocation buf, /*out*/float* outputArray) {
+    outputArray[0] = *((ushort *) rsGetElementAt(buf, x - 1, y - 1));
+    outputArray[1] = *((ushort *) rsGetElementAt(buf, x, y - 1));
+    outputArray[2] = *((ushort *) rsGetElementAt(buf, x + 1, y - 1));
+    outputArray[3] = *((ushort *) rsGetElementAt(buf, x - 1, y));
+    outputArray[4] = *((ushort *) rsGetElementAt(buf, x, y));
+    outputArray[5] = *((ushort *) rsGetElementAt(buf, x + 1, y));
+    outputArray[6] = *((ushort *) rsGetElementAt(buf, x - 1, y + 1));
+    outputArray[7] = *((ushort *) rsGetElementAt(buf, x, y + 1));
+    outputArray[8] = *((ushort *) rsGetElementAt(buf, x + 1, y + 1));
+}
+
+// Blacklevel subtract, and normalize each pixel in the outputArray, and apply the
+// gain map.
+static void linearizeAndGainmap(uint x, uint y, ushort4 blackLevel, int whiteLevel,
+        uint cfa, /*inout*/float* outputArray) {
+    uint kk = 0;
+    for (uint j = y - 1; j <= y + 1; j++) {
+        for (uint i = x - 1; i <= x + 1; i++) {
+            uint index = (i & 1) | ((j & 1) << 1);  // bits [0,1] are blacklevel offset
+            index |= (cfa << 2);  // bits [2,3] are cfa
+            float bl = 0.f;
+            float g = 1.f;
+            float4 gains = 1.f;
+            if (hasGainMap) {
+                gains = getGain(i, j);
+            }
+            switch (index) {
+                // RGGB
+                case 0:
+                    bl = blackLevel.x;
+                    g = gains.x;
+                    break;
+                case 1:
+                    bl = blackLevel.y;
+                    g = gains.y;
+                    break;
+                case 2:
+                    bl = blackLevel.z;
+                    g = gains.z;
+                    break;
+                case 3:
+                    bl = blackLevel.w;
+                    g = gains.w;
+                    break;
+                // GRBG
+                case 4:
+                    bl = blackLevel.x;
+                    g = gains.y;
+                    break;
+                case 5:
+                    bl = blackLevel.y;
+                    g = gains.x;
+                    break;
+                case 6:
+                    bl = blackLevel.z;
+                    g = gains.w;
+                    break;
+                case 7:
+                    bl = blackLevel.w;
+                    g = gains.z;
+                    break;
+                // GBRG
+                case 8:
+                    bl = blackLevel.x;
+                    g = gains.y;
+                    break;
+                case 9:
+                    bl = blackLevel.y;
+                    g = gains.w;
+                    break;
+                case 10:
+                    bl = blackLevel.z;
+                    g = gains.x;
+                    break;
+                case 11:
+                    bl = blackLevel.w;
+                    g = gains.z;
+                    break;
+                // BGGR
+                case 12:
+                    bl = blackLevel.x;
+                    g = gains.w;
+                    break;
+                case 13:
+                    bl = blackLevel.y;
+                    g = gains.y;
+                    break;
+                case 14:
+                    bl = blackLevel.z;
+                    g = gains.z;
+                    break;
+                case 15:
+                    bl = blackLevel.w;
+                    g = gains.x;
+                    break;
+            }
+            outputArray[kk] = clamp(g * (outputArray[kk] - bl) / (whiteLevel - bl), 0.f, 1.f);
+            kk++;
+        }
+    }
+}
+
+// Apply bilinear-interpolation to demosaic
+static float3 demosaic(uint x, uint y, uint cfa, float* inputArray) {
+    uint index = (x & 1) | ((y & 1) << 1);
+    index |= (cfa << 2);
+    float3 pRGB;
+    switch (index) {
+        case 0:
+        case 5:
+        case 10:
+        case 15:  // Red centered
+                  // B G B
+                  // G R G
+                  // B G B
+            pRGB.x = inputArray[4];
+            pRGB.y = (inputArray[1] + inputArray[3] + inputArray[5] + inputArray[7]) / 4;
+            pRGB.z = (inputArray[0] + inputArray[2] + inputArray[6] + inputArray[8]) / 4;
+            break;
+        case 1:
+        case 4:
+        case 11:
+        case 14: // Green centered w/ horizontally adjacent Red
+                 // G B G
+                 // R G R
+                 // G B G
+            pRGB.x = (inputArray[3] + inputArray[5]) / 2;
+            pRGB.y = inputArray[4];
+            pRGB.z = (inputArray[1] + inputArray[7]) / 2;
+            break;
+        case 2:
+        case 7:
+        case 8:
+        case 13: // Green centered w/ horizontally adjacent Blue
+                 // G R G
+                 // B G B
+                 // G R G
+            pRGB.x = (inputArray[1] + inputArray[7]) / 2;
+            pRGB.y = inputArray[4];
+            pRGB.z = (inputArray[3] + inputArray[5]) / 2;
+            break;
+        case 3:
+        case 6:
+        case 9:
+        case 12: // Blue centered
+                 // R G R
+                 // G B G
+                 // R G R
+            pRGB.x = (inputArray[0] + inputArray[2] + inputArray[6] + inputArray[8]) / 4;
+            pRGB.y = (inputArray[1] + inputArray[3] + inputArray[5] + inputArray[7]) / 4;
+            pRGB.z = inputArray[4];
+            break;
+    }
+
+    return pRGB;
+}
+
+// Full RAW->ARGB bitmap conversion kernel
+uchar4 RS_KERNEL convert_RAW_To_ARGB(uint x, uint y) {
+    float3 pRGB;
+    uint xP = x + offsetX;
+    uint yP = y + offsetY;
+    if (xP == 0) xP = 1;
+    if (yP == 0) yP = 1;
+    if (xP == rawWidth - 1) xP = rawWidth - 2;
+    if (yP == rawHeight - 1) yP = rawHeight  - 2;
+
+    float patch[9];
+    // TODO: Once ScriptGroup and RS kernels have been updated to allow for iteration over 3x3 pixel
+    // patches, this can be optimized to avoid re-applying the pre-demosaic steps for each pixel,
+    // potentially achieving a 9x speedup here.
+    load3x3(xP, yP, inputRawBuffer, /*out*/ patch);
+    linearizeAndGainmap(xP, yP, blackLevelPattern, whiteLevel, cfaPattern, /*inout*/patch);
+    pRGB = demosaic(xP, yP, cfaPattern, patch);
+
+    return rsPackColorTo8888(applyColorspace(pRGB));
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorBatchingTests.java b/tests/tests/hardware/src/android/hardware/cts/SensorBatchingTests.java
index 687826c..7640cd7 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorBatchingTests.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorBatchingTests.java
@@ -20,9 +20,7 @@
 import android.hardware.SensorManager;
 import android.hardware.cts.helpers.SensorStats;
 import android.hardware.cts.helpers.TestSensorEnvironment;
-import android.hardware.cts.helpers.sensoroperations.TestSensorFlushOperation;
 import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
-import android.hardware.cts.helpers.sensoroperations.VerifiableSensorOperation;
 import android.hardware.cts.helpers.sensorverification.ISensorVerification;
 
 import java.util.concurrent.TimeUnit;
@@ -263,7 +261,7 @@
                 rateUs,
                 maxBatchReportLatencyUs);
         TestSensorOperation operation =
-                new TestSensorOperation(environment, testDurationSec, TimeUnit.SECONDS);
+                TestSensorOperation.createOperation(environment, testDurationSec, TimeUnit.SECONDS);
 
         executeTest(environment, operation, false /* flushExpected */);
     }
@@ -279,23 +277,23 @@
                 shouldEmulateSensorUnderLoad(),
                 rateUs,
                 maxBatchReportLatencyUs);
-        TestSensorFlushOperation operation =
-                new TestSensorFlushOperation(environment, flushDurationSec, TimeUnit.SECONDS);
+        TestSensorOperation operation = TestSensorOperation
+                .createFlushOperation(environment, flushDurationSec, TimeUnit.SECONDS);
 
         executeTest(environment, operation, true /* flushExpected */);
     }
 
     private void executeTest(
             TestSensorEnvironment environment,
-            VerifiableSensorOperation operation,
+            TestSensorOperation operation,
             boolean flushExpected) throws Throwable {
         operation.addDefaultVerifications();
-        operation.setLogEvents(true);
 
         try {
-            operation.execute();
+            operation.execute(getCurrentTestNode());
         } finally {
-            SensorStats.logStats(TAG, operation.getStats());
+            SensorStats stats = operation.getStats();
+            stats.log(TAG);
 
             String sensorRate;
             if (environment.getRequestedSamplingPeriodUs() == SensorManager.SENSOR_DELAY_FASTEST) {
@@ -311,7 +309,7 @@
                     sensorRate,
                     batching,
                     flush);
-            SensorStats.logStatsToFile(fileName, operation.getStats());
+            stats.logToFile(fileName);
         }
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java b/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java
index 50cb12d..8c3fb7a 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorIntegrationTests.java
@@ -18,13 +18,11 @@
 import android.content.Context;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
-import android.hardware.cts.helpers.SensorStats;
 import android.hardware.cts.helpers.TestSensorEnvironment;
 import android.hardware.cts.helpers.sensoroperations.ParallelSensorOperation;
 import android.hardware.cts.helpers.sensoroperations.RepeatingSensorOperation;
 import android.hardware.cts.helpers.sensoroperations.SequentialSensorOperation;
 import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
-import android.hardware.cts.helpers.sensoroperations.VerifiableSensorOperation;
 import android.hardware.cts.helpers.sensorverification.EventOrderingVerification;
 
 import java.util.Random;
@@ -81,7 +79,7 @@
                     shouldEmulateSensorUnderLoad(),
                     SensorManager.SENSOR_DELAY_FASTEST);
             TestSensorOperation continuousOperation =
-                    new TestSensorOperation(environment, 100 /* eventCount */);
+                    TestSensorOperation.createOperation(environment, 100 /* eventCount */);
             continuousOperation.addVerification(new EventOrderingVerification());
             operation.add(new RepeatingSensorOperation(continuousOperation, ITERATIONS));
 
@@ -93,12 +91,12 @@
                     sensor.getMinDelay(),
                     MAX_REPORTING_LATENCY_US);
             TestSensorOperation batchingOperation =
-                    new TestSensorOperation(batchingEnvironment, 100 /* eventCount */);
+                    TestSensorOperation.createOperation(batchingEnvironment, 100 /* eventCount */);
             batchingOperation.addVerification(new EventOrderingVerification());
             operation.add(new RepeatingSensorOperation(batchingOperation, ITERATIONS));
         }
-        operation.execute();
-        SensorStats.logStats(TAG, operation.getStats());
+        operation.execute(getCurrentTestNode());
+        operation.getStats().log(TAG);
     }
 
     /**
@@ -145,7 +143,7 @@
                             generateSamplingRateInUs(sensorType),
                             generateReportLatencyInUs());
                     TestSensorOperation sensorOperation =
-                            new TestSensorOperation(environment, 100 /* eventCount */);
+                            TestSensorOperation.createOperation(environment, 100 /* eventCount */);
                     sensorOperation.addVerification(new EventOrderingVerification());
                     sequentialOperation.add(sensorOperation);
                 }
@@ -153,8 +151,8 @@
             }
         }
 
-        operation.execute();
-        SensorStats.logStats(TAG, operation.getStats());
+        operation.execute(getCurrentTestNode());
+        operation.getStats().log(TAG);
     }
 
     /**
@@ -229,7 +227,7 @@
                 shouldEmulateSensorUnderLoad(),
                 SensorManager.SENSOR_DELAY_FASTEST);
         TestSensorOperation tester =
-                new TestSensorOperation(testerEnvironment, 100 /* event count */);
+                TestSensorOperation.createOperation(testerEnvironment, 100 /* event count */);
         tester.addVerification(new EventOrderingVerification());
 
         TestSensorEnvironment testeeEnvironment = new TestSensorEnvironment(
@@ -237,18 +235,18 @@
                 sensorTypeTestee,
                 shouldEmulateSensorUnderLoad(),
                 SensorManager.SENSOR_DELAY_FASTEST);
-        VerifiableSensorOperation testee =
-                new TestSensorOperation(testeeEnvironment, 100 /* event count */);
+        TestSensorOperation testee =
+                TestSensorOperation.createOperation(testeeEnvironment, 100 /* event count */);
         testee.addVerification(new EventOrderingVerification());
 
         ParallelSensorOperation operation = new ParallelSensorOperation();
         operation.add(tester, testee);
-        operation.execute();
-        SensorStats.logStats(TAG, operation.getStats());
+        operation.execute(getCurrentTestNode());
+        operation.getStats().log(TAG);
 
         testee = testee.clone();
-        testee.execute();
-        SensorStats.logStats(TAG, testee.getStats());
+        testee.execute(getCurrentTestNode());
+        testee.getStats().log(TAG);
     }
 
     /**
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorTest.java b/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
index d8b8e51..d606b70 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorTest.java
@@ -18,6 +18,8 @@
 
 import com.android.cts.util.TimeoutReq;
 
+import junit.framework.Assert;
+
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.hardware.Sensor;
@@ -27,44 +29,71 @@
 import android.hardware.SensorManager;
 import android.hardware.TriggerEvent;
 import android.hardware.TriggerEventListener;
+import android.hardware.cts.helpers.SensorNotSupportedException;
+import android.hardware.cts.helpers.SensorTestStateNotSupportedException;
+import android.hardware.cts.helpers.TestSensorEnvironment;
+import android.hardware.cts.helpers.TestSensorEventListener;
+import android.hardware.cts.helpers.TestSensorManager;
+import android.hardware.cts.helpers.sensoroperations.ParallelSensorOperation;
+import android.hardware.cts.helpers.sensoroperations.TestSensorOperation;
+import android.hardware.cts.helpers.sensorverification.EventGapVerification;
+import android.hardware.cts.helpers.sensorverification.EventOrderingVerification;
+import android.hardware.cts.helpers.sensorverification.EventTimestampSynchronizationVerification;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.PowerManager;
 import android.os.SystemClock;
 import android.util.Log;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 public class SensorTest extends SensorTestCase {
-    private SensorManager mSensorManager;
-    private TriggerListener mTriggerListener;
-    private SensorListener mSensorListener;
-    private List<Sensor> mSensorList;
     private static final String TAG = "SensorTest";
+
     // Test only SDK defined sensors. Any sensors with type > 100 are ignored.
     private static final int MAX_OFFICIAL_ANDROID_SENSOR_TYPE = 100;
-    private static final long TIMEOUT_TOLERANCE_US = TimeUnit.SECONDS.toMicros(5);
-    private static final double MIN_SAMPLING_FREQUENCY_MULTIPLIER_TOLERANCE = 0.9;
+
     private PowerManager.WakeLock mWakeLock;
+    private SensorManager mSensorManager;
+    private NullTriggerEventListener mNullTriggerEventListener;
+    private NullSensorEventListener mNullSensorEventListener;
+    private List<Sensor> mSensorList;
 
     @Override
     protected void setUp() throws Exception {
-        super.setUp();
-        mSensorManager = (SensorManager) getContext().getSystemService(Context.SENSOR_SERVICE);
-        mTriggerListener = new TriggerListener();
-        mSensorListener = new SensorListener();
-        mSensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
-        PowerManager pm = (PowerManager) getContext().getSystemService(Context.POWER_SERVICE);
+        Context context = getContext();
+        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+
+        mSensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+        mNullTriggerEventListener = new NullTriggerEventListener();
+        mNullSensorEventListener = new NullSensorEventListener();
+
+        mSensorList = mSensorManager.getSensorList(Sensor.TYPE_ALL);
+        assertNotNull("SensorList was null.", mSensorList);
+        if (mSensorList.isEmpty()) {
+            // several devices will not have sensors, so we need to skip the tests in those cases
+            throw new SensorTestStateNotSupportedException(
+                    "Sensors are not available in the system.");
+        }
+
+        mWakeLock.acquire();
     }
 
+    @Override
+    protected void tearDown(){
+        if (mWakeLock != null && mWakeLock.isHeld()) {
+            mWakeLock.release();
+        }
+    }
+
+    @SuppressWarnings("deprecation")
     public void testSensorOperations() {
         // Because we can't know every sensors unit details, so we can't assert
         // get values with specified values.
-        List<Sensor> sensors = mSensorManager.getSensorList(Sensor.TYPE_ALL);
-        assertNotNull(sensors);
         Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
         boolean hasAccelerometer = getContext().getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_SENSOR_ACCELEROMETER);
@@ -149,7 +178,6 @@
         }
         assertTrue(sensors.get(0).getName() + " defined as non-wake-up sensor",
                 sensors.get(0).isWakeUpSensor());
-        return;
     }
 
     // Some sensors like proximity, significant motion etc. are defined as wake-up sensors by
@@ -207,372 +235,141 @@
 
     public void testRequestTriggerWithNonTriggerSensor() {
         Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
-        boolean result;
-        if (sensor != null) {
-            result = mSensorManager.requestTriggerSensor(mTriggerListener, sensor);
-            assertFalse(result);
+        if (sensor == null) {
+            throw new SensorNotSupportedException(Sensor.TYPE_ACCELEROMETER);
         }
+        boolean  result = mSensorManager.requestTriggerSensor(mNullTriggerEventListener, sensor);
+        assertFalse(result);
     }
 
     public void testCancelTriggerWithNonTriggerSensor() {
         Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
-        boolean result;
-        if (sensor != null) {
-            result = mSensorManager.cancelTriggerSensor(mTriggerListener, sensor);
-            assertFalse(result);
+        if (sensor == null) {
+            throw new SensorNotSupportedException(Sensor.TYPE_ACCELEROMETER);
         }
+        boolean result = mSensorManager.cancelTriggerSensor(mNullTriggerEventListener, sensor);
+        assertFalse(result);
     }
 
     public void testRegisterWithTriggerSensor() {
         Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_SIGNIFICANT_MOTION);
-        boolean result;
-        if (sensor != null) {
-            result = mSensorManager.registerListener(mSensorListener, sensor,
-                    SensorManager.SENSOR_DELAY_NORMAL);
-            assertFalse(result);
+        if (sensor == null) {
+            throw new SensorNotSupportedException(Sensor.TYPE_SIGNIFICANT_MOTION);
         }
+        boolean result = mSensorManager.registerListener(
+                mNullSensorEventListener,
+                sensor,
+                SensorManager.SENSOR_DELAY_NORMAL);
+        assertFalse(result);
     }
 
     public void testRegisterTwiceWithSameSensor() {
         Sensor sensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
-        boolean result;
-        if (sensor != null) {
-            result = mSensorManager.registerListener(mSensorListener, sensor,
-                    SensorManager.SENSOR_DELAY_NORMAL);
-            assertTrue(result);
-            result = mSensorManager.registerListener(mSensorListener, sensor,
-                    SensorManager.SENSOR_DELAY_NORMAL);
-            assertFalse(result);
+        if (sensor == null) {
+            throw new SensorNotSupportedException(Sensor.TYPE_ACCELEROMETER);
         }
+
+        boolean result = mSensorManager.registerListener(mNullSensorEventListener, sensor,
+                SensorManager.SENSOR_DELAY_NORMAL);
+        assertTrue(result);
+
+        result = mSensorManager.registerListener(mNullSensorEventListener, sensor,
+                SensorManager.SENSOR_DELAY_NORMAL);
+        assertFalse(result);
     }
 
-    class SensorEventTimeStampListener implements SensorEventListener {
-        SensorEventTimeStampListener(long eventReportLatencyNs, CountDownLatch latch) {
-            mEventReportLatencyNs = eventReportLatencyNs;
-            mPrevTimeStampNs = -1;
-            mLatch = latch;
-            numErrors = 0;
-        }
-
-        @Override
-        public void onSensorChanged(SensorEvent event) {
-            if (mPrevTimeStampNs == -1) {
-                mPrevTimeStampNs = event.timestamp;
-                return;
-            }
-            long currTimeStampNs = event.timestamp;
-            if (currTimeStampNs <= mPrevTimeStampNs) {
-                Log.w(TAG, "Timestamps not monotonically increasing curr_ts_ns=" +
-                        event.timestamp + " prev_ts_ns=" + mPrevTimeStampNs);
-                numErrors++;
-                mPrevTimeStampNs = currTimeStampNs;
-                return;
-            }
-            mLatch.countDown();
-
-            final long elapsedRealtimeNs = SystemClock.elapsedRealtimeNanos();
-
-            if (elapsedRealtimeNs <= currTimeStampNs) {
-                Log.w(TAG, "Timestamps into the future curr elapsedRealTimeNs=" + elapsedRealtimeNs
-                         + " current sensor ts_ns=" + currTimeStampNs);
-                ++numErrors;
-            } else if (elapsedRealtimeNs-currTimeStampNs > SYNC_TOLERANCE + mEventReportLatencyNs) {
-                Log.w(TAG, "Timestamp sync error elapsedRealTimeNs=" + elapsedRealtimeNs +
-                        " curr_ts_ns=" + currTimeStampNs +
-                        " diff_ns=" + (elapsedRealtimeNs - currTimeStampNs) +
-                        " SYNC_TOLERANCE_NS=" + SYNC_TOLERANCE +
-                        " eventReportLatencyNs=" + mEventReportLatencyNs);
-                ++numErrors;
-            }
-            mPrevTimeStampNs = currTimeStampNs;
-        }
-
-        public int getNumErrors() {
-            return numErrors;
-        }
-
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
-        }
-
-        private int numErrors;
-        private long mEventReportLatencyNs;
-        private long mPrevTimeStampNs;
-        private final CountDownLatch mLatch;
-        private final long SYNC_TOLERANCE = 500000000L; // 500 milli seconds approx.
-    }
-
-    // Register for each sensor and compare the timestamps of SensorEvents that you get with
-    // elapsedRealTimeNano.
+    // TODO: remove when parametized tests are supported and EventTimestampSynchronization
+    //       verification is added to default verifications
     @TimeoutReq(minutes=60)
     public void testSensorTimeStamps() throws Exception {
-        final int numEvents = 2000;
-        try {
-            mWakeLock.acquire();
-            int numErrors = 0;
-            for (Sensor sensor : mSensorList) {
-                // Skip OEM defined sensors and non continuous sensors.
-                if (sensor.getReportingMode() != Sensor.REPORTING_MODE_CONTINUOUS) {
-                    continue;
-                }
-
-                for (int iterations = 0; iterations < 2; ++iterations) {
-                    // Test in both batch mode and non-batch mode for every sensor.
-                    long maxBatchReportLatencyNs = 10000000000L; // 10 secs
-                    if (iterations % 2 == 0) maxBatchReportLatencyNs = 0;
-
-                    final long samplingPeriodNs = (long)(TimeUnit.MICROSECONDS.toNanos(
-                            sensor.getMinDelay())/MIN_SAMPLING_FREQUENCY_MULTIPLIER_TOLERANCE);
-                    // If there is a FIFO and a wake-lock is held, events will be reported when
-                    // the batch timeout expires or when the FIFO is full which ever occurs
-                    // earlier.
-                    final long eventReportLatencyNs = Math.min(maxBatchReportLatencyNs,
-                            sensor.getFifoMaxEventCount() * samplingPeriodNs);
-
-                    final CountDownLatch eventsRemaining = new CountDownLatch(numEvents);
-                    SensorEventTimeStampListener listener = new SensorEventTimeStampListener(
-                            eventReportLatencyNs, eventsRemaining);
-
-                    Log.i(TAG, "Running timeStamp test on " + sensor.getName());
-                    boolean result = mSensorManager.registerListener(listener, sensor,
-                            SensorManager.SENSOR_DELAY_FASTEST,
-                            (int)maxBatchReportLatencyNs/1000);
-                    assertTrue("Sensor registerListener failed ", result);
-
-                    long timeToWaitUs = samplingPeriodNs/1000 + eventReportLatencyNs/1000 +
-                            TIMEOUT_TOLERANCE_US;
-                    long totalTimeWaitedUs = waitToCollectAllEvents(timeToWaitUs,
-                            (int)(eventReportLatencyNs/1000), eventsRemaining);
-
-                    mSensorManager.unregisterListener(listener);
-                    if (eventsRemaining.getCount() > 0) {
-                        failTimedOut(sensor.getName(), (double) totalTimeWaitedUs/1000,
-                                numEvents, (double) sensor.getMinDelay()/1000,
-                                eventsRemaining.getCount(),
-                                numEvents - eventsRemaining.getCount());
-                    }
-                    if (listener.getNumErrors() > 5) {
-                        fail("Check logcat. Timestamp test failed. numErrors=" +
-                                listener.getNumErrors() + " " + sensor.getName() +
-                                " maxBatchReportLatencyNs=" + maxBatchReportLatencyNs +
-                                " samplingPeriodNs=" + sensor.getMinDelay());
-                        numErrors += listener.getNumErrors();
-                    } else {
-                        Log.i(TAG, "TimeStamp test PASS'd on " + sensor.getName());
-                    }
-                }
-            }
-        } finally {
-            mWakeLock.release();
+        ArrayList<Throwable> errorsFound = new ArrayList<>();
+        for (Sensor sensor : mSensorList) {
+            // test both continuous and batching mode sensors
+            verifyLongActivation(sensor, 0 /* maxReportLatencyUs */, errorsFound);
+            verifyLongActivation(sensor, (int) TimeUnit.SECONDS.toMicros(10), errorsFound);
         }
+        assertOnErrors(errorsFound);
     }
 
-    private void failTimedOut(String sensorName, double totalTimeWaitedMs, int numEvents,
-            double minDelayMs, long eventsRemaining, long eventsReceived) {
-        final String TIMED_OUT_FORMAT = "Timed out waiting for events from %s " +
-                "waited for time=%.1fms to receive totalEvents=%d at samplingRate=%.1fHz" +
-                " remainingEvents=%d  received events=%d";
-        fail(String.format(TIMED_OUT_FORMAT, sensorName, totalTimeWaitedMs, numEvents,
-                1000/minDelayMs, eventsRemaining, eventsReceived));
-    }
-
-    // Register for updates from each continuous mode sensor, wait for N events, call flush and
-    // wait for flushCompleteEvent before unregistering for the sensor.
+    // TODO: remove when parameterized tests are supported (see SensorBatchingTests.java)
     @TimeoutReq(minutes=20)
     public void testBatchAndFlush() throws Exception {
-        try {
-            mWakeLock.acquire();
-            for (Sensor sensor : mSensorList) {
-                // Skip ONLY one-shot sensors.
-                if (sensor.getReportingMode() != Sensor.REPORTING_MODE_ONE_SHOT) {
-                    registerListenerCallFlush(sensor, null);
-                }
-            }
-        } finally {
-            mWakeLock.release();
+        ArrayList<Throwable> errorsFound = new ArrayList<>();
+        for (Sensor sensor : mSensorList) {
+            verifyRegisterListenerCallFlush(sensor, null /* handler */, errorsFound);
         }
+        assertOnErrors(errorsFound);
     }
 
-    // Same as testBatchAndFlush but using Handler version of the API to register for sensors.
-    // onSensorChanged is now called on a background thread.
+    /**
+     * Verifies that sensor events arrive in the given message queue (Handler).
+     */
     @TimeoutReq(minutes=10)
     public void testBatchAndFlushWithHandler() throws Exception {
-        try {
-            mWakeLock.acquire();
-            HandlerThread handlerThread = new HandlerThread("sensorThread");
-            handlerThread.start();
-            Handler handler = new Handler(handlerThread.getLooper());
-            for (Sensor sensor : mSensorList) {
-                // Skip ONLY one-shot sensors.
-                if (sensor.getReportingMode() != Sensor.REPORTING_MODE_ONE_SHOT) {
-                    registerListenerCallFlush(sensor, handler);
-                }
+        Sensor sensor = null;
+        for (Sensor s : mSensorList) {
+            if (s.getReportingMode() == Sensor.REPORTING_MODE_CONTINUOUS) {
+                sensor = s;
+                break;
             }
-        }  finally {
-            mWakeLock.release();
         }
+        if (sensor == null) {
+            throw new SensorTestStateNotSupportedException(
+                    "There are no Continuous sensors in the device.");
+        }
+
+        TestSensorEnvironment environment = new TestSensorEnvironment(
+                getContext(),
+                sensor,
+                SensorManager.SENSOR_DELAY_FASTEST,
+                (int) TimeUnit.SECONDS.toMicros(5));
+        TestSensorManager sensorManager = new TestSensorManager(environment);
+
+        HandlerThread handlerThread = new HandlerThread("sensorThread");
+        handlerThread.start();
+        Handler handler = new Handler(handlerThread.getLooper());
+        TestSensorEventListener listener = new TestSensorEventListener(environment, handler);
+
+        CountDownLatch eventLatch = sensorManager.registerListener(listener, 1);
+        listener.waitForEvents(eventLatch, 1);
+        CountDownLatch flushLatch = sensorManager.requestFlush();
+        listener.waitForFlushComplete(flushLatch);
+        listener.assertEventsReceivedInHandler();
     }
 
-    private void registerListenerCallFlush(Sensor sensor, Handler handler)
-            throws InterruptedException {
-        if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
-            return;
-        }
-        final int numEvents = 500;
-        final int rateUs = 0; // DELAY_FASTEST
-        final int maxBatchReportLatencyUs = 10000000;
-        final CountDownLatch eventsRemaining = new CountDownLatch(numEvents);
-        final CountDownLatch flushReceived = new CountDownLatch(1);
-        SensorEventListener2 listener = new SensorEventListener2() {
-            @Override
-            public void onSensorChanged(SensorEvent event) {
-                eventsRemaining.countDown();
-            }
-
-            @Override
-            public void onAccuracyChanged(Sensor sensor, int accuracy) {
-            }
-
-            @Override
-            public void onFlushCompleted(Sensor sensor) {
-                flushReceived.countDown();
-            }
-        };
-        // Consider only continuous mode sensors for testing registerListener.
-        // For on-change sensors, call registerListener() so that the listener is associated
-        // with the sensor so that flush(listener) can be called on it.
-        try {
-            Log.i(TAG, "testBatch " + sensor.getName());
-            boolean result = mSensorManager.registerListener(listener, sensor,
-                    rateUs, maxBatchReportLatencyUs, handler);
-            assertTrue("registerListener failed " + sensor.getName(), result);
-
-            // Wait for 500 events or N seconds before the test times out.
-            if (sensor.getReportingMode() == Sensor.REPORTING_MODE_CONTINUOUS) {
-                // Wait for approximately the time required to generate these events + a tolerance
-                // of 10 seconds.
-                long timeToWaitUs =
-                  numEvents*(long)(sensor.getMinDelay()/MIN_SAMPLING_FREQUENCY_MULTIPLIER_TOLERANCE)
-                        + maxBatchReportLatencyUs + TIMEOUT_TOLERANCE_US;
-
-                long totalTimeWaitedUs = waitToCollectAllEvents(timeToWaitUs,
-                        maxBatchReportLatencyUs, eventsRemaining);
-                if (eventsRemaining.getCount() > 0) {
-                    failTimedOut(sensor.getName(), (double)totalTimeWaitedUs/1000, numEvents,
-                            (double)sensor.getMinDelay()/1000,
-                            eventsRemaining.getCount(), numEvents - eventsRemaining.getCount());
-                }
-            }
-            Log.i(TAG, "testFlush " + sensor.getName());
-            result = mSensorManager.flush(listener);
-            assertTrue("flush failed " + sensor.getName(), result);
-            boolean collectedAllEvents = flushReceived.await(TIMEOUT_TOLERANCE_US,
-                    TimeUnit.MICROSECONDS);
-            if (!collectedAllEvents) {
-                fail("Timed out waiting for flushCompleteEvent from " + sensor.getName() +
-                        " waitedFor="+ TIMEOUT_TOLERANCE_US/1000 + "ms");
-            }
-            Log.i(TAG, "testBatchAndFlush PASS " + sensor.getName());
-        } finally {
-            mSensorManager.unregisterListener(listener);
-        }
-    }
-
-    // Wait till the CountDownLatch counts down to zero. If the events are not delivered within
-    // timetoWaitUs, wait an additional maxReportLantencyUs and check if the sensor is streaming
-    // data or not. If the sensor is not streaming at all, fail the test or else wait in increments
-    // of maxReportLantencyUs to collect sensor events.
-    private long waitToCollectAllEvents(long timeToWaitUs, int maxReportLatencyUs,
-            CountDownLatch eventsRemaining)
-            throws InterruptedException {
-        boolean collectedAllEvents = false;
-        long totalTimeWaitedUs = 0;
-        long remainingEvents;
-        final long INCREMENTAL_WAIT_US = maxReportLatencyUs + TimeUnit.SECONDS.toMicros(1);
-        do {
-            totalTimeWaitedUs += timeToWaitUs;
-            remainingEvents = eventsRemaining.getCount();
-            collectedAllEvents = eventsRemaining.await(timeToWaitUs, TimeUnit.MICROSECONDS);
-            timeToWaitUs = INCREMENTAL_WAIT_US;
-        } while (!collectedAllEvents &&
-                (remainingEvents - eventsRemaining.getCount() >=(long)INCREMENTAL_WAIT_US/1000000));
-        return totalTimeWaitedUs;
-    }
-
-    // Call registerListener for multiple sensors at a time and call flush.
+    // TODO: after L release move to SensorBatchingTests and run in all sensors with default
+    //       verifications enabled
     public void testBatchAndFlushWithMutipleSensors() throws Exception {
-        final int MAX_SENSORS = 3;
-        int numSensors = mSensorList.size() < MAX_SENSORS ? mSensorList.size() : MAX_SENSORS;
-        if (numSensors == 0) {
+        final int maxSensors = 3;
+        final int maxReportLatencyUs = (int) TimeUnit.SECONDS.toMicros(10);
+        List<Sensor> sensorsToTest = new ArrayList<Sensor>();
+        for (Sensor sensor : mSensorList) {
+            if (sensor.getReportingMode() == Sensor.REPORTING_MODE_CONTINUOUS) {
+                sensorsToTest.add(sensor);
+                if (sensorsToTest.size()  == maxSensors) break;
+            }
+        }
+        final int numSensorsToTest = sensorsToTest.size();
+        if (numSensorsToTest == 0) {
             return;
         }
-        final int numEvents = 500;
-        int rateUs = 0; // DELAY_FASTEST
-        final int maxBatchReportLatencyUs = 10000000;
-        final CountDownLatch eventsRemaining = new CountDownLatch(numSensors * numEvents);
-        final CountDownLatch flushReceived = new CountDownLatch(numSensors);
-        SensorEventListener2 listener = new SensorEventListener2() {
-            @Override
-            public void onSensorChanged(SensorEvent event) {
-                eventsRemaining.countDown();
-            }
 
-            @Override
-            public void onAccuracyChanged(Sensor sensor, int accuracy) {
-            }
-
-            @Override
-            public void onFlushCompleted(Sensor sensor) {
-                flushReceived.countDown();
-            }
-        };
-
-        try {
-            mWakeLock.acquire();
-            StringBuilder registeredSensors = new StringBuilder(30);
-            for (Sensor sensor : mSensorList) {
-                // Skip all non-continuous sensors.
-                if (sensor.getReportingMode() != Sensor.REPORTING_MODE_CONTINUOUS) {
-                    continue;
-                }
-                rateUs = Math.max(sensor.getMinDelay(), rateUs);
-                boolean result = mSensorManager.registerListener(listener, sensor,
-                        SensorManager.SENSOR_DELAY_FASTEST, maxBatchReportLatencyUs);
-                assertTrue("registerListener failed for " + sensor.getName(), result);
-                registeredSensors.append(sensor.getName());
-                registeredSensors.append(" ");
-                if (--numSensors == 0) {
-                    break;
-                }
-            }
-            if (registeredSensors.toString().isEmpty()) {
-                return;
-            }
-
-            Log.i(TAG, "testBatchAndFlushWithMutipleSensors " + registeredSensors);
-            long timeToWaitUs =
-                    numEvents*(long)(rateUs/MIN_SAMPLING_FREQUENCY_MULTIPLIER_TOLERANCE) +
-                    maxBatchReportLatencyUs + TIMEOUT_TOLERANCE_US;
-            long totalTimeWaitedUs = waitToCollectAllEvents(timeToWaitUs, maxBatchReportLatencyUs,
-                    eventsRemaining);
-            if (eventsRemaining.getCount() > 0) {
-                failTimedOut(registeredSensors.toString(), (double)totalTimeWaitedUs/1000,
-                        numEvents, (double)rateUs/1000, eventsRemaining.getCount(),
-                        numEvents - eventsRemaining.getCount());
-            }
-            boolean result = mSensorManager.flush(listener);
-            assertTrue("flush failed " + registeredSensors.toString(), result);
-            boolean collectedFlushEvent =
-                    flushReceived.await(TIMEOUT_TOLERANCE_US, TimeUnit.MICROSECONDS);
-            if (!collectedFlushEvent) {
-                fail("Timed out waiting for flushCompleteEvent from " +
-                      registeredSensors.toString() + " waited for=" + timeToWaitUs/1000 + "ms");
-            }
-            Log.i(TAG, "testBatchAndFlushWithMutipleSensors PASS'd");
-        } finally {
-            mSensorManager.unregisterListener(listener);
-            mWakeLock.release();
+        StringBuilder builder = new StringBuilder();
+        ParallelSensorOperation parallelSensorOperation = new ParallelSensorOperation();
+        for (Sensor sensor : sensorsToTest) {
+            TestSensorEnvironment environment = new TestSensorEnvironment(
+                    getContext(),
+                    sensor,
+                    shouldEmulateSensorUnderLoad(),
+                    SensorManager.SENSOR_DELAY_FASTEST,
+                    maxReportLatencyUs);
+            FlushExecutor executor = new FlushExecutor(environment, 500 /* eventCount */);
+            parallelSensorOperation.add(new TestSensorOperation(environment, executor));
+            builder.append(sensor.getName()).append(", ");
         }
+
+        Log.i(TAG, "Testing batch/flush for sensors: " + builder);
+        parallelSensorOperation.execute(getCurrentTestNode());
     }
 
     private void assertSensorValues(Sensor sensor) {
@@ -583,8 +380,8 @@
         assertTrue("Max resolution must be positive. Resolution=" + sensor.getResolution() +
                 " " + sensor.getName(), sensor.getResolution() >= 0);
         assertNotNull("Vendor name must not be null " + sensor.getName(), sensor.getVendor());
-        assertTrue("Version must be positive version="  + sensor.getVersion() + " " +
-                    sensor.getName(), sensor.getVersion() > 0);
+        assertTrue("Version must be positive version=" + sensor.getVersion() + " " +
+                sensor.getName(), sensor.getVersion() > 0);
         int fifoMaxEventCount = sensor.getFifoMaxEventCount();
         int fifoReservedEventCount = sensor.getFifoReservedEventCount();
         assertTrue(fifoMaxEventCount >= 0);
@@ -617,19 +414,142 @@
         assertEquals(sensors, mSensorManager.getSensors());
     }
 
-    class TriggerListener extends TriggerEventListener {
-        @Override
-        public void onTrigger(TriggerEvent event) {
+    /**
+     * Verifies that a continuous sensor produces events that have timestamps synchronized with
+     * {@link SystemClock#elapsedRealtimeNanos()}.
+     */
+    private void verifyLongActivation(
+            Sensor sensor,
+            int maxReportLatencyUs,
+            ArrayList<Throwable> errorsFound) throws InterruptedException {
+        if (sensor.getReportingMode() != Sensor.REPORTING_MODE_CONTINUOUS) {
+            return;
+        }
+
+        try {
+            TestSensorEnvironment environment = new TestSensorEnvironment(
+                    getContext(),
+                    sensor,
+                    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());
+            operation.execute(getCurrentTestNode());
+        } catch (InterruptedException e) {
+            // propagate so the test can stop
+            throw e;
+        } catch (Throwable e) {
+            errorsFound.add(e);
+            Log.e(TAG, e.getMessage());
         }
     }
 
-    class SensorListener implements SensorEventListener {
-        @Override
-        public void onSensorChanged(SensorEvent event) {
+    /**
+     * Verifies that a client can listen for events, and that
+     * {@link SensorManager#flush(SensorEventListener)} will trigger the appropriate notification
+     * for {@link SensorEventListener2#onFlushCompleted(Sensor)}.
+     */
+    private void verifyRegisterListenerCallFlush(
+            Sensor sensor,
+            Handler handler,
+            ArrayList<Throwable> errorsFound)
+            throws InterruptedException {
+        if (sensor.getReportingMode() == Sensor.REPORTING_MODE_ONE_SHOT) {
+            return;
         }
 
-        @Override
-        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+        try {
+            TestSensorEnvironment environment = new TestSensorEnvironment(
+                    getContext(),
+                    sensor,
+                    shouldEmulateSensorUnderLoad(),
+                    SensorManager.SENSOR_DELAY_FASTEST,
+                    (int) TimeUnit.SECONDS.toMicros(10));
+            FlushExecutor executor = new FlushExecutor(environment, 500 /* eventCount */);
+            TestSensorOperation operation = new TestSensorOperation(environment, executor, handler);
+
+            Log.i(TAG, "Running flush test on: " + sensor.getName());
+            operation.execute(getCurrentTestNode());
+        } catch (InterruptedException e) {
+            // propagate so the test can stop
+            throw e;
+        } catch (Throwable e) {
+            errorsFound.add(e);
+            Log.e(TAG, e.getMessage());
         }
     }
+
+    private void assertOnErrors(List<Throwable> errorsFound) {
+        if (!errorsFound.isEmpty()) {
+            StringBuilder builder = new StringBuilder();
+            for (Throwable error : errorsFound) {
+                builder.append(error.getMessage()).append("\n");
+            }
+            Assert.fail(builder.toString());
+        }
+    }
+
+    /**
+     * A delegate that drives the execution of Batch/Flush tests.
+     * It performs several operations in order:
+     * - registration
+     * - for continuous sensors it first ensures that the FIFO is filled
+     *      - if events do not arrive on time, an assert will be triggered
+     * - requests flush of sensor data
+     * - waits for {@link SensorEventListener2#onFlushCompleted(Sensor)}
+     *      - if the event does not arrive, an assert will be triggered
+     */
+    private class FlushExecutor implements TestSensorOperation.Executor {
+        private final TestSensorEnvironment mEnvironment;
+        private final int mEventCount;
+
+        public FlushExecutor(TestSensorEnvironment environment, int eventCount) {
+            mEnvironment = environment;
+            mEventCount = eventCount;
+        }
+
+        /**
+         * Consider only continuous mode sensors for testing register listener.
+         *
+         * For on-change sensors, we only use
+         * {@link TestSensorManager#registerListener(TestSensorEventListener)} to associate the
+         * listener with the sensor. So that {@link TestSensorManager#requestFlush()} can be
+         * invoked on it.
+         */
+        @Override
+        public void execute(TestSensorManager sensorManager, TestSensorEventListener listener)
+                throws InterruptedException {
+            int sensorReportingMode = mEnvironment.getSensor().getReportingMode();
+            try {
+                CountDownLatch eventLatch = sensorManager.registerListener(listener, mEventCount);
+                if (sensorReportingMode == Sensor.REPORTING_MODE_CONTINUOUS) {
+                    listener.waitForEvents(eventLatch, mEventCount);
+                }
+                CountDownLatch flushLatch = sensorManager.requestFlush();
+                listener.waitForFlushComplete(flushLatch);
+            } finally {
+                sensorManager.unregisterListener();
+            }
+        }
+    }
+
+    private class NullTriggerEventListener extends TriggerEventListener {
+        @Override
+        public void onTrigger(TriggerEvent event) {}
+    }
+
+    private class NullSensorEventListener implements SensorEventListener {
+        @Override
+        public void onSensorChanged(SensorEvent event) {}
+
+        @Override
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {}
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/SensorTestCase.java b/tests/tests/hardware/src/android/hardware/cts/SensorTestCase.java
index 6454678..42b8d33 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SensorTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SensorTestCase.java
@@ -16,17 +16,11 @@
 
 package android.hardware.cts;
 
-import com.android.cts.util.ReportLog;
-import com.android.cts.util.ResultType;
-import com.android.cts.util.ResultUnit;
-
-import android.app.Instrumentation;
-import android.cts.util.DeviceReportLog;
 import android.hardware.Sensor;
-import android.hardware.cts.helpers.SensorStats;
 import android.hardware.cts.helpers.SensorTestStateNotSupportedException;
 import android.hardware.cts.helpers.TestSensorEnvironment;
-import android.hardware.cts.helpers.sensoroperations.ISensorOperation;
+import android.hardware.cts.helpers.reporting.ISensorTestNode;
+import android.hardware.cts.helpers.sensoroperations.SensorOperation;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
@@ -35,25 +29,30 @@
  */
 public abstract class SensorTestCase extends AndroidTestCase {
     // TODO: consolidate all log tags
-    protected final String LOG_TAG = "TestRunner";
+    protected static final String LOG_TAG = "TestRunner";
 
     /**
      * By default tests need to run in a {@link TestSensorEnvironment} that assumes each sensor is
      * running with a load of several listeners, requesting data at different rates.
      *
-     * In a better world the component acting as builder of {@link ISensorOperation} would compute
+     * In a better world the component acting as builder of {@link SensorOperation} would compute
      * this value based on the tests composed.
      *
      * Ideally, each {@link Sensor} object would expose this information to clients.
      */
     private volatile boolean mEmulateSensorUnderLoad = true;
 
+    /**
+     * By default the test class is the root of the test hierarchy.
+     */
+    private volatile ISensorTestNode mCurrentTestNode = new TestClassNode(getClass());
+
     protected SensorTestCase() {}
 
     @Override
-    public void runTest() throws Throwable {
+    public void runBare() throws Throwable {
         try {
-            super.runTest();
+            super.runBare();
         } catch (SensorTestStateNotSupportedException e) {
             // the sensor state is not supported in the device, log a warning and skip the test
             Log.w(LOG_TAG, e.getMessage());
@@ -68,33 +67,24 @@
         return mEmulateSensorUnderLoad;
     }
 
-    /**
-     * Utility method to log selected stats to a {@link ReportLog} object.  The stats must be
-     * a number or an array of numbers.
-     */
-    public static void logSelectedStatsToReportLog(Instrumentation instrumentation, int depth,
-            String[] keys, SensorStats stats) {
-        DeviceReportLog reportLog = new DeviceReportLog(depth);
+    public void setCurrentTestNode(ISensorTestNode value) {
+        mCurrentTestNode = value;
+    }
 
-        for (String key : keys) {
-            Object value = stats.getValue(key);
-            if (value instanceof Integer) {
-                reportLog.printValue(key, (Integer) value, ResultType.NEUTRAL, ResultUnit.NONE);
-            } else if (value instanceof Double) {
-                reportLog.printValue(key, (Double) value, ResultType.NEUTRAL, ResultUnit.NONE);
-            } else if (value instanceof Float) {
-                reportLog.printValue(key, (Float) value, ResultType.NEUTRAL, ResultUnit.NONE);
-            } else if (value instanceof double[]) {
-                reportLog.printArray(key, (double[]) value, ResultType.NEUTRAL, ResultUnit.NONE);
-            } else if (value instanceof float[]) {
-                float[] tmpFloat = (float[]) value;
-                double[] tmpDouble = new double[tmpFloat.length];
-                for (int i = 0; i < tmpDouble.length; i++) tmpDouble[i] = tmpFloat[i];
-                reportLog.printArray(key, tmpDouble, ResultType.NEUTRAL, ResultUnit.NONE);
-            }
+    protected ISensorTestNode getCurrentTestNode() {
+        return mCurrentTestNode;
+    }
+
+    private class TestClassNode implements ISensorTestNode {
+        private final Class<?> mTestClass;
+
+        public TestClassNode(Class<?> testClass) {
+            mTestClass = testClass;
         }
 
-        reportLog.printSummary("summary", 0, ResultType.NEUTRAL, ResultUnit.NONE);
-        reportLog.deliverReportToHost(instrumentation);
+        @Override
+        public String getName() {
+            return mTestClass.getSimpleName();
+        }
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/SingleSensorTests.java b/tests/tests/hardware/src/android/hardware/cts/SingleSensorTests.java
index 3bd4a03..42cbdfb 100644
--- a/tests/tests/hardware/src/android/hardware/cts/SingleSensorTests.java
+++ b/tests/tests/hardware/src/android/hardware/cts/SingleSensorTests.java
@@ -105,7 +105,7 @@
      */
     public void testSensorProperties() {
         // sensor type: [getMinDelay()]
-        Map<Integer, Object[]> expectedProperties = new HashMap<Integer, Object[]>(3);
+        Map<Integer, Object[]> expectedProperties = new HashMap<>(3);
         expectedProperties.put(Sensor.TYPE_ACCELEROMETER, new Object[]{10000});
         expectedProperties.put(Sensor.TYPE_GYROSCOPE, new Object[]{10000});
         expectedProperties.put(Sensor.TYPE_MAGNETIC_FIELD, new Object[]{100000});
@@ -541,25 +541,21 @@
                 sensorType,
                 shouldEmulateSensorUnderLoad(),
                 rateUs);
-        TestSensorOperation op = new TestSensorOperation(environment, 5, TimeUnit.SECONDS);
+        TestSensorOperation op =
+                TestSensorOperation.createOperation(environment, 5, TimeUnit.SECONDS);
         op.addDefaultVerifications();
-        op.setLogEvents(true);
-        try {
-            op.execute();
-        } finally {
-            SensorStats.logStats(TAG, op.getStats());
 
-            String sensorRate;
-            if (rateUs == SensorManager.SENSOR_DELAY_FASTEST) {
-                sensorRate = "fastest";
-            } else {
-                sensorRate = String.format("%.0fhz", environment.getFrequencyHz());
-            }
+        try {
+            op.execute(getCurrentTestNode());
+        } finally {
+            SensorStats stats = op.getStats();
+            stats.log(TAG);
+
             String fileName = String.format(
                     "single_%s_%s.txt",
                     SensorStats.getSanitizedSensorName(environment.getSensor()),
-                    sensorRate);
-            SensorStats.logStatsToFile(fileName, op.getStats());
+                    environment.getFrequencyString());
+            stats.logToFile(fileName);
         }
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/CollectingSensorEventListener.java b/tests/tests/hardware/src/android/hardware/cts/helpers/CollectingSensorEventListener.java
deleted file mode 100644
index ca7d133..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/CollectingSensorEventListener.java
+++ /dev/null
@@ -1,101 +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.hardware.cts.helpers;
-
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener2;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A {@link TestSensorEventListener} which collects events to be processed after the test is run.
- * This should only be used for short tests.
- */
-public class CollectingSensorEventListener extends TestSensorEventListener {
-    private final ArrayList<TestSensorEvent> mSensorEventsList = new ArrayList<TestSensorEvent>();
-
-    /**
-     * Constructs a {@link CollectingSensorEventListener} with an additional
-     * {@link SensorEventListener2}.
-     */
-    public CollectingSensorEventListener(SensorEventListener2 listener) {
-        super(listener);
-    }
-
-    /**
-     * Constructs a {@link CollectingSensorEventListener}.
-     */
-    public CollectingSensorEventListener() {
-        this(null);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onSensorChanged(SensorEvent event) {
-        super.onSensorChanged(event);
-        synchronized (mSensorEventsList) {
-            mSensorEventsList.add(new TestSensorEvent(event));
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p>
-     * Clears the event queue before starting.
-     * </p>
-     */
-    @Override
-    public void waitForEvents(int eventCount) throws InterruptedException {
-        clearEvents();
-        super.waitForEvents(eventCount);
-    }
-
-    /**
-     * {@inheritDoc}
-     * <p>
-     * Clears the event queue before starting.
-     * </p>
-     */
-    @Override
-    public void waitForEvents(long duration, TimeUnit timeUnit) throws InterruptedException {
-        clearEvents();
-        super.waitForEvents(duration, timeUnit);
-    }
-
-    /**
-     * Get the {@link TestSensorEvent} array from the event queue.
-     */
-    public List<TestSensorEvent> getEvents() {
-        synchronized (mSensorEventsList) {
-            return Collections.unmodifiableList(mSensorEventsList);
-        }
-    }
-
-    /**
-     * Clear the event queue.
-     */
-    public void clearEvents() {
-        synchronized (mSensorEventsList) {
-            mSensorEventsList.clear();
-        }
-    }
-}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCalibratedUncalibratedVerifier.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCalibratedUncalibratedVerifier.java
index b3b8559..d3dcf37 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCalibratedUncalibratedVerifier.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCalibratedUncalibratedVerifier.java
@@ -35,6 +35,8 @@
 
     private final TestSensorManager mCalibratedSensorManager;
     private final TestSensorManager mUncalibratedSensorManager;
+    private final TestSensorEventListener mCalibratedTestListener;
+    private final TestSensorEventListener mUncalibratedTestListener;
     private final float mThreshold;
 
     public SensorCalibratedUncalibratedVerifier(
@@ -43,6 +45,8 @@
             float threshold) {
         mCalibratedSensorManager = new TestSensorManager(calibratedEnvironment);
         mUncalibratedSensorManager = new TestSensorManager(uncalibratedEnvironment);
+        mCalibratedTestListener = new TestSensorEventListener(calibratedEnvironment);
+        mUncalibratedTestListener = new TestSensorEventListener(uncalibratedEnvironment);
         mThreshold = threshold;
     }
 
@@ -50,10 +54,8 @@
      * Executes the operation: it collects the data and run verifications on it.
      */
     public void execute() throws Throwable {
-        CollectingSensorEventListener calibratedTestListener = new CollectingSensorEventListener();
-        CollectingSensorEventListener uncalibratedTestListener = new CollectingSensorEventListener();
-        mCalibratedSensorManager.registerListener(calibratedTestListener);
-        mUncalibratedSensorManager.registerListener(uncalibratedTestListener);
+        mCalibratedSensorManager.registerListener(mCalibratedTestListener);
+        mUncalibratedSensorManager.registerListener(mUncalibratedTestListener);
 
         Thread.sleep(TimeUnit.SECONDS.toMillis(10));
 
@@ -61,8 +63,8 @@
         mUncalibratedSensorManager.unregisterListener();
 
         verifyMeasurements(
-                calibratedTestListener.getEvents(),
-                uncalibratedTestListener.getEvents(),
+                mCalibratedTestListener.getCollectedEvents(),
+                mUncalibratedTestListener.getCollectedEvents(),
                 mThreshold);
     }
 
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java
index a79e5b1..490e965 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorCtsHelper.java
@@ -192,7 +192,7 @@
             TestSensorEnvironment environment,
             String extras) {
         return String.format(
-                "%s | sensor='%s', samplingPeriodUs=%d, maxReportLatencyUs=%d | %s",
+                "%s | sensor='%s', samplingPeriod=%dus, maxReportLatency=%dus | %s",
                 label,
                 environment.getSensor().getName(),
                 environment.getRequestedSamplingPeriodUs(),
@@ -219,6 +219,25 @@
     }
 
     /**
+     * Sanitizes a string so it can be used in file names.
+     *
+     * @param value The string to sanitize.
+     * @return The sanitized string.
+     *
+     * @throws SensorTestPlatformException If the string cannot be sanitized.
+     */
+    public static String sanitizeStringForFileName(String value)
+            throws SensorTestPlatformException {
+        String sanitizedValue = value.replaceAll("[^a-zA-Z0-9_\\-]", "_");
+        if (sanitizedValue.matches("_*")) {
+            throw new SensorTestPlatformException(
+                    "Unable to sanitize string '%s' for file name.",
+                    value);
+        }
+        return sanitizedValue;
+    }
+
+    /**
      * Ensures that the directory structure represented by the given {@link File} is created.
      */
     private static File createDirectoryStructure(File directoryStructure) throws IOException {
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java
index 7be6c0c..8067aed 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorStats.java
@@ -17,7 +17,7 @@
 package android.hardware.cts.helpers;
 
 import android.hardware.Sensor;
-import android.hardware.cts.helpers.sensoroperations.ISensorOperation;
+import android.hardware.cts.helpers.sensoroperations.SensorOperation;
 import android.util.Log;
 
 import java.io.BufferedWriter;
@@ -34,28 +34,29 @@
 import java.util.Set;
 
 /**
- * Class used to store stats related to {@link ISensorOperation}s.  Sensor stats may be linked
+ * Class used to store stats related to {@link SensorOperation}s. Sensor stats may be linked
  * together so that they form a tree.
  */
 public class SensorStats {
     public static final String DELIMITER = "__";
 
-    public static final String FIRST_TIMESTAMP_KEY = "first_timestamp";
-    public static final String LAST_TIMESTAMP_KEY = "last_timestamp";
     public static final String ERROR = "error";
-    public static final String EVENT_COUNT_KEY = "event_count";
     public static final String EVENT_GAP_COUNT_KEY = "event_gap_count";
     public static final String EVENT_GAP_POSITIONS_KEY = "event_gap_positions";
     public static final String EVENT_OUT_OF_ORDER_COUNT_KEY = "event_out_of_order_count";
     public static final String EVENT_OUT_OF_ORDER_POSITIONS_KEY = "event_out_of_order_positions";
+    public static final String EVENT_TIME_SYNCHRONIZATION_COUNT_KEY =
+            "event_time_synchronization_count";
+    public static final String EVENT_TIME_SYNCHRONIZATION_POSITIONS_KEY =
+            "event_time_synchronization_positions";
     public static final String FREQUENCY_KEY = "frequency";
     public static final String JITTER_95_PERCENTILE_PERCENT_KEY = "jitter_95_percentile_percent";
     public static final String MEAN_KEY = "mean";
     public static final String STANDARD_DEVIATION_KEY = "standard_deviation";
     public static final String MAGNITUDE_KEY = "magnitude";
 
-    private final Map<String, Object> mValues = new HashMap<String, Object>();
-    private final Map<String, SensorStats> mSensorStats = new HashMap<String, SensorStats>();
+    private final Map<String, Object> mValues = new HashMap<>();
+    private final Map<String, SensorStats> mSensorStats = new HashMap<>();
 
     /**
      * Add a value.
@@ -72,7 +73,7 @@
 
     /**
      * Add a nested {@link SensorStats}. This is useful for keeping track of stats in a
-     * {@link ISensorOperation} tree.
+     * {@link SensorOperation} tree.
      *
      * @param key the key
      * @param stats the sub {@link SensorStats} object.
@@ -103,13 +104,13 @@
     /**
      * Flattens the map and all sub {@link SensorStats} objects. Keys will be flattened using
      * {@value #DELIMITER}. For example, if a sub {@link SensorStats} is added with key
-     * {@code "key1"} containing the key value pair {@code ("key2", "value")}, the flattened map
-     * will contain the entry {@code ("key1__key2", "value")}.
+     * {@code "key1"} containing the key value pair {@code \("key2", "value"\)}, the flattened map
+     * will contain the entry {@code \("key1__key2", "value"\)}.
      *
      * @return a {@link Map} containing all stats from the value and sub {@link SensorStats}.
      */
     public synchronized Map<String, Object> flatten() {
-        final Map<String, Object> flattenedMap = new HashMap<String, Object>(mValues);
+        final Map<String, Object> flattenedMap = new HashMap<>(mValues);
         for (Entry<String, SensorStats> statsEntry : mSensorStats.entrySet()) {
             for (Entry<String, Object> valueEntry : statsEntry.getValue().flatten().entrySet()) {
                 String key = statsEntry.getKey() + DELIMITER + valueEntry.getKey();
@@ -122,8 +123,8 @@
     /**
      * Utility method to log the stats to the logcat.
      */
-    public static void logStats(String tag, SensorStats stats) {
-        final Map<String, Object> flattened = stats.flatten();
+    public void log(String tag) {
+        final Map<String, Object> flattened = flatten();
         for (String key : getSortedKeys(flattened)) {
             Object value = flattened.get(key);
             Log.v(tag, String.format("%s: %s", key, getValueString(value)));
@@ -133,39 +134,29 @@
     /**
      * Utility method to log the stats to a file. Will overwrite the file if it already exists.
      */
-    public static void logStatsToFile(String fileName, SensorStats stats) throws IOException {
+    public void logToFile(String fileName) throws IOException {
         File statsDirectory = SensorCtsHelper.getSensorTestDataDirectory("stats/");
         File logFile = new File(statsDirectory, fileName);
-        final BufferedWriter writer =
-                new BufferedWriter(new FileWriter(logFile, false /* append */));
-        final Map<String, Object> flattened = stats.flatten();
-        try {
+        final Map<String, Object> flattened = flatten();
+        FileWriter fileWriter = new FileWriter(logFile, false /* append */);
+        try (BufferedWriter writer = new BufferedWriter(fileWriter)) {
             for (String key : getSortedKeys(flattened)) {
                 Object value = flattened.get(key);
                 writer.write(String.format("%s: %s\n", key, getValueString(value)));
             }
-        } finally {
-            writer.flush();
-            writer.close();
         }
     }
 
     /**
      * Provides a sanitized sensor name, that can be used in file names.
-     * See {@link #logStatsToFile(String, SensorStats)}.
+     * See {@link #logToFile(String)}.
      */
-    public static String getSanitizedSensorName(Sensor sensor) throws IOException {
-        String sensorType = sensor.getStringType();
-        String sanitizedSensorType = sensorType.replaceAll("[^a-zA-Z0-9_\\-]", "_");
-        if (sanitizedSensorType.matches("_*")) {
-            throw new IOException("Unable to sanitize sensor type (" + sensorType + "). This is a"
-                    + " 'test framework' issue and the sanitation routine must be fixed.");
-        }
-        return sanitizedSensorType;
+    public static String getSanitizedSensorName(Sensor sensor) throws SensorTestPlatformException {
+        return SensorCtsHelper.sanitizeStringForFileName(sensor.getStringType());
     }
 
     private static List<String> getSortedKeys(Map<String, Object> flattenedStats) {
-        List<String> keys = new ArrayList<String>(flattenedStats.keySet());
+        List<String> keys = new ArrayList<>(flattenedStats.keySet());
         Collections.sort(keys);
         return keys;
     }
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/SensorTestPlatformException.java b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorTestPlatformException.java
new file mode 100644
index 0000000..b230749
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/SensorTestPlatformException.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.hardware.cts.helpers;
+
+/**
+ * Exception that indicates an issue in the Sensor Test Platform.
+ * It usually constitutes a bug in the platform that needs to be fixed.
+ */
+public class SensorTestPlatformException extends Exception {
+    public SensorTestPlatformException(String format, Object ... params) {
+        this(String.format(format, params));
+    }
+
+    public SensorTestPlatformException(String message) {
+        super(message + " (This is a 'sensor test platform' issue, please report it so it can be fixed)");
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEnvironment.java b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEnvironment.java
index 8f33f92..7d10f91 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEnvironment.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEnvironment.java
@@ -19,15 +19,22 @@
 import android.content.Context;
 import android.hardware.Sensor;
 import android.hardware.SensorManager;
-import android.hardware.cts.helpers.sensoroperations.ISensorOperation;
+import android.hardware.cts.helpers.sensoroperations.SensorOperation;
 
 import java.util.concurrent.TimeUnit;
 
 /**
- * A class that encapsulates base environment information for the {@link ISensorOperation}.
+ * A class that encapsulates base environment information for the {@link SensorOperation}.
  * The environment is self contained and carries its state around all the sensor test framework.
  */
 public class TestSensorEnvironment {
+
+    /**
+     * It represents the fraction of the expected sampling frequency, at which the sensor can
+     * actually produce events.
+     */
+    private static final float MAXIMUM_EXPECTED_SAMPLING_FREQUENCY_MULTIPLIER = 0.9f;
+
     private final Context mContext;
     private final Sensor mSensor;
     private final boolean mSensorMightHaveMoreListeners;
@@ -40,7 +47,10 @@
      * @param context The context for the test
      * @param sensorType The type of the sensor under test
      * @param samplingPeriodUs The requested collection period for the sensor under test
+     *
+     * @deprecated Use variants with {@link Sensor} objects.
      */
+    @Deprecated
     public TestSensorEnvironment(Context context, int sensorType, int samplingPeriodUs) {
         this(context, sensorType, false /* sensorMightHaveMoreListeners */, samplingPeriodUs);
     }
@@ -52,7 +62,10 @@
      * @param sensorType The type of the sensor under test
      * @param samplingPeriodUs The requested collection period for the sensor under test
      * @param maxReportLatencyUs The requested collection report latency for the sensor under test
+     *
+     * @deprecated Use variants with {@link Sensor} objects.
      */
+    @Deprecated
     public TestSensorEnvironment(
             Context context,
             int sensorType,
@@ -72,7 +85,10 @@
      * @param sensorType The type of the sensor under test
      * @param sensorMightHaveMoreListeners Whether the sensor under test is acting under load
      * @param samplingPeriodUs The requested collection period for the sensor under test
+     *
+     * @deprecated Use variants with {@link Sensor} objects.
      */
+    @Deprecated
     public TestSensorEnvironment(
             Context context,
             int sensorType,
@@ -93,7 +109,10 @@
      * @param sensorMightHaveMoreListeners Whether the sensor under test is acting under load
      * @param samplingPeriodUs The requested collection period for the sensor under test
      * @param maxReportLatencyUs The requested collection report latency for the sensor under test
+     *
+     * @deprecated Use variants with {@link Sensor} objects.
      */
+    @Deprecated
     public TestSensorEnvironment(
             Context context,
             int sensorType,
@@ -112,6 +131,26 @@
      *
      * @param context The context for the test
      * @param sensor The sensor under test
+     * @param samplingPeriodUs The requested collection period for the sensor under test
+     * @param maxReportLatencyUs The requested collection report latency for the sensor under test
+     */
+    public TestSensorEnvironment(
+            Context context,
+            Sensor sensor,
+            int samplingPeriodUs,
+            int maxReportLatencyUs) {
+        this(context,
+                sensor,
+                false /* sensorMightHaveMoreListeners */,
+                samplingPeriodUs,
+                maxReportLatencyUs);
+    }
+
+    /**
+     * Constructs an environment for sensor testing.
+     *
+     * @param context The context for the test
+     * @param sensor The sensor under test
      * @param sensorMightHaveMoreListeners Whether the sensor under test is acting under load (this
      *                                     usually implies that there are several listeners
      *                                     requesting different sampling periods)
@@ -160,6 +199,17 @@
     }
 
     /**
+     * @return A string representing the frequency equivalent to
+     * {@link #getRequestedSamplingPeriodUs()}.
+     */
+    public String getFrequencyString() {
+        if (mSamplingPeriodUs == SensorManager.SENSOR_DELAY_FASTEST) {
+            return "fastest";
+        }
+        return String.format("%.0fhz", getFrequencyHz());
+    }
+
+    /**
      * @return The requested collection max batch report latency in microseconds.
      */
     public int getMaxReportLatencyUs() {
@@ -171,7 +221,8 @@
      * data at different sampling rates (the rates are unknown); false otherwise.
      */
     public boolean isSensorSamplingRateOverloaded() {
-        return mSensorMightHaveMoreListeners && mSamplingPeriodUs != SensorManager.SENSOR_DELAY_FASTEST;
+        return mSensorMightHaveMoreListeners
+                && mSamplingPeriodUs != SensorManager.SENSOR_DELAY_FASTEST;
     }
 
     /**
@@ -197,6 +248,15 @@
     }
 
     /**
+     * @return The actual sampling period at which a sensor can sample data. This value is a
+     *         fraction of {@link #getExpectedSamplingPeriodUs()}.
+     */
+    public int getMaximumExpectedSamplingPeriodUs() {
+        int expectedSamplingPeriodUs = getExpectedSamplingPeriodUs();
+        return (int) (expectedSamplingPeriodUs / MAXIMUM_EXPECTED_SAMPLING_FREQUENCY_MULTIPLIER);
+    }
+
+    /**
      * @return The number of axes in the coordinate system of the sensor under test.
      */
     public int getSensorAxesCount() {
@@ -266,6 +326,17 @@
         return TimeUnit.SECONDS.toNanos(reportLatencySec);
     }
 
+    @Override
+    public String toString() {
+        return String.format(
+                "Sensor='%s', SamplingRateOverloaded=%s, SamplingPeriod=%sus, "
+                        + "MaxReportLatency=%sus",
+                mSensor,
+                isSensorSamplingRateOverloaded(),
+                mSamplingPeriodUs,
+                mMaxReportLatencyUs);
+    }
+
     /**
      * Return true if {@link #getRequestedSamplingPeriodUs()} is not one of
      * {@link SensorManager#SENSOR_DELAY_GAME}, {@link SensorManager#SENSOR_DELAY_UI}, or
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEvent.java b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEvent.java
index e8500f1..86b2436 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEvent.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEvent.java
@@ -21,6 +21,8 @@
 import android.hardware.SensorEventListener2;
 import android.os.SystemClock;
 
+import java.util.Arrays;
+
 /**
  * Class for holding information about individual {@link SensorEvent}s.
  */
@@ -75,4 +77,14 @@
         this.accuracy = accuracy;
         this.values = values;
     }
+
+    @Override
+    public String toString() {
+        return String.format(
+                "Timestamp=%sns, ReceivedTimestamp=%sns, Accuracy=%s, Values=%s",
+                this.timestamp,
+                this.receivedTimestamp,
+                this.accuracy,
+                Arrays.toString(this.values));
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEventListener.java b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEventListener.java
index 9b3a5e4..7b1a499 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEventListener.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorEventListener.java
@@ -21,12 +21,21 @@
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener2;
+import android.os.Handler;
+import android.os.Looper;
 import android.os.SystemClock;
-import android.util.Log;
 
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * A {@link SensorEventListener2} which performs operations such as waiting for a specific number of
@@ -36,51 +45,39 @@
  */
 public class TestSensorEventListener implements SensorEventListener2 {
     public static final String LOG_TAG = "TestSensorEventListener";
-    private static final long EVENT_TIMEOUT_US = TimeUnit.MICROSECONDS.convert(5, TimeUnit.SECONDS);
-    private static final long FLUSH_TIMEOUT_US = TimeUnit.MICROSECONDS.convert(5, TimeUnit.SECONDS);
 
-    private final SensorEventListener2 mListener;
+    private static final long EVENT_TIMEOUT_US = TimeUnit.SECONDS.toMicros(5);
+    private static final long FLUSH_TIMEOUT_US = TimeUnit.SECONDS.toMicros(10);
 
-    private volatile CountDownLatch mEventLatch;
-    private volatile CountDownLatch mFlushLatch = new CountDownLatch(1);
-    private volatile TestSensorEnvironment mEnvironment;
-    private volatile boolean mLogEvents;
+    private final List<TestSensorEvent> mCollectedEvents = new ArrayList<>();
+    private final List<CountDownLatch> mEventLatches = new ArrayList<>();
+    private final List<CountDownLatch> mFlushLatches = new ArrayList<>();
+    private final AtomicInteger mEventsReceivedOutsideHandler = new AtomicInteger();
+
+    private final Handler mHandler;
+    private final TestSensorEnvironment mEnvironment;
+
+    /**
+     * @deprecated Use {@link TestSensorEventListener(TestSensorEnvironment)}.
+     */
+    @Deprecated
+    public TestSensorEventListener() {
+        this(null /* environment */);
+    }
 
     /**
      * Construct a {@link TestSensorEventListener}.
      */
-    public TestSensorEventListener() {
-        this(null);
+    public TestSensorEventListener(TestSensorEnvironment environment) {
+        this(environment, null /* handler */);
     }
 
     /**
-     * Construct a {@link TestSensorEventListener} that wraps a {@link SensorEventListener2}.
+     * Construct a {@link TestSensorEventListener}.
      */
-    public TestSensorEventListener(SensorEventListener2 listener) {
-        if (listener != null) {
-            mListener = listener;
-        } else {
-            // use a Null Object to simplify handling the listener
-            mListener = new SensorEventListener2() {
-                public void onFlushCompleted(Sensor sensor) {}
-                public void onSensorChanged(SensorEvent sensorEvent) {}
-                public void onAccuracyChanged(Sensor sensor, int i) {}
-            };
-        }
-    }
-
-    /**
-     * Set the sensor, rate, and batch report latency used for the assertions.
-     */
-    public void setEnvironment(TestSensorEnvironment environment) {
+    public TestSensorEventListener(TestSensorEnvironment environment, Handler handler) {
         mEnvironment = environment;
-    }
-
-    /**
-     * Set whether or not to log events
-     */
-    public void setLogEvents(boolean log) {
-        mLogEvents = log;
+        mHandler = handler;
     }
 
     /**
@@ -88,19 +85,15 @@
      */
     @Override
     public void onSensorChanged(SensorEvent event) {
-        mListener.onSensorChanged(event);
-        if (mLogEvents) {
-            Log.v(LOG_TAG, String.format(
-                    "Sensor %d: sensor_timestamp=%dns, received_timestamp=%dns, values=%s",
-                    mEnvironment.getSensor().getType(),
-                    event.timestamp,
-                    SystemClock.elapsedRealtimeNanos(),
-                    Arrays.toString(event.values)));
+        long timestampNs = SystemClock.elapsedRealtimeNanos();
+        checkHandler();
+        synchronized (mCollectedEvents) {
+            mCollectedEvents.add(new TestSensorEvent(event, timestampNs));
         }
-
-        CountDownLatch eventLatch = mEventLatch;
-        if(eventLatch != null) {
-            eventLatch.countDown();
+        synchronized (mEventLatches) {
+            for (CountDownLatch latch : mEventLatches) {
+                latch.countDown();
+            }
         }
     }
 
@@ -109,7 +102,32 @@
      */
     @Override
     public void onAccuracyChanged(Sensor sensor, int accuracy) {
-        mListener.onAccuracyChanged(sensor, accuracy);
+        checkHandler();
+    }
+
+    /**
+     * @param eventCount
+     * @return A CountDownLatch initialzed with eventCount and decremented as sensor events arrive
+     * for this listerner.
+     */
+    public CountDownLatch getLatchForSensorEvents(int eventCount) {
+        CountDownLatch latch = new CountDownLatch(eventCount);
+        synchronized (mEventLatches) {
+            mEventLatches.add(latch);
+        }
+        return latch;
+    }
+
+    /**
+     * @return A CountDownLatch initialzed with 1 and decremented as a flush complete arrives
+     * for this listerner.
+     */
+    public CountDownLatch getLatchForFlushCompleteEvent() {
+        CountDownLatch latch = new CountDownLatch(1);
+        synchronized (mFlushLatches) {
+            mFlushLatches.add(latch);
+        }
+        return latch;
     }
 
     /**
@@ -117,12 +135,71 @@
      */
     @Override
     public void onFlushCompleted(Sensor sensor) {
-        CountDownLatch latch = mFlushLatch;
-        mFlushLatch = new CountDownLatch(1);
-        if(latch != null) {
-            latch.countDown();
+        checkHandler();
+        synchronized (mFlushLatches) {
+            for (CountDownLatch latch : mFlushLatches) {
+                latch.countDown();
+            }
         }
-        mListener.onFlushCompleted(sensor);
+    }
+
+    /**
+     * @return The handler (if any) associated with the instance.
+     */
+    public Handler getHandler() {
+        return mHandler;
+    }
+
+    /**
+     * @return A list of {@link TestSensorEvent}s collected by the listener.
+     */
+    public List<TestSensorEvent> getCollectedEvents() {
+        synchronized (mCollectedEvents){
+            return Collections.unmodifiableList(mCollectedEvents);
+        }
+    }
+
+    /**
+     * Clears the internal list of collected {@link TestSensorEvent}s.
+     */
+    public void clearEvents() {
+        synchronized (mCollectedEvents) {
+            mCollectedEvents.clear();
+        }
+    }
+
+
+    /**
+     * Utility method to log the collected events to a file.
+     * It will overwrite the file if it already exists, the file is created in a relative directory
+     * named 'events' under the sensor test directory (part of external storage).
+     */
+    public void logCollectedEventsToFile(String fileName) throws IOException {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Sensor='").append(mEnvironment.getSensor()).append("', ");
+        builder.append("SamplingRateOverloaded=")
+                .append(mEnvironment.isSensorSamplingRateOverloaded()).append(", ");
+        builder.append("RequestedSamplingPeriod=")
+                .append(mEnvironment.getRequestedSamplingPeriodUs()).append("us, ");
+        builder.append("MaxReportLatency=")
+                .append(mEnvironment.getMaxReportLatencyUs()).append("us");
+
+        synchronized (mCollectedEvents) {
+            for (TestSensorEvent event : mCollectedEvents) {
+                builder.append("\n");
+                builder.append("Timestamp=").append(event.timestamp).append("ns, ");
+                builder.append("ReceivedTimestamp=").append(event.receivedTimestamp).append("ns, ");
+                builder.append("Accuracy=").append(event.accuracy).append(", ");
+                builder.append("Values=").append(Arrays.toString(event.values));
+            }
+        }
+
+        File eventsDirectory = SensorCtsHelper.getSensorTestDataDirectory("events/");
+        File logFile = new File(eventsDirectory, fileName);
+        FileWriter fileWriter = new FileWriter(logFile, false /* append */);
+        try (BufferedWriter writer = new BufferedWriter(fileWriter)) {
+            writer.write(builder.toString());
+        }
     }
 
     /**
@@ -130,14 +207,20 @@
      *
      * @throws AssertionError if there was a timeout after {@link #FLUSH_TIMEOUT_US} &micro;s
      */
-    public void waitForFlushComplete() throws InterruptedException {
-        CountDownLatch latch = mFlushLatch;
-        if(latch == null) {
-            return;
+    public void waitForFlushComplete(CountDownLatch latch) throws InterruptedException {
+        clearEvents();
+        try {
+            String message = SensorCtsHelper.formatAssertionMessage(
+                    "WaitForFlush",
+                    mEnvironment,
+                    "timeout=%dus",
+                    FLUSH_TIMEOUT_US);
+            Assert.assertTrue(message, latch.await(FLUSH_TIMEOUT_US, TimeUnit.MICROSECONDS));
+        } finally {
+            synchronized (mFlushLatches) {
+                mFlushLatches.remove(latch);
+            }
         }
-        Assert.assertTrue(
-                SensorCtsHelper.formatAssertionMessage("WaitForFlush", mEnvironment),
-                latch.await(FLUSH_TIMEOUT_US, TimeUnit.MICROSECONDS));
     }
 
     /**
@@ -145,23 +228,32 @@
      *
      * @throws AssertionError if there was a timeout after {@link #FLUSH_TIMEOUT_US} &micro;s
      */
-    public void waitForEvents(int eventCount) throws InterruptedException {
-        mEventLatch = new CountDownLatch(eventCount);
+    public void waitForEvents(CountDownLatch latch, int eventCount) throws InterruptedException {
+        clearEvents();
         try {
-            int rateUs = mEnvironment.getExpectedSamplingPeriodUs();
-            // Timeout is 2 * event count * expected period + batch timeout + default wait
-            long timeoutUs = (2 * eventCount * rateUs)
+            long samplingPeriodUs = mEnvironment.getMaximumExpectedSamplingPeriodUs();
+            // timeout is 2 * event count * expected period + batch timeout + default wait
+            // we multiply by two as not to raise an error in this function even if the events are
+            // streaming at a lower rate than expected, as long as it's not streaming twice as slow
+            // as expected
+            long timeoutUs = (2 * eventCount * samplingPeriodUs)
                     + mEnvironment.getMaxReportLatencyUs()
                     + EVENT_TIMEOUT_US;
-            String message = SensorCtsHelper.formatAssertionMessage(
-                    "WaitForEvents",
-                    mEnvironment,
-                    "requested:%d, received:%d",
-                    eventCount,
-                    eventCount - mEventLatch.getCount());
-            Assert.assertTrue(message, mEventLatch.await(timeoutUs, TimeUnit.MICROSECONDS));
+            boolean success = latch.await(timeoutUs, TimeUnit.MICROSECONDS);
+            if (!success) {
+                String message = SensorCtsHelper.formatAssertionMessage(
+                        "WaitForEvents",
+                        mEnvironment,
+                        "requested=%d, received=%d, timeout=%dus",
+                        eventCount,
+                        eventCount - latch.getCount(),
+                        timeoutUs);
+                Assert.fail(message);
+            }
         } finally {
-            mEventLatch = null;
+            synchronized (mEventLatches) {
+                mEventLatches.remove(latch);
+            }
         }
     }
 
@@ -171,4 +263,28 @@
     public void waitForEvents(long duration, TimeUnit timeUnit) throws InterruptedException {
         SensorCtsHelper.sleep(duration, timeUnit);
     }
+
+    /**
+     * Asserts that sensor events arrived in the proper thread if a {@link Handler} was associated
+     * with the current instance.
+     *
+     * If no events were received this assertion will be evaluated to {@code true}.
+     */
+    public void assertEventsReceivedInHandler() {
+        int eventsOutsideHandler = mEventsReceivedOutsideHandler.get();
+        String message = String.format(
+                "Events arrived outside the associated Looper. Expected=0, Found=%d",
+                eventsOutsideHandler);
+        Assert.assertEquals(message, 0 /* expected */, eventsOutsideHandler);
+    }
+
+    /**
+     * Keeps track of the number of events that arrived in a different {@link Looper} than the one
+     * associated with the {@link TestSensorEventListener}.
+     */
+    private void checkHandler() {
+        if (mHandler != null && mHandler.getLooper() != Looper.myLooper()) {
+            mEventsReceivedOutsideHandler.incrementAndGet();
+        }
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorManager.java b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorManager.java
index dc40ff4..2468bd1 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorManager.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/TestSensorManager.java
@@ -19,35 +19,20 @@
 import junit.framework.Assert;
 
 import android.content.Context;
-import android.hardware.Sensor;
 import android.hardware.SensorEventListener;
-import android.hardware.SensorEventListener2;
 import android.hardware.SensorManager;
 import android.util.Log;
 
-import java.util.concurrent.TimeUnit;
+import java.util.concurrent.CountDownLatch;
 
 /**
- * A test class that performs the actions of {@link SensorManager} on a single sensor. This
- * class allows for a single sensor to be registered and unregistered as well as performing
- * operations such as flushing the sensor events and gathering events. This class also manages
- * performing the test verifications for the sensor manager.
- * <p>
- * This class requires that operations are performed in the following order:
- * <p><ul>
- * <li>{@link #registerListener(TestSensorEventListener)}</li>
- * <li>{@link #startFlush()}, {@link #waitForFlushCompleted()}, or {@link #flush()}.
- * <li>{@link #unregisterListener()}</li>
- * </ul><p>Or:</p><ul>
- * <li>{@link #runSensor(TestSensorEventListener, int)}</li>
- * </ul><p>Or:</p><ul>
- * <li>{@link #runSensor(TestSensorEventListener, long, TimeUnit)}</li>
- * </ul><p>
- * If methods are called outside of this order, they will print a warning to the log and then
- * return. Both {@link #runSensor(TestSensorEventListener, int)}} and
- * {@link #runSensor(TestSensorEventListener, long, TimeUnit)} will perform the appropriate
- * set up and tear down.
- * <p>
+ * A test class that performs the actions of {@link SensorManager} on a single sensor.
+ * This class allows for a single sensor to be registered and unregistered as well as performing
+ * operations such as flushing the sensor events and gathering events.
+ * This class also manages performing the test verifications for the sensor manager.
+ *
+ * NOTE: this class is expected to mirror {@link SensorManager} operations, and perform the
+ * required test verifications along with them.
  */
 public class TestSensorManager {
     private static final String LOG_TAG = "TestSensorManager";
@@ -55,7 +40,7 @@
     private final SensorManager mSensorManager;
     private final TestSensorEnvironment mEnvironment;
 
-    private TestSensorEventListener mTestSensorEventListener;
+    private volatile TestSensorEventListener mTestSensorEventListener;
 
     /**
      * @deprecated Use {@link #TestSensorManager(TestSensorEnvironment)} instead.
@@ -90,19 +75,45 @@
             return;
         }
 
-        mTestSensorEventListener = listener != null ? listener : new TestSensorEventListener();
-        mTestSensorEventListener.setEnvironment(mEnvironment);
-
+        mTestSensorEventListener = listener;
         String message = SensorCtsHelper.formatAssertionMessage("registerListener", mEnvironment);
         boolean result = mSensorManager.registerListener(
                 mTestSensorEventListener,
                 mEnvironment.getSensor(),
                 mEnvironment.getRequestedSamplingPeriodUs(),
-                mEnvironment.getMaxReportLatencyUs());
+                mEnvironment.getMaxReportLatencyUs(),
+                mTestSensorEventListener.getHandler());
         Assert.assertTrue(message, result);
     }
 
     /**
+     * Register the listener. This method will perform a no-op if the sensor is already registered.
+     *
+     * @return A CountDownLatch initialized with eventCount which is used to wait for sensor
+     * events.
+     * @throws AssertionError if there was an error registering the listener with the
+     * {@link SensorManager}
+     */
+    public CountDownLatch registerListener(TestSensorEventListener listener, int eventCount) {
+        if (mTestSensorEventListener != null) {
+            Log.w(LOG_TAG, "Listener already registered, returning.");
+            return null;
+        }
+
+        CountDownLatch latch = listener.getLatchForSensorEvents(eventCount);
+        mTestSensorEventListener = listener;
+        String message = SensorCtsHelper.formatAssertionMessage("registerListener", mEnvironment);
+        boolean result = mSensorManager.registerListener(
+                mTestSensorEventListener,
+                mEnvironment.getSensor(),
+                mEnvironment.getRequestedSamplingPeriodUs(),
+                mEnvironment.getMaxReportLatencyUs(),
+                mTestSensorEventListener.getHandler());
+        Assert.assertTrue(message, result);
+        return latch;
+    }
+
+    /**
      * Unregister the listener. This method will perform a no-op if the sensor is not registered.
      */
     public void unregisterListener() {
@@ -110,136 +121,27 @@
             Log.w(LOG_TAG, "No listener registered, returning.");
             return;
         }
-
-        mSensorManager.unregisterListener(
-                mTestSensorEventListener,
-                mEnvironment.getSensor());
+        mSensorManager.unregisterListener(mTestSensorEventListener, mEnvironment.getSensor());
+        mTestSensorEventListener.assertEventsReceivedInHandler();
         mTestSensorEventListener = null;
     }
 
     /**
-     * Wait for a specific number of events.
-     */
-    public void waitForEvents(int eventCount) throws InterruptedException {
-        if (mTestSensorEventListener == null) {
-            Log.w(LOG_TAG, "No listener registered, returning.");
-            return;
-        }
-        mTestSensorEventListener.waitForEvents(eventCount);
-    }
-
-    /**
-     * Wait for a specific duration.
-     */
-    public void waitForEvents(long duration, TimeUnit timeUnit) throws InterruptedException {
-        if (mTestSensorEventListener == null) {
-            Log.w(LOG_TAG, "No listener registered, returning.");
-            return;
-        }
-        mTestSensorEventListener.waitForEvents(duration, timeUnit);
-    }
-
-    /**
      * Call {@link SensorManager#flush(SensorEventListener)}. This method will perform a no-op if
      * the sensor is not registered.
      *
-     * @throws AssertionError if {@link SensorManager#flush(SensorEventListener)} returns false
+     * @return A CountDownLatch which can be used to wait for a flush complete event.
+     * @throws AssertionError if {@link SensorManager#flush(SensorEventListener)} fails.
      */
-    public void startFlush() {
+    public CountDownLatch requestFlush() {
         if (mTestSensorEventListener == null) {
-            return;
+            Log.w(LOG_TAG, "No listener registered, returning.");
+            return null;
         }
-
+        CountDownLatch latch = mTestSensorEventListener.getLatchForFlushCompleteEvent();
         Assert.assertTrue(
                 SensorCtsHelper.formatAssertionMessage("Flush", mEnvironment),
                 mSensorManager.flush(mTestSensorEventListener));
-    }
-
-    /**
-     * Wait for {@link SensorEventListener2#onFlushCompleted(Sensor)} to be called. This method will
-     * perform a no-op if the sensor is not registered.
-     *
-     * @throws AssertionError if there is a time out
-     * @throws InterruptedException if the thread was interrupted
-     */
-    public void waitForFlushCompleted() throws InterruptedException {
-        if (mTestSensorEventListener == null) {
-            return;
-        }
-        mTestSensorEventListener.waitForFlushComplete();
-    }
-
-    /**
-     * Call {@link SensorManager#flush(SensorEventListener)} and wait for
-     * {@link SensorEventListener2#onFlushCompleted(Sensor)} to be called. This method will perform
-     * a no-op if the sensor is not registered.
-     *
-     * @throws AssertionError if {@link SensorManager#flush(SensorEventListener)} returns false or
-     * if there is a time out
-     * @throws InterruptedException if the thread was interrupted
-     */
-    public void flush() throws InterruptedException {
-        if (mTestSensorEventListener == null) {
-            return;
-        }
-        startFlush();
-        waitForFlushCompleted();
-    }
-
-    /**
-     * Register a listener, wait for a specific number of events, and then unregister the listener.
-     */
-    public void runSensor(TestSensorEventListener listener, int eventCount)
-            throws InterruptedException {
-        if (mTestSensorEventListener != null) {
-            Log.w(LOG_TAG, "Listener already registered, returning.");
-            return;
-        }
-        try {
-            registerListener(listener);
-            waitForEvents(eventCount);
-        } finally {
-            unregisterListener();
-        }
-    }
-
-    /**
-     * Register a listener, wait for a specific duration, and then unregister the listener.
-     */
-    public void runSensor(TestSensorEventListener listener, long duration, TimeUnit timeUnit)
-            throws InterruptedException {
-        if (mTestSensorEventListener != null) {
-            Log.w(LOG_TAG, "Listener already registered, returning.");
-            return;
-        }
-        try {
-            registerListener(listener);
-            waitForEvents(duration, timeUnit);
-        } finally {
-            unregisterListener();
-        }
-    }
-
-    /**
-     * Registers a listener, waits for a specific duration, calls flush, and waits for flush to
-     * complete.
-     */
-    public void runSensorAndFlush(
-            TestSensorEventListener listener,
-            long duration,
-            TimeUnit timeUnit) throws InterruptedException {
-        if (mTestSensorEventListener != null) {
-            Log.w(LOG_TAG, "Listener already registered, returning.");
-            return;
-        }
-
-        try {
-            registerListener(listener);
-            SensorCtsHelper.sleep(duration, timeUnit);
-            startFlush();
-            listener.waitForFlushComplete();
-        } finally {
-            unregisterListener();
-        }
+        return latch;
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/ValidatingSensorEventListener.java b/tests/tests/hardware/src/android/hardware/cts/helpers/ValidatingSensorEventListener.java
deleted file mode 100644
index 299f470..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/ValidatingSensorEventListener.java
+++ /dev/null
@@ -1,82 +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.hardware.cts.helpers;
-
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener2;
-import android.hardware.cts.helpers.sensorverification.ISensorVerification;
-
-import java.util.Collection;
-import java.util.LinkedList;
-
-/**
- * A {@link TestSensorEventListener} which performs validations on the received events on the fly.
- * This class is useful for long running tests where it is not practical to store all the events to
- * be processed after.
- */
-public class ValidatingSensorEventListener extends TestSensorEventListener {
-
-    private final Collection<ISensorVerification> mVerifications =
-            new LinkedList<ISensorVerification>();
-
-    /**
-     * Construct a {@link ValidatingSensorEventListener} with an additional
-     * {@link SensorEventListener2}.
-     */
-    public ValidatingSensorEventListener(SensorEventListener2 listener,
-            ISensorVerification ... verifications) {
-        super(listener);
-        for (ISensorVerification verification : verifications) {
-            mVerifications.add(verification);
-        }
-    }
-
-    /**
-     * Construct a {@link ValidatingSensorEventListener} with an additional
-     * {@link SensorEventListener2}.
-     */
-    public ValidatingSensorEventListener(SensorEventListener2 listener,
-            Collection<ISensorVerification> verifications) {
-        this(listener, verifications.toArray(new ISensorVerification[0]));
-    }
-
-    /**
-     * Construct a {@link ValidatingSensorEventListener}.
-     */
-    public ValidatingSensorEventListener(ISensorVerification ... verifications) {
-        this(null, verifications);
-    }
-
-    /**
-     * Construct a {@link ValidatingSensorEventListener}.
-     */
-    public ValidatingSensorEventListener(Collection<ISensorVerification> verifications) {
-        this(null, verifications);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public void onSensorChanged(SensorEvent event) {
-        TestSensorEvent testEvent = new TestSensorEvent(event);
-        for (ISensorVerification verification : mVerifications) {
-            verification.addSensorEvent(testEvent);
-        }
-        super.onSensorChanged(event);
-    }
-}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/reporting/ISensorTestNode.java b/tests/tests/hardware/src/android/hardware/cts/helpers/reporting/ISensorTestNode.java
new file mode 100644
index 0000000..d34c0af
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/reporting/ISensorTestNode.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.cts.helpers.reporting;
+
+import android.hardware.cts.helpers.SensorTestPlatformException;
+
+/**
+ * Interface that represents a node in a hierarchy built by the sensor test platform.
+ */
+// TODO: this is an intermediate state to introduce a full-blown centralized recorder data produced
+//       by sensor tests
+public interface ISensorTestNode {
+
+    /**
+     * Provides a name (tag) that can be used to identify the current node.
+     */
+    String getName() throws SensorTestPlatformException;
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AbstractSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AbstractSensorOperation.java
deleted file mode 100644
index 5b969f2..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AbstractSensorOperation.java
+++ /dev/null
@@ -1,60 +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.hardware.cts.helpers.sensoroperations;
-
-import android.hardware.cts.helpers.SensorStats;
-
-/**
- * A {@link ISensorOperation} which contains a common implementation for gathering
- * {@link SensorStats}.
- */
-public abstract class AbstractSensorOperation implements ISensorOperation {
-
-    private final SensorStats mStats = new SensorStats();
-
-    /**
-     * Wrapper around {@link SensorStats#addSensorStats(String, SensorStats)}
-     */
-    protected void addSensorStats(String key, SensorStats stats) {
-        mStats.addSensorStats(key, stats);
-    }
-
-    /**
-     * Wrapper around {@link SensorStats#addSensorStats(String, SensorStats)} that allows an index
-     * to be added. This is useful for {@link ISensorOperation}s that have many iterations or child
-     * operations. The key added is in the form {@code key + "_" + index} where index may be zero
-     * padded.
-     */
-    protected void addSensorStats(String key, int index, SensorStats stats) {
-        addSensorStats(String.format("%s_%03d", key, index), stats);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public SensorStats getStats() {
-        return mStats;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public abstract ISensorOperation clone();
-
-}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
index 88e4954..436a7cf 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/AlarmOperation.java
@@ -22,14 +22,14 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.reporting.ISensorTestNode;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 
 import java.util.concurrent.TimeUnit;
 
 /**
- * An {@link ISensorOperation} which performs another {@link ISensorOperation} and then wakes up
+ * An {@link SensorOperation} which performs another {@link SensorOperation} and then wakes up
  * after a specified period of time and waits for the child operation to complete.
  * <p>
  * This operation can be used to allow the device to go to sleep and wake it up after a specified
@@ -40,11 +40,11 @@
  * but wake the device one time at the specified period.
  * </p>
  */
-public class AlarmOperation extends AbstractSensorOperation {
+public class AlarmOperation extends SensorOperation {
     private static final String ACTION = "AlarmOperationAction";
     private static final String WAKE_LOCK_TAG = "AlarmOperationWakeLock";
 
-    private final ISensorOperation mOperation;
+    private final SensorOperation mOperation;
     private final Context mContext;
     private final long mSleepDuration;
     private final TimeUnit mTimeUnit;
@@ -55,13 +55,17 @@
     /**
      * Constructor for {@link DelaySensorOperation}
      *
-     * @param operation the child {@link ISensorOperation} to perform after the delay
+     * @param operation the child {@link SensorOperation} to perform after the delay
      * @param context the context used to access the alarm manager
      * @param sleepDuration the amount of time to sleep
      * @param timeUnit the unit of the duration
      */
-    public AlarmOperation(ISensorOperation operation, Context context, long sleepDuration,
+    public AlarmOperation(
+            SensorOperation operation,
+            Context context,
+            long sleepDuration,
             TimeUnit timeUnit) {
+        super(operation.getStats());
         mOperation = operation;
         mContext = context;
         mSleepDuration = sleepDuration;
@@ -72,7 +76,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void execute() throws InterruptedException {
+    public void execute(ISensorTestNode parent) throws InterruptedException {
         // Start alarm
         IntentFilter intentFilter = new IntentFilter(ACTION);
         BroadcastReceiver receiver = new BroadcastReceiver() {
@@ -92,7 +96,7 @@
 
         // Execute operation
         try {
-            mOperation.execute();
+            mOperation.execute(asTestNode(parent));
         } finally {
             releaseWakeLock();
         }
@@ -102,14 +106,6 @@
      * {@inheritDoc}
      */
     @Override
-    public SensorStats getStats() {
-        return mOperation.getStats();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
     public AlarmOperation clone() {
         return new AlarmOperation(mOperation, mContext, mSleepDuration, mTimeUnit);
     }
@@ -120,7 +116,7 @@
      */
     private synchronized void acquireWakeLock() {
         // Don't acquire wake lock if the operation has already completed.
-        if (mCompleted == true || mWakeLock != null) {
+        if (mCompleted || mWakeLock != null) {
             return;
         }
         PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java
index b4d1f23..8c52222 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/DelaySensorOperation.java
@@ -17,30 +17,28 @@
 package android.hardware.cts.helpers.sensoroperations;
 
 import android.hardware.cts.helpers.SensorCtsHelper;
-import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.reporting.ISensorTestNode;
 
 import java.util.concurrent.TimeUnit;
 
 /**
- * An {@link ISensorOperation} which delays for a specified period of time before performing another
- * {@link ISensorOperation}.
+ * An {@link SensorOperation} which delays for a specified period of time before performing another
+ * {@link SensorOperation}.
  */
-public class DelaySensorOperation implements ISensorOperation {
-    private final ISensorOperation mOperation;
+public class DelaySensorOperation extends SensorOperation {
+    private final SensorOperation mOperation;
     private final long mDelay;
     private final TimeUnit mTimeUnit;
 
     /**
      * Constructor for {@link DelaySensorOperation}
      *
-     * @param operation the child {@link ISensorOperation} to perform after the delay
+     * @param operation the child {@link SensorOperation} to perform after the delay
      * @param delay the amount of time to delay
      * @param timeUnit the unit of the delay
      */
-    public DelaySensorOperation(ISensorOperation operation, long delay, TimeUnit timeUnit) {
-        if (operation == null || timeUnit == null) {
-            throw new IllegalArgumentException("Arguments cannot be null");
-        }
+    public DelaySensorOperation(SensorOperation operation, long delay, TimeUnit timeUnit) {
+        super(operation.getStats());
         mOperation = operation;
         mDelay = delay;
         mTimeUnit = timeUnit;
@@ -50,17 +48,9 @@
      * {@inheritDoc}
      */
     @Override
-    public void execute() throws InterruptedException {
+    public void execute(ISensorTestNode parent) throws InterruptedException {
         SensorCtsHelper.sleep(mDelay, mTimeUnit);
-        mOperation.execute();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public SensorStats getStats() {
-        return mOperation.getStats();
+        mOperation.execute(asTestNode(parent));
     }
 
     /**
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/FakeSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/FakeSensorOperation.java
index bb64dfa..238956b 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/FakeSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/FakeSensorOperation.java
@@ -17,16 +17,17 @@
 package android.hardware.cts.helpers.sensoroperations;
 
 import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.reporting.ISensorTestNode;
 
 import junit.framework.Assert;
 
 import java.util.concurrent.TimeUnit;
 
 /**
- * A fake {@ISensorOperation} that will run for a specified time and then pass or fail. Useful when
- * debugging the framework.
+ * A fake {@link SensorOperation} that will run for a specified time and then pass or fail. Useful
+ * when debugging the framework.
  */
-public class FakeSensorOperation extends AbstractSensorOperation {
+public class FakeSensorOperation extends SensorOperation {
     private static final int NANOS_PER_MILLI = 1000000;
 
     private final boolean mFail;
@@ -56,7 +57,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void execute() {
+    public void execute(ISensorTestNode parent) {
         long delayNs = TimeUnit.NANOSECONDS.convert(mDelay, mTimeUnit);
         try {
             Thread.sleep(delayNs / NANOS_PER_MILLI, (int) (delayNs % NANOS_PER_MILLI));
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ISensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ISensorOperation.java
deleted file mode 100644
index 62a4e9e..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ISensorOperation.java
+++ /dev/null
@@ -1,63 +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.hardware.cts.helpers.sensoroperations;
-
-import android.hardware.cts.helpers.SensorStats;
-
-/**
- * Interface used by all sensor operations. This allows for complex operations such as chaining
- * operations together or running operations in parallel.
- * <p>
- * Certain restrictions exist for {@link ISensorOperation}s:
- * <p><ul>
- * <li>{@link #execute()} should only be called once and behavior is undefined for subsequent calls.
- * Once {@link #execute()} is called, the class should not be modified. Generally, there is no
- * synchronization for operations.</li>
- * <li>{@link #getStats()} should only be called after {@link #execute()}. If it is called before,
- * the returned value is undefined.</li>
- * <li>{@link #clone()} may be called any time and should return an operation with the same
- * parameters as the original.</li>
- * </ul>
- */
-public interface ISensorOperation {
-
-    /**
-     * Executes the sensor operation.  This may throw {@link RuntimeException}s such as
-     * {@link AssertionError}s.
-     *
-     * NOTE: the operation is expected to handle interruption by:
-     * - cleaning up on {@link InterruptedException}
-     * - propagating the exception down the stack
-     */
-    public void execute() throws InterruptedException;
-
-    /**
-     * Get the stats for the operation.
-     *
-     * @return The {@link SensorStats} for the operation.
-     */
-    public SensorStats getStats();
-
-    /**
-     * Clones the {@link ISensorOperation}. The implementation should also clone all child
-     * operations, so that a cloned operation will run with the exact same parameters as the
-     * original. The stats should not be cloned.
-     *
-     * @return The cloned {@link ISensorOperation}
-     */
-    public ISensorOperation clone();
-}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ParallelSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ParallelSensorOperation.java
index 5a4466c..ed70b70 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ParallelSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/ParallelSensorOperation.java
@@ -19,10 +19,10 @@
 import junit.framework.Assert;
 
 import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.reporting.ISensorTestNode;
 import android.os.SystemClock;
 
 import java.util.ArrayList;
-import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.Callable;
 import java.util.concurrent.ExecutionException;
@@ -33,14 +33,14 @@
 import java.util.concurrent.TimeoutException;
 
 /**
- * A {@link ISensorOperation} that executes a set of children {@link ISensorOperation}s in parallel.
+ * A {@link SensorOperation} that executes a set of children {@link SensorOperation}s in parallel.
  * The children are run in parallel but are given an index label in the order they are added. This
- * class can be combined to compose complex {@link ISensorOperation}s.
+ * class can be combined to compose complex {@link SensorOperation}s.
  */
-public class ParallelSensorOperation extends AbstractSensorOperation {
+public class ParallelSensorOperation extends SensorOperation {
     public static final String STATS_TAG = "parallel";
 
-    private final List<ISensorOperation> mOperations = new LinkedList<ISensorOperation>();
+    private final ArrayList<SensorOperation> mOperations = new ArrayList<>();
     private final Long mTimeout;
     private final TimeUnit mTimeUnit;
 
@@ -65,10 +65,10 @@
     }
 
     /**
-     * Add a set of {@link ISensorOperation}s.
+     * Add a set of {@link SensorOperation}s.
      */
-    public void add(ISensorOperation ... operations) {
-        for (ISensorOperation operation : operations) {
+    public void add(SensorOperation ... operations) {
+        for (SensorOperation operation : operations) {
             if (operation == null) {
                 throw new IllegalArgumentException("Arguments cannot be null");
             }
@@ -77,11 +77,11 @@
     }
 
     /**
-     * Executes the {@link ISensorOperation}s in parallel. If an exception occurs one or more
+     * Executes the {@link SensorOperation}s in parallel. If an exception occurs one or more
      * operations, the first exception will be thrown once all operations are completed.
      */
     @Override
-    public void execute() throws InterruptedException {
+    public void execute(final ISensorTestNode parent) throws InterruptedException {
         int operationsCount = mOperations.size();
         ThreadPoolExecutor executor = new ThreadPoolExecutor(
                 operationsCount,
@@ -92,12 +92,13 @@
         executor.allowCoreThreadTimeOut(true);
         executor.prestartAllCoreThreads();
 
-        ArrayList<Future<ISensorOperation>> futures = new ArrayList<Future<ISensorOperation>>();
-        for (final ISensorOperation operation : mOperations) {
-            Future<ISensorOperation> future = executor.submit(new Callable<ISensorOperation>() {
+        final ISensorTestNode currentNode = asTestNode(parent);
+        ArrayList<Future<SensorOperation>> futures = new ArrayList<>();
+        for (final SensorOperation operation : mOperations) {
+            Future<SensorOperation> future = executor.submit(new Callable<SensorOperation>() {
                 @Override
-                public ISensorOperation call() throws Exception {
-                    operation.execute();
+                public SensorOperation call() throws Exception {
+                    operation.execute(currentNode);
                     return operation;
                 }
             });
@@ -111,12 +112,12 @@
         }
 
         boolean hasAssertionErrors = false;
-        ArrayList<Integer> timeoutIndices = new ArrayList<Integer>();
-        ArrayList<Throwable> exceptions = new ArrayList<Throwable>();
+        ArrayList<Integer> timeoutIndices = new ArrayList<>();
+        ArrayList<Throwable> exceptions = new ArrayList<>();
         for (int i = 0; i < operationsCount; ++i) {
-            Future<ISensorOperation> future = futures.get(i);
+            Future<SensorOperation> future = futures.get(i);
             try {
-                ISensorOperation operation = getFutureResult(future, executionTimeNs);
+                SensorOperation operation = getFutureResult(future, executionTimeNs);
                 addSensorStats(STATS_TAG, i, operation.getStats());
             } catch (ExecutionException e) {
                 // extract the exception thrown by the worker thread
@@ -151,7 +152,7 @@
     @Override
     public ParallelSensorOperation clone() {
         ParallelSensorOperation operation = new ParallelSensorOperation();
-        for (ISensorOperation subOperation : mOperations) {
+        for (SensorOperation subOperation : mOperations) {
             operation.add(subOperation.clone());
         }
         return operation;
@@ -160,7 +161,7 @@
     /**
      * Helper method that waits for a {@link Future} to complete, and returns its result.
      */
-    private ISensorOperation getFutureResult(Future<ISensorOperation> future, Long timeoutNs)
+    private SensorOperation getFutureResult(Future<SensorOperation> future, Long timeoutNs)
             throws ExecutionException, TimeoutException, InterruptedException {
         if (timeoutNs == null) {
             return future.get();
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java
index 3d682fe..5b333b8 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/RepeatingSensorOperation.java
@@ -17,42 +17,44 @@
 package android.hardware.cts.helpers.sensoroperations;
 
 import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.SensorTestPlatformException;
+import android.hardware.cts.helpers.reporting.ISensorTestNode;
 
 /**
- * A {@link ISensorOperation} that executes a single {@link ISensorOperation} a given number of
- * times. This class can be combined to compose complex {@link ISensorOperation}s.
+ * A {@link SensorOperation} that executes a single {@link SensorOperation} a given number of
+ * times. This class can be combined to compose complex {@link SensorOperation}s.
  */
-public class RepeatingSensorOperation extends AbstractSensorOperation {
+public class RepeatingSensorOperation extends SensorOperation {
     public static final String STATS_TAG = "repeating";
 
-    private final ISensorOperation mOperation;
+    private final SensorOperation mOperation;
     private final int mIterations;
 
     /**
      * Constructor for {@link RepeatingSensorOperation}.
      *
-     * @param operation the {@link ISensorOperation} to run.
+     * @param operation the {@link SensorOperation} to run.
      * @param iterations the number of iterations to run the operation for.
      */
-    public RepeatingSensorOperation(ISensorOperation operation, int iterations) {
+    public RepeatingSensorOperation(SensorOperation operation, int iterations) {
         if (operation == null) {
             throw new IllegalArgumentException("Arguments cannot be null");
         }
         mOperation = operation;
         mIterations = iterations;
-
     }
 
     /**
-     * Executes the {@link ISensorOperation}s the given number of times. If an exception occurs
-     * in one iterations, it is thrown and all subsequent iterations will not run.
+     * Executes the {@link SensorOperation}s the given number of times. If an exception occurs in
+     * one iterations, it is thrown and all subsequent iterations will not run.
      */
     @Override
-    public void execute() throws InterruptedException {
+    public void execute(ISensorTestNode parent) throws InterruptedException {
+        ISensorTestNode currentNode = asTestNode(parent);
         for(int i = 0; i < mIterations; ++i) {
-            ISensorOperation operation = mOperation.clone();
+            SensorOperation operation = mOperation.clone();
             try {
-                operation.execute();
+                operation.execute(new TestNode(currentNode, i));
             } catch (AssertionError e) {
                 String msg = String.format("Iteration %d failed: \"%s\"", i, e.getMessage());
                 getStats().addValue(SensorStats.ERROR, msg);
@@ -70,4 +72,19 @@
     public RepeatingSensorOperation clone() {
         return new RepeatingSensorOperation(mOperation.clone(), mIterations);
     }
+
+    private class TestNode implements ISensorTestNode {
+        private final ISensorTestNode mTestNode;
+        private final int mIteration;
+
+        public TestNode(ISensorTestNode parent, int iteration) {
+            mTestNode = asTestNode(parent);
+            mIteration = iteration;
+        }
+
+        @Override
+        public String getName() throws SensorTestPlatformException {
+            return String.format("%s-iteration%d", mTestNode.getName(), mIteration);
+        }
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperation.java
new file mode 100644
index 0000000..66604d3
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperation.java
@@ -0,0 +1,110 @@
+/*
+ * 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.hardware.cts.helpers.sensoroperations;
+
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.SensorTestPlatformException;
+import android.hardware.cts.helpers.reporting.ISensorTestNode;
+
+/**
+ * Base class used by all sensor operations. This allows for complex operations such as chaining
+ * operations together or running operations in parallel.
+ * <p>
+ * Certain restrictions exist for {@link SensorOperation}s:
+ * <p><ul>
+ * <li>{@link #execute(ISensorTestNode)} should only be called once and behavior is undefined for
+ * subsequent calls.
+ * Once {@link #execute(ISensorTestNode)} is called, the class should not be modified. Generally,
+ * there is no synchronization for operations.</li>
+ * <li>{@link #getStats()} should only be called after {@link #execute(ISensorTestNode)}. If it
+ * is called before, the returned value is undefined.</li>
+ * <li>{@link #clone()} may be called any time and should return an operation with the same
+ * parameters as the original.</li>
+ * </ul>
+ */
+public abstract class SensorOperation {
+    private final SensorStats mStats;
+
+    protected SensorOperation() {
+        this(new SensorStats());
+    }
+
+    protected SensorOperation(SensorStats stats) {
+        mStats = stats;
+    }
+
+    /**
+     * @return The {@link SensorStats} for the operation.
+     */
+    public SensorStats getStats() {
+        return mStats;
+    }
+
+    /**
+     * Executes the sensor operation.
+     * This may throw {@link RuntimeException}s such as {@link AssertionError}s.
+     *
+     * NOTE: the operation is expected to handle interruption by:
+     * - cleaning up on {@link InterruptedException}
+     * - propagating the exception down the stack
+     */
+    public abstract void execute(ISensorTestNode parent) throws InterruptedException;
+
+    /**
+     * @return The cloned {@link SensorOperation}.
+     *
+     * NOTE: The implementation should also clone all child operations, so that a cloned operation
+     * will run with the exact same parameters as the original. The stats should not be cloned.
+     */
+    public abstract SensorOperation clone();
+
+    /**
+     * Wrapper around {@link SensorStats#addSensorStats(String, SensorStats)}
+     */
+    protected void addSensorStats(String key, SensorStats stats) {
+        getStats().addSensorStats(key, stats);
+    }
+
+    /**
+     * Wrapper around {@link SensorStats#addSensorStats(String, SensorStats)} that allows an index
+     * to be added. This is useful for {@link SensorOperation}s that have many iterations or child
+     * operations. The key added is in the form {@code key + "_" + index} where index may be zero
+     * padded.
+     */
+    protected void addSensorStats(String key, int index, SensorStats stats) {
+        addSensorStats(String.format("%s_%03d", key, index), stats);
+    }
+
+    protected ISensorTestNode asTestNode(ISensorTestNode parent) {
+        return new SensorTestNode(parent, this);
+    }
+
+    private class SensorTestNode implements ISensorTestNode {
+        private final ISensorTestNode mParent;
+        private final SensorOperation mOperation;
+
+        public SensorTestNode(ISensorTestNode parent, SensorOperation operation) {
+            mParent = parent;
+            mOperation = operation;
+        }
+
+        @Override
+        public String getName() throws SensorTestPlatformException {
+            return mParent.getName() + "-" + mOperation.getClass().getSimpleName();
+        }
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java
index bc48725..30da9a0 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SensorOperationTest.java
@@ -19,18 +19,27 @@
 import junit.framework.TestCase;
 
 import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.SensorTestPlatformException;
+import android.hardware.cts.helpers.reporting.ISensorTestNode;
 
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 /**
- * Tests for the primitive {@link ISensorOperation}s including {@link DelaySensorOperation},
+ * Tests for the primitive {@link SensorOperation}s including {@link DelaySensorOperation},
  * {@link ParallelSensorOperation}, {@link RepeatingSensorOperation} and
  * {@link SequentialSensorOperation}.
  */
 public class SensorOperationTest extends TestCase {
     private static final long TEST_DURATION_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(5);
 
+    private final ISensorTestNode mTestNode = new ISensorTestNode() {
+        @Override
+        public String getName() throws SensorTestPlatformException {
+            return "SensorOperationUnitTest";
+        }
+    };
+
     /**
      * Test that the {@link FakeSensorOperation} functions correctly. Other tests in this class
      * rely on this operation.
@@ -38,18 +47,18 @@
     public void testFakeSensorOperation() throws InterruptedException {
         final int opDurationMs = 100;
 
-        ISensorOperation op = new FakeSensorOperation(opDurationMs, TimeUnit.MILLISECONDS);
+        SensorOperation op = new FakeSensorOperation(opDurationMs, TimeUnit.MILLISECONDS);
 
         assertFalse(op.getStats().flatten().containsKey("executed"));
         long start = System.currentTimeMillis();
-        op.execute();
+        op.execute(mTestNode);
         long duration = System.currentTimeMillis() - start;
         assertTrue(Math.abs(opDurationMs - duration) < TEST_DURATION_THRESHOLD_MS);
         assertTrue(op.getStats().flatten().containsKey("executed"));
 
         op = new FakeSensorOperation(true, 0, TimeUnit.MILLISECONDS);
         try {
-            op.execute();
+            op.execute(mTestNode);
             fail("AssertionError expected");
         } catch (AssertionError e) {
             // Expected
@@ -65,12 +74,12 @@
         final int subOpDurationMs = 100;
 
         FakeSensorOperation subOp = new FakeSensorOperation(subOpDurationMs, TimeUnit.MILLISECONDS);
-        ISensorOperation op = new DelaySensorOperation(subOp, opDurationMs, TimeUnit.MILLISECONDS);
+        SensorOperation op = new DelaySensorOperation(subOp, opDurationMs, TimeUnit.MILLISECONDS);
 
         long startMs = System.currentTimeMillis();
-        op.execute();
-        long dirationMs = System.currentTimeMillis() - startMs;
-        long durationDeltaMs = Math.abs(opDurationMs + subOpDurationMs - dirationMs);
+        op.execute(mTestNode);
+        long durationMs = System.currentTimeMillis() - startMs;
+        long durationDeltaMs = Math.abs(opDurationMs + subOpDurationMs - durationMs);
         assertTrue(durationDeltaMs < TEST_DURATION_THRESHOLD_MS);
     }
 
@@ -83,7 +92,7 @@
 
         ParallelSensorOperation op = new ParallelSensorOperation();
         for (int i = 0; i < subOpCount; i++) {
-            ISensorOperation subOp = new FakeSensorOperation(subOpDurationMs,
+            SensorOperation subOp = new FakeSensorOperation(subOpDurationMs,
                     TimeUnit.MILLISECONDS);
             op.add(subOp);
         }
@@ -92,7 +101,7 @@
         assertEquals(0, statsKeys.size());
 
         long start = System.currentTimeMillis();
-        op.execute();
+        op.execute(mTestNode);
         long durationMs = System.currentTimeMillis() - start;
         long durationDeltaMs = Math.abs(subOpDurationMs - durationMs);
         String message = String.format(
@@ -124,7 +133,7 @@
         ParallelSensorOperation op = new ParallelSensorOperation();
         for (int i = 0; i < subOpCount; i++) {
             // Trigger failures in the 5th, 55th operations at t=5ms, t=55ms
-            ISensorOperation subOp = new FakeSensorOperation(i % 50 == 5, i, TimeUnit.MILLISECONDS);
+            SensorOperation subOp = new FakeSensorOperation(i % 50 == 5, i, TimeUnit.MILLISECONDS);
             op.add(subOp);
         }
 
@@ -132,7 +141,7 @@
         assertEquals(0, statsKeys.size());
 
         try {
-            op.execute();
+            op.execute(mTestNode);
             fail("AssertionError expected");
         } catch (AssertionError e) {
             // Expected
@@ -164,7 +173,7 @@
         ParallelSensorOperation op = new ParallelSensorOperation(1, TimeUnit.SECONDS);
         for (int i = 0; i < subOpCount; i++) {
             // Trigger timeouts in the 5th, 55th operations (5 seconds vs 1 seconds)
-            ISensorOperation subOp = new FakeSensorOperation(i % 50 == 5 ? 5 : 0, TimeUnit.SECONDS);
+            SensorOperation subOp = new FakeSensorOperation(i % 50 == 5 ? 5 : 0, TimeUnit.SECONDS);
             op.add(subOp);
         }
 
@@ -172,7 +181,7 @@
         assertEquals(0, statsKeys.size());
 
         try {
-            op.execute();
+            op.execute(mTestNode);
             fail("AssertionError expected");
         } catch (AssertionError e) {
             // Expected
@@ -196,14 +205,14 @@
         final int iterations = 10;
         final int subOpDurationMs = 100;
 
-        ISensorOperation subOp = new FakeSensorOperation(subOpDurationMs, TimeUnit.MILLISECONDS);
-        ISensorOperation op = new RepeatingSensorOperation(subOp, iterations);
+        SensorOperation subOp = new FakeSensorOperation(subOpDurationMs, TimeUnit.MILLISECONDS);
+        SensorOperation op = new RepeatingSensorOperation(subOp, iterations);
 
         Set<String> statsKeys = op.getStats().flatten().keySet();
         assertEquals(0, statsKeys.size());
 
         long start = System.currentTimeMillis();
-        op.execute();
+        op.execute(mTestNode);
         long duration = System.currentTimeMillis() - start;
         assertTrue(Math.abs(subOpDurationMs * iterations - duration) < TEST_DURATION_THRESHOLD_MS);
 
@@ -223,13 +232,13 @@
         final int iterations = 100;
         final int failCount = 75;
 
-        ISensorOperation subOp = new FakeSensorOperation(0, TimeUnit.MILLISECONDS) {
+        SensorOperation subOp = new FakeSensorOperation(0, TimeUnit.MILLISECONDS) {
             private int mExecutedCount = 0;
             private SensorStats mFakeStats = new SensorStats();
 
             @Override
-            public void execute() {
-                super.execute();
+            public void execute(ISensorTestNode parent) {
+                super.execute(parent);
                 mExecutedCount++;
 
                 if (failCount == mExecutedCount) {
@@ -249,13 +258,13 @@
                 return mFakeStats;
             }
         };
-        ISensorOperation op = new RepeatingSensorOperation(subOp, iterations);
+        SensorOperation op = new RepeatingSensorOperation(subOp, iterations);
 
         Set<String> statsKeys = op.getStats().flatten().keySet();
         assertEquals(0, statsKeys.size());
 
         try {
-            op.execute();
+            op.execute(mTestNode);
             fail("AssertionError expected");
         } catch (AssertionError e) {
             // Expected
@@ -283,7 +292,7 @@
 
         SequentialSensorOperation op = new SequentialSensorOperation();
         for (int i = 0; i < subOpCount; i++) {
-            ISensorOperation subOp = new FakeSensorOperation(subOpDurationMs,
+            SensorOperation subOp = new FakeSensorOperation(subOpDurationMs,
                     TimeUnit.MILLISECONDS);
             op.add(subOp);
         }
@@ -292,7 +301,7 @@
         assertEquals(0, statsKeys.size());
 
         long start = System.currentTimeMillis();
-        op.execute();
+        op.execute(mTestNode);
         long duration = System.currentTimeMillis() - start;
         assertTrue(Math.abs(subOpDurationMs * subOpCount - duration) < TEST_DURATION_THRESHOLD_MS);
 
@@ -315,7 +324,7 @@
         SequentialSensorOperation op = new SequentialSensorOperation();
         for (int i = 0; i < subOpCount; i++) {
             // Trigger a failure in the 75th operation only
-            ISensorOperation subOp = new FakeSensorOperation(i + 1 == failCount, 0,
+            SensorOperation subOp = new FakeSensorOperation(i + 1 == failCount, 0,
                     TimeUnit.MILLISECONDS);
             op.add(subOp);
         }
@@ -324,7 +333,7 @@
         assertEquals(0, statsKeys.size());
 
         try {
-            op.execute();
+            op.execute(mTestNode);
             fail("AssertionError expected");
         } catch (AssertionError e) {
             // Expected
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java
index 2ed0ca6..847c0f2 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/SequentialSensorOperation.java
@@ -17,25 +17,25 @@
 package android.hardware.cts.helpers.sensoroperations;
 
 import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.reporting.ISensorTestNode;
 
-import java.util.LinkedList;
-import java.util.List;
+import java.util.ArrayList;
 
 /**
- * A {@link ISensorOperation} that executes a set of children {@link ISensorOperation}s in a
+ * A {@link SensorOperation} that executes a set of children {@link SensorOperation}s in a
  * sequence. The children are executed in the order they are added. This class can be combined to
- * compose complex {@link ISensorOperation}s.
+ * compose complex {@link SensorOperation}s.
  */
-public class SequentialSensorOperation extends AbstractSensorOperation {
+public class SequentialSensorOperation extends SensorOperation {
     public static final String STATS_TAG = "sequential";
 
-    private final List<ISensorOperation> mOperations = new LinkedList<ISensorOperation>();
+    private final ArrayList<SensorOperation> mOperations = new ArrayList<>();
 
     /**
-     * Add a set of {@link ISensorOperation}s.
+     * Add a set of {@link SensorOperation}s.
      */
-    public void add(ISensorOperation ... operations) {
-        for (ISensorOperation operation : operations) {
+    public void add(SensorOperation ... operations) {
+        for (SensorOperation operation : operations) {
             if (operation == null) {
                 throw new IllegalArgumentException("Arguments cannot be null");
             }
@@ -44,15 +44,16 @@
     }
 
     /**
-     * Executes the {@link ISensorOperation}s in the order they were added. If an exception occurs
+     * Executes the {@link SensorOperation}s in the order they were added. If an exception occurs
      * in one operation, it is thrown and all subsequent operations will not run.
      */
     @Override
-    public void execute() throws InterruptedException {
+    public void execute(ISensorTestNode parent) throws InterruptedException {
+        ISensorTestNode currentNode = asTestNode(parent);
         for (int i = 0; i < mOperations.size(); i++) {
-            ISensorOperation operation = mOperations.get(i);
+            SensorOperation operation = mOperations.get(i);
             try {
-                operation.execute();
+                operation.execute(currentNode);
             } catch (AssertionError e) {
                 String msg = String.format("Operation %d failed: \"%s\"", i, e.getMessage());
                 getStats().addValue(SensorStats.ERROR, msg);
@@ -69,7 +70,7 @@
     @Override
     public SequentialSensorOperation clone() {
         SequentialSensorOperation operation = new SequentialSensorOperation();
-        for (ISensorOperation subOperation : mOperations) {
+        for (SensorOperation subOperation : mOperations) {
             operation.add(subOperation.clone());
         }
         return operation;
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/TestSensorFlushOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/TestSensorFlushOperation.java
deleted file mode 100644
index d5aa4b9..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/TestSensorFlushOperation.java
+++ /dev/null
@@ -1,67 +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.hardware.cts.helpers.sensoroperations;
-
-import android.hardware.cts.helpers.TestSensorEnvironment;
-import android.hardware.cts.helpers.TestSensorEventListener;
-
-import java.util.concurrent.TimeUnit;
-
-/**
- * A {@link ISensorOperation} used to verify that sensor events and sensor values are correct.
- * <p>
- * Provides methods to set test expectations as well as providing a set of default expectations
- * depending on sensor type.  When {{@link #execute()} is called, the sensor will collect the
- * events, call flush, and then run all the tests.
- * </p>
- */
-public class TestSensorFlushOperation extends VerifiableSensorOperation {
-    private final Long mDuration;
-    private final TimeUnit mTimeUnit;
-
-    /**
-     * Create a {@link TestSensorOperation}.
-     *
-     * @param environment the test environment
-     * @param duration the duration to gather events before calling {@code SensorManager.flush()}
-     * @param timeUnit the time unit of the duration
-     */
-    public TestSensorFlushOperation(
-            TestSensorEnvironment environment,
-            long duration,
-            TimeUnit timeUnit) {
-        super(environment);
-        mDuration = duration;
-        mTimeUnit = timeUnit;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    protected void doExecute(TestSensorEventListener listener) throws InterruptedException {
-        mSensorManager.runSensorAndFlush(listener, mDuration, mTimeUnit);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    protected VerifiableSensorOperation doClone() {
-        return new TestSensorFlushOperation(mEnvironment, mDuration,mTimeUnit);
-    }
-}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java
index 695e1a7..3b90b15 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/TestSensorOperation.java
@@ -16,83 +16,265 @@
 
 package android.hardware.cts.helpers.sensoroperations;
 
-import android.hardware.cts.helpers.TestSensorEnvironment;
-import android.hardware.cts.helpers.TestSensorEventListener;
+import junit.framework.Assert;
 
+import android.hardware.cts.helpers.SensorCtsHelper;
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.SensorTestPlatformException;
+import android.hardware.cts.helpers.TestSensorEnvironment;
+import android.hardware.cts.helpers.TestSensorEvent;
+import android.hardware.cts.helpers.TestSensorEventListener;
+import android.hardware.cts.helpers.TestSensorManager;
+import android.hardware.cts.helpers.reporting.ISensorTestNode;
+import android.hardware.cts.helpers.sensorverification.EventGapVerification;
+import android.hardware.cts.helpers.sensorverification.EventOrderingVerification;
+import android.hardware.cts.helpers.sensorverification.EventTimestampSynchronizationVerification;
+import android.hardware.cts.helpers.sensorverification.FrequencyVerification;
+import android.hardware.cts.helpers.sensorverification.ISensorVerification;
+import android.hardware.cts.helpers.sensorverification.JitterVerification;
+import android.hardware.cts.helpers.sensorverification.MagnitudeVerification;
+import android.hardware.cts.helpers.sensorverification.MeanVerification;
+import android.hardware.cts.helpers.sensorverification.StandardDeviationVerification;
+import android.os.Handler;
+import android.util.Log;
+
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 /**
- * A {@link ISensorOperation} used to verify that sensor events and sensor values are correct.
+ * A {@link SensorOperation} used to verify that sensor events and sensor values are correct.
  * <p>
  * Provides methods to set test expectations as well as providing a set of default expectations
- * depending on sensor type.  When {{@link #execute()} is called, the sensor will collect the
- * events and then run all the tests.
+ * depending on sensor type.  When {{@link #execute(ISensorTestNode)} is called, the sensor will
+ * collect the events and then run all the tests.
  * </p>
  */
-public class TestSensorOperation extends VerifiableSensorOperation {
-    private final Integer mEventCount;
-    private final Long mDuration;
-    private final TimeUnit mTimeUnit;
+public class TestSensorOperation extends SensorOperation {
+    private static final String TAG = "TestSensorOperation";
+
+    private final HashSet<ISensorVerification> mVerifications = new HashSet<>();
+
+    private final TestSensorManager mSensorManager;
+    private final TestSensorEnvironment mEnvironment;
+    private final Executor mExecutor;
+    private final Handler mHandler;
 
     /**
-     * Create a {@link TestSensorOperation}.
-     *
-     * @param environment the test environment
-     * @param eventCount the number of events to gather
+     * An interface that defines an abstraction for operations to be performed by the
+     * {@link TestSensorOperation}.
      */
-    public TestSensorOperation(TestSensorEnvironment environment, int eventCount) {
-        this(environment, eventCount, null /* duration */, null /* timeUnit */);
+    public interface Executor {
+        void execute(TestSensorManager sensorManager, TestSensorEventListener listener)
+                throws InterruptedException;
     }
 
     /**
      * Create a {@link TestSensorOperation}.
-     *
-     * @param environment the test environment
-     * @param duration the duration to gather events for
-     * @param timeUnit the time unit of the duration
+     */
+    public TestSensorOperation(TestSensorEnvironment environment, Executor executor) {
+        this(environment, executor, null /* handler */);
+    }
+
+    /**
+     * Create a {@link TestSensorOperation}.
      */
     public TestSensorOperation(
             TestSensorEnvironment environment,
-            long duration,
-            TimeUnit timeUnit) {
-        this(environment, null /* eventCount */, duration, timeUnit);
+            Executor executor,
+            Handler handler) {
+        mEnvironment = environment;
+        mExecutor = executor;
+        mHandler = handler;
+        mSensorManager = new TestSensorManager(mEnvironment);
     }
 
     /**
-     * Private helper constructor.
+     * Set all of the default test expectations.
      */
-    private TestSensorOperation(
+    public void addDefaultVerifications() {
+        addVerification(EventGapVerification.getDefault(mEnvironment));
+        addVerification(EventOrderingVerification.getDefault(mEnvironment));
+        addVerification(FrequencyVerification.getDefault(mEnvironment));
+        addVerification(JitterVerification.getDefault(mEnvironment));
+        addVerification(MagnitudeVerification.getDefault(mEnvironment));
+        addVerification(MeanVerification.getDefault(mEnvironment));
+        addVerification(StandardDeviationVerification.getDefault(mEnvironment));
+        addVerification(EventTimestampSynchronizationVerification.getDefault(mEnvironment));
+    }
+
+    public void addVerification(ISensorVerification verification) {
+        if (verification != null) {
+            mVerifications.add(verification);
+        }
+    }
+
+    /**
+     * Collect the specified number of events from the sensor and run all enabled verifications.
+     */
+    @Override
+    public void execute(ISensorTestNode parent) throws InterruptedException {
+        getStats().addValue("sensor_name", mEnvironment.getSensor().getName());
+        TestSensorEventListener listener = new TestSensorEventListener(mEnvironment, mHandler);
+        mExecutor.execute(mSensorManager, listener);
+
+        boolean failed = false;
+        StringBuilder sb = new StringBuilder();
+        List<TestSensorEvent> collectedEvents = listener.getCollectedEvents();
+        for (ISensorVerification verification : mVerifications) {
+            failed |= evaluateResults(collectedEvents, verification, sb);
+        }
+
+        if (failed) {
+            trySaveCollectedEvents(parent, listener);
+
+            String msg = SensorCtsHelper
+                    .formatAssertionMessage("VerifySensorOperation", mEnvironment, sb.toString());
+            getStats().addValue(SensorStats.ERROR, msg);
+            Assert.fail(msg);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public TestSensorOperation clone() {
+        TestSensorOperation operation = new TestSensorOperation(mEnvironment, mExecutor);
+        for (ISensorVerification verification : mVerifications) {
+            operation.addVerification(verification.clone());
+        }
+        return operation;
+    }
+
+    /**
+     * Evaluate the results of a test, aggregate the stats, and build the error message.
+     */
+    private boolean evaluateResults(
+            List<TestSensorEvent> events,
+            ISensorVerification verification,
+            StringBuilder sb) {
+        try {
+            // this is an intermediate state in refactoring, at some point verifications might
+            // become stateless
+            verification.addSensorEvents(events);
+            verification.verify(mEnvironment, getStats());
+        } catch (AssertionError e) {
+            if (sb.length() > 0) {
+                sb.append(", ");
+            }
+            sb.append(e.getMessage());
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Tries to save collected {@link TestSensorEvent}s to a file.
+     *
+     * NOTE: it is more important to handle verifications and its results, than failing if the file
+     * cannot be created. So we silently fail if necessary.
+     */
+    private void trySaveCollectedEvents(ISensorTestNode parent, TestSensorEventListener listener) {
+        String sanitizedFileName;
+        try {
+            String fileName = asTestNode(parent).getName();
+            sanitizedFileName = String.format(
+                    "%s-%s-%s_%dus.txt",
+                    SensorCtsHelper.sanitizeStringForFileName(fileName),
+                    SensorStats.getSanitizedSensorName(mEnvironment.getSensor()),
+                    mEnvironment.getFrequencyString(),
+                    mEnvironment.getMaxReportLatencyUs());
+        } catch (SensorTestPlatformException e) {
+            Log.w(TAG, "Unable to generate file name to save collected events", e);
+            return;
+        }
+
+        try {
+            listener.logCollectedEventsToFile(sanitizedFileName);
+        } catch (IOException e) {
+            Log.w(TAG, "Unable to save collected events to file: " + sanitizedFileName, e);
+        }
+    }
+
+    /**
+     * Creates an operation that will wait for a given amount of events to arrive.
+     *
+     * @param environment The test environment.
+     * @param eventCount The number of events to wait for.
+     */
+    public static TestSensorOperation createOperation(
             TestSensorEnvironment environment,
-            Integer eventCount,
-            Long duration,
-            TimeUnit timeUnit) {
-        super(environment);
-        mEventCount = eventCount;
-        mDuration = duration;
-        mTimeUnit = timeUnit;
+            final int eventCount) {
+        Executor executor = new Executor() {
+            @Override
+            public void execute(TestSensorManager sensorManager, TestSensorEventListener listener)
+                    throws InterruptedException {
+                try {
+                    CountDownLatch latch = sensorManager.registerListener(listener, eventCount);
+                    listener.waitForEvents(latch, eventCount);
+                } finally {
+                    sensorManager.unregisterListener();
+                }
+            }
+        };
+        return new TestSensorOperation(environment, executor);
     }
 
     /**
-     * {@inheritDoc}
+     * Creates an operation that will wait for a given amount of time to collect events.
+     *
+     * @param environment The test environment.
+     * @param duration The duration to wait for events.
+     * @param timeUnit The time unit for {@code duration}.
      */
-    @Override
-    protected void doExecute(TestSensorEventListener listener) throws InterruptedException {
-        if (mEventCount != null) {
-            mSensorManager.runSensor(listener, mEventCount);
-        } else {
-            mSensorManager.runSensor(listener, mDuration, mTimeUnit);
-        }
+    public static TestSensorOperation createOperation(
+            TestSensorEnvironment environment,
+            final long duration,
+            final TimeUnit timeUnit) {
+        Executor executor = new Executor() {
+            @Override
+            public void execute(TestSensorManager sensorManager, TestSensorEventListener listener)
+                    throws InterruptedException {
+                try {
+                    sensorManager.registerListener(listener);
+                    listener.waitForEvents(duration, timeUnit);
+                } finally {
+                    sensorManager.unregisterListener();
+                }
+            }
+        };
+        return new TestSensorOperation(environment, executor);
     }
 
     /**
-     * {@inheritDoc}
+     * Creates an operation that will wait for a given amount of time before calling
+     * {@link TestSensorManager#requestFlush()}.
+     *
+     * @param environment The test environment.
+     * @param duration The duration to wait before calling {@link TestSensorManager#requestFlush()}.
+     * @param timeUnit The time unit for {@code duration}.
      */
-    @Override
-    protected VerifiableSensorOperation doClone() {
-        if (mEventCount != null) {
-            return new TestSensorOperation(mEnvironment, mEventCount);
-        } else {
-            return new TestSensorOperation(mEnvironment, mDuration, mTimeUnit);
-        }
+    public static TestSensorOperation createFlushOperation(
+            TestSensorEnvironment environment,
+            final long duration,
+            final TimeUnit timeUnit) {
+        Executor executor = new Executor() {
+            @Override
+            public void execute(TestSensorManager sensorManager, TestSensorEventListener listener)
+                    throws InterruptedException {
+                try {
+                    sensorManager.registerListener(listener);
+                    SensorCtsHelper.sleep(duration, timeUnit);
+                    CountDownLatch latch = sensorManager.requestFlush();
+                    listener.waitForFlushComplete(latch);
+                } finally {
+                    sensorManager.unregisterListener();
+                }
+            }
+        };
+        return new TestSensorOperation(environment, executor);
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/VerifiableSensorOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/VerifiableSensorOperation.java
deleted file mode 100644
index 57018eb..0000000
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/VerifiableSensorOperation.java
+++ /dev/null
@@ -1,156 +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.hardware.cts.helpers.sensoroperations;
-
-import junit.framework.Assert;
-
-import android.hardware.cts.helpers.SensorCtsHelper;
-import android.hardware.cts.helpers.SensorStats;
-import android.hardware.cts.helpers.TestSensorEnvironment;
-import android.hardware.cts.helpers.TestSensorEventListener;
-import android.hardware.cts.helpers.TestSensorManager;
-import android.hardware.cts.helpers.ValidatingSensorEventListener;
-import android.hardware.cts.helpers.sensorverification.EventGapVerification;
-import android.hardware.cts.helpers.sensorverification.EventOrderingVerification;
-import android.hardware.cts.helpers.sensorverification.FrequencyVerification;
-import android.hardware.cts.helpers.sensorverification.ISensorVerification;
-import android.hardware.cts.helpers.sensorverification.JitterVerification;
-import android.hardware.cts.helpers.sensorverification.MagnitudeVerification;
-import android.hardware.cts.helpers.sensorverification.MeanVerification;
-import android.hardware.cts.helpers.sensorverification.StandardDeviationVerification;
-
-import java.util.Collection;
-import java.util.HashSet;
-
-/**
- * A {@link ISensorOperation} used to verify that sensor events and sensor values are correct.
- * <p>
- * Provides methods to set test expectations as well as providing a set of default expectations
- * depending on sensor type.  When {{@link #execute()} is called, the sensor will collect the
- * events and then run all the tests.
- * </p>
- */
-public abstract class VerifiableSensorOperation extends AbstractSensorOperation {
-    protected final TestSensorManager mSensorManager;
-    protected final TestSensorEnvironment mEnvironment;
-
-    private final Collection<ISensorVerification> mVerifications =
-            new HashSet<ISensorVerification>();
-
-    private boolean mLogEvents = false;
-
-    /**
-     * Create a {@link TestSensorOperation}.
-     *
-     * @param environment the test environment
-     */
-    public VerifiableSensorOperation(TestSensorEnvironment environment) {
-        mEnvironment = environment;
-        mSensorManager = new TestSensorManager(mEnvironment);
-    }
-
-    /**
-     * Set whether to log events.
-     */
-    public void setLogEvents(boolean logEvents) {
-        mLogEvents = logEvents;
-    }
-
-    /**
-     * Set all of the default test expectations.
-     */
-    public void addDefaultVerifications() {
-        addVerification(EventGapVerification.getDefault(mEnvironment));
-        addVerification(EventOrderingVerification.getDefault(mEnvironment));
-        addVerification(FrequencyVerification.getDefault(mEnvironment));
-        addVerification(JitterVerification.getDefault(mEnvironment));
-        addVerification(MagnitudeVerification.getDefault(mEnvironment));
-        addVerification(MeanVerification.getDefault(mEnvironment));
-        // Skip SigNumVerification since it has no default
-        addVerification(StandardDeviationVerification.getDefault(mEnvironment));
-    }
-
-    public void addVerification(ISensorVerification verification) {
-        if (verification != null) {
-            mVerifications.add(verification);
-        }
-    }
-
-    /**
-     * Collect the specified number of events from the sensor and run all enabled verifications.
-     */
-    @Override
-    public void execute() throws InterruptedException {
-        getStats().addValue("sensor_name", mEnvironment.getSensor().getName());
-
-        ValidatingSensorEventListener listener = new ValidatingSensorEventListener(mVerifications);
-        listener.setLogEvents(mLogEvents);
-
-        doExecute(listener);
-
-        boolean failed = false;
-        StringBuilder sb = new StringBuilder();
-        for (ISensorVerification verification : mVerifications) {
-            failed |= evaluateResults(verification, sb);
-        }
-
-        if (failed) {
-            String msg = SensorCtsHelper
-                    .formatAssertionMessage("VerifySensorOperation", mEnvironment, sb.toString());
-            getStats().addValue(SensorStats.ERROR, msg);
-            Assert.fail(msg);
-        }
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public VerifiableSensorOperation clone() {
-        VerifiableSensorOperation operation = doClone();
-        for (ISensorVerification verification : mVerifications) {
-            operation.addVerification(verification.clone());
-        }
-        return operation;
-    }
-
-    /**
-     * Execute operations in a {@link TestSensorManager}.
-     */
-    protected abstract void doExecute(TestSensorEventListener listener) throws InterruptedException;
-
-    /**
-     * Clone the subclass operation.
-     */
-    protected abstract VerifiableSensorOperation doClone();
-
-    /**
-     * Evaluate the results of a test, aggregate the stats, and build the error message.
-     */
-    private boolean evaluateResults(ISensorVerification verification, StringBuilder sb) {
-        try {
-            verification.verify(mEnvironment, getStats());
-        } catch (AssertionError e) {
-            if (sb.length() > 0) {
-                sb.append(", ");
-            }
-            sb.append(e.getMessage());
-            return true;
-        }
-        return false;
-    }
-}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/WakeLockOperation.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/WakeLockOperation.java
index b500ea7..9f03f31 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/WakeLockOperation.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensoroperations/WakeLockOperation.java
@@ -17,41 +17,42 @@
 package android.hardware.cts.helpers.sensoroperations;
 
 import android.content.Context;
-import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.reporting.ISensorTestNode;
 import android.os.PowerManager;
 import android.os.PowerManager.WakeLock;
 
 /**
- * An {@link ISensorOperation} which holds a wakelock while performing another
- * {@link ISensorOperation}.
+ * An {@link SensorOperation} which holds a wake-lock while performing another
+ * {@link SensorOperation}.
  */
-public class WakeLockOperation extends AbstractSensorOperation {
+public class WakeLockOperation extends SensorOperation {
     private static final String TAG = "WakeLockOperation";
 
-    private final ISensorOperation mOperation;
+    private final SensorOperation mOperation;
     private final Context mContext;
-    private final int mWakelockFlags;
+    private final int mWakeLockFlags;
 
     /**
      * Constructor for {@link WakeLockOperation}.
      *
-     * @param operation the child {@link ISensorOperation} to perform after the delay
+     * @param operation the child {@link SensorOperation} to perform after the delay
      * @param context the context used to access the power manager
-     * @param wakelockFlags the flags used when acquiring the wakelock
+     * @param wakeLockFlags the flags used when acquiring the wake-lock
      */
-    public WakeLockOperation(ISensorOperation operation, Context context, int wakelockFlags) {
+    public WakeLockOperation(SensorOperation operation, Context context, int wakeLockFlags) {
+        super(operation.getStats());
         mOperation = operation;
         mContext = context;
-        mWakelockFlags = wakelockFlags;
+        mWakeLockFlags = wakeLockFlags;
     }
 
     /**
      * Constructor for {@link WakeLockOperation}.
      *
-     * @param operation the child {@link ISensorOperation} to perform after the delay
+     * @param operation the child {@link SensorOperation} to perform after the delay
      * @param context the context used to access the power manager
      */
-    public WakeLockOperation(ISensorOperation operation, Context context) {
+    public WakeLockOperation(SensorOperation operation, Context context) {
         this(operation, context, PowerManager.PARTIAL_WAKE_LOCK);
     }
 
@@ -59,13 +60,12 @@
      * {@inheritDoc}
      */
     @Override
-    public void execute() throws InterruptedException {
+    public void execute(ISensorTestNode parent) throws InterruptedException {
         PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        WakeLock wakeLock = pm.newWakeLock(mWakelockFlags, TAG);
-
+        WakeLock wakeLock = pm.newWakeLock(mWakeLockFlags, TAG);
         wakeLock.acquire();
         try {
-            mOperation.execute();
+            mOperation.execute(asTestNode(parent));
         } finally {
             wakeLock.release();
         }
@@ -75,15 +75,7 @@
      * {@inheritDoc}
      */
     @Override
-    public SensorStats getStats() {
-        return mOperation.getStats();
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public ISensorOperation clone() {
-        return new WakeLockOperation(mOperation, mContext, mWakelockFlags);
+    public SensorOperation clone() {
+        return new WakeLockOperation(mOperation.clone(), mContext, mWakeLockFlags);
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/AbstractSensorVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/AbstractSensorVerification.java
index 911ae3a..1e775e3 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/AbstractSensorVerification.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/AbstractSensorVerification.java
@@ -18,6 +18,9 @@
 
 import android.hardware.cts.helpers.TestSensorEvent;
 
+import java.util.Collection;
+import java.util.List;
+
 /**
  * Abstract class that deals with the synchronization of the sensor verifications.
  */
@@ -27,7 +30,7 @@
      * {@inheritDoc}
      */
     @Override
-    public synchronized void addSensorEvents(TestSensorEvent ... events) {
+    public synchronized void addSensorEvents(Collection<TestSensorEvent> events) {
         for (TestSensorEvent event : events) {
             addSensorEventInternal(event);
         }
@@ -37,14 +40,6 @@
      * {@inheritDoc}
      */
     @Override
-    public synchronized void addSensorEvent(TestSensorEvent event) {
-        addSensorEventInternal(event);
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
     public abstract ISensorVerification clone();
 
     /**
@@ -52,18 +47,38 @@
      */
     protected abstract void addSensorEventInternal(TestSensorEvent event);
 
+    protected <TEvent extends IndexedEvent> int[] getIndexArray(List<TEvent> indexedEvents) {
+        int eventsCount = indexedEvents.size();
+        int[] indices = new int[eventsCount];
+        for (int i = 0; i < eventsCount; i++) {
+            indices[i] = indexedEvents.get(i).index;
+        }
+        return indices;
+    }
+
+    /**
+     * Helper class to store the index and current event.
+     * Events are added to the verification in the order they are generated, the index represents
+     * the position of the given event, in the list of added events.
+     */
+    protected class IndexedEvent {
+        public final int index;
+        public final TestSensorEvent event;
+
+        public IndexedEvent(int index, TestSensorEvent event) {
+            this.index = index;
+            this.event = event;
+        }
+    }
+
     /**
      * Helper class to store the index, previous event, and current event.
      */
-    protected class IndexedEventPair {
-        public final int index;
-        public final TestSensorEvent event;
+    protected class IndexedEventPair extends IndexedEvent {
         public final TestSensorEvent previousEvent;
 
-        public IndexedEventPair(int index, TestSensorEvent event,
-                TestSensorEvent previousEvent) {
-            this.index = index;
-            this.event = event;
+        public IndexedEventPair(int index, TestSensorEvent event, TestSensorEvent previousEvent) {
+            super(index, event);
             this.previousEvent = previousEvent;
         }
     }
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventGapVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventGapVerification.java
index 156afa8..b692f0f 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventGapVerification.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventGapVerification.java
@@ -63,19 +63,14 @@
     public void verify(TestSensorEnvironment environment, SensorStats stats) {
         if (environment.isSensorSamplingRateOverloaded()) {
             // the verification is not reliable on environments under load
-            stats.addValue(PASSED_KEY, true);
+            stats.addValue(PASSED_KEY, "skipped (under load)");
             return;
         }
 
         final int count = mEventGaps.size();
         stats.addValue(PASSED_KEY, count == 0);
         stats.addValue(SensorStats.EVENT_GAP_COUNT_KEY, count);
-
-        final int[] indices = new int[count];
-        for (int i = 0; i < indices.length; i++) {
-            indices[i] = mEventGaps.get(i).index;
-        }
-        stats.addValue(SensorStats.EVENT_GAP_POSITIONS_KEY, indices);
+        stats.addValue(SensorStats.EVENT_GAP_POSITIONS_KEY, getIndexArray(mEventGaps));
 
         if (count > 0) {
             StringBuilder sb = new StringBuilder();
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventGapVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventGapVerificationTest.java
index d01e108..6f17e7b 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventGapVerificationTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventGapVerificationTest.java
@@ -22,6 +22,9 @@
 import android.hardware.cts.helpers.TestSensorEnvironment;
 import android.hardware.cts.helpers.TestSensorEvent;
 
+import java.util.ArrayList;
+import java.util.Collection;
+
 /**
  * Tests for {@link EventGapVerification}.
  */
@@ -90,11 +93,13 @@
         }
     }
 
-    private ISensorVerification getVerification(int expected, long ... timestamps) {
-        ISensorVerification verification = new EventGapVerification(expected);
+    private static EventGapVerification getVerification(int expected, long ... timestamps) {
+        Collection<TestSensorEvent> events = new ArrayList<>(timestamps.length);
         for (long timestamp : timestamps) {
-            verification.addSensorEvent(new TestSensorEvent(null, timestamp, 0, null));
+            events.add(new TestSensorEvent(null, timestamp, 0, null));
         }
+        EventGapVerification verification = new EventGapVerification(expected);
+        verification.addSensorEvents(events);
         return verification;
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerification.java
index 6598725..7b77ead 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerification.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerification.java
@@ -75,12 +75,9 @@
         final int count = mOutOfOrderEvents.size();
         stats.addValue(PASSED_KEY, count == 0);
         stats.addValue(SensorStats.EVENT_OUT_OF_ORDER_COUNT_KEY, count);
-
-        final int[] indices = new int[count];
-        for (int i = 0; i < indices.length; i++) {
-            indices[i] = mOutOfOrderEvents.get(i).index;
-        }
-        stats.addValue(SensorStats.EVENT_OUT_OF_ORDER_POSITIONS_KEY, indices);
+        stats.addValue(
+                SensorStats.EVENT_OUT_OF_ORDER_POSITIONS_KEY,
+                getIndexArray(mOutOfOrderEvents));
 
         if (count > 0) {
             StringBuilder sb = new StringBuilder();
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java
index 88d5c19..b9848fa 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java
@@ -22,6 +22,7 @@
 import android.hardware.cts.helpers.TestSensorEvent;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -96,11 +97,13 @@
         assertTrue(indices.contains(4));
     }
 
-    private EventOrderingVerification getVerification(long ... timestamps) {
-        EventOrderingVerification verification = new EventOrderingVerification();
+    private static EventOrderingVerification getVerification(long ... timestamps) {
+        Collection<TestSensorEvent> events = new ArrayList<>(timestamps.length);
         for (long timestamp : timestamps) {
-            verification.addSensorEvent(new TestSensorEvent(null, timestamp, 0, null));
+            events.add(new TestSensorEvent(null, timestamp, 0, null));
         }
+        EventOrderingVerification verification = new EventOrderingVerification();
+        verification.addSensorEvents(events);
         return verification;
     }
 
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventTimestampSynchronizationVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventTimestampSynchronizationVerification.java
new file mode 100644
index 0000000..d4a1f38
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventTimestampSynchronizationVerification.java
@@ -0,0 +1,153 @@
+/*
+ * 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.hardware.cts.helpers.sensorverification;
+
+import junit.framework.Assert;
+
+import android.hardware.SensorEvent;
+import android.hardware.cts.helpers.SensorStats;
+import android.hardware.cts.helpers.TestSensorEnvironment;
+import android.hardware.cts.helpers.TestSensorEvent;
+import android.os.SystemClock;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A {@link ISensorVerification} which verifies that the timestamp of the {@link SensorEvent} is
+ * synchronized with {@link SystemClock#elapsedRealtimeNanos()}, based on a given threshold.
+ */
+public class EventTimestampSynchronizationVerification extends AbstractSensorVerification {
+    public static final String PASSED_KEY = "timestamp_synchronization_passed";
+
+    // number of indices to print in assertion message before truncating
+    private static final int TRUNCATE_MESSAGE_LENGTH = 3;
+
+    private static final long DEFAULT_THRESHOLD_NS = TimeUnit.MILLISECONDS.toNanos(500);
+
+    private final ArrayList<TestSensorEvent> mCollectedEvents = new ArrayList<TestSensorEvent>();
+
+    private final long mMaximumSynchronizationErrorNs;
+    private final long mReportLatencyNs;
+
+    /**
+     * Constructs an instance of {@link EventTimestampSynchronizationVerification}.
+     *
+     * @param maximumSynchronizationErrorNs The valid threshold for timestamp synchronization.
+     * @param reportLatencyNs The latency on which batching events are received
+     */
+    public EventTimestampSynchronizationVerification(
+            long maximumSynchronizationErrorNs,
+            long reportLatencyNs) {
+        mMaximumSynchronizationErrorNs = maximumSynchronizationErrorNs;
+        mReportLatencyNs = reportLatencyNs;
+    }
+
+    /**
+     * Gets a default {@link EventTimestampSynchronizationVerification}.
+     *
+     * @param environment The test environment
+     * @return The verification or null if the verification is not supported in the given
+     *         environment.
+     */
+    public static EventTimestampSynchronizationVerification getDefault(
+            TestSensorEnvironment environment) {
+        int reportLatencyUs = environment.getMaxReportLatencyUs();
+        int fifoMaxEventCount = environment.getSensor().getFifoMaxEventCount();
+        if (fifoMaxEventCount > 0) {
+            int fifoBasedReportLatencyUs =
+                    fifoMaxEventCount * environment.getMaximumExpectedSamplingPeriodUs();
+            reportLatencyUs = Math.min(reportLatencyUs, fifoBasedReportLatencyUs);
+
+        }
+        long reportLatencyNs = TimeUnit.MICROSECONDS.toNanos(reportLatencyUs);
+        return new EventTimestampSynchronizationVerification(DEFAULT_THRESHOLD_NS, reportLatencyNs);
+    }
+
+    @Override
+    public void verify(TestSensorEnvironment environment, SensorStats stats) {
+        StringBuilder errorMessageBuilder =
+                new StringBuilder(" event timestamp synchronization failures: ");
+        List<IndexedEvent> failures = verifyTimestampSynchronization(errorMessageBuilder);
+
+        int failuresCount = failures.size();
+        stats.addValue(SensorStats.EVENT_TIME_SYNCHRONIZATION_COUNT_KEY, failuresCount);
+        stats.addValue(
+                SensorStats.EVENT_TIME_SYNCHRONIZATION_POSITIONS_KEY,
+                getIndexArray(failures));
+
+        boolean success = failures.isEmpty();
+        stats.addValue(PASSED_KEY, success);
+        errorMessageBuilder.insert(0, failuresCount);
+        Assert.assertTrue(errorMessageBuilder.toString(), success);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public EventTimestampSynchronizationVerification clone() {
+        return new EventTimestampSynchronizationVerification(
+                mMaximumSynchronizationErrorNs,
+                mReportLatencyNs);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected void addSensorEventInternal(TestSensorEvent event) {
+        mCollectedEvents.add(event);
+    }
+
+    /**
+     * Verifies timestamp synchronization for all sensor events.
+     * The verification accounts for a lower and upper threshold, such thresholds are adjusted for
+     * batching cases.
+     *
+     * @param builder A string builder to store error messaged found in the collected sensor events.
+     * @return A list of events tha failed the verification.
+     */
+    private List<IndexedEvent> verifyTimestampSynchronization(StringBuilder builder) {
+        int collectedEventsCount = mCollectedEvents.size();
+        ArrayList<IndexedEvent> failures = new ArrayList<IndexedEvent>();
+
+        for (int i = 0; i < collectedEventsCount; ++i) {
+            TestSensorEvent event = mCollectedEvents.get(i);
+            long eventTimestampNs = event.timestamp;
+            long receivedTimestampNs = event.receivedTimestamp;
+            long upperThresholdNs = receivedTimestampNs;
+            long lowerThresholdNs = receivedTimestampNs - mMaximumSynchronizationErrorNs
+                    - mReportLatencyNs;
+
+            if (eventTimestampNs < lowerThresholdNs || eventTimestampNs > upperThresholdNs) {
+                if (failures.size() < TRUNCATE_MESSAGE_LENGTH) {
+                    builder.append("position=").append(i);
+                    builder.append(", timestamp=").append(eventTimestampNs).append("ns");
+                    builder.append(", expected=[").append(lowerThresholdNs);
+                    builder.append(", ").append(upperThresholdNs).append("]ns; ");
+                }
+                failures.add(new IndexedEvent(i, event));
+            }
+        }
+        if (failures.size() >= TRUNCATE_MESSAGE_LENGTH) {
+            builder.append("more; ");
+        }
+        return failures;
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/FrequencyVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/FrequencyVerificationTest.java
index 24349ce..bbf022a 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/FrequencyVerificationTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/FrequencyVerificationTest.java
@@ -22,6 +22,9 @@
 import android.hardware.cts.helpers.TestSensorEnvironment;
 import android.hardware.cts.helpers.TestSensorEvent;
 
+import java.util.ArrayList;
+import java.util.Collection;
+
 /**
  * Tests for {@link EventOrderingVerification}.
  */
@@ -73,15 +76,17 @@
         return new TestSensorEnvironment(getContext(), Sensor.TYPE_ALL, rateUs);
     }
 
-    private ISensorVerification getVerification(
+    private static FrequencyVerification getVerification(
             double lowerThreshold,
             double upperThreshold,
             long ... timestamps) {
-        ISensorVerification verification =
-                new FrequencyVerification(lowerThreshold, upperThreshold);
+        Collection<TestSensorEvent> events = new ArrayList<>(timestamps.length);
         for (long timestamp : timestamps) {
-            verification.addSensorEvent(new TestSensorEvent(null, timestamp, 0, null));
+            events.add(new TestSensorEvent(null, timestamp, 0, null));
         }
+        FrequencyVerification verification =
+                new FrequencyVerification(lowerThreshold, upperThreshold);
+        verification.addSensorEvents(events);
         return verification;
     }
 
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/ISensorVerification.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/ISensorVerification.java
index 4f7b65a..2027f0f 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/ISensorVerification.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/ISensorVerification.java
@@ -20,24 +20,25 @@
 import android.hardware.cts.helpers.TestSensorEnvironment;
 import android.hardware.cts.helpers.TestSensorEvent;
 
+import java.util.Collection;
+
 /**
- * Interface describing the sensor verification. This class was designed for to handle streaming
- * events. The methods {@link #addSensorEvent(TestSensorEvent)} and
- * {@link #addSensorEvents(TestSensorEvent...)} should be called in the order that the events are
- * received. The method {@link #verify(TestSensorEnvironment, SensorStats)} should be called after
- * all events are added.
+ * Interface describing the sensor verification.
+ * This class was designed to handle streaming of events.
+ *
+ * The method {@link #addSensorEvents(Collection)} should be called in the order that the events are
+ * received.
+ *
+ * The method {@link #verify(TestSensorEnvironment, SensorStats)} should be called after all events
+ * are added.
  */
 public interface ISensorVerification {
 
     /**
-     * Add a single {@link TestSensorEvent} to be evaluated.
+     * Add a list of {@link TestSensorEvent}s to be evaluated.
      */
-    public void addSensorEvent(TestSensorEvent event);
-
-    /**
-     * Add multiple {@link TestSensorEvent}s to be evaluated.
-     */
-    public void addSensorEvents(TestSensorEvent ... events);
+    // TODO: refactor verifications to be stateless, and pass the list of events in verify()
+    void addSensorEvents(Collection<TestSensorEvent> events);
 
     /**
      * Evaluate all added {@link TestSensorEvent}s and update stats.
@@ -45,10 +46,10 @@
      * @param stats a {@link SensorStats} object used to keep track of the stats.
      * @throws AssertionError if the verification fails.
      */
-    public void verify(TestSensorEnvironment environment, SensorStats stats);
+    void verify(TestSensorEnvironment environment, SensorStats stats);
 
     /**
      * Clones the {@link ISensorVerification}
      */
-    public ISensorVerification clone();
+    ISensorVerification clone();
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerificationTest.java
index 0c85b63..50e288c 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerificationTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/JitterVerificationTest.java
@@ -22,6 +22,8 @@
 import android.hardware.cts.helpers.TestSensorEnvironment;
 import android.hardware.cts.helpers.TestSensorEvent;
 
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -98,11 +100,13 @@
         assertEquals(3.0, jitterValues.get(3));
     }
 
-    private JitterVerification getVerification(int threshold, long ... timestamps) {
-        JitterVerification verification = new JitterVerification(threshold);
+    private static JitterVerification getVerification(int threshold, long ... timestamps) {
+        Collection<TestSensorEvent> events = new ArrayList<>(timestamps.length);
         for (long timestamp : timestamps) {
-            verification.addSensorEvent(new TestSensorEvent(null, timestamp, 0, null));
+            events.add(new TestSensorEvent(null, timestamp, 0, null));
         }
+        JitterVerification verification = new JitterVerification(threshold);
+        verification.addSensorEvents(events);
         return verification;
     }
 
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MagnitudeVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MagnitudeVerificationTest.java
index bb29330..ac873c1 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MagnitudeVerificationTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MagnitudeVerificationTest.java
@@ -22,6 +22,9 @@
 import android.hardware.cts.helpers.TestSensorEnvironment;
 import android.hardware.cts.helpers.TestSensorEvent;
 
+import java.util.ArrayList;
+import java.util.Collection;
+
 /**
  * Tests for {@link MagnitudeVerification}.
  */
@@ -63,12 +66,14 @@
         assertEquals(magnitude, (Float) stats.getValue(SensorStats.MAGNITUDE_KEY), 0.01);
     }
 
-    private MagnitudeVerification getVerification(float expected, float threshold,
+    private static MagnitudeVerification getVerification(float expected, float threshold,
             float[] ... values) {
-        MagnitudeVerification verification = new MagnitudeVerification(expected, threshold);
+        Collection<TestSensorEvent> events = new ArrayList<>(values.length);
         for (float[] value : values) {
-            verification.addSensorEvent(new TestSensorEvent(null, 0, 0, value));
+            events.add(new TestSensorEvent(null, 0, 0, value));
         }
+        MagnitudeVerification verification = new MagnitudeVerification(expected, threshold);
+        verification.addSensorEvents(events);
         return verification;
     }
 }
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MeanVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MeanVerificationTest.java
index b07ea50..d7fcf9f 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MeanVerificationTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/MeanVerificationTest.java
@@ -22,6 +22,9 @@
 import android.hardware.cts.helpers.TestSensorEnvironment;
 import android.hardware.cts.helpers.TestSensorEvent;
 
+import java.util.ArrayList;
+import java.util.Collection;
+
 /**
  * Tests for {@link MeanVerification}.
  */
@@ -89,12 +92,14 @@
         verifyStats(stats, false, new float[]{2.0f, 3.0f, 6.0f});
     }
 
-    private MeanVerification getVerification(float[] expected, float[] threshold,
+    private static MeanVerification getVerification(float[] expected, float[] threshold,
             float[] ... values) {
-        MeanVerification verification = new MeanVerification(expected, threshold);
+        Collection<TestSensorEvent> events = new ArrayList<>(values.length);
         for (float[] value : values) {
-            verification.addSensorEvent(new TestSensorEvent(null, 0, 0, value));
+            events.add(new TestSensorEvent(null, 0, 0, value));
         }
+        MeanVerification verification = new MeanVerification(expected, threshold);
+        verification.addSensorEvents(events);
         return verification;
     }
 
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/StandardDeviationVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/StandardDeviationVerificationTest.java
index 5d958f5..617a438 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/StandardDeviationVerificationTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/StandardDeviationVerificationTest.java
@@ -22,6 +22,9 @@
 import android.hardware.cts.helpers.TestSensorEnvironment;
 import android.hardware.cts.helpers.TestSensorEvent;
 
+import java.util.ArrayList;
+import java.util.Collection;
+
 /**
  * Tests for {@link StandardDeviationVerification}.
  */
@@ -79,11 +82,15 @@
         }
     }
 
-    private StandardDeviationVerification getVerification(float[] threshold, float[] ... values) {
-        StandardDeviationVerification verification = new StandardDeviationVerification(threshold);
+    private static StandardDeviationVerification getVerification(
+            float[] threshold,
+            float[] ... values) {
+        Collection<TestSensorEvent> events = new ArrayList<>(values.length);
         for (float[] value : values) {
-            verification.addSensorEvent(new TestSensorEvent(null, 0, 0, value));
+            events.add(new TestSensorEvent(null, 0, 0, value));
         }
+        StandardDeviationVerification verification = new StandardDeviationVerification(threshold);
+        verification.addSensorEvents(events);
         return verification;
     }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyChainTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyChainTest.java
index db6d6ba..7aa6a9d 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyChainTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyChainTest.java
@@ -16,10 +16,11 @@
 
 package android.keystore.cts;
 
+import android.content.pm.PackageManager;
 import android.security.KeyChain;
-import junit.framework.TestCase;
+import android.test.AndroidTestCase;
 
-public class KeyChainTest extends TestCase {
+public class KeyChainTest extends AndroidTestCase {
     public void testIsKeyAlgorithmSupported_RequiredAlgorithmsSupported() throws Exception {
         assertTrue("DSA must be supported", KeyChain.isKeyAlgorithmSupported("DSA"));
         assertTrue("EC must be supported", KeyChain.isKeyAlgorithmSupported("EC"));
@@ -34,11 +35,21 @@
      * tests in hardware/libhardware/tests/keymaster/
      */
     public void testIsBoundKeyAlgorithm_RequiredAlgorithmsSupported() throws Exception {
-        assertTrue("RSA must be hardware-backed by a hardware-specific Keymaster HAL",
-                KeyChain.isBoundKeyAlgorithm("RSA"));
+        if (isLeanbackOnly()) {
+            KeyChain.isBoundKeyAlgorithm("RSA");
+        }
+        else {
+            assertTrue("RSA must be hardware-backed by a hardware-specific Keymaster HAL",
+                       KeyChain.isBoundKeyAlgorithm("RSA"));
+        }
 
         // These are not required, but must not throw an exception
         KeyChain.isBoundKeyAlgorithm("DSA");
         KeyChain.isBoundKeyAlgorithm("EC");
     }
+
+    private boolean isLeanbackOnly() {
+        PackageManager pm = getContext().getPackageManager();
+        return (pm != null && pm.hasSystemFeature("android.software.leanback_only"));
+    }
 }
diff --git a/tests/tests/location2/src/android/location2/cts/LocationManagerTest.java b/tests/tests/location2/src/android/location2/cts/LocationManagerTest.java
index 3765809..b298b97 100644
--- a/tests/tests/location2/src/android/location2/cts/LocationManagerTest.java
+++ b/tests/tests/location2/src/android/location2/cts/LocationManagerTest.java
@@ -26,6 +26,7 @@
 import android.location.Criteria;
 import android.location.GpsStatus;
 import android.location.GpsStatus.Listener;
+import android.location.GpsStatus.NmeaListener;
 import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
@@ -227,18 +228,32 @@
     @UiThreadTest
     public void testGpsStatusListener() {
         try {
-            // .addGpsStatusListener returns true if the listener added successfully
-            if (mManager.addGpsStatusListener(new MockGpsStatusListener())) {
-                fail("Should have failed to add a gps status listener");
-            }
+            mManager.addGpsStatusListener(new MockGpsStatusListener());
+            fail("Should have failed to add a gps status listener");
         } catch (SecurityException e) {
             // expected
         }
 
         try {
-            if (mManager.addGpsStatusListener(null)) {
-                fail("Should have failed to add null as a gps status listener");
-            }
+            mManager.addGpsStatusListener(null);
+            fail("Should have failed to add null as a gps status listener");
+        } catch (SecurityException e) {
+            // expected
+        }
+    }
+
+    @UiThreadTest
+    public void testGpsStatusNmeaListener() {
+        try {
+            mManager.addNmeaListener(new MockGpsStatusNmeaListener());
+            fail("Should have failed to add a gps status nmea listener");
+        } catch (SecurityException e) {
+            // expected
+        }
+
+        try {
+            mManager.addNmeaListener(null);
+            fail("Should have failed to add null as a gps status nmea listener");
         } catch (SecurityException e) {
             // expected
         }
@@ -478,4 +493,20 @@
             mHasCallOnGpsStatusChanged = true;
         }
     }
+
+    private static class MockGpsStatusNmeaListener implements NmeaListener {
+        private boolean mHasCallOnNmeaReceived;
+
+        public boolean hasCallOnNmeaReceived() {
+            return mHasCallOnNmeaReceived;
+        }
+
+        public void reset(){
+            mHasCallOnNmeaReceived = false;
+        }
+
+        public void onNmeaReceived(long timestamp, String nmea) {
+            mHasCallOnNmeaReceived = true;
+        }
+    }
 }
diff --git a/tests/tests/media/Android.mk b/tests/tests/media/Android.mk
index 15237a8..77d4bb7 100644
--- a/tests/tests/media/Android.mk
+++ b/tests/tests/media/Android.mk
@@ -16,6 +16,20 @@
 
 include $(CLEAR_VARS)
 
+LOCAL_SRC_FILES := \
+	src/android/media/cts/CodecImage.java \
+	src/android/media/cts/CodecUtils.java
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := ctsmediautil
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+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
@@ -24,7 +38,8 @@
 # include both the 32 and 64 bit versions
 LOCAL_MULTILIB := both
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestserver ctstestrunner
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctsmediautil ctsdeviceutil ctstestserver ctstestrunner
 
 LOCAL_JNI_SHARED_LIBRARIES := libctsmediacodec_jni
 
diff --git a/tests/tests/media/libmediandkjni/Android.mk b/tests/tests/media/libmediandkjni/Android.mk
index 2d2033f..23f9f5c 100644
--- a/tests/tests/media/libmediandkjni/Android.mk
+++ b/tests/tests/media/libmediandkjni/Android.mk
@@ -20,9 +20,13 @@
 
 LOCAL_MODULE_TAGS := optional
 
-LOCAL_SRC_FILES := native-media-jni.cpp
+LOCAL_SRC_FILES := \
+	native-media-jni.cpp \
+	codec-utils-jni.cpp
 
-LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
+LOCAL_C_INCLUDES := \
+	$(JNI_H_INCLUDE) \
+	system/core/include
 
 LOCAL_C_INCLUDES += $(call include-path-for, mediandk)
 
diff --git a/tests/tests/media/libmediandkjni/codec-utils-jni.cpp b/tests/tests/media/libmediandkjni/codec-utils-jni.cpp
new file mode 100644
index 0000000..4f87bb4
--- /dev/null
+++ b/tests/tests/media/libmediandkjni/codec-utils-jni.cpp
@@ -0,0 +1,488 @@
+/*
+ * 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.
+ */
+
+/* Original code copied from NDK Native-media sample code */
+
+//#define LOG_NDEBUG 0
+#define TAG "CodecUtilsJNI"
+#include <log/log.h>
+
+#include <stdint.h>
+#include <sys/types.h>
+#include <jni.h>
+
+#include <ScopedLocalRef.h>
+#include <JNIHelp.h>
+
+#include <math.h>
+
+typedef ssize_t offs_t;
+
+struct NativeImage {
+    struct crop {
+        int left;
+        int top;
+        int right;
+        int bottom;
+    } crop;
+    struct plane {
+        const uint8_t *buffer;
+        size_t size;
+        ssize_t colInc;
+        ssize_t rowInc;
+        offs_t cropOffs;
+        size_t cropWidth;
+        size_t cropHeight;
+    } plane[3];
+    int width;
+    int height;
+    int format;
+    long timestamp;
+    size_t numPlanes;
+};
+
+struct ChecksumAlg {
+    virtual void init() = 0;
+    virtual void update(uint8_t c) = 0;
+    virtual uint32_t checksum() = 0;
+    virtual size_t length() = 0;
+protected:
+    virtual ~ChecksumAlg() {}
+};
+
+struct Adler32 : ChecksumAlg {
+    Adler32() {
+        init();
+    }
+    void init() {
+        a = 1;
+        len = b = 0;
+    }
+    void update(uint8_t c) {
+        a += c;
+        b += a;
+        ++len;
+    }
+    uint32_t checksum() {
+        return (a % 65521) + ((b % 65521) << 16);
+    }
+    size_t length() {
+        return len;
+    }
+private:
+    uint32_t a, b;
+    size_t len;
+};
+
+static struct ImageFieldsAndMethods {
+    // android.graphics.ImageFormat
+    int YUV_420_888;
+    // android.media.Image
+    jmethodID methodWidth;
+    jmethodID methodHeight;
+    jmethodID methodFormat;
+    jmethodID methodTimestamp;
+    jmethodID methodPlanes;
+    jmethodID methodCrop;
+    // android.media.Image.Plane
+    jmethodID methodBuffer;
+    jmethodID methodPixelStride;
+    jmethodID methodRowStride;
+    // android.graphics.Rect
+    jfieldID fieldLeft;
+    jfieldID fieldTop;
+    jfieldID fieldRight;
+    jfieldID fieldBottom;
+} gFields;
+static bool gFieldsInitialized = false;
+
+void initializeGlobalFields(JNIEnv *env) {
+    if (gFieldsInitialized) {
+        return;
+    }
+    {   // ImageFormat
+        jclass imageFormatClazz = env->FindClass("android/graphics/ImageFormat");
+        const jfieldID fieldYUV420888 = env->GetStaticFieldID(imageFormatClazz, "YUV_420_888", "I");
+        gFields.YUV_420_888 = env->GetStaticIntField(imageFormatClazz, fieldYUV420888);
+        env->DeleteLocalRef(imageFormatClazz);
+        imageFormatClazz = NULL;
+    }
+
+    {   // Image
+        jclass imageClazz = env->FindClass("android/media/cts/CodecImage");
+        gFields.methodWidth  = env->GetMethodID(imageClazz, "getWidth", "()I");
+        gFields.methodHeight = env->GetMethodID(imageClazz, "getHeight", "()I");
+        gFields.methodFormat = env->GetMethodID(imageClazz, "getFormat", "()I");
+        gFields.methodTimestamp = env->GetMethodID(imageClazz, "getTimestamp", "()J");
+        gFields.methodPlanes = env->GetMethodID(
+                imageClazz, "getPlanes", "()[Landroid/media/cts/CodecImage$Plane;");
+        gFields.methodCrop   = env->GetMethodID(
+                imageClazz, "getCropRect", "()Landroid/graphics/Rect;");
+        env->DeleteLocalRef(imageClazz);
+        imageClazz = NULL;
+    }
+
+    {   // Image.Plane
+        jclass planeClazz = env->FindClass("android/media/cts/CodecImage$Plane");
+        gFields.methodBuffer = env->GetMethodID(planeClazz, "getBuffer", "()Ljava/nio/ByteBuffer;");
+        gFields.methodPixelStride = env->GetMethodID(planeClazz, "getPixelStride", "()I");
+        gFields.methodRowStride = env->GetMethodID(planeClazz, "getRowStride", "()I");
+        env->DeleteLocalRef(planeClazz);
+        planeClazz = NULL;
+    }
+
+    {   // Rect
+        jclass rectClazz = env->FindClass("android/graphics/Rect");
+        gFields.fieldLeft   = env->GetFieldID(rectClazz, "left", "I");
+        gFields.fieldTop    = env->GetFieldID(rectClazz, "top", "I");
+        gFields.fieldRight  = env->GetFieldID(rectClazz, "right", "I");
+        gFields.fieldBottom = env->GetFieldID(rectClazz, "bottom", "I");
+        env->DeleteLocalRef(rectClazz);
+        rectClazz = NULL;
+    }
+    gFieldsInitialized = true;
+}
+
+NativeImage *getNativeImage(JNIEnv *env, jobject image, jobject area = NULL) {
+    if (image == NULL) {
+        jniThrowNullPointerException(env, "image is null");
+        return NULL;
+    }
+
+    initializeGlobalFields(env);
+
+    NativeImage *img = new NativeImage;
+    img->format = env->CallIntMethod(image, gFields.methodFormat);
+    img->width  = env->CallIntMethod(image, gFields.methodWidth);
+    img->height = env->CallIntMethod(image, gFields.methodHeight);
+    img->timestamp = env->CallLongMethod(image, gFields.methodTimestamp);
+
+    jobject cropRect = NULL;
+    if (area == NULL) {
+        cropRect = env->CallObjectMethod(image, gFields.methodCrop);
+        area = cropRect;
+    }
+
+    img->crop.left   = env->GetIntField(area, gFields.fieldLeft);
+    img->crop.top    = env->GetIntField(area, gFields.fieldTop);
+    img->crop.right  = env->GetIntField(area, gFields.fieldRight);
+    img->crop.bottom = env->GetIntField(area, gFields.fieldBottom);
+    if (img->crop.right == 0 && img->crop.bottom == 0) {
+        img->crop.right  = img->width;
+        img->crop.bottom = img->height;
+    }
+
+    if (cropRect != NULL) {
+        env->DeleteLocalRef(cropRect);
+        cropRect = NULL;
+    }
+
+    if (img->format != gFields.YUV_420_888) {
+        jniThrowException(
+                env, "java/lang/UnsupportedOperationException",
+                "only support YUV_420_888 images");
+        delete img;
+        img = NULL;
+        return NULL;
+    }
+    img->numPlanes = 3;
+
+    ScopedLocalRef<jobjectArray> planesArray(
+            env, (jobjectArray)env->CallObjectMethod(image, gFields.methodPlanes));
+    int xDecim = 0;
+    int yDecim = 0;
+    for (size_t ix = 0; ix < img->numPlanes; ++ix) {
+        ScopedLocalRef<jobject> plane(
+                env, env->GetObjectArrayElement(planesArray.get(), (jsize)ix));
+        img->plane[ix].colInc = env->CallIntMethod(plane.get(), gFields.methodPixelStride);
+        img->plane[ix].rowInc = env->CallIntMethod(plane.get(), gFields.methodRowStride);
+        ScopedLocalRef<jobject> buffer(
+                env, env->CallObjectMethod(plane.get(), gFields.methodBuffer));
+
+        img->plane[ix].buffer = (const uint8_t *)env->GetDirectBufferAddress(buffer.get());
+        img->plane[ix].size = env->GetDirectBufferCapacity(buffer.get());
+
+        img->plane[ix].cropOffs =
+            (img->crop.left >> xDecim) * img->plane[ix].colInc
+                    + (img->crop.top >> yDecim) * img->plane[ix].rowInc;
+        img->plane[ix].cropHeight =
+            ((img->crop.bottom + (1 << yDecim) - 1) >> yDecim) - (img->crop.top >> yDecim);
+        img->plane[ix].cropWidth =
+            ((img->crop.right + (1 << xDecim) - 1) >> xDecim) - (img->crop.left >> xDecim);
+
+        // sanity check on increments
+        ssize_t widthOffs =
+            (((img->width + (1 << xDecim) - 1) >> xDecim) - 1) * img->plane[ix].colInc;
+        ssize_t heightOffs =
+            (((img->height + (1 << yDecim) - 1) >> yDecim) - 1) * img->plane[ix].rowInc;
+        if (widthOffs < 0 || heightOffs < 0
+                || widthOffs + heightOffs >= (ssize_t)img->plane[ix].size) {
+            jniThrowException(
+                    env, "java/lang/IndexOutOfBoundsException", "plane exceeds bytearray");
+            delete img;
+            img = NULL;
+            return NULL;
+        }
+        xDecim = yDecim = 1;
+    }
+    return img;
+}
+
+extern "C" jint Java_android_media_cts_CodecUtils_getImageChecksum(JNIEnv *env,
+        jclass /*clazz*/, jobject image)
+{
+    NativeImage *img = getNativeImage(env, image);
+    if (img == NULL) {
+        return 0;
+    }
+
+    Adler32 adler;
+    for (size_t ix = 0; ix < img->numPlanes; ++ix) {
+        const uint8_t *row = img->plane[ix].buffer + img->plane[ix].cropOffs;
+        for (size_t y = img->plane[ix].cropHeight; y > 0; --y) {
+            const uint8_t *col = row;
+            ssize_t colInc = img->plane[ix].colInc;
+            for (size_t x = img->plane[ix].cropWidth; x > 0; --x) {
+                adler.update(*col);
+                col += colInc;
+            }
+            row += img->plane[ix].rowInc;
+        }
+    }
+    ALOGV("adler %zu/%u", adler.length(), adler.checksum());
+    return adler.checksum();
+}
+
+/* tiled copy that loops around source image boundary */
+extern "C" void Java_android_media_cts_CodecUtils_copyFlexYUVImage(JNIEnv *env,
+        jclass /*clazz*/, jobject target, jobject source)
+{
+    NativeImage *tgt = getNativeImage(env, target);
+    NativeImage *src = getNativeImage(env, source);
+    if (tgt != NULL && src != NULL) {
+        ALOGV("copyFlexYUVImage %dx%d (%d,%d..%d,%d) (%zux%zu) %+zd%+zd %+zd%+zd %+zd%+zd <= "
+                "%dx%d (%d, %d..%d, %d) (%zux%zu) %+zd%+zd %+zd%+zd %+zd%+zd",
+                tgt->width, tgt->height,
+                tgt->crop.left, tgt->crop.top, tgt->crop.right, tgt->crop.bottom,
+                tgt->plane[0].cropWidth, tgt->plane[0].cropHeight,
+                tgt->plane[0].rowInc, tgt->plane[0].colInc,
+                tgt->plane[1].rowInc, tgt->plane[1].colInc,
+                tgt->plane[2].rowInc, tgt->plane[2].colInc,
+                src->width, src->height,
+                src->crop.left, src->crop.top, src->crop.right, src->crop.bottom,
+                src->plane[0].cropWidth, src->plane[0].cropHeight,
+                src->plane[0].rowInc, src->plane[0].colInc,
+                src->plane[1].rowInc, src->plane[1].colInc,
+                src->plane[2].rowInc, src->plane[2].colInc);
+        for (size_t ix = 0; ix < tgt->numPlanes; ++ix) {
+            uint8_t *row = const_cast<uint8_t *>(tgt->plane[ix].buffer) + tgt->plane[ix].cropOffs;
+            for (size_t y = 0; y < tgt->plane[ix].cropHeight; ++y) {
+                uint8_t *col = row;
+                ssize_t colInc = tgt->plane[ix].colInc;
+                const uint8_t *srcRow = (src->plane[ix].buffer + src->plane[ix].cropOffs
+                        + src->plane[ix].rowInc * (y % src->plane[ix].cropHeight));
+                for (size_t x = 0; x < tgt->plane[ix].cropWidth; ++x) {
+                    *col = srcRow[src->plane[ix].colInc * (x % src->plane[ix].cropWidth)];
+                    col += colInc;
+                }
+                row += tgt->plane[ix].rowInc;
+            }
+        }
+    }
+}
+
+extern "C" void Java_android_media_cts_CodecUtils_fillImageRectWithYUV(JNIEnv *env,
+        jclass /*clazz*/, jobject image, jobject area, jint y, jint u, jint v)
+{
+    NativeImage *img = getNativeImage(env, image, area);
+    if (img == NULL) {
+        return;
+    }
+
+    for (size_t ix = 0; ix < img->numPlanes; ++ix) {
+        const uint8_t *row = img->plane[ix].buffer + img->plane[ix].cropOffs;
+        uint8_t val = ix == 0 ? y : ix == 1 ? u : v;
+        for (size_t y = img->plane[ix].cropHeight; y > 0; --y) {
+            uint8_t *col = (uint8_t *)row;
+            ssize_t colInc = img->plane[ix].colInc;
+            for (size_t x = img->plane[ix].cropWidth; x > 0; --x) {
+                *col = val;
+                col += colInc;
+            }
+            row += img->plane[ix].rowInc;
+        }
+    }
+}
+
+void getRawStats(NativeImage *img, jlong rawStats[10])
+{
+    // this works best if crop area is even
+
+    uint64_t sum_x[3]  = { 0, 0, 0 }; // Y, U, V
+    uint64_t sum_xx[3] = { 0, 0, 0 }; // YY, UU, VV
+    uint64_t sum_xy[3] = { 0, 0, 0 }; // YU, YV, UV
+
+    const uint8_t *yrow = img->plane[0].buffer + img->plane[0].cropOffs;
+    const uint8_t *urow = img->plane[1].buffer + img->plane[1].cropOffs;
+    const uint8_t *vrow = img->plane[2].buffer + img->plane[2].cropOffs;
+
+    ssize_t ycolInc = img->plane[0].colInc;
+    ssize_t ucolInc = img->plane[1].colInc;
+    ssize_t vcolInc = img->plane[2].colInc;
+
+    ssize_t yrowInc = img->plane[0].rowInc;
+    ssize_t urowInc = img->plane[1].rowInc;
+    ssize_t vrowInc = img->plane[2].rowInc;
+
+    size_t rightOdd = img->crop.right & 1;
+    size_t bottomOdd = img->crop.bottom & 1;
+
+    for (size_t y = img->plane[0].cropHeight; y; --y) {
+        uint8_t *ycol = (uint8_t *)yrow;
+        uint8_t *ucol = (uint8_t *)urow;
+        uint8_t *vcol = (uint8_t *)vrow;
+
+        for (size_t x = img->plane[0].cropWidth; x; --x) {
+            uint64_t Y = *ycol;
+            uint64_t U = *ucol;
+            uint64_t V = *vcol;
+
+            sum_x[0] += Y;
+            sum_x[1] += U;
+            sum_x[2] += V;
+            sum_xx[0] += Y * Y;
+            sum_xx[1] += U * U;
+            sum_xx[2] += V * V;
+            sum_xy[0] += Y * U;
+            sum_xy[1] += Y * V;
+            sum_xy[2] += U * V;
+
+            ycol += ycolInc;
+            if (rightOdd ^ (x & 1)) {
+                ucol += ucolInc;
+                vcol += vcolInc;
+            }
+        }
+
+        yrow += yrowInc;
+        if (bottomOdd ^ (y & 1)) {
+            urow += urowInc;
+            vrow += vrowInc;
+        }
+    }
+
+    rawStats[0] = img->plane[0].cropWidth * (uint64_t)img->plane[0].cropHeight;
+    for (size_t i = 0; i < 3; i++) {
+        rawStats[i + 1] = sum_x[i];
+        rawStats[i + 4] = sum_xx[i];
+        rawStats[i + 7] = sum_xy[i];
+    }
+}
+
+bool Raw2YUVStats(jlong rawStats[10], jfloat stats[9]) {
+    int64_t sum_x[3], sum_xx[3]; // Y, U, V
+    int64_t sum_xy[3];           // YU, YV, UV
+
+    int64_t num = rawStats[0];   // #Y,U,V
+    for (size_t i = 0; i < 3; i++) {
+        sum_x[i] = rawStats[i + 1];
+        sum_xx[i] = rawStats[i + 4];
+        sum_xy[i] = rawStats[i + 7];
+    }
+
+    if (num > 0) {
+        stats[0] = sum_x[0] / (float)num;  // y average
+        stats[1] = sum_x[1] / (float)num;  // u average
+        stats[2] = sum_x[2] / (float)num;  // v average
+
+        // 60 bits for 4Mpixel image
+        // adding 1 to avoid degenerate case when deviation is 0
+        stats[3] = sqrtf((sum_xx[0] + 1) * num - sum_x[0] * sum_x[0]) / num; // y stdev
+        stats[4] = sqrtf((sum_xx[1] + 1) * num - sum_x[1] * sum_x[1]) / num; // u stdev
+        stats[5] = sqrtf((sum_xx[2] + 1) * num - sum_x[2] * sum_x[2]) / num; // v stdev
+
+        // yu covar
+        stats[6] = (float)(sum_xy[0] + 1 - sum_x[0] * sum_x[1] / num) / num / stats[3] / stats[4];
+        // yv covar
+        stats[7] = (float)(sum_xy[1] + 1 - sum_x[0] * sum_x[2] / num) / num / stats[3] / stats[5];
+        // uv covar
+        stats[8] = (float)(sum_xy[2] + 1 - sum_x[1] * sum_x[2] / num) / num / stats[4] / stats[5];
+        return true;
+    } else {
+        return false;
+    }
+}
+
+extern "C" jobject Java_android_media_cts_CodecUtils_getRawStats(JNIEnv *env,
+        jclass /*clazz*/, jobject image, jobject area)
+{
+    NativeImage *img = getNativeImage(env, image, area);
+    if (img == NULL) {
+        return NULL;
+    }
+
+    jlong rawStats[10];
+    getRawStats(img, rawStats);
+    jlongArray jstats = env->NewLongArray(10);
+    if (jstats != NULL) {
+        env->SetLongArrayRegion(jstats, 0, 10, rawStats);
+    }
+    return jstats;
+}
+
+extern "C" jobject Java_android_media_cts_CodecUtils_getYUVStats(JNIEnv *env,
+        jclass /*clazz*/, jobject image, jobject area)
+{
+    NativeImage *img = getNativeImage(env, image, area);
+    if (img == NULL) {
+        return NULL;
+    }
+
+    jlong rawStats[10];
+    getRawStats(img, rawStats);
+    jfloat stats[9];
+    jfloatArray jstats = NULL;
+    if (Raw2YUVStats(rawStats, stats)) {
+        jstats = env->NewFloatArray(9);
+        if (jstats != NULL) {
+            env->SetFloatArrayRegion(jstats, 0, 9, stats);
+        }
+    } else {
+        jniThrowRuntimeException(env, "empty area");
+    }
+
+    return jstats;
+}
+
+extern "C" jobject Java_android_media_cts_CodecUtils_Raw2YUVStats(JNIEnv *env,
+        jclass /*clazz*/, jobject jrawStats)
+{
+    jfloatArray jstats = NULL;
+    jlong rawStats[10];
+    env->GetLongArrayRegion((jlongArray)jrawStats, 0, 10, rawStats);
+    if (!env->ExceptionCheck()) {
+        jfloat stats[9];
+        if (Raw2YUVStats(rawStats, stats)) {
+            jstats = env->NewFloatArray(9);
+            if (jstats != NULL) {
+                env->SetFloatArrayRegion(jstats, 0, 9, stats);
+            }
+        } else {
+            jniThrowRuntimeException(env, "no raw statistics");
+        }
+    }
+    return jstats;
+}
diff --git a/tests/tests/media/libmediandkjni/native-media-jni.cpp b/tests/tests/media/libmediandkjni/native-media-jni.cpp
index 850932f1..9bca242 100644
--- a/tests/tests/media/libmediandkjni/native-media-jni.cpp
+++ b/tests/tests/media/libmediandkjni/native-media-jni.cpp
@@ -16,7 +16,10 @@
 
 /* Original code copied from NDK Native-media sample code */
 
-#undef NDEBUG
+//#define LOG_NDEBUG 0
+#define TAG "NativeMedia"
+#include <log/log.h>
+
 #include <assert.h>
 #include <jni.h>
 #include <pthread.h>
@@ -27,13 +30,6 @@
 
 #include <android/native_window_jni.h>
 
-// for __android_log_print(ANDROID_LOG_INFO, "YourApp", "formatted message");
-#include <android/log.h>
-#define TAG "NativeMedia"
-#define ALOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, TAG, __VA_ARGS__)
-#define ALOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
-#define ALOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
-
 #include "ndk/NdkMediaExtractor.h"
 #include "ndk/NdkMediaCodec.h"
 #include "ndk/NdkMediaCrypto.h"
diff --git a/tests/tests/media/res/raw/midi8sec.mid b/tests/tests/media/res/raw/midi8sec.mid
new file mode 100644
index 0000000..746aca1
--- /dev/null
+++ b/tests/tests/media/res/raw/midi8sec.mid
Binary files differ
diff --git a/tests/tests/media/res/raw/midi_a.mid b/tests/tests/media/res/raw/midi_a.mid
new file mode 100644
index 0000000..941885f
--- /dev/null
+++ b/tests/tests/media/res/raw/midi_a.mid
Binary files differ
diff --git a/tests/tests/media/res/raw/midi_b.mid b/tests/tests/media/res/raw/midi_b.mid
new file mode 100644
index 0000000..424c960
--- /dev/null
+++ b/tests/tests/media/res/raw/midi_b.mid
Binary files differ
diff --git a/tests/tests/media/res/raw/midi_cs.mid b/tests/tests/media/res/raw/midi_cs.mid
new file mode 100644
index 0000000..ee9cb23
--- /dev/null
+++ b/tests/tests/media/res/raw/midi_cs.mid
Binary files differ
diff --git a/tests/tests/media/res/raw/midi_e.mid b/tests/tests/media/res/raw/midi_e.mid
new file mode 100644
index 0000000..dac820d
--- /dev/null
+++ b/tests/tests/media/res/raw/midi_e.mid
Binary files differ
diff --git a/tests/tests/media/res/raw/midi_gs.mid b/tests/tests/media/res/raw/midi_gs.mid
new file mode 100644
index 0000000..1d43841
--- /dev/null
+++ b/tests/tests/media/res/raw/midi_gs.mid
Binary files differ
diff --git a/tests/tests/media/res/raw/sine1khzm40db.wav b/tests/tests/media/res/raw/sine1khzm40db.wav
new file mode 100644
index 0000000..ba541c4
--- /dev/null
+++ b/tests/tests/media/res/raw/sine1khzm40db.wav
Binary files differ
diff --git a/tests/tests/media/res/raw/sine1khzs40dblong.mp3 b/tests/tests/media/res/raw/sine1khzs40dblong.mp3
new file mode 100644
index 0000000..29bc683
--- /dev/null
+++ b/tests/tests/media/res/raw/sine1khzs40dblong.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_128x128_h264.mp4 b/tests/tests/media/res/raw/swirl_128x128_h264.mp4
new file mode 100644
index 0000000..3ff485a
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_128x128_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_128x128_h265.mp4 b/tests/tests/media/res/raw/swirl_128x128_h265.mp4
new file mode 100644
index 0000000..a0b112b
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_128x128_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_128x128_mpeg4.mp4 b/tests/tests/media/res/raw/swirl_128x128_mpeg4.mp4
new file mode 100644
index 0000000..694ce95
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_128x128_mpeg4.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_128x128_vp8.webm b/tests/tests/media/res/raw/swirl_128x128_vp8.webm
new file mode 100644
index 0000000..7b606a2
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_128x128_vp8.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_128x128_vp9.webm b/tests/tests/media/res/raw/swirl_128x128_vp9.webm
new file mode 100644
index 0000000..7acff11
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_128x128_vp9.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_128x96_h263.3gp b/tests/tests/media/res/raw/swirl_128x96_h263.3gp
new file mode 100644
index 0000000..f0ef242
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_128x96_h263.3gp
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_130x132_h264.mp4 b/tests/tests/media/res/raw/swirl_130x132_h264.mp4
new file mode 100644
index 0000000..60027fd
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_130x132_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_130x132_h265.mp4 b/tests/tests/media/res/raw/swirl_130x132_h265.mp4
new file mode 100644
index 0000000..46fab26
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_130x132_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_130x132_mpeg4.mp4 b/tests/tests/media/res/raw/swirl_130x132_mpeg4.mp4
new file mode 100644
index 0000000..ed6b529
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_130x132_mpeg4.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_130x132_vp8.webm b/tests/tests/media/res/raw/swirl_130x132_vp8.webm
new file mode 100644
index 0000000..a3f2d21
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_130x132_vp8.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_130x132_vp9.webm b/tests/tests/media/res/raw/swirl_130x132_vp9.webm
new file mode 100644
index 0000000..840dc59
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_130x132_vp9.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_132x130_h264.mp4 b/tests/tests/media/res/raw/swirl_132x130_h264.mp4
new file mode 100644
index 0000000..dc17f8f
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_132x130_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_132x130_h265.mp4 b/tests/tests/media/res/raw/swirl_132x130_h265.mp4
new file mode 100644
index 0000000..f9a59f5
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_132x130_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_132x130_mpeg4.mp4 b/tests/tests/media/res/raw/swirl_132x130_mpeg4.mp4
new file mode 100644
index 0000000..ed975db
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_132x130_mpeg4.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_132x130_vp8.webm b/tests/tests/media/res/raw/swirl_132x130_vp8.webm
new file mode 100644
index 0000000..8cd8d4e
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_132x130_vp8.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_132x130_vp9.webm b/tests/tests/media/res/raw/swirl_132x130_vp9.webm
new file mode 100644
index 0000000..4a8d79f
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_132x130_vp9.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_136x144_h264.mp4 b/tests/tests/media/res/raw/swirl_136x144_h264.mp4
new file mode 100644
index 0000000..bc5fadf
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_136x144_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_136x144_h265.mp4 b/tests/tests/media/res/raw/swirl_136x144_h265.mp4
new file mode 100644
index 0000000..38f1fc8
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_136x144_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_136x144_mpeg4.mp4 b/tests/tests/media/res/raw/swirl_136x144_mpeg4.mp4
new file mode 100644
index 0000000..c74bd96
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_136x144_mpeg4.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_136x144_vp8.webm b/tests/tests/media/res/raw/swirl_136x144_vp8.webm
new file mode 100644
index 0000000..960d02f
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_136x144_vp8.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_136x144_vp9.webm b/tests/tests/media/res/raw/swirl_136x144_vp9.webm
new file mode 100644
index 0000000..5898f07
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_136x144_vp9.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_144x136_h264.mp4 b/tests/tests/media/res/raw/swirl_144x136_h264.mp4
new file mode 100644
index 0000000..962a218
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_144x136_h264.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_144x136_h265.mp4 b/tests/tests/media/res/raw/swirl_144x136_h265.mp4
new file mode 100644
index 0000000..8098621
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_144x136_h265.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_144x136_mpeg4.mp4 b/tests/tests/media/res/raw/swirl_144x136_mpeg4.mp4
new file mode 100644
index 0000000..81c1db3
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_144x136_mpeg4.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_144x136_vp8.webm b/tests/tests/media/res/raw/swirl_144x136_vp8.webm
new file mode 100644
index 0000000..b050ade
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_144x136_vp8.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_144x136_vp9.webm b/tests/tests/media/res/raw/swirl_144x136_vp9.webm
new file mode 100644
index 0000000..9c0539a
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_144x136_vp9.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_176x144_h263.3gp b/tests/tests/media/res/raw/swirl_176x144_h263.3gp
new file mode 100644
index 0000000..ee51660
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_176x144_h263.3gp
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_352x288_h263.3gp b/tests/tests/media/res/raw/swirl_352x288_h263.3gp
new file mode 100644
index 0000000..53830a9
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_352x288_h263.3gp
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_mp4_h264_8192kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_1280x720_mp4_h264_8192kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..2c9c3d3
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_mp4_h264_8192kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_mp4_h264_8192kbps_60fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_1280x720_mp4_h264_8192kbps_60fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..71150af
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_mp4_h264_8192kbps_60fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_mp4_hevc_4096kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_1280x720_mp4_hevc_4096kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..18d53e6
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_mp4_hevc_4096kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_webm_vp8_8192kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1280x720_webm_vp8_8192kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..bf93ab4
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_webm_vp8_8192kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_webm_vp8_8192kbps_60fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1280x720_webm_vp8_8192kbps_60fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..ae0e0e3
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_webm_vp8_8192kbps_60fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz.webm b/tests/tests/media/res/raw/video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz.webm
deleted file mode 100644
index 65b436a..0000000
--- a/tests/tests/media/res/raw/video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..b79e3f6
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1280x720_webm_vp9_4096kbps_30fps_vorbis_stereo_128kbps_44100hz.webm b/tests/tests/media/res/raw/video_1280x720_webm_vp9_4096kbps_30fps_vorbis_stereo_128kbps_44100hz.webm
new file mode 100644
index 0000000..a5eddd3
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1280x720_webm_vp9_4096kbps_30fps_vorbis_stereo_128kbps_44100hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1920x1080_mp4_h264_20480kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_1920x1080_mp4_h264_20480kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..35ab7a0
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1920x1080_mp4_h264_20480kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1920x1080_mp4_h264_20480kbps_60fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_1920x1080_mp4_h264_20480kbps_60fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..b837dc2
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1920x1080_mp4_h264_20480kbps_60fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1920x1080_mp4_hevc_10240kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_1920x1080_mp4_hevc_10240kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..631ea04
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1920x1080_mp4_hevc_10240kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1920x1080_webm_vp8_20480kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1920x1080_webm_vp8_20480kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..c147461
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1920x1080_webm_vp8_20480kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1920x1080_webm_vp8_20480kbps_60fps_vorbis_stereo_128kbps_44100hz.webm b/tests/tests/media/res/raw/video_1920x1080_webm_vp8_20480kbps_60fps_vorbis_stereo_128kbps_44100hz.webm
new file mode 100644
index 0000000..d6f0f3b
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1920x1080_webm_vp8_20480kbps_60fps_vorbis_stereo_128kbps_44100hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_1920x1080_webm_vp9_10240kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_1920x1080_webm_vp9_10240kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..949b327
--- /dev/null
+++ b/tests/tests/media/res/raw/video_1920x1080_webm_vp9_10240kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_320x240_mp4_h264_800kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_320x240_mp4_h264_800kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..12e07ce
--- /dev/null
+++ b/tests/tests/media/res/raw/video_320x240_mp4_h264_800kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_44100hz.webm b/tests/tests/media/res/raw/video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_44100hz.webm
new file mode 100644
index 0000000..15c34b9
--- /dev/null
+++ b/tests/tests/media/res/raw/video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_44100hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_320x240_webm_vp9_600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_320x240_webm_vp9_600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..086f144
--- /dev/null
+++ b/tests/tests/media/res/raw/video_320x240_webm_vp9_600kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_352x288_mp4_hevc_600kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_352x288_mp4_hevc_600kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..4404877
--- /dev/null
+++ b/tests/tests/media/res/raw/video_352x288_mp4_hevc_600kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_3840x2160_mp4_hevc_20480kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_3840x2160_mp4_hevc_20480kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..9277fbd
--- /dev/null
+++ b/tests/tests/media/res/raw/video_3840x2160_mp4_hevc_20480kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_3840x2160_webm_vp9_20480kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_3840x2160_webm_vp9_20480kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..d6ed441
--- /dev/null
+++ b/tests/tests/media/res/raw/video_3840x2160_webm_vp9_20480kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_480x360_mp4_h264_871kbps_30fps.mp4 b/tests/tests/media/res/raw/video_480x360_mp4_h264_871kbps_30fps.mp4
new file mode 100644
index 0000000..55a83e7
--- /dev/null
+++ b/tests/tests/media/res/raw/video_480x360_mp4_h264_871kbps_30fps.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm b/tests/tests/media/res/raw/video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm
deleted file mode 100644
index f64aec3..0000000
--- a/tests/tests/media/res/raw/video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/res/raw/video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..c101291
--- /dev/null
+++ b/tests/tests/media/res/raw/video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm b/tests/tests/media/res/raw/video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm
deleted file mode 100644
index 59b0c44..0000000
--- a/tests/tests/media/res/raw/video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz.webm
+++ /dev/null
Binary files differ
diff --git a/tests/tests/media/res/raw/video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..0a33e54
--- /dev/null
+++ b/tests/tests/media/res/raw/video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_640x360_webm_vp8_2048kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_640x360_webm_vp8_2048kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..60860f1
--- /dev/null
+++ b/tests/tests/media/res/raw/video_640x360_webm_vp8_2048kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_640x360_webm_vp9_1638kbps_30fps_vorbis_stereo_128kbps_48000hz.webm b/tests/tests/media/res/raw/video_640x360_webm_vp9_1638kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
new file mode 100644
index 0000000..418cc91
--- /dev/null
+++ b/tests/tests/media/res/raw/video_640x360_webm_vp9_1638kbps_30fps_vorbis_stereo_128kbps_48000hz.webm
Binary files differ
diff --git a/tests/tests/media/res/raw/video_720x480_mp4_h264_2048kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_720x480_mp4_h264_2048kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..055e217
--- /dev/null
+++ b/tests/tests/media/res/raw/video_720x480_mp4_h264_2048kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/video_720x480_mp4_hevc_1638kbps_30fps_aac_stereo_128kbps_44100hz.mp4 b/tests/tests/media/res/raw/video_720x480_mp4_hevc_1638kbps_30fps_aac_stereo_128kbps_44100hz.mp4
new file mode 100644
index 0000000..87c6897
--- /dev/null
+++ b/tests/tests/media/res/raw/video_720x480_mp4_hevc_1638kbps_30fps_aac_stereo_128kbps_44100hz.mp4
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
index 5c9f1b1..67473e1 100644
--- a/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
+++ b/tests/tests/media/src/android/media/cts/AdaptivePlaybackTest.java
@@ -19,6 +19,7 @@
 import com.android.cts.media.R;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
@@ -49,7 +50,7 @@
     public Iterable<Codec> H264(CodecFactory factory) {
         return factory.createCodecList(
                 mContext,
-                "video/avc",
+                MediaFormat.MIMETYPE_VIDEO_AVC,
                 "OMX.google.h264.decoder",
                 R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
                 R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
@@ -58,7 +59,7 @@
     public Iterable<Codec> HEVC(CodecFactory factory) {
         return factory.createCodecList(
                 mContext,
-                "video/hevc",
+                MediaFormat.MIMETYPE_VIDEO_HEVC,
                 "OMX.google.hevc.decoder",
                 R.raw.video_640x360_mp4_hevc_450kbps_30fps_aac_stereo_128kbps_48000hz,
                 R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz);
@@ -67,7 +68,7 @@
     public Iterable<Codec> H263(CodecFactory factory) {
         return factory.createCodecList(
                 mContext,
-                "video/3gpp",
+                MediaFormat.MIMETYPE_VIDEO_H263,
                 "OMX.google.h263.decoder",
                 R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
                 R.raw.video_352x288_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz);
@@ -76,7 +77,7 @@
     public Iterable<Codec> Mpeg4(CodecFactory factory) {
         return factory.createCodecList(
                 mContext,
-                "video/mp4v-es",
+                MediaFormat.MIMETYPE_VIDEO_MPEG4,
                 "OMX.google.mpeg4.decoder",
 
                 R.raw.video_1280x720_mp4_mpeg4_1000kbps_25fps_aac_stereo_128kbps_44100hz,
@@ -86,19 +87,19 @@
     public Iterable<Codec> VP8(CodecFactory factory) {
         return factory.createCodecList(
                 mContext,
-                "video/x-vnd.on2.vp8",
+                MediaFormat.MIMETYPE_VIDEO_VP8,
                 "OMX.google.vp8.decoder",
-                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
+                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
                 R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz);
     }
 
     public Iterable<Codec> VP9(CodecFactory factory) {
         return factory.createCodecList(
                 mContext,
-                "video/x-vnd.on2.vp9",
+                MediaFormat.MIMETYPE_VIDEO_VP9,
                 "OMX.google.vp9.decoder",
-                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz);
+                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
+                R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz);
     }
 
     CodecFactory ALL = new CodecFactory();
@@ -285,6 +286,11 @@
     }
 
     private void ex(Iterable<Codec> codecList, Test[] testList) {
+        if (codecList == null) {
+            Log.i(TAG, "CodecList was empty. Skipping test.");
+            return;
+        }
+
         TestList tests = new TestList();
         for (Codec c : codecList) {
             for (Test test : testList) {
@@ -1282,9 +1288,8 @@
             }
 
             /* enumerate codecs */
-            int codecCount = MediaCodecList.getCodecCount();
-            for (int codecIx = 0; codecIx < codecCount; codecIx++) {
-                MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(codecIx);
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
                 if (codecInfo.isEncoder()) {
                     continue;
                 }
@@ -1305,11 +1310,13 @@
 
             /* test if the explicitly named codec is present on the system */
             if (explicitCodecName != null) {
-                MediaCodec codec = MediaCodec.createByCodecName(explicitCodecName);
-                if (codec != null) {
-                    codec.release();
-                    add(new Codec(explicitCodecName, null, mediaList));
-                }
+                try {
+                    MediaCodec codec = MediaCodec.createByCodecName(explicitCodecName);
+                    if (codec != null) {
+                        codec.release();
+                        add(new Codec(explicitCodecName, null, mediaList));
+                    }
+                } catch (Exception e) {}
             }
         } catch (Throwable t) {
             Log.wtf("Constructor failed", t);
@@ -1342,8 +1349,21 @@
 }
 
 class CodecFactory {
+    protected boolean hasCodec(String codecName) {
+        MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo info : list.getCodecInfos()) {
+            if (codecName.equals(info.getName())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public CodecList createCodecList(
             Context context, String mime, String googleCodecName, int ...resources) {
+        if (!hasCodec(googleCodecName)) {
+            return null;
+        }
         return new CodecFamily(context, mime, googleCodecName, resources);
     }
 }
@@ -1351,6 +1371,9 @@
 class SWCodecFactory extends CodecFactory {
     public CodecList createCodecList(
             Context context, String mime, String googleCodecName, int ...resources) {
+        if (!hasCodec(googleCodecName)) {
+            return null;
+        }
         return new CodecByName(context, mime, googleCodecName, resources);
     }
 }
@@ -1358,6 +1381,9 @@
 class HWCodecFactory extends CodecFactory {
     public CodecList createCodecList(
             Context context, String mime, String googleCodecName, int ...resources) {
+        if (!hasCodec(googleCodecName)) {
+            return null;
+        }
         return new CodecFamilyExcept(context, mime, googleCodecName, resources);
     }
 }
diff --git a/tests/tests/media/src/android/media/cts/AudioManagerTest.java b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
index e65fb0b..f58e6ab 100644
--- a/tests/tests/media/src/android/media/cts/AudioManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioManagerTest.java
@@ -38,10 +38,10 @@
 
 import com.android.cts.media.R;
 
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.media.AudioManager;
+import android.content.pm.PackageManager;
 import android.media.MediaPlayer;
 import android.os.Vibrator;
 import android.provider.Settings;
@@ -49,13 +49,19 @@
 import android.test.AndroidTestCase;
 import android.view.SoundEffectConstants;
 
+import java.util.TreeMap;
+
 public class AudioManagerTest extends AndroidTestCase {
 
     private final static int MP3_TO_PLAY = R.raw.testmp3;
     private final static long TIME_TO_PLAY = 2000;
     private AudioManager mAudioManager;
     private boolean mHasVibrator;
+    private boolean mUseMasterVolume;
     private boolean mUseFixedVolume;
+    private int[] mMasterVolumeRamp;
+    private TreeMap<Integer, Integer> mMasterVolumeMap = new TreeMap<Integer, Integer>();
+    private boolean mIsTelevision;
 
     @Override
     protected void setUp() throws Exception {
@@ -63,8 +69,20 @@
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         Vibrator vibrator = (Vibrator) mContext.getSystemService(Context.VIBRATOR_SERVICE);
         mHasVibrator = (vibrator != null) && vibrator.hasVibrator();
+        mUseMasterVolume = mContext.getResources().getBoolean(
+                Resources.getSystem().getIdentifier("config_useMasterVolume", "bool", "android"));
         mUseFixedVolume = mContext.getResources().getBoolean(
                 Resources.getSystem().getIdentifier("config_useFixedVolume", "bool", "android"));
+        mMasterVolumeRamp = mContext.getResources().getIntArray(
+                Resources.getSystem().getIdentifier("config_masterVolumeRamp", "array", "android"));
+        assertTrue((mMasterVolumeRamp.length > 0) && (mMasterVolumeRamp.length % 2 == 0));
+        for (int i = 0; i < mMasterVolumeRamp.length; i+=2) {
+            mMasterVolumeMap.put(mMasterVolumeRamp[i], mMasterVolumeRamp[i+1]);
+        }
+        PackageManager packageManager = mContext.getPackageManager();
+        mIsTelevision = packageManager != null
+                && (packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+                        || packageManager.hasSystemFeature(PackageManager.FEATURE_TELEVISION));
     }
 
     public void testMicrophoneMute() throws Exception {
@@ -171,7 +189,7 @@
     }
 
     public void testVibrateNotification() throws Exception {
-        if (mUseFixedVolume) {
+        if (mUseFixedVolume || !mHasVibrator) {
             return;
         }
         // VIBRATE_SETTING_ON
@@ -232,7 +250,7 @@
     }
 
     public void testVibrateRinger() throws Exception {
-        if (mUseFixedVolume) {
+        if (mUseFixedVolume || !mHasVibrator) {
             return;
         }
         // VIBRATE_TYPE_RINGER
@@ -298,14 +316,16 @@
         assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
 
         mAudioManager.setRingerMode(RINGER_MODE_SILENT);
-        if (mUseFixedVolume) {
+        // AudioService#setRingerMode() has:
+        // if (isTelevision) return;
+        if (mUseFixedVolume || mIsTelevision) {
             assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
         } else {
             assertEquals(RINGER_MODE_SILENT, mAudioManager.getRingerMode());
         }
 
         mAudioManager.setRingerMode(RINGER_MODE_VIBRATE);
-        if (mUseFixedVolume) {
+        if (mUseFixedVolume || mIsTelevision) {
             assertEquals(RINGER_MODE_NORMAL, mAudioManager.getRingerMode());
         } else {
             assertEquals(mHasVibrator ? RINGER_MODE_VIBRATE : RINGER_MODE_SILENT,
@@ -314,6 +334,7 @@
     }
 
     public void testVolume() throws Exception {
+        int volume, volumeDelta;
         int[] streams = { AudioManager.STREAM_ALARM,
                           AudioManager.STREAM_MUSIC,
                           AudioManager.STREAM_VOICE_CALL,
@@ -356,22 +377,32 @@
             mAudioManager.adjustStreamVolume(streams[i], ADJUST_RAISE, 0);
             assertEquals(maxVolume, mAudioManager.getStreamVolume(streams[i]));
 
+            volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(streams[i]));
             mAudioManager.adjustSuggestedStreamVolume(ADJUST_LOWER, streams[i], 0);
-            assertEquals(maxVolume - 1, mAudioManager.getStreamVolume(streams[i]));
+            assertEquals(maxVolume - volumeDelta, mAudioManager.getStreamVolume(streams[i]));
 
             // volume lower
             mAudioManager.setStreamVolume(streams[i], maxVolume, 0);
-            for (int k = maxVolume; k > 0; k--) {
+            volume = mAudioManager.getStreamVolume(streams[i]);
+            while (volume > 0) {
+                volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(streams[i]));
                 mAudioManager.adjustStreamVolume(streams[i], ADJUST_LOWER, 0);
-                assertEquals(k - 1, mAudioManager.getStreamVolume(streams[i]));
+                assertEquals(Math.max(0, volume - volumeDelta),
+                             mAudioManager.getStreamVolume(streams[i]));
+                volume = mAudioManager.getStreamVolume(streams[i]);
             }
 
             mAudioManager.adjustStreamVolume(streams[i], ADJUST_SAME, 0);
+
             // volume raise
             mAudioManager.setStreamVolume(streams[i], 1, 0);
-            for (int k = 1; k < maxVolume; k++) {
+            volume = mAudioManager.getStreamVolume(streams[i]);
+            while (volume < maxVolume) {
+                volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(streams[i]));
                 mAudioManager.adjustStreamVolume(streams[i], ADJUST_RAISE, 0);
-                assertEquals(1 + k, mAudioManager.getStreamVolume(streams[i]));
+                assertEquals(Math.min(volume + volumeDelta, maxVolume),
+                             mAudioManager.getStreamVolume(streams[i]));
+                volume = mAudioManager.getStreamVolume(streams[i]);
             }
 
             // volume same
@@ -406,18 +437,20 @@
         }
 
         // adjust volume as ADJUST_RAISE
-        mAudioManager.setStreamVolume(STREAM_MUSIC, 1, 0);
-        for (int k = 0; k < maxMusicVolume - 1; k++) {
-            mAudioManager.adjustVolume(ADJUST_RAISE, 0);
-            assertEquals(2 + k, mAudioManager.getStreamVolume(STREAM_MUSIC));
-        }
+        mAudioManager.setStreamVolume(STREAM_MUSIC, 0, 0);
+        volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
+        mAudioManager.adjustVolume(ADJUST_RAISE, 0);
+        assertEquals(Math.min(volumeDelta, maxMusicVolume),
+                     mAudioManager.getStreamVolume(STREAM_MUSIC));
 
         // adjust volume as ADJUST_LOWER
         mAudioManager.setStreamVolume(STREAM_MUSIC, maxMusicVolume, 0);
         maxMusicVolume = mAudioManager.getStreamVolume(STREAM_MUSIC);
-
+        volumeDelta = getVolumeDelta(mAudioManager.getStreamVolume(STREAM_MUSIC));
         mAudioManager.adjustVolume(ADJUST_LOWER, 0);
-        assertEquals(maxMusicVolume - 1, mAudioManager.getStreamVolume(STREAM_MUSIC));
+        assertEquals(Math.max(0, maxMusicVolume - volumeDelta),
+                     mAudioManager.getStreamVolume(STREAM_MUSIC));
+
         mp.stop();
         mp.release();
         Thread.sleep(TIME_TO_PLAY);
@@ -432,4 +465,13 @@
         mAudioManager.setRingerMode(-3007);
         assertEquals(ringerMode, mAudioManager.getRingerMode());
     }
+
+    private int getVolumeDelta(int volume) {
+        if (!mUseMasterVolume) {
+            return 1;
+        }
+        int volumeDelta = mMasterVolumeMap.floorEntry(volume).getValue();
+        assertTrue(volumeDelta > 0);
+        return volumeDelta;
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index d01ecec..4f6d27f 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -16,6 +16,7 @@
 
 package android.media.cts;
 
+import android.content.pm.PackageManager;
 import android.cts.util.CtsAndroidTestCase;
 import android.media.AudioFormat;
 import android.media.AudioManager;
@@ -1309,36 +1310,160 @@
     }
 
     public static byte[] createSoundDataInByteArray(int bufferSamples, final int sampleRate,
-            final double frequency) {
+            final double frequency, double sweep) {
         final double rad = 2 * Math.PI * frequency / sampleRate;
         byte[] vai = new byte[bufferSamples];
+        sweep = Math.PI * sweep / ((double)sampleRate * vai.length);
         for (int j = 0; j < vai.length; j++) {
-            int unsigned =  (int)(Math.sin(j * rad) * Byte.MAX_VALUE) + Byte.MAX_VALUE & 0xFF;
+            int unsigned =  (int)(Math.sin(j * (rad + j * sweep)) * Byte.MAX_VALUE)
+                    + Byte.MAX_VALUE & 0xFF;
             vai[j] = (byte) unsigned;
         }
         return vai;
     }
 
     public static short[] createSoundDataInShortArray(int bufferSamples, final int sampleRate,
-            final double frequency) {
+            final double frequency, double sweep) {
         final double rad = 2 * Math.PI * frequency / sampleRate;
         short[] vai = new short[bufferSamples];
+        sweep = Math.PI * sweep / ((double)sampleRate * vai.length);
         for (int j = 0; j < vai.length; j++) {
-            vai[j] = (short)(Math.sin(j * rad) * Short.MAX_VALUE);
+            vai[j] = (short)(Math.sin(j * (rad + j * sweep)) * Short.MAX_VALUE);
         }
         return vai;
     }
 
     public static float[] createSoundDataInFloatArray(int bufferSamples, final int sampleRate,
-            final double frequency) {
+            final double frequency, double sweep) {
         final double rad = 2 * Math.PI * frequency / sampleRate;
         float[] vaf = new float[bufferSamples];
+        sweep = Math.PI * sweep / ((double)sampleRate * vaf.length);
         for (int j = 0; j < vaf.length; j++) {
-            vaf[j] = (float)(Math.sin(j * rad));
+            vaf[j] = (float)(Math.sin(j * (rad + j * sweep)));
         }
         return vaf;
     }
 
+    public static byte[] createSoundDataInByteArray(int bufferSamples, final int sampleRate,
+            final double frequency) {
+        return createSoundDataInByteArray(bufferSamples, sampleRate, frequency, 0 /*sweep*/);
+    }
+
+    public static short[] createSoundDataInShortArray(int bufferSamples, final int sampleRate,
+            final double frequency) {
+        return createSoundDataInShortArray(bufferSamples, sampleRate, frequency, 0 /*sweep*/);
+    }
+
+    public static float[] createSoundDataInFloatArray(int bufferSamples, final int sampleRate,
+            final double frequency) {
+        return createSoundDataInFloatArray(bufferSamples, sampleRate, frequency, 0 /*sweep*/);
+    }
+
+    public void testPlayStaticData() throws Exception {
+        // constants for test
+        final String TEST_NAME = "testPlayStaticData";
+        final int TEST_FORMAT_ARRAY[] = {  // 6 chirps repeated (TEST_LOOPS+1) times, 3 times
+                AudioFormat.ENCODING_PCM_8BIT,
+                AudioFormat.ENCODING_PCM_16BIT,
+                AudioFormat.ENCODING_PCM_FLOAT,
+        };
+        final int TEST_SR_ARRAY[] = {
+                12055, // Note multichannel tracks will sound very short at low sample rates
+                48000,
+        };
+        final int TEST_CONF_ARRAY[] = {
+                AudioFormat.CHANNEL_OUT_MONO,    // 1.0
+                AudioFormat.CHANNEL_OUT_STEREO,  // 2.0
+                AudioFormat.CHANNEL_OUT_7POINT1_SURROUND, // 7.1
+        };
+        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;
+
+        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, track.getState(), AudioTrack.STATE_NO_STATIC_DATA);
+
+                    // -------- test --------------
+
+                    // test setLoopPoints and setPosition can be called here.
+                    assertEquals(TEST_NAME,
+                            track.setPlaybackHeadPosition(bufferFrames/2),
+                            android.media.AudioTrack.SUCCESS);
+                    assertEquals(TEST_NAME,
+                            track.setLoopPoints(
+                                    0 /*startInFrames*/, bufferFrames, 10 /*loopCount*/),
+                            android.media.AudioTrack.SUCCESS);
+                    // only need to write once to the static track
+                    switch (TEST_FORMAT) {
+                    case AudioFormat.ENCODING_PCM_8BIT: {
+                        byte data[] = createSoundDataInByteArray(
+                                bufferSamples, TEST_SR,
+                                testFrequency, TEST_SWEEP);
+                        assertEquals(TEST_NAME,
+                                track.write(data, 0 /*offsetInBytes*/, data.length),
+                                bufferSamples);
+                        } break;
+                    case AudioFormat.ENCODING_PCM_16BIT: {
+                        short data[] = createSoundDataInShortArray(
+                                bufferSamples, TEST_SR,
+                                testFrequency, TEST_SWEEP);
+                        assertEquals(TEST_NAME,
+                                track.write(data, 0 /*offsetInBytes*/, data.length),
+                                bufferSamples);
+                        } break;
+                    case AudioFormat.ENCODING_PCM_FLOAT: {
+                        float data[] = createSoundDataInFloatArray(
+                                bufferSamples, TEST_SR,
+                                testFrequency, TEST_SWEEP);
+                        assertEquals(TEST_NAME,
+                                track.write(data, 0 /*offsetInBytes*/, data.length,
+                                        AudioTrack.WRITE_BLOCKING),
+                                bufferSamples);
+                        } break;
+                    }
+                    assertEquals(TEST_NAME, track.getState(), AudioTrack.STATE_INITIALIZED);
+                    // test setLoopPoints and setPosition can be called here.
+                    assertEquals(TEST_NAME,
+                            track.setPlaybackHeadPosition(0 /*positionInFrames*/),
+                            android.media.AudioTrack.SUCCESS);
+                    assertEquals(TEST_NAME,
+                            track.setLoopPoints(0 /*startInFrames*/, bufferFrames, TEST_LOOPS),
+                            android.media.AudioTrack.SUCCESS);
+
+                    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, position, bufferFrames * (TEST_LOOPS + 1));
+
+                    track.stop();
+                    Thread.sleep(WAIT_MSEC);
+                    // -------- tear down --------------
+                    track.release();
+                    frequency += 70; // increment test tone frequency
+                }
+            }
+        }
+    }
+
     public void testPlayStreamData() throws Exception {
         // constants for test
         final String TEST_NAME = "testPlayStreamData";
@@ -1541,7 +1666,16 @@
         }
     }
 
+    private boolean hasAudioOutput() {
+        return getContext().getPackageManager()
+            .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+    }
+
     public void testGetTimestamp() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        
         // constants for test
         final String TEST_NAME = "testGetTimestamp";
         final int TEST_SR = 22050;
diff --git a/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java b/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java
index 8130a9a..dd96c2c 100644
--- a/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java
+++ b/tests/tests/media/src/android/media/cts/CamcorderProfileTest.java
@@ -25,12 +25,47 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import java.util.Arrays;
 import java.util.List;
 
 public class CamcorderProfileTest extends AndroidTestCase {
 
     private static final String TAG = "CamcorderProfileTest";
     private static final int MIN_HIGH_SPEED_FPS = 100;
+    private static final Integer[] ALL_SUPPORTED_QUALITIES = {
+        CamcorderProfile.QUALITY_LOW,
+        CamcorderProfile.QUALITY_HIGH,
+        CamcorderProfile.QUALITY_QCIF,
+        CamcorderProfile.QUALITY_CIF,
+        CamcorderProfile.QUALITY_480P,
+        CamcorderProfile.QUALITY_720P,
+        CamcorderProfile.QUALITY_1080P,
+        CamcorderProfile.QUALITY_QVGA,
+        CamcorderProfile.QUALITY_2160P,
+        CamcorderProfile.QUALITY_TIME_LAPSE_LOW,
+        CamcorderProfile.QUALITY_TIME_LAPSE_HIGH,
+        CamcorderProfile.QUALITY_TIME_LAPSE_QCIF,
+        CamcorderProfile.QUALITY_TIME_LAPSE_CIF,
+        CamcorderProfile.QUALITY_TIME_LAPSE_480P,
+        CamcorderProfile.QUALITY_TIME_LAPSE_720P,
+        CamcorderProfile.QUALITY_TIME_LAPSE_1080P,
+        CamcorderProfile.QUALITY_TIME_LAPSE_QVGA,
+        CamcorderProfile.QUALITY_TIME_LAPSE_2160P,
+        CamcorderProfile.QUALITY_HIGH_SPEED_LOW,
+        CamcorderProfile.QUALITY_HIGH_SPEED_HIGH,
+        CamcorderProfile.QUALITY_HIGH_SPEED_480P,
+        CamcorderProfile.QUALITY_HIGH_SPEED_720P,
+        CamcorderProfile.QUALITY_HIGH_SPEED_1080P,
+        CamcorderProfile.QUALITY_HIGH_SPEED_2160P
+    };
+    private static final int LAST_QUALITY = CamcorderProfile.QUALITY_2160P;
+    private static final int LAST_TIMELAPSE_QUALITY = CamcorderProfile.QUALITY_TIME_LAPSE_2160P;
+    private static final int LAST_HIGH_SPEED_QUALITY = CamcorderProfile.QUALITY_HIGH_SPEED_2160P;
+    private static final Integer[] UNKNOWN_QUALITIES = {
+        LAST_QUALITY + 1, // Unknown normal profile quality
+        LAST_TIMELAPSE_QUALITY + 1, // Unknown timelapse profile quality
+        LAST_HIGH_SPEED_QUALITY + 1 // Unknown high speed timelapse profile quality
+    };
 
     // Uses get without id if cameraId == -1 and get with id otherwise.
     private CamcorderProfile getWithOptionalId(int quality, int cameraId) {
@@ -59,27 +94,7 @@
             profile.audioSampleRate,
             profile.audioChannels));
         assertTrue(profile.duration > 0);
-        assertTrue(profile.quality == CamcorderProfile.QUALITY_LOW ||
-                   profile.quality == CamcorderProfile.QUALITY_HIGH ||
-                   profile.quality == CamcorderProfile.QUALITY_QCIF ||
-                   profile.quality == CamcorderProfile.QUALITY_CIF ||
-                   profile.quality == CamcorderProfile.QUALITY_480P ||
-                   profile.quality == CamcorderProfile.QUALITY_720P ||
-                   profile.quality == CamcorderProfile.QUALITY_1080P ||
-                   profile.quality == CamcorderProfile.QUALITY_2160P ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_LOW ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_HIGH ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_QCIF ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_CIF ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_480P ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_720P ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_1080P ||
-                   profile.quality == CamcorderProfile.QUALITY_TIME_LAPSE_2160P ||
-                   profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_LOW ||
-                   profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_HIGH ||
-                   profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_480P ||
-                   profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_720P ||
-                   profile.quality == CamcorderProfile.QUALITY_HIGH_SPEED_1080P);
+        assertTrue(Arrays.asList(ALL_SUPPORTED_QUALITIES).contains(profile.quality));
         assertTrue(profile.videoBitRate > 0);
         assertTrue(profile.videoFrameRate > 0);
         assertTrue(profile.videoFrameWidth > 0);
@@ -233,19 +248,30 @@
 
         final List<Size> videoSizes = getSupportedVideoSizes(cameraId);
 
-        CamcorderProfile lowProfile =
-            getWithOptionalId(CamcorderProfile.QUALITY_LOW, cameraId);
-        CamcorderProfile highProfile =
-            getWithOptionalId(CamcorderProfile.QUALITY_HIGH, cameraId);
-        checkProfile(lowProfile, videoSizes);
-        checkProfile(highProfile, videoSizes);
+        /**
+         * Check all possible supported profiles: get profile should work, and the profile
+         * should be sane. Note that, timelapse and high speed video sizes may not be listed
+         * as supported video sizes from camera, skip the size check.
+         */
+        for (Integer quality : ALL_SUPPORTED_QUALITIES) {
+            if (CamcorderProfile.hasProfile(cameraId, quality) || isProfileMandatory(quality)) {
+                List<Size> videoSizesToCheck = null;
+                if (quality >= CamcorderProfile.QUALITY_LOW &&
+                                quality <= CamcorderProfile.QUALITY_2160P) {
+                    videoSizesToCheck = videoSizes;
+                }
+                CamcorderProfile profile = getWithOptionalId(quality, cameraId);
+                checkProfile(profile, videoSizesToCheck);
+            }
+        }
 
-        CamcorderProfile lowTimeLapseProfile =
-            getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_LOW, cameraId);
-        CamcorderProfile highTimeLapseProfile =
-            getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, cameraId);
-        checkProfile(lowTimeLapseProfile, null);
-        checkProfile(highTimeLapseProfile, null);
+        /**
+         * Check unknown profiles: hasProfile() should return false.
+         */
+        for (Integer quality : UNKNOWN_QUALITIES) {
+            assertFalse("Unknown profile quality " + quality + " shouldn't be supported by camera "
+                    + cameraId, CamcorderProfile.hasProfile(cameraId, quality));
+        }
 
         // High speed low and high profile are optional,
         // but they should be both present or missing.
@@ -288,8 +314,17 @@
 
         int[] specificHighSpeedProfileQualities = {CamcorderProfile.QUALITY_HIGH_SPEED_480P,
                                                    CamcorderProfile.QUALITY_HIGH_SPEED_720P,
-                                                   CamcorderProfile.QUALITY_HIGH_SPEED_1080P};
+                                                   CamcorderProfile.QUALITY_HIGH_SPEED_1080P,
+                                                   CamcorderProfile.QUALITY_HIGH_SPEED_2160P};
 
+        CamcorderProfile lowProfile =
+                getWithOptionalId(CamcorderProfile.QUALITY_LOW, cameraId);
+        CamcorderProfile highProfile =
+                getWithOptionalId(CamcorderProfile.QUALITY_HIGH, cameraId);
+        CamcorderProfile lowTimeLapseProfile =
+                getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_LOW, cameraId);
+        CamcorderProfile highTimeLapseProfile =
+                getWithOptionalId(CamcorderProfile.QUALITY_TIME_LAPSE_HIGH, cameraId);
         checkSpecificProfiles(cameraId, lowProfile, highProfile,
                 specificProfileQualities, videoSizes);
         checkSpecificProfiles(cameraId, lowTimeLapseProfile, highTimeLapseProfile,
@@ -342,4 +377,11 @@
         Log.e(TAG, "Size (" + width + "x" + height + ") is not supported");
         return false;
     }
+
+    private boolean isProfileMandatory(int quality) {
+        return (quality == CamcorderProfile.QUALITY_LOW) ||
+                (quality == CamcorderProfile.QUALITY_HIGH) ||
+                (quality == CamcorderProfile.QUALITY_TIME_LAPSE_LOW) ||
+                (quality == CamcorderProfile.QUALITY_TIME_LAPSE_HIGH);
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java b/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
index c05a605..c837d0a 100644
--- a/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
+++ b/tests/tests/media/src/android/media/cts/ClearKeySystemTest.java
@@ -16,6 +16,7 @@
 package android.media.cts;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.CodecCapabilities;
@@ -23,6 +24,7 @@
 import android.media.MediaCodecList;
 import android.media.MediaDrm;
 import android.media.MediaDrmException;
+import android.media.MediaFormat;
 import android.media.CamcorderProfile;
 import android.net.Uri;
 import android.os.Environment;
@@ -62,7 +64,7 @@
     private static final int VIDEO_WIDTH = 1280;
     private static final int VIDEO_HEIGHT = 720;
     private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
-    private static final String MIME_VIDEO_AVC = "video/avc";
+    private static final String MIME_VIDEO_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
 
     private static final Uri AUDIO_URL = Uri.parse(
             "http://yt-dash-mse-test.commondatastorage.googleapis.com/media/car_cenc-20120827-8c.mp4");
@@ -302,110 +304,26 @@
             }
         }
 
-        CodecCapabilities cap;
-        int highestProfileLevel = 0;
-        MediaCodecInfo codecInfo;
-
-        for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
-            codecInfo = MediaCodecList.getCodecInfoAt(i);
-            if (codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; ++j) {
-                if (!types[j].equalsIgnoreCase(MIME_VIDEO_AVC)) {
-                    continue;
-                }
-
-                Log.d(TAG, "codec: " + codecInfo.getName() + "types: " + types[j]);
-                cap = codecInfo.getCapabilitiesForType(types[j]);
-                for (CodecProfileLevel profileLevel : cap.profileLevels) {
-                    Log.i(TAG, "codec " + codecInfo.getName() + ", level " + profileLevel.level);
-                    if (profileLevel.level > highestProfileLevel) {
-                        highestProfileLevel = profileLevel.level;
-                    }
-                }
-                Log.i(TAG, "codec " + codecInfo.getName() + ", highest level " + highestProfileLevel);
-            }
+        MediaFormat format = MediaFormat.createVideoFormat(
+                MIME_VIDEO_AVC, videoWidth, videoHeight);
+        // using secure codec even though it is clear key DRM
+        format.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, true);
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        if (mcl.findDecoderForFormat(format) == null) {
+            Log.i(TAG, "could not find codec for " + format);
+            return false;
         }
-
-        // AVCLevel and its resolution is taken from http://en.wikipedia.org/wiki/H.264/MPEG-4_AVC
-        switch(highestProfileLevel) {
-        case CodecProfileLevel.AVCLevel1:
-        case CodecProfileLevel.AVCLevel1b:
-            return (videoWidth <= 176 && videoHeight <= 144);
-        case CodecProfileLevel.AVCLevel11:
-        case CodecProfileLevel.AVCLevel12:
-        case CodecProfileLevel.AVCLevel13:
-        case CodecProfileLevel.AVCLevel2:
-            return (videoWidth <= 352 && videoHeight <= 288);
-        case CodecProfileLevel.AVCLevel21:
-            return (videoWidth <= 352 && videoHeight <= 576);
-        case CodecProfileLevel.AVCLevel22:
-        case CodecProfileLevel.AVCLevel3:
-            return (videoWidth <= 720 && videoHeight <= 576);
-        case CodecProfileLevel.AVCLevel31:
-            return (videoWidth <= 1280 && videoHeight <= 720);
-        case CodecProfileLevel.AVCLevel32:
-            return (videoWidth <= 1280 && videoHeight <= 1024);
-        case CodecProfileLevel.AVCLevel4:
-        case CodecProfileLevel.AVCLevel41:
-            // 1280 x 720
-            // 1920 x 1080
-            // 2048 x 1024
-            if (videoWidth <= 1920) {
-                return (videoHeight <= 1080);
-            } else if (videoWidth <= 2048) {
-                return (videoHeight <= 1024);
-            } else {
-                return false;
-            }
-        case CodecProfileLevel.AVCLevel42:
-            return (videoWidth <= 2048 && videoHeight <= 1080);
-        case CodecProfileLevel.AVCLevel5:
-            // 1920 x 1080
-            // 2048 x 1024
-            // 2048 x 1080
-            // 2560 x 1920
-            // 3672 x 1536
-            if (videoWidth <= 1920) {
-                return (videoHeight <= 1080);
-            } else if (videoWidth <= 2048) {
-                return (videoHeight <= 1080);
-            } else if (videoWidth <= 2560) {
-                return (videoHeight <= 1920);
-            } else if (videoWidth <= 3672) {
-                return (videoHeight <= 1536);
-            } else {
-                return false;
-            }
-        case CodecProfileLevel.AVCLevel51:
-        default:  // any future extension will cap at level 5.1
-            // 1920 x 1080
-            // 2560 x 1920
-            // 3840 x 2160
-            // 4096 x 2048
-            // 4096 x 2160
-            // 4096 x 2304
-            if (videoWidth <= 1920) {
-                return (videoHeight <= 1080);
-            } else if (videoWidth <= 2560) {
-                return (videoHeight <= 1920);
-            } else if (videoWidth <= 3840) {
-                return (videoHeight <= 2160);
-            } else if (videoWidth <= 4096) {
-                return (videoHeight <= 2304);
-            } else {
-                return false;
-            }
-        }
+        return true;
     }
 
     /**
      * Tests clear key system playback.
      */
     public void testClearKeyPlayback() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+
         MediaDrm drm = startDrm();
         if (null == drm) {
             throw new Error("Failed to create drm.");
diff --git a/tests/tests/media/src/android/media/cts/CodecImage.java b/tests/tests/media/src/android/media/cts/CodecImage.java
new file mode 100644
index 0000000..60a644a
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/CodecImage.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import java.nio.ByteBuffer;
+import java.lang.AutoCloseable;
+
+import android.graphics.Rect;
+
+/**
+ * <p>A single complete image buffer to use with a media source such as a
+ * {@link MediaCodec} or a
+ * {@link android.hardware.camera2.CameraDevice CameraDevice}.</p>
+ *
+ * <p>This class allows for efficient direct application access to the pixel
+ * data of the CodecImage through one or more
+ * {@link java.nio.ByteBuffer ByteBuffers}. Each buffer is encapsulated in a
+ * {@link Plane} that describes the layout of the pixel data in that plane. Due
+ * to this direct access, and unlike the {@link android.graphics.Bitmap Bitmap} class,
+ * Images are not directly usable as UI resources.</p>
+ *
+ * <p>Since Images are often directly produced or consumed by hardware
+ * components, they are a limited resource shared across the system, and should
+ * be closed as soon as they are no longer needed.</p>
+ *
+ * <p>For example, when using the {@link ImageReader} class to read out Images
+ * from various media sources, not closing old CodecImage objects will prevent the
+ * availability of new Images once
+ * {@link ImageReader#getMaxImages the maximum outstanding image count} is
+ * reached. When this happens, the function acquiring new Images will typically
+ * throw an {@link IllegalStateException}.</p>
+ *
+ * @see ImageReader
+ */
+public abstract class CodecImage implements AutoCloseable {
+    /**
+     * Get the format for this image. This format determines the number of
+     * ByteBuffers needed to represent the image, and the general layout of the
+     * pixel data in each in ByteBuffer.
+     *
+     * <p>
+     * The format is one of the values from
+     * {@link android.graphics.ImageFormat ImageFormat}. The mapping between the
+     * formats and the planes is as follows:
+     * </p>
+     *
+     * <table>
+     * <tr>
+     *   <th>Format</th>
+     *   <th>Plane count</th>
+     *   <th>Layout details</th>
+     * </tr>
+     * <tr>
+     *   <td>{@link android.graphics.ImageFormat#JPEG JPEG}</td>
+     *   <td>1</td>
+     *   <td>Compressed data, so row and pixel strides are 0. To uncompress, use
+     *      {@link android.graphics.BitmapFactory#decodeByteArray BitmapFactory#decodeByteArray}.
+     *   </td>
+     * </tr>
+     * <tr>
+     *   <td>{@link android.graphics.ImageFormat#YUV_420_888 YUV_420_888}</td>
+     *   <td>3</td>
+     *   <td>A luminance plane followed by the Cb and Cr chroma planes.
+     *     The chroma planes have half the width and height of the luminance
+     *     plane (4:2:0 subsampling). Each pixel sample in each plane has 8 bits.
+     *     Each plane has its own row stride and pixel stride.</td>
+     * </tr>
+     * <tr>
+     *   <td>{@link android.graphics.ImageFormat#RAW_SENSOR RAW_SENSOR}</td>
+     *   <td>1</td>
+     *   <td>A single plane of raw sensor image data, with 16 bits per color
+     *     sample. The details of the layout need to be queried from the source of
+     *     the raw sensor data, such as
+     *     {@link android.hardware.camera2.CameraDevice CameraDevice}.
+     *   </td>
+     * </tr>
+     * </table>
+     *
+     * @see android.graphics.ImageFormat
+     */
+    public abstract int getFormat();
+
+    /**
+     * The width of the image in pixels. For formats where some color channels
+     * are subsampled, this is the width of the largest-resolution plane.
+     */
+    public abstract int getWidth();
+
+    /**
+     * The height of the image in pixels. For formats where some color channels
+     * are subsampled, this is the height of the largest-resolution plane.
+     */
+    public abstract int getHeight();
+
+    /**
+     * Get the timestamp associated with this frame.
+     * <p>
+     * The timestamp is measured in nanoseconds, and is monotonically
+     * increasing. However, the zero point and whether the timestamp can be
+     * compared against other sources of time or images depend on the source of
+     * this image.
+     * </p>
+     */
+    public abstract long getTimestamp();
+
+    private Rect mCropRect;
+
+    /**
+     * Get the crop rectangle associated with this frame.
+     * <p>
+     * The crop rectangle specifies the region of valid pixels in the image,
+     * using coordinates in the largest-resolution plane.
+     */
+    public Rect getCropRect() {
+        if (mCropRect == null) {
+            return new Rect(0, 0, getWidth(), getHeight());
+        } else {
+            return new Rect(mCropRect); // return a copy
+        }
+    }
+
+    /**
+     * Set the crop rectangle associated with this frame.
+     * <p>
+     * The crop rectangle specifies the region of valid pixels in the image,
+     * using coordinates in the largest-resolution plane.
+     */
+    public void setCropRect(Rect cropRect) {
+        if (cropRect != null) {
+            cropRect = new Rect(cropRect);  // make a copy
+            cropRect.intersect(0, 0, getWidth(), getHeight());
+        }
+        mCropRect = cropRect;
+    }
+
+    /**
+     * Get the array of pixel planes for this CodecImage. The number of planes is
+     * determined by the format of the CodecImage.
+     */
+    public abstract Plane[] getPlanes();
+
+    /**
+     * Free up this frame for reuse.
+     * <p>
+     * After calling this method, calling any methods on this {@code CodecImage} will
+     * result in an {@link IllegalStateException}, and attempting to read from
+     * {@link ByteBuffer ByteBuffers} returned by an earlier
+     * {@link Plane#getBuffer} call will have undefined behavior.
+     * </p>
+     */
+    @Override
+    public abstract void close();
+
+    /**
+     * <p>A single color plane of image data.</p>
+     *
+     * <p>The number and meaning of the planes in an CodecImage are determined by the
+     * format of the CodecImage.</p>
+     *
+     * <p>Once the CodecImage has been closed, any access to the the plane's
+     * ByteBuffer will fail.</p>
+     *
+     * @see #getFormat
+     */
+    public static abstract class Plane {
+        /**
+         * <p>The row stride for this color plane, in bytes.</p>
+         *
+         * <p>This is the distance between the start of two consecutive rows of
+         * pixels in the image. The row stride is always greater than 0.</p>
+         */
+        public abstract int getRowStride();
+        /**
+         * <p>The distance between adjacent pixel samples, in bytes.</p>
+         *
+         * <p>This is the distance between two consecutive pixel values in a row
+         * of pixels. It may be larger than the size of a single pixel to
+         * account for interleaved image data or padded formats.
+         * The pixel stride is always greater than 0.</p>
+         */
+        public abstract int getPixelStride();
+        /**
+         * <p>Get a direct {@link java.nio.ByteBuffer ByteBuffer}
+         * containing the frame data.</p>
+         *
+         * <p>In particular, the buffer returned will always have
+         * {@link java.nio.ByteBuffer#isDirect isDirect} return {@code true}, so
+         * the underlying data could be mapped as a pointer in JNI without doing
+         * any copies with {@code GetDirectBufferAddress}.</p>
+         *
+         * @return the byte buffer containing the image data for this plane.
+         */
+        public abstract ByteBuffer getBuffer();
+    }
+
+}
diff --git a/tests/tests/media/src/android/media/cts/CodecState.java b/tests/tests/media/src/android/media/cts/CodecState.java
index cd6b68f..8f62227 100644
--- a/tests/tests/media/src/android/media/cts/CodecState.java
+++ b/tests/tests/media/src/android/media/cts/CodecState.java
@@ -33,6 +33,9 @@
 
     private boolean mSawInputEOS, mSawOutputEOS;
     private boolean mLimitQueueDepth;
+    private boolean mTunneled;
+    private boolean mIsAudio;
+    private int mAudioSessionId;
     private ByteBuffer[] mCodecInputBuffers;
     private ByteBuffer[] mCodecOutputBuffers;
     private int mTrackIndex;
@@ -40,8 +43,9 @@
     private LinkedList<Integer> mAvailableOutputBufferIndices;
     private LinkedList<MediaCodec.BufferInfo> mAvailableOutputBufferInfos;
     private long mPresentationTimeUs;
+    private long mSampleBaseTimeUs;
     private MediaCodec mCodec;
-    private MediaCodecCencPlayer mMediaCodecPlayer;
+    private MediaTimeProvider mMediaTimeProvider;
     private MediaExtractor mExtractor;
     private MediaFormat mFormat;
     private MediaFormat mOutputFormat;
@@ -51,19 +55,23 @@
      * Manages audio and video playback using MediaCodec and AudioTrack.
      */
     public CodecState(
-            MediaCodecCencPlayer mediaCodecPlayer,
+            MediaTimeProvider mediaTimeProvider,
             MediaExtractor extractor,
             int trackIndex,
             MediaFormat format,
             MediaCodec codec,
-            boolean limitQueueDepth) {
-
-        mMediaCodecPlayer = mediaCodecPlayer;
+            boolean limitQueueDepth,
+            boolean tunneled,
+            int audioSessionId) {
+        mMediaTimeProvider = mediaTimeProvider;
         mExtractor = extractor;
         mTrackIndex = trackIndex;
         mFormat = format;
         mSawInputEOS = mSawOutputEOS = false;
         mLimitQueueDepth = limitQueueDepth;
+        mTunneled = tunneled;
+        mAudioSessionId = audioSessionId;
+        mSampleBaseTimeUs = -1;
 
         mCodec = codec;
 
@@ -72,6 +80,10 @@
         mAvailableOutputBufferInfos = new LinkedList<MediaCodec.BufferInfo>();
 
         mPresentationTimeUs = 0;
+
+        String mime = mFormat.getString(MediaFormat.KEY_MIME);
+        Log.d(TAG, "CodecState::onOutputFormatChanged " + mime);
+        mIsAudio = mime.startsWith("audio/");
     }
 
     public void release() {
@@ -100,7 +112,9 @@
     public void start() {
         mCodec.start();
         mCodecInputBuffers = mCodec.getInputBuffers();
-        mCodecOutputBuffers = mCodec.getOutputBuffers();
+        if (!mTunneled || mIsAudio) {
+            mCodecOutputBuffers = mCodec.getOutputBuffers();
+        }
 
         if (mAudioTrack != null) {
             mAudioTrack.play();
@@ -119,15 +133,17 @@
 
     public void flush() {
         mAvailableInputBufferIndices.clear();
-        mAvailableOutputBufferIndices.clear();
-        mAvailableOutputBufferInfos.clear();
+        if (!mTunneled || mIsAudio) {
+            mAvailableOutputBufferIndices.clear();
+            mAvailableOutputBufferInfos.clear();
+        }
 
         mSawInputEOS = false;
         mSawOutputEOS = false;
 
         if (mAudioTrack != null
-                && mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_STOPPED) {
-            mAudioTrack.play();
+                && mAudioTrack.getPlayState() != AudioTrack.PLAYSTATE_PLAYING) {
+            mAudioTrack.flush();
         }
 
         mCodec.flush();
@@ -153,20 +169,22 @@
         while (feedInputBuffer()) {
         }
 
-        MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
-        int indexOutput = mCodec.dequeueOutputBuffer(info, 0 /* timeoutUs */);
+        if (mIsAudio || !mTunneled) {
+            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+            int indexOutput = mCodec.dequeueOutputBuffer(info, 0 /* timeoutUs */);
 
-        if (indexOutput == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
-            mOutputFormat = mCodec.getOutputFormat();
-            onOutputFormatChanged();
-        } else if (indexOutput == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
-            mCodecOutputBuffers = mCodec.getOutputBuffers();
-        } else if (indexOutput != MediaCodec.INFO_TRY_AGAIN_LATER) {
-            mAvailableOutputBufferIndices.add(indexOutput);
-            mAvailableOutputBufferInfos.add(info);
-        }
+            if (indexOutput == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                mOutputFormat = mCodec.getOutputFormat();
+                onOutputFormatChanged();
+            } else if (indexOutput == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                mCodecOutputBuffers = mCodec.getOutputBuffers();
+            } else if (indexOutput != MediaCodec.INFO_TRY_AGAIN_LATER) {
+                mAvailableOutputBufferIndices.add(indexOutput);
+                mAvailableOutputBufferInfos.add(info);
+            }
 
-        while (drainOutputBuffer()) {
+            while (drainOutputBuffer()) {
+            }
         }
     }
 
@@ -200,9 +218,24 @@
                 Log.d(TAG, "sampleSize: " + sampleSize + " trackIndex:" + trackIndex +
                         " sampleTime:" + sampleTime + " sampleFlags:" + sampleFlags);
                 mSawInputEOS = true;
+                // FIX-ME: in tunneled mode we currently use input EOS as output EOS indicator
+                // we should stream duration
+                if (mTunneled && !mIsAudio) {
+                    mSawOutputEOS = true;
+                }
                 return false;
             }
 
+            if (mTunneled && !mIsAudio) {
+                if (mSampleBaseTimeUs == -1) {
+                    mSampleBaseTimeUs = sampleTime;
+                }
+                sampleTime -= mSampleBaseTimeUs;
+                // FIX-ME: in tunneled mode we currently use input buffer time
+                // as video presentation time. This is not accurate and should be fixed
+                mPresentationTimeUs = sampleTime;
+            }
+
             if ((sampleFlags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) {
                 MediaCodec.CryptoInfo info = new MediaCodec.CryptoInfo();
                 mExtractor.getSampleCryptoInfo(info);
@@ -255,7 +288,8 @@
                     sampleRate < 8000 || sampleRate > 128000) {
                 return;
             }
-            mAudioTrack = new NonBlockingAudioTrack(sampleRate, channelCount);
+            mAudioTrack = new NonBlockingAudioTrack(sampleRate, channelCount,
+                                    mTunneled, mAudioSessionId);
             mAudioTrack.play();
         }
 
@@ -285,9 +319,9 @@
         }
 
         long realTimeUs =
-            mMediaCodecPlayer.getRealTimeUsForMediaTime(info.presentationTimeUs);
+            mMediaTimeProvider.getRealTimeUsForMediaTime(info.presentationTimeUs);
 
-        long nowUs = mMediaCodecPlayer.getNowUs();
+        long nowUs = mMediaTimeProvider.getNowUs();
 
         long lateUs = nowUs - realTimeUs;
 
diff --git a/tests/tests/media/src/android/media/cts/CodecUtils.java b/tests/tests/media/src/android/media/cts/CodecUtils.java
new file mode 100644
index 0000000..ef20e67
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/CodecUtils.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import android.graphics.Rect;
+import android.media.cts.CodecImage;
+import android.media.Image;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+
+public class CodecUtils  {
+    private static final String TAG = "CodecUtils";
+
+    /** Load jni on initialization */
+    static {
+        Log.i(TAG, "before loadlibrary");
+        System.loadLibrary("ctsmediacodec_jni");
+        Log.i(TAG, "after loadlibrary");
+    }
+
+    private static class ImageWrapper extends CodecImage {
+        private final Image mImage;
+        private final Plane[] mPlanes;
+
+        private ImageWrapper(Image image) {
+            mImage = image;
+            Image.Plane[] planes = mImage.getPlanes();
+
+            mPlanes = new Plane[planes.length];
+            for (int i = 0; i < planes.length; i++) {
+                mPlanes[i] = new PlaneWrapper(planes[i]);
+            }
+        }
+
+        public static ImageWrapper createFromImage(Image image) {
+            return new ImageWrapper(image);
+        }
+
+        @Override
+        public int getFormat() {
+            return mImage.getFormat();
+        }
+
+        @Override
+        public int getWidth() {
+            return mImage.getWidth();
+        }
+
+        @Override
+        public int getHeight() {
+            return mImage.getHeight();
+        }
+
+        @Override
+        public long getTimestamp() {
+            return mImage.getTimestamp();
+        }
+
+        @Override
+        public Plane[] getPlanes() {
+            return mPlanes;
+        }
+
+        @Override
+        public void close() {
+            mImage.close();
+        }
+
+        private static class PlaneWrapper extends CodecImage.Plane {
+            private final Image.Plane mPlane;
+
+            PlaneWrapper(Image.Plane plane) {
+                mPlane = plane;
+            }
+
+            @Override
+            public int getRowStride() {
+                return mPlane.getRowStride();
+            }
+
+           @Override
+            public int getPixelStride() {
+               return mPlane.getPixelStride();
+            }
+
+            @Override
+            public ByteBuffer getBuffer() {
+                return mPlane.getBuffer();
+            }
+        }
+    }
+
+
+    public native static int getImageChecksum(CodecImage image);
+    public native static void copyFlexYUVImage(CodecImage target, CodecImage source);
+
+    public static void copyFlexYUVImage(Image target, CodecImage source) {
+        copyFlexYUVImage(ImageWrapper.createFromImage(target), source);
+    }
+    public static void copyFlexYUVImage(Image target, Image source) {
+        copyFlexYUVImage(
+                ImageWrapper.createFromImage(target),
+                ImageWrapper.createFromImage(source));
+    }
+
+    public native static void fillImageRectWithYUV(
+            CodecImage image, Rect area, int y, int u, int v);
+
+    public static void fillImageRectWithYUV(Image image, Rect area, int y, int u, int v) {
+        fillImageRectWithYUV(ImageWrapper.createFromImage(image), area, y, u, v);
+    }
+
+    public native static long[] getRawStats(CodecImage image, Rect area);
+
+    public static long[] getRawStats(Image image, Rect area) {
+        return getRawStats(ImageWrapper.createFromImage(image), area);
+    }
+
+    public native static float[] getYUVStats(CodecImage image, Rect area);
+
+    public static float[] getYUVStats(Image image, Rect area) {
+        return getYUVStats(ImageWrapper.createFromImage(image), area);
+    }
+
+    public native static float[] Raw2YUVStats(long[] rawStats);
+}
+
diff --git a/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java b/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
index 9ee3118..1ceb025 100644
--- a/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
+++ b/tests/tests/media/src/android/media/cts/DecodeEditEncodeTest.java
@@ -53,7 +53,8 @@
     private static final boolean DEBUG_SAVE_FILE = false;   // save copy of encoded movie
 
     // parameters for the encoder
-    private static final String MIME_TYPE = "video/avc";    // H.264 Advanced Video Coding
+                                                            // H.264 Advanced Video Coding
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
     private static final int FRAME_RATE = 15;               // 15fps
     private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
 
@@ -806,7 +807,7 @@
 
 
     /**
-     * The elementary stream coming out of the "video/avc" encoder needs to be fed back into
+     * The elementary stream coming out of the encoder needs to be fed back into
      * the decoder one chunk at a time.  If we just wrote the data to a file, we would lose
      * the information about chunk boundaries.  This class stores the encoded data in memory,
      * retaining the chunk organization.
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index ba70f32..318a43f 100644
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -18,16 +18,23 @@
 
 import com.android.cts.media.R;
 
+import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
+import android.cts.util.MediaUtils;
 import android.graphics.ImageFormat;
 import android.media.Image;
+import android.media.AudioManager;
 import android.media.MediaCodec;
+import android.media.MediaCodecList;
 import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
 import android.util.Log;
 import android.view.Surface;
+import android.net.Uri;
 
 import java.io.BufferedInputStream;
 import java.io.IOException;
@@ -37,6 +44,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.zip.CRC32;
+import java.util.concurrent.TimeUnit;
 
 public class DecoderTest extends MediaPlayerTestBase {
     private static final String TAG = "DecoderTest";
@@ -54,6 +62,24 @@
     private Resources mResources;
     short[] mMasterBuffer;
 
+    private MediaCodecTunneledPlayer mMediaCodecPlayer;
+    private static final int SLEEP_TIME_MS = 1000;
+    private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
+    private static final Uri AUDIO_URL = Uri.parse(
+            "http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
+                + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
+                + "&sparams=ip,ipbits,expire,id,itag,source"
+                + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26."
+                + "49582D382B4A9AFAA163DED38D2AE531D85603C0"
+                + "&key=ik0&user=android-device-test");  // H.264 Base + AAC
+    private static final Uri VIDEO_URL = Uri.parse(
+            "http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
+                + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
+                + "&sparams=ip,ipbits,expire,id,itag,source"
+                + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26."
+                + "49582D382B4A9AFAA163DED38D2AE531D85603C0"
+                + "&key=ik0&user=android-device-test");  // H.264 Base + AAC
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -80,14 +106,15 @@
 
     // TODO: add similar tests for other audio and video formats
     public void testBug11696552() throws Exception {
-        MediaCodec mMediaCodec = MediaCodec.createDecoderByType("audio/mp4a-latm");
-        MediaFormat mFormat = MediaFormat.createAudioFormat("audio/mp4a-latm", 48000, 2);
+        MediaCodec mMediaCodec = MediaCodec.createDecoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
+        MediaFormat mFormat = MediaFormat.createAudioFormat(
+                MediaFormat.MIMETYPE_AUDIO_AAC, 48000 /* frequency */, 2 /* channels */);
         mFormat.setByteBuffer("csd-0", ByteBuffer.wrap( new byte [] {0x13, 0x10} ));
         mFormat.setInteger(MediaFormat.KEY_IS_ADTS, 1);
         mMediaCodec.configure(mFormat, null, null, 0);
         mMediaCodec.start();
         int index = mMediaCodec.dequeueInputBuffer(250000);
-        mMediaCodec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM );
+        mMediaCodec.queueInputBuffer(index, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
         MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
         mMediaCodec.dequeueOutputBuffer(info, 250000);
     }
@@ -164,17 +191,26 @@
     }
 
     public void testBFrames() throws Exception {
-        testBFrames(R.raw.video_h264_main_b_frames);
-        testBFrames(R.raw.video_h264_main_b_frames_frag);
+        int testsRun =
+            testBFrames(R.raw.video_h264_main_b_frames) +
+            testBFrames(R.raw.video_h264_main_b_frames_frag);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no codec found");
+        }
     }
 
-    public void testBFrames(int res) throws Exception {
+    public int testBFrames(int res) throws Exception {
         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
         MediaExtractor ex = new MediaExtractor();
         ex.setDataSource(fd.getFileDescriptor(), fd.getStartOffset(), fd.getLength());
         MediaFormat format = ex.getTrackFormat(0);
         String mime = format.getString(MediaFormat.KEY_MIME);
         assertTrue("not a video track. Wrong test file?", mime.startsWith("video/"));
+        if (!MediaUtils.canDecode(format)) {
+            ex.release();
+            fd.close();
+            return 0; // skip
+        }
         MediaCodec dec = MediaCodec.createDecoderByType(mime);
         Surface s = getActivity().getSurfaceHolder().getSurface();
         dec.configure(format, s, null, 0);
@@ -217,6 +253,8 @@
         assertTrue("extractor timestamps were ordered, wrong test file?", inputoutoforder);
         dec.release();
         ex.release();
+        fd.close();
+        return 1;
       }
 
     private void testTrackSelection(int resid) throws Exception {
@@ -839,130 +877,270 @@
         return numsamples;
     }
 
-    public void testCodecBasicH264() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 240, frames1);
+    private void testDecode(int testVideo, int frameNum) throws Exception {
+        if (!MediaUtils.checkCodecForResource(mContext, testVideo, 0 /* track */)) {
+            return; // skip
+        }
 
-        int frames2 = countFrames(
-                R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, null);
+        // Decode to Surface.
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        int frames1 = countFrames(testVideo, RESET_MODE_NONE, -1 /* eosframe */, s);
+        assertEquals("wrong number of frames decoded", frameNum, frames1);
+
+        // Decode to buffer.
+        int frames2 = countFrames(testVideo, RESET_MODE_NONE, -1 /* eosframe */, null);
         assertEquals("different number of frames when using Surface", frames1, frames2);
     }
 
+    public void testCodecBasicH264() throws Exception {
+        testDecode(R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz, 240);
+    }
+
     public void testCodecBasicHEVC() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz,
-                RESET_MODE_NONE, -1 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 300, frames1);
-
-        int frames2 = countFrames(
-                R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz,
-                RESET_MODE_NONE, -1 /* eosframe */, null);
-        assertEquals("different number of frames when using Surface", frames1, frames2);
+        testDecode(R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz, 300);
     }
 
     public void testCodecBasicH263() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
-                RESET_MODE_NONE, -1 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 122, frames1);
-
-        int frames2 = countFrames(
-                R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
-                RESET_MODE_NONE, -1 /* eosframe */, null);
-        assertEquals("different number of frames when using Surface", frames1, frames2);
+        testDecode(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz, 122);
     }
 
     public void testCodecBasicMpeg4() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 249, frames1);
-
-        int frames2 = countFrames(
-                R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, null);
-        assertEquals("different number of frames when using Surface", frames1, frames2);
+        testDecode(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz, 249);
     }
 
     public void testCodecBasicVP8() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 240, frames1);
-
-        int frames2 = countFrames(
-                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, null);
-        assertEquals("different number of frames when using Surface", frames1, frames2);
+        testDecode(R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz, 240);
     }
 
     public void testCodecBasicVP9() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 240, frames1);
+        testDecode(R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz, 240);
+    }
 
-        int frames2 = countFrames(
-                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, -1 /* eosframe */, null);
-        assertEquals("different number of frames when using Surface", frames1, frames2);
+    public void testH264Decode320x240() throws Exception {
+        testDecode(R.raw.video_320x240_mp4_h264_800kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testH264Decode720x480() throws Exception {
+        testDecode(R.raw.video_720x480_mp4_h264_2048kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testH264Decode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 30));
+        }
+    }
+
+    public void testH264SecureDecode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            verifySecureVideoDecodeSupport(MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 30);
+        }
+    }
+
+    public void testH264Decode30fps1280x720() throws Exception {
+        testDecode(R.raw.video_1280x720_mp4_h264_8192kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testH264Decode60fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 60));
+        }
+    }
+
+    public void testH264SecureDecode60fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            verifySecureVideoDecodeSupport(MediaFormat.MIMETYPE_VIDEO_AVC, 1280, 720, 60);
+        }
+    }
+
+    public void testH264Decode60fps1280x720() throws Exception {
+        testDecode(R.raw.video_1280x720_mp4_h264_8192kbps_60fps_aac_stereo_128kbps_44100hz, 596);
+    }
+
+    public void testH264Decode30fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 30));
+        }
+    }
+
+    public void testH264SecureDecode30fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            verifySecureVideoDecodeSupport(MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 30);
+        }
+    }
+
+    public void testH264Decode30fps1920x1080() throws Exception {
+        testDecode(R.raw.video_1920x1080_mp4_h264_20480kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testH264Decode60fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 60));
+        }
+    }
+
+    public void testH264SecureDecode60fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            verifySecureVideoDecodeSupport(MediaFormat.MIMETYPE_VIDEO_AVC, 1920, 1080, 60);
+        }
+    }
+
+    public void testH264Decode60fps1920x1080() throws Exception {
+        testDecode(R.raw.video_1920x1080_mp4_h264_20480kbps_60fps_aac_stereo_128kbps_44100hz, 596);
+    }
+
+    public void testVP8Decode320x240() throws Exception {
+        testDecode(R.raw.video_320x240_webm_vp8_800kbps_30fps_vorbis_stereo_128kbps_44100hz, 249);
+    }
+
+    public void testVP8Decode640x360() throws Exception {
+        testDecode(R.raw.video_640x360_webm_vp8_2048kbps_30fps_vorbis_stereo_128kbps_48000hz, 249);
+    }
+
+    public void testVP8Decode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1280, 720, 30));
+        }
+    }
+
+    public void testVP8Decode30fps1280x720() throws Exception {
+        testDecode(R.raw.video_1280x720_webm_vp8_8192kbps_30fps_vorbis_stereo_128kbps_48000hz, 249);
+    }
+
+    public void testVP8Decode60fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1280, 720, 60));
+        }
+    }
+
+    public void testVP8Decode60fps1280x720() throws Exception {
+        testDecode(R.raw.video_1280x720_webm_vp8_8192kbps_60fps_vorbis_stereo_128kbps_48000hz, 249);
+    }
+
+    public void testVP8Decode30fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1920, 1080, 30));
+        }
+    }
+
+    public void testVP8Decode30fps1920x1080() throws Exception {
+        testDecode(R.raw.video_1920x1080_webm_vp8_20480kbps_30fps_vorbis_stereo_128kbps_48000hz,
+                249);
+    }
+
+    public void testVP8Decode60fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP8, 1920, 1080, 60));
+        }
+    }
+
+    public void testVP8Decode60fps1920x1080() throws Exception {
+        testDecode(R.raw.video_1920x1080_webm_vp8_20480kbps_60fps_vorbis_stereo_128kbps_44100hz,
+                249);
+    }
+
+    public void testVP9Decode320x240() throws Exception {
+        testDecode(R.raw.video_320x240_webm_vp9_600kbps_30fps_vorbis_stereo_128kbps_48000hz, 249);
+    }
+
+    public void testVP9Decode640x360() throws Exception {
+        testDecode(R.raw.video_640x360_webm_vp9_1638kbps_30fps_vorbis_stereo_128kbps_48000hz, 249);
+    }
+
+    public void testVP9Decode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_VP9, 1280, 720, 30));
+        }
+    }
+
+    public void testVP9Decode30fps1280x720() throws Exception {
+        testDecode(R.raw.video_1280x720_webm_vp9_4096kbps_30fps_vorbis_stereo_128kbps_44100hz, 249);
+    }
+
+    public void testVP9Decode30fps1920x1080() throws Exception {
+        testDecode(R.raw.video_1920x1080_webm_vp9_10240kbps_30fps_vorbis_stereo_128kbps_48000hz,
+                249);
+    }
+
+    public void testVP9Decode30fps3840x2160() throws Exception {
+        testDecode(R.raw.video_3840x2160_webm_vp9_20480kbps_30fps_vorbis_stereo_128kbps_48000hz,
+                249);
+    }
+
+    public void testHEVCDecode352x288() throws Exception {
+        testDecode(R.raw.video_352x288_mp4_hevc_600kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testHEVCDecode720x480() throws Exception {
+        testDecode(R.raw.video_720x480_mp4_hevc_1638kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testHEVCDecode30fps1280x720Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_HEVC, 1280, 720, 30));
+        }
+    }
+
+    public void testHEVCDecode30fps1280x720() throws Exception {
+        testDecode(R.raw.video_1280x720_mp4_hevc_4096kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testHEVCDecode30fps1920x1080Tv() throws Exception {
+        if (checkTv()) {
+            assertTrue(MediaUtils.canDecodeVideo(MediaFormat.MIMETYPE_VIDEO_HEVC, 1920, 1080, 30));
+        }
+    }
+
+    public void testHEVCDecode30fps1920x1080() throws Exception {
+        testDecode(R.raw.video_1920x1080_mp4_hevc_10240kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    public void testHEVCDecode30fps3840x2160() throws Exception {
+        testDecode(R.raw.video_3840x2160_mp4_hevc_20480kbps_30fps_aac_stereo_128kbps_44100hz, 299);
+    }
+
+    private void testCodecEarlyEOS(int resid, int eosFrame) throws Exception {
+        if (!MediaUtils.checkCodecForResource(mContext, resid, 0 /* track */)) {
+            return; // skip
+        }
+        Surface s = getActivity().getSurfaceHolder().getSurface();
+        int frames1 = countFrames(resid, RESET_MODE_NONE, eosFrame, s);
+        assertEquals("wrong number of frames decoded", eosFrame, frames1);
     }
 
     public void testCodecEarlyEOSH263() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
+        testCodecEarlyEOS(
                 R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_stereo_128kbps_22050hz,
-                RESET_MODE_NONE, 64 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 64, frames1);
+                64 /* eosframe */);
     }
 
     public void testCodecEarlyEOSH264() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
+        testCodecEarlyEOS(
                 R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, 120 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 120, frames1);
+                120 /* eosframe */);
     }
 
     public void testCodecEarlyEOSHEVC() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
+        testCodecEarlyEOS(
                 R.raw.video_1280x720_mp4_hevc_1150kbps_30fps_aac_stereo_128kbps_48000hz,
-                RESET_MODE_NONE, 120 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 120, frames1);
+                120 /* eosframe */);
     }
 
     public void testCodecEarlyEOSMpeg4() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
+        testCodecEarlyEOS(
                 R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, 120 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 120, frames1);
+                120 /* eosframe */);
     }
 
     public void testCodecEarlyEOSVP8() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, 120 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 120, frames1);
+        testCodecEarlyEOS(
+                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
+                120 /* eosframe */);
     }
 
     public void testCodecEarlyEOSVP9() throws Exception {
-        Surface s = getActivity().getSurfaceHolder().getSurface();
-        int frames1 = countFrames(
-                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
-                RESET_MODE_NONE, 120 /* eosframe */, s);
-        assertEquals("wrong number of frames decoded", 120, frames1);
+        testCodecEarlyEOS(
+                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
+                120 /* eosframe */);
     }
 
     public void testCodecResetsH264WithoutSurface() throws Exception {
@@ -1011,24 +1189,24 @@
 
     public void testCodecResetsVP8WithoutSurface() throws Exception {
         testCodecResets(
-                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz, null);
+                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz, null);
     }
 
     public void testCodecResetsVP8WithSurface() throws Exception {
         Surface s = getActivity().getSurfaceHolder().getSurface();
         testCodecResets(
-                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz, s);
+                R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz, s);
     }
 
     public void testCodecResetsVP9WithoutSurface() throws Exception {
         testCodecResets(
-                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz, null);
+                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz, null);
     }
 
     public void testCodecResetsVP9WithSurface() throws Exception {
         Surface s = getActivity().getSurfaceHolder().getSurface();
         testCodecResets(
-                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz, s);
+                R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz, s);
     }
 
 //    public void testCodecResetsOgg() throws Exception {
@@ -1054,6 +1232,10 @@
     }
 
     private void testCodecResets(int video, Surface s) throws Exception {
+        if (!MediaUtils.checkCodecForResource(mContext, video, 0 /* track */)) {
+            return; // skip
+        }
+
         int frames1 = countFrames(video, RESET_MODE_NONE, -1 /* eosframe */, s);
         int frames2 = countFrames(video, RESET_MODE_RECONFIGURE, -1 /* eosframe */, s);
         int frames3 = countFrames(video, RESET_MODE_FLUSH, -1 /* eosframe */, s);
@@ -1061,6 +1243,23 @@
         assertEquals("different number of frames when using flushed codec", frames1, frames3);
     }
 
+    private static void verifySecureVideoDecodeSupport(String mime, int width, int height, float rate) {
+        MediaFormat baseFormat = new MediaFormat();
+        baseFormat.setString(MediaFormat.KEY_MIME, mime);
+        baseFormat.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, true);
+
+        MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
+        format.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, true);
+        format.setFloat(MediaFormat.KEY_FRAME_RATE, rate);
+
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        if (mcl.findDecoderForFormat(baseFormat) == null) {
+            MediaUtils.skipTest("no secure decoder for " + mime);
+            return;
+        }
+        assertNotNull("no decoder for " + format, mcl.findDecoderForFormat(format));
+    }
+
     private static MediaCodec createDecoder(String mime) {
         try {
             if (false) {
@@ -1132,6 +1331,10 @@
         extractor.setDataSource(testFd.getFileDescriptor(), testFd.getStartOffset(),
                 testFd.getLength());
         extractor.selectTrack(0); // consider variable looping on track
+        MediaFormat format = extractor.getTrackFormat(0);
+        if (!MediaUtils.checkDecoderForFormat(format)) {
+            return; // skip
+        }
         List<Long> outputChecksums = new ArrayList<Long>();
         List<Long> outputTimestamps = new ArrayList<Long>();
         Arrays.sort(stopAtSample);
@@ -1436,13 +1639,13 @@
 
     public void testEOSBehaviorVP8() throws Exception {
         // this video has an I frame at 46
-        testEOSBehavior(R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
+        testEOSBehavior(R.raw.video_480x360_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
                 new int[] {46, 47, 57, 45});
     }
 
     public void testEOSBehaviorVP9() throws Exception {
         // this video has an I frame at 44
-        testEOSBehavior(R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_44100hz,
+        testEOSBehavior(R.raw.video_480x360_webm_vp9_333kbps_25fps_vorbis_stereo_128kbps_48000hz,
                 new int[] {44, 45, 55, 43});
     }
 
@@ -1451,12 +1654,12 @@
         // Log.d(TAG, "color format: " + String.format("0x%08x", colorFormat));
         switch (colorFormat) {
         // these are the formats we know how to handle for this test
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
-            case MediaCodecInfo.CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar:
+            case CodecCapabilities.COLOR_FormatYUV420Planar:
+            case CodecCapabilities.COLOR_FormatYUV420PackedPlanar:
+            case CodecCapabilities.COLOR_FormatYUV420SemiPlanar:
+            case CodecCapabilities.COLOR_FormatYUV420PackedSemiPlanar:
+            case CodecCapabilities.COLOR_TI_FormatYUV420PackedSemiPlanar:
+            case CodecCapabilities.COLOR_QCOM_FormatYUV420SemiPlanar:
                 /*
                  * TODO: Check newer formats or ignore.
                  * OMX_SEC_COLOR_FormatNV12Tiled = 0x7FC00002
@@ -1711,5 +1914,81 @@
         return maxvalue;
     }
 
+    /* return true if a particular video feature is supported for the given mimetype */
+    private boolean isVideoFeatureSupported(String mimeType, String feature) {
+        MediaFormat format = MediaFormat.createVideoFormat( mimeType, 1920, 1080);
+        format.setFeatureEnabled(feature, true);
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        String codecName = mcl.findDecoderForFormat(format);
+        return (codecName == null) ? false : true;
+    }
+
+
+    /**
+     * Test tunneled video playback mode if supported
+     */
+    public void testTunneledVideoPlayback() throws Exception {
+        if (!isVideoFeatureSupported(MediaFormat.MIMETYPE_VIDEO_AVC,
+                CodecCapabilities.FEATURE_TunneledPlayback)) {
+            MediaUtils.skipTest(TAG, "No tunneled video playback codec found!");
+            return;
+        }
+
+        AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
+        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
+                getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
+
+        mMediaCodecPlayer.setAudioDataSource(AUDIO_URL, null);
+        mMediaCodecPlayer.setVideoDataSource(VIDEO_URL, null);
+        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
+        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
+
+        // starts video playback
+        mMediaCodecPlayer.startThread();
+
+        long timeOut = System.currentTimeMillis() + 4*PLAY_TIME_MS;
+        while (timeOut > System.currentTimeMillis() && !mMediaCodecPlayer.isEnded()) {
+            Thread.sleep(SLEEP_TIME_MS);
+            if (mMediaCodecPlayer.getCurrentPosition() >= mMediaCodecPlayer.getDuration() ) {
+                Log.d(TAG, "testTunneledVideoPlayback -- current pos = " +
+                        mMediaCodecPlayer.getCurrentPosition() +
+                        ">= duration = " + mMediaCodecPlayer.getDuration());
+                break;
+            }
+        }
+        assertTrue("Tunneled video playback timeout exceeded!",
+                timeOut > System.currentTimeMillis());
+
+        Log.d(TAG, "playVideo player.reset()");
+        mMediaCodecPlayer.reset();
+    }
+
+    /**
+     * Test tunneled video playback flush if supported
+     */
+    public void testTunneledVideoFlush() throws Exception {
+        if (!isVideoFeatureSupported(MediaFormat.MIMETYPE_VIDEO_AVC,
+                CodecCapabilities.FEATURE_TunneledPlayback)) {
+            MediaUtils.skipTest(TAG, "No tunneled video playback codec found!");
+            return;
+        }
+
+        AudioManager am = (AudioManager)mContext.getSystemService(Context.AUDIO_SERVICE);
+        mMediaCodecPlayer = new MediaCodecTunneledPlayer(
+                getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
+
+        mMediaCodecPlayer.setAudioDataSource(AUDIO_URL, null);
+        mMediaCodecPlayer.setVideoDataSource(VIDEO_URL, null);
+        assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
+        assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
+
+        // starts video playback
+        mMediaCodecPlayer.startThread();
+        Thread.sleep(SLEEP_TIME_MS);
+        mMediaCodecPlayer.pause();
+        mMediaCodecPlayer.flush();
+        Thread.sleep(SLEEP_TIME_MS);
+        mMediaCodecPlayer.reset();
+    }
 }
 
diff --git a/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
index a480d97..0796515 100644
--- a/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeDecodeTest.java
@@ -51,8 +51,9 @@
     private static final String DEBUG_FILE_NAME_BASE = "/sdcard/test.";
 
     // parameters for the encoder
-    private static final String MIME_TYPE_AVC = "video/avc";    // H.264 Advanced Video Coding
-    private static final String MIME_TYPE_VP8 = "video/x-vnd.on2.vp8";
+                                                            // H.264 Advanced Video Coding
+    private static final String MIME_TYPE_AVC = MediaFormat.MIMETYPE_VIDEO_AVC;
+    private static final String MIME_TYPE_VP8 = MediaFormat.MIMETYPE_VIDEO_VP8;
     private static final int FRAME_RATE = 15;               // 15fps
     private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
 
@@ -297,20 +298,24 @@
         mLargestColorDelta = -1;
 
         try {
-            MediaCodecInfo codecInfo = selectCodec(mMimeType);
-            if (codecInfo == null) {
-                // Don't fail CTS if they don't have an AVC codec (not here, anyway).
-                Log.e(TAG, "Unable to find an appropriate codec for " + mMimeType);
-                return;
-            }
-            if (VERBOSE) Log.d(TAG, "found codec: " + codecInfo.getName());
-
-            int colorFormat = selectColorFormat(codecInfo, mMimeType);
-            if (VERBOSE) Log.d(TAG, "found colorFormat: " + colorFormat);
-
             // We avoid the device-specific limitations on width and height by using values that
             // are multiples of 16, which all tested devices seem to be able to handle.
             MediaFormat format = MediaFormat.createVideoFormat(mMimeType, mWidth, mHeight);
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            String codec = mcl.findEncoderForFormat(format);
+            if (codec == null) {
+                // Don't fail CTS if they don't have an AVC codec (not here, anyway).
+                Log.e(TAG, "Unable to find an appropriate codec for " + format);
+                return;
+            }
+            if (VERBOSE) Log.d(TAG, "found codec: " + codec);
+
+            // Create a MediaCodec for the desired codec, then configure it as an encoder with
+            // our desired properties.
+            encoder = MediaCodec.createByCodecName(codec);
+
+            int colorFormat = selectColorFormat(encoder.getCodecInfo(), mMimeType);
+            if (VERBOSE) Log.d(TAG, "found colorFormat: " + colorFormat);
 
             // Set some properties.  Failing to specify some of these can cause the MediaCodec
             // configure() call to throw an unhelpful exception.
@@ -320,9 +325,6 @@
             format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
             if (VERBOSE) Log.d(TAG, "format: " + format);
 
-            // Create a MediaCodec for the desired codec, then configure it as an encoder with
-            // our desired properties.
-            encoder = MediaCodec.createByCodecName(codecInfo.getName());
             encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
             encoder.start();
 
@@ -362,19 +364,19 @@
         mLargestColorDelta = -1;
 
         try {
-            MediaCodecInfo codecInfo = selectCodec(mMimeType);
-            if (codecInfo == null) {
-                // Don't fail CTS if they don't have an AVC codec (not here, anyway).
-                Log.e(TAG, "Unable to find an appropriate codec for " + mMimeType);
-                return;
-            }
-            if (VERBOSE) Log.d(TAG, "found codec: " + codecInfo.getName());
-
-            int colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
-
             // We avoid the device-specific limitations on width and height by using values that
             // are multiples of 16, which all tested devices seem to be able to handle.
             MediaFormat format = MediaFormat.createVideoFormat(mMimeType, mWidth, mHeight);
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            String codec = mcl.findEncoderForFormat(format);
+            if (codec == null) {
+                // Don't fail CTS if they don't have an AVC codec (not here, anyway).
+                Log.e(TAG, "Unable to find an appropriate codec for " + format);
+                return;
+            }
+            if (VERBOSE) Log.d(TAG, "found codec: " + codec);
+
+            int colorFormat = MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
 
             // Set some properties.  Failing to specify some of these can cause the MediaCodec
             // configure() call to throw an unhelpful exception.
@@ -396,7 +398,7 @@
 
             // Create a MediaCodec for the desired codec, then configure it as an encoder with
             // our desired properties.  Request a Surface to use for input.
-            encoder = MediaCodec.createByCodecName(codecInfo.getName());
+            encoder = MediaCodec.createByCodecName(codec);
             encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
             inputSurface = new InputSurface(encoder.createInputSurface());
             encoder.start();
@@ -424,29 +426,6 @@
     }
 
     /**
-     * Returns the first codec capable of encoding the specified MIME type, or null if no
-     * match was found.
-     */
-    private static MediaCodecInfo selectCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return codecInfo;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
      * Returns a color format that is supported by the codec and by this test code.  If no
      * match is found, this throws a test failure -- the set of formats known to the test
      * should be expanded for new platforms.
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
index 89b06dc..ba67a42 100755
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
@@ -67,24 +67,24 @@
 
     // Encoder parameters table, sort by encoder level from high to low.
     private static final int[][] ENCODER_PARAM_TABLE = {
-        // encoder level,                             width,   height,  bitrate,    framerate
-        {MediaCodecInfo.CodecProfileLevel.AVCLevel31, 1280,     720,    14000000,   30},
-        {MediaCodecInfo.CodecProfileLevel.AVCLevel3,   720,     480,    10000000,   30},
-        {MediaCodecInfo.CodecProfileLevel.AVCLevel22,  720,     480,    4000000,    15},
-        {MediaCodecInfo.CodecProfileLevel.AVCLevel21,  352,     576,    4000000,    25},
+        // width,  height,  bitrate,    framerate  /* level */
+        { 1280,     720,    14000000,   30 },  /* AVCLevel31 */
+        {  720,     480,    10000000,   30 },  /* AVCLevel3  */
+        {  720,     480,    4000000,    15 },  /* AVCLevel22 */
+        {  352,     576,    4000000,    25 },  /* AVCLevel21 */
     };
 
     // Virtual display characteristics.  Scaled down from full display size because not all
     // devices can encode at the resolution of their own display.
     private static final String NAME = TAG;
-    private static int sWidth = ENCODER_PARAM_TABLE[ENCODER_PARAM_TABLE.length-1][1];
-    private static int sHeight = ENCODER_PARAM_TABLE[ENCODER_PARAM_TABLE.length-1][2];
+    private static int sWidth = ENCODER_PARAM_TABLE[ENCODER_PARAM_TABLE.length-1][0];
+    private static int sHeight = ENCODER_PARAM_TABLE[ENCODER_PARAM_TABLE.length-1][1];
     private static final int DENSITY = DisplayMetrics.DENSITY_HIGH;
     private static final int UI_TIMEOUT_MS = 2000;
     private static final int UI_RENDER_PAUSE_MS = 400;
 
     // Encoder parameters.  We use the same width/height as the virtual display.
-    private static final String MIME_TYPE = "video/avc";
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
     private static int sFrameRate = 15;               // 15fps
     private static final int IFRAME_INTERVAL = 10;    // 10 seconds between I-frames
     private static int sBitRate = 6000000;            // 6Mbps
@@ -161,56 +161,16 @@
         }
     }
 
-    private static boolean hasCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     /**
      * Returns true if the encoder level, specified in the ENCODER_PARAM_TABLE, can be supported.
      */
-    private static boolean verifySupportForEncoderLevel(int index) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-
-                if (false == types[j].equalsIgnoreCase(MIME_TYPE)) {
-                    continue;
-                }
-
-                MediaCodecInfo.CodecCapabilities caps = codecInfo.getCapabilitiesForType(types[j]);
-                for (int k = 0; k < caps.profileLevels.length; k++) {
-                    int profile = caps.profileLevels[k].profile;
-                    int level = caps.profileLevels[k].level;
-                    //Log.d(TAG, "[" + k + "] supported profile = " + profile + ", level = " + level);
-                    if (caps.profileLevels[k].level >= ENCODER_PARAM_TABLE[index][0]) {
-                        return true;
-                    }
-                }
-            }
-        }
-        return false;
+    private static boolean verifySupportForEncoderLevel(int i) {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        MediaFormat format = MediaFormat.createVideoFormat(
+                MIME_TYPE, ENCODER_PARAM_TABLE[i][0], ENCODER_PARAM_TABLE[i][1]);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, ENCODER_PARAM_TABLE[i][2]);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, ENCODER_PARAM_TABLE[i][3]);
+        return mcl.findEncoderForFormat(format) != null;
     }
 
     /**
@@ -224,10 +184,10 @@
             // Check if we can support it?
             if (verifySupportForEncoderLevel(i)) {
 
-                sWidth = ENCODER_PARAM_TABLE[i][1];
-                sHeight = ENCODER_PARAM_TABLE[i][2];
-                sBitRate = ENCODER_PARAM_TABLE[i][3];
-                sFrameRate = ENCODER_PARAM_TABLE[i][4];
+                sWidth = ENCODER_PARAM_TABLE[i][0];
+                sHeight = ENCODER_PARAM_TABLE[i][1];
+                sBitRate = ENCODER_PARAM_TABLE[i][2];
+                sFrameRate = ENCODER_PARAM_TABLE[i][3];
 
                 Log.d(TAG, "encoder parameters changed: width = " + sWidth + ", height = " + sHeight
                     + ", bitrate = " + sBitRate + ", framerate = " + sFrameRate);
@@ -245,11 +205,6 @@
         OutputSurface outputSurface = null;
         VirtualDisplay virtualDisplay = null;
 
-        // Don't run the test of the codec isn't present.
-        if (!hasCodec(MIME_TYPE)) {
-            return;
-        }
-
         try {
             // Encoded video resolution matches virtual display.
             MediaFormat encoderFormat = MediaFormat.createVideoFormat(MIME_TYPE, sWidth, sHeight);
@@ -259,7 +214,15 @@
             encoderFormat.setInteger(MediaFormat.KEY_FRAME_RATE, sFrameRate);
             encoderFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
 
-            encoder = MediaCodec.createEncoderByType(MIME_TYPE);
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            String codec = mcl.findEncoderForFormat(encoderFormat);
+            if (codec == null) {
+                // Don't run the test if the codec isn't present.
+                Log.i(TAG, "SKIPPING test: no support for " + encoderFormat);
+                return;
+            }
+
+            encoder = MediaCodec.createByCodecName(codec);
             encoder.configure(encoderFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
             Surface inputSurface = encoder.createInputSurface();
             encoder.start();
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
index 7b21997..89d6efa 100644
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTest.java
@@ -46,7 +46,7 @@
 import android.test.AndroidTestCase;
 import android.util.AttributeSet;
 import android.util.Log;
-import android.util.Pair;
+import android.util.Size;
 import android.view.Display;
 import android.view.Surface;
 import android.view.TextureView;
@@ -79,7 +79,7 @@
 public class EncodeVirtualDisplayWithCompositionTest extends AndroidTestCase {
     private static final String TAG = "EncodeVirtualDisplayWithCompositionTest";
     private static final boolean DBG = true;
-    private static final String MIME_TYPE = "video/avc";
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
 
     private static final long DEFAULT_WAIT_TIMEOUT_MS = 3000;
     private static final long DEFAULT_WAIT_TIMEOUT_US = 3000000;
@@ -92,6 +92,8 @@
     private static final int BITRATE_1080p = 20000000;
     private static final int BITRATE_720p = 14000000;
     private static final int BITRATE_800x480 = 14000000;
+    private static final int BITRATE_DEFAULT = 10000000;
+
     private static final int IFRAME_INTERVAL = 10;
 
     private static final int MAX_NUM_WINDOWS = 3;
@@ -138,73 +140,59 @@
 
     public void testRendering800x480Locally() throws Throwable {
         Log.i(TAG, "testRendering800x480Locally");
-        Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
-        if (maxRes == null) {
-            fail("codec not supported");
-        }
-        if (maxRes.first >= 800 && maxRes.second >= 480) {
+        if (isConcurrentEncodingDecodingSupported(800, 480, BITRATE_800x480)) {
             runTestRenderingInSeparateThread(800, 480, false, false);
         } else {
-            Log.w(TAG, "This H/W does not support 800x480");
+            Log.i(TAG, "SKIPPING testRendering800x480Locally(): codec not supported");
         }
     }
 
     public void testRenderingMaxResolutionLocally() throws Throwable {
         Log.i(TAG, "testRenderingMaxResolutionLocally");
-        Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+        Size maxRes = checkMaxConcurrentEncodingDecodingResolution();
         if (maxRes == null) {
-            fail("codec not supported");
+            Log.i(TAG, "SKIPPING testRenderingMaxResolutionLocally(): codec not supported");
+        } else {
+            Log.w(TAG, "Trying resolution " + maxRes);
+            runTestRenderingInSeparateThread(maxRes.getWidth(), maxRes.getHeight(), false, false);
         }
-        Log.w(TAG, "Trying resolution w:" + maxRes.first + " h:" + maxRes.second);
-        runTestRenderingInSeparateThread(maxRes.first, maxRes.second, false, false);
     }
 
     public void testRendering800x480Remotely() throws Throwable {
         Log.i(TAG, "testRendering800x480Remotely");
-        Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
-        if (maxRes == null) {
-            fail("codec not supported");
-        }
-        if (maxRes.first >= 800 && maxRes.second >= 480) {
+        if (isConcurrentEncodingDecodingSupported(800, 480, BITRATE_800x480)) {
             runTestRenderingInSeparateThread(800, 480, true, false);
         } else {
-            Log.w(TAG, "This H/W does not support 800x480");
+            Log.i(TAG, "SKIPPING testRendering800x480Remotely(): codec not supported");
         }
     }
 
     public void testRenderingMaxResolutionRemotely() throws Throwable {
         Log.i(TAG, "testRenderingMaxResolutionRemotely");
-        Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
+        Size maxRes = checkMaxConcurrentEncodingDecodingResolution();
         if (maxRes == null) {
-            fail("codec not supported");
+            Log.i(TAG, "SKIPPING testRenderingMaxResolutionRemotely(): codec not supported");
+        } else {
+            Log.w(TAG, "Trying resolution " + maxRes);
+            runTestRenderingInSeparateThread(maxRes.getWidth(), maxRes.getHeight(), true, false);
         }
-        Log.w(TAG, "Trying resolution w:" + maxRes.first + " h:" + maxRes.second);
-        runTestRenderingInSeparateThread(maxRes.first, maxRes.second, true, false);
     }
 
     public void testRendering800x480RemotelyWith3Windows() throws Throwable {
         Log.i(TAG, "testRendering800x480RemotelyWith3Windows");
-        Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
-        if (maxRes == null) {
-            fail("codec not supported");
-        }
-        if (maxRes.first >= 800 && maxRes.second >= 480) {
+        if (isConcurrentEncodingDecodingSupported(800, 480, BITRATE_800x480)) {
             runTestRenderingInSeparateThread(800, 480, true, true);
         } else {
-            Log.w(TAG, "This H/W does not support 800x480");
+            Log.i(TAG, "SKIPPING testRendering800x480RemotelyWith3Windows(): codec not supported");
         }
     }
 
     public void testRendering800x480LocallyWith3Windows() throws Throwable {
         Log.i(TAG, "testRendering800x480LocallyWith3Windows");
-        Pair<Integer, Integer> maxRes = checkMaxConcurrentEncodingDecodingResolution();
-        if (maxRes == null) {
-            fail("codec not supported");
-        }
-        if (maxRes.first >= 800 && maxRes.second >= 480) {
+        if (isConcurrentEncodingDecodingSupported(800, 480, BITRATE_800x480)) {
             runTestRenderingInSeparateThread(800, 480, false, true);
         } else {
-            Log.w(TAG, "This H/W does not support 800x480");
+            Log.i(TAG, "SKIPPING testRendering800x480LocallyWith3Windows(): codec not supported");
         }
     }
 
@@ -418,8 +406,8 @@
     private static final int NUM_DISPLAY_CREATION = 10;
     private static final int NUM_RENDERING = 10;
     private void doTestVirtualDisplayRecycles(int numDisplays) throws Exception {
-        CodecInfo codecInfo = getAvcSupportedFormatInfo();
-        if (codecInfo == null) {
+        Size maxSize = getMaxSupportedEncoderSize();
+        if (maxSize == null) {
             Log.i(TAG, "no codec found, skipping");
             return;
         }
@@ -431,13 +419,13 @@
                 Log.i(TAG, "start encoding");
             }
             EncodingHelper encodingHelper = new EncodingHelper();
-            mEncodingSurface = encodingHelper.startEncoding(codecInfo.mMaxW, codecInfo.mMaxH,
+            mEncodingSurface = encodingHelper.startEncoding(maxSize.getWidth(), maxSize.getHeight(),
                     mEncoderEventListener);
             GlCompositor compositor = new GlCompositor();
             if (DBG) {
                 Log.i(TAG, "start composition");
             }
-            compositor.startComposition(mEncodingSurface, codecInfo.mMaxW, codecInfo.mMaxH,
+            compositor.startComposition(mEncodingSurface, maxSize.getWidth(), maxSize.getHeight(),
                     numDisplays);
             for (int j = 0; j < NUM_DISPLAY_CREATION; j++) {
                 if (DBG) {
@@ -447,7 +435,7 @@
                     virtualDisplays[k] =
                         new VirtualDisplayPresentation(getContext(),
                                 compositor.getWindowSurface(k),
-                                codecInfo.mMaxW/numDisplays, codecInfo.mMaxH);
+                                maxSize.getWidth()/numDisplays, maxSize.getHeight());
                     virtualDisplays[k].createVirtualDisplay();
                     virtualDisplays[k].createPresentation();
                 }
@@ -543,7 +531,7 @@
             MediaFormat format = MediaFormat.createVideoFormat(MIME_TYPE, mW, mH);
             format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
                     MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
-            int bitRate = 10000000;
+            int bitRate = BITRATE_DEFAULT;
             if (mW == 1920 && mH == 1080) {
                 bitRate = BITRATE_1080p;
             } else if (mW == 1280 && mH == 720) {
@@ -1329,152 +1317,56 @@
         }
     }
 
-    private static class CodecInfo {
-        public int mMaxW;
-        public int mMaxH;
-        public int mFps;
-        public int mBitRate;
-        public String mCodecName;
-    };
+    private static Size getMaxSupportedEncoderSize() {
+        final Size[] standardSizes = new Size[] {
+            new Size(1920, 1080),
+            new Size(1280, 720),
+            new Size(720, 480),
+            new Size(352, 576)
+        };
 
-    /**
-     * Returns the first codec capable of encoding the specified MIME type, or null if no
-     * match was found.
-     */
-    private static MediaCodecInfo selectCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return codecInfo;
-                }
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (Size sz : standardSizes) {
+            MediaFormat format = MediaFormat.createVideoFormat(
+                MIME_TYPE, sz.getWidth(), sz.getHeight());
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, 15); // require at least 15fps
+            if (mcl.findEncoderForFormat(format) != null) {
+                return sz;
             }
         }
         return null;
     }
 
-    private static CodecInfo getAvcSupportedFormatInfo() {
-        MediaCodecInfo mediaCodecInfo = selectCodec(MIME_TYPE);
-        if (mediaCodecInfo == null) {
-            return null;
-        }
-        CodecCapabilities cap = mediaCodecInfo.getCapabilitiesForType(MIME_TYPE);
-        if (cap == null) { // not supported
-            return null;
-        }
-        CodecInfo info = new CodecInfo();
-        int highestLevel = 0;
-        for (CodecProfileLevel lvl : cap.profileLevels) {
-            if (lvl.level > highestLevel) {
-                highestLevel = lvl.level;
-            }
-        }
-        int maxW = 0;
-        int maxH = 0;
-        int bitRate = 0;
-        int fps = 0; // frame rate for the max resolution
-        switch(highestLevel) {
-            // Do not support Level 1 to 2.
-            case CodecProfileLevel.AVCLevel1:
-            case CodecProfileLevel.AVCLevel11:
-            case CodecProfileLevel.AVCLevel12:
-            case CodecProfileLevel.AVCLevel13:
-            case CodecProfileLevel.AVCLevel1b:
-            case CodecProfileLevel.AVCLevel2:
-                return null;
-            case CodecProfileLevel.AVCLevel21:
-                maxW = 352;
-                maxH = 576;
-                bitRate = 4000000;
-                fps = 25;
-                break;
-            case CodecProfileLevel.AVCLevel22:
-                maxW = 720;
-                maxH = 480;
-                bitRate = 4000000;
-                fps = 15;
-                break;
-            case CodecProfileLevel.AVCLevel3:
-                maxW = 720;
-                maxH = 480;
-                bitRate = 10000000;
-                fps = 30;
-                break;
-            case CodecProfileLevel.AVCLevel31:
-                maxW = 1280;
-                maxH = 720;
-                bitRate = 14000000;
-                fps = 30;
-                break;
-            case CodecProfileLevel.AVCLevel32:
-                maxW = 1280;
-                maxH = 720;
-                bitRate = 20000000;
-                fps = 60;
-                break;
-            case CodecProfileLevel.AVCLevel4: // only try up to 1080p
-            default:
-                maxW = 1920;
-                maxH = 1080;
-                bitRate = 20000000;
-                fps = 30;
-                break;
-        }
-        info.mMaxW = maxW;
-        info.mMaxH = maxH;
-        info.mFps = fps;
-        info.mBitRate = bitRate;
-        info.mCodecName = mediaCodecInfo.getName();
-        Log.i(TAG, "AVC Level 0x" + Integer.toHexString(highestLevel) + " bit rate " + bitRate +
-                " fps " + info.mFps + " w " + maxW + " h " + maxH);
-
-        return info;
-    }
-
     /**
      * Check maximum concurrent encoding / decoding resolution allowed.
      * Some H/Ws cannot support maximum resolution reported in encoder if decoder is running
      * at the same time.
-     * Check is done for 4 different levels: 1080p, 720p, 800x480 and max of encoder if is is
-     * smaller than 800x480.
+     * Check is done for 4 different levels: 1080p, 720p, 800x480, 480p
+     * (The last one is required by CDD.)
      */
-    private Pair<Integer, Integer> checkMaxConcurrentEncodingDecodingResolution() {
-        CodecInfo codecInfo = getAvcSupportedFormatInfo();
-        if (codecInfo == null) {
-            return null;
+    private Size checkMaxConcurrentEncodingDecodingResolution() {
+        if (isConcurrentEncodingDecodingSupported(1920, 1080, BITRATE_1080p)) {
+            return new Size(1920, 1080);
+        } else if (isConcurrentEncodingDecodingSupported(1280, 720, BITRATE_720p)) {
+            return new Size(1280, 720);
+        } else if (isConcurrentEncodingDecodingSupported(800, 480, BITRATE_800x480)) {
+            return new Size(800, 480);
+        } else if (isConcurrentEncodingDecodingSupported(720, 480, BITRATE_DEFAULT)) {
+            return new Size(720, 480);
         }
-        int maxW = codecInfo.mMaxW;
-        int maxH = codecInfo.mMaxH;
-        if (maxW >= 1920 && maxH >= 1080) {
-            if (isConcurrentEncodingDecodingSupported(1920, 1080, BITRATE_1080p)) {
-                return new Pair<Integer, Integer>(1920, 1080);
-            }
-        }
-        if (maxW >= 1280 && maxH >= 720) {
-            if (isConcurrentEncodingDecodingSupported(1280, 720, BITRATE_720p)) {
-                return new Pair<Integer, Integer>(1280, 720);
-            }
-        }
-        if (maxW >= 800 && maxH >= 480) {
-            if (isConcurrentEncodingDecodingSupported(800, 480, BITRATE_800x480)) {
-                return new Pair<Integer, Integer>(800, 480);
-            }
-        }
-        if (!isConcurrentEncodingDecodingSupported(codecInfo.mMaxW, codecInfo.mMaxH,
-                codecInfo.mBitRate)) {
-            fail("should work with advertised resolution");
-        }
-        return new Pair<Integer, Integer>(maxW, maxH);
+        Log.i(TAG, "SKIPPING test: concurrent encoding and decoding is not supported");
+        return null;
     }
 
     private boolean isConcurrentEncodingDecodingSupported(int w, int h, int bitRate) {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        MediaFormat testFormat = MediaFormat.createVideoFormat(MIME_TYPE, w, h);
+        testFormat.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+        if (mcl.findDecoderForFormat(testFormat) == null
+                || mcl.findEncoderForFormat(testFormat) == null) {
+            return false;
+        }
+
         MediaCodec decoder = null;
         OutputSurface decodingSurface = null;
         MediaCodec encoder = null;
diff --git a/tests/tests/media/src/android/media/cts/EncoderTest.java b/tests/tests/media/src/android/media/cts/EncoderTest.java
index c2e59d4..4b2a168 100644
--- a/tests/tests/media/src/android/media/cts/EncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/EncoderTest.java
@@ -50,14 +50,14 @@
 
         for (int j = 0; j < kBitRates.length; ++j) {
             MediaFormat format  = new MediaFormat();
-            format.setString(MediaFormat.KEY_MIME, "audio/3gpp");
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AMR_NB);
             format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000);
             format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
             format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
             formats.push(format);
         }
 
-        testEncoderWithFormats("audio/3gpp", formats);
+        testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_AMR_NB, formats);
     }
 
     public void testAMRWBEncoders() {
@@ -68,14 +68,14 @@
 
         for (int j = 0; j < kBitRates.length; ++j) {
             MediaFormat format  = new MediaFormat();
-            format.setString(MediaFormat.KEY_MIME, "audio/amr-wb");
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AMR_WB);
             format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000);
             format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
             format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
             formats.push(format);
         }
 
-        testEncoderWithFormats("audio/amr-wb", formats);
+        testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_AMR_WB, formats);
     }
 
     public void testAACEncoders() {
@@ -99,7 +99,7 @@
                 for (int j = 0; j < kBitRates.length; ++j) {
                     for (int ch = 1; ch <= 2; ++ch) {
                         MediaFormat format  = new MediaFormat();
-                        format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
+                        format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
 
                         format.setInteger(
                                 MediaFormat.KEY_AAC_PROFILE, kAACProfiles[k]);
@@ -115,7 +115,7 @@
             }
         }
 
-        testEncoderWithFormats("audio/mp4a-latm", formats);
+        testEncoderWithFormats(MediaFormat.MIMETYPE_AUDIO_AAC, formats);
     }
 
     private void testEncoderWithFormats(
@@ -135,27 +135,13 @@
     private List<String> getEncoderNamesForType(String mime) {
         LinkedList<String> names = new LinkedList<String>();
 
-        int n = MediaCodecList.getCodecCount();
-        for (int i = 0; i < n; ++i) {
-            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
-
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
             if (!info.isEncoder()) {
                 continue;
             }
-
-            if (!info.getName().startsWith("OMX.")) {
-                // Unfortunately for legacy reasons, "AACEncoder", a
-                // non OMX component had to be in this list for the video
-                // editor code to work... but it cannot actually be instantiated
-                // using MediaCodec.
-                Log.d(TAG, "skipping '" + info.getName() + "'.");
-                continue;
-            }
-
-            String[] supportedTypes = info.getSupportedTypes();
-
-            for (int j = 0; j < supportedTypes.length; ++j) {
-                if (supportedTypes[j].equalsIgnoreCase(mime)) {
+            for (String type : info.getSupportedTypes()) {
+                if (type.equalsIgnoreCase(mime)) {
                     names.push(info.getName());
                     break;
                 }
diff --git a/tests/tests/media/src/android/media/cts/EnvReverbTest.java b/tests/tests/media/src/android/media/cts/EnvReverbTest.java
index e2e9b6d..4cfb744 100644
--- a/tests/tests/media/src/android/media/cts/EnvReverbTest.java
+++ b/tests/tests/media/src/android/media/cts/EnvReverbTest.java
@@ -304,6 +304,9 @@
 
     //Test case 2.1: test setEnabled() throws exception after release
     public void test2_1SetEnabledAfterRelease() throws Exception {
+        if (!isEnvReverbAvailable()) {
+            return;
+        }
         getReverb(0);
         mReverb.release();
         try {
diff --git a/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java b/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
index 43b769a..029a632 100644
--- a/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
+++ b/tests/tests/media/src/android/media/cts/ExtractDecodeEditEncodeMuxTest.java
@@ -62,16 +62,18 @@
     private static final File OUTPUT_FILENAME_DIR = Environment.getExternalStorageDirectory();
 
     // parameters for the video encoder
-    private static final String OUTPUT_VIDEO_MIME_TYPE = "video/avc"; // H.264 Advanced Video Coding
-    private static final int OUTPUT_VIDEO_BIT_RATE = 2000000; // 2Mbps
-    private static final int OUTPUT_VIDEO_FRAME_RATE = 15; // 15fps
+                                                                // H.264 Advanced Video Coding
+    private static final String OUTPUT_VIDEO_MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
+    private static final int OUTPUT_VIDEO_BIT_RATE = 2000000;   // 2Mbps
+    private static final int OUTPUT_VIDEO_FRAME_RATE = 15;      // 15fps
     private static final int OUTPUT_VIDEO_IFRAME_INTERVAL = 10; // 10 seconds between I-frames
     private static final int OUTPUT_VIDEO_COLOR_FORMAT =
             MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
 
     // parameters for the audio encoder
-    private static final String OUTPUT_AUDIO_MIME_TYPE = "audio/mp4a-latm"; // Advanced Audio Coding
-    private static final int OUTPUT_AUDIO_CHANNEL_COUNT = 2; // Must match the input stream.
+                                                                // Advanced Audio Coding
+    private static final String OUTPUT_AUDIO_MIME_TYPE = MediaFormat.MIMETYPE_AUDIO_AAC;
+    private static final int OUTPUT_AUDIO_CHANNEL_COUNT = 2;    // Must match the input stream.
     private static final int OUTPUT_AUDIO_BIT_RATE = 128 * 1024;
     private static final int OUTPUT_AUDIO_AAC_PROFILE =
             MediaCodecInfo.CodecProfileLevel.AACObjectHE;
@@ -259,21 +261,45 @@
         // Exception that may be thrown during release.
         Exception exception = null;
 
-        MediaCodecInfo videoCodecInfo = selectCodec(OUTPUT_VIDEO_MIME_TYPE);
-        if (videoCodecInfo == null) {
-            // Don't fail CTS if they don't have an AVC codec (not here, anyway).
-            Log.e(TAG, "Unable to find an appropriate codec for " + OUTPUT_VIDEO_MIME_TYPE);
-            return;
-        }
-        if (VERBOSE) Log.d(TAG, "video found codec: " + videoCodecInfo.getName());
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
 
-        MediaCodecInfo audioCodecInfo = selectCodec(OUTPUT_AUDIO_MIME_TYPE);
-        if (audioCodecInfo == null) {
-            // Don't fail CTS if they don't have an AAC codec (not here, anyway).
-            Log.e(TAG, "Unable to find an appropriate codec for " + OUTPUT_AUDIO_MIME_TYPE);
+        // We avoid the device-specific limitations on width and height by using values
+        // that are multiples of 16, which all tested devices seem to be able to handle.
+        MediaFormat outputVideoFormat =
+                MediaFormat.createVideoFormat(OUTPUT_VIDEO_MIME_TYPE, mWidth, mHeight);
+
+        // Set some properties. Failing to specify some of these can cause the MediaCodec
+        // configure() call to throw an unhelpful exception.
+        outputVideoFormat.setInteger(
+                MediaFormat.KEY_COLOR_FORMAT, OUTPUT_VIDEO_COLOR_FORMAT);
+        outputVideoFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_VIDEO_BIT_RATE);
+        outputVideoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, OUTPUT_VIDEO_FRAME_RATE);
+        outputVideoFormat.setInteger(
+                MediaFormat.KEY_I_FRAME_INTERVAL, OUTPUT_VIDEO_IFRAME_INTERVAL);
+        if (VERBOSE) Log.d(TAG, "video format: " + outputVideoFormat);
+
+        String videoEncoderName = mcl.findEncoderForFormat(outputVideoFormat);
+        if (videoEncoderName == null) {
+            // Don't fail CTS if they don't have an AVC codec (not here, anyway).
+            Log.e(TAG, "Unable to find an appropriate codec for " + outputVideoFormat);
             return;
         }
-        if (VERBOSE) Log.d(TAG, "audio found codec: " + audioCodecInfo.getName());
+        if (VERBOSE) Log.d(TAG, "video found codec: " + videoEncoderName);
+
+        MediaFormat outputAudioFormat =
+                MediaFormat.createAudioFormat(
+                        OUTPUT_AUDIO_MIME_TYPE, OUTPUT_AUDIO_SAMPLE_RATE_HZ,
+                        OUTPUT_AUDIO_CHANNEL_COUNT);
+        outputAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_AUDIO_BIT_RATE);
+        outputAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, OUTPUT_AUDIO_AAC_PROFILE);
+
+        String audioEncoderName = mcl.findEncoderForFormat(outputAudioFormat);
+        if (audioEncoderName == null) {
+            // Don't fail CTS if they don't have an AAC codec (not here, anyway).
+            Log.e(TAG, "Unable to find an appropriate codec for " + outputAudioFormat);
+            return;
+        }
+        if (VERBOSE) Log.d(TAG, "audio found codec: " + audioEncoderName);
 
         MediaExtractor videoExtractor = null;
         MediaExtractor audioExtractor = null;
@@ -293,32 +319,17 @@
                 assertTrue("missing video track in test video", videoInputTrack != -1);
                 MediaFormat inputFormat = videoExtractor.getTrackFormat(videoInputTrack);
 
-                // We avoid the device-specific limitations on width and height by using values
-                // that are multiples of 16, which all tested devices seem to be able to handle.
-                MediaFormat outputVideoFormat =
-                        MediaFormat.createVideoFormat(OUTPUT_VIDEO_MIME_TYPE, mWidth, mHeight);
-
-                // Set some properties. Failing to specify some of these can cause the MediaCodec
-                // configure() call to throw an unhelpful exception.
-                outputVideoFormat.setInteger(
-                        MediaFormat.KEY_COLOR_FORMAT, OUTPUT_VIDEO_COLOR_FORMAT);
-                outputVideoFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_VIDEO_BIT_RATE);
-                outputVideoFormat.setInteger(MediaFormat.KEY_FRAME_RATE, OUTPUT_VIDEO_FRAME_RATE);
-                outputVideoFormat.setInteger(
-                        MediaFormat.KEY_I_FRAME_INTERVAL, OUTPUT_VIDEO_IFRAME_INTERVAL);
-                if (VERBOSE) Log.d(TAG, "video format: " + outputVideoFormat);
-
                 // Create a MediaCodec for the desired codec, then configure it as an encoder with
                 // our desired properties. Request a Surface to use for input.
                 AtomicReference<Surface> inputSurfaceReference = new AtomicReference<Surface>();
                 videoEncoder = createVideoEncoder(
-                        videoCodecInfo, outputVideoFormat, inputSurfaceReference);
+                        videoEncoderName, outputVideoFormat, inputSurfaceReference);
                 inputSurface = new InputSurface(inputSurfaceReference.get());
                 inputSurface.makeCurrent();
                 // Create a MediaCodec for the decoder, based on the extractor's format.
                 outputSurface = new OutputSurface();
                 outputSurface.changeFragmentShader(FRAGMENT_SHADER);
-                videoDecoder = createVideoDecoder(inputFormat, outputSurface.getSurface());
+                videoDecoder = createVideoDecoder(mcl, inputFormat, outputSurface.getSurface());
             }
 
             if (mCopyAudio) {
@@ -327,18 +338,11 @@
                 assertTrue("missing audio track in test video", audioInputTrack != -1);
                 MediaFormat inputFormat = audioExtractor.getTrackFormat(audioInputTrack);
 
-                MediaFormat outputAudioFormat =
-                        MediaFormat.createAudioFormat(
-                                OUTPUT_AUDIO_MIME_TYPE, OUTPUT_AUDIO_SAMPLE_RATE_HZ,
-                                OUTPUT_AUDIO_CHANNEL_COUNT);
-                outputAudioFormat.setInteger(MediaFormat.KEY_BIT_RATE, OUTPUT_AUDIO_BIT_RATE);
-                outputAudioFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, OUTPUT_AUDIO_AAC_PROFILE);
-
                 // Create a MediaCodec for the desired codec, then configure it as an encoder with
                 // our desired properties. Request a Surface to use for input.
-                audioEncoder = createAudioEncoder(audioCodecInfo, outputAudioFormat);
+                audioEncoder = createAudioEncoder(audioEncoderName, outputAudioFormat);
                 // Create a MediaCodec for the decoder, based on the extractor's format.
-                audioDecoder = createAudioDecoder(inputFormat);
+                audioDecoder = createAudioDecoder(mcl, inputFormat);
             }
 
             // Creates a muxer but do not start or add tracks just yet.
@@ -517,9 +521,9 @@
      * @param inputFormat the format of the stream to decode
      * @param surface into which to decode the frames
      */
-    private MediaCodec createVideoDecoder(MediaFormat inputFormat, Surface surface)
-            throws IOException {
-        MediaCodec decoder = MediaCodec.createDecoderByType(getMimeTypeFor(inputFormat));
+    private MediaCodec createVideoDecoder(
+            MediaCodecList mcl, MediaFormat inputFormat, Surface surface) throws IOException {
+        MediaCodec decoder = MediaCodec.createByCodecName(mcl.findDecoderForFormat(inputFormat));
         decoder.configure(inputFormat, surface, null, 0);
         decoder.start();
         return decoder;
@@ -536,11 +540,11 @@
      * @param surfaceReference to store the surface to use as input
      */
     private MediaCodec createVideoEncoder(
-            MediaCodecInfo codecInfo,
+            String codecName,
             MediaFormat format,
             AtomicReference<Surface> surfaceReference)
             throws IOException {
-        MediaCodec encoder = MediaCodec.createByCodecName(codecInfo.getName());
+        MediaCodec encoder = MediaCodec.createByCodecName(codecName);
         encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
         // Must be called before start() is.
         surfaceReference.set(encoder.createInputSurface());
@@ -553,9 +557,9 @@
      *
      * @param inputFormat the format of the stream to decode
      */
-    private MediaCodec createAudioDecoder(MediaFormat inputFormat)
-            throws IOException {
-        MediaCodec decoder = MediaCodec.createDecoderByType(getMimeTypeFor(inputFormat));
+    private MediaCodec createAudioDecoder(
+            MediaCodecList mcl, MediaFormat inputFormat) throws IOException {
+        MediaCodec decoder = MediaCodec.createByCodecName(mcl.findDecoderForFormat(inputFormat));
         decoder.configure(inputFormat, null, null, 0);
         decoder.start();
         return decoder;
@@ -567,9 +571,9 @@
      * @param codecInfo of the codec to use
      * @param format of the stream to be produced
      */
-    private MediaCodec createAudioEncoder(MediaCodecInfo codecInfo, MediaFormat format) 
+    private MediaCodec createAudioEncoder(String codecName, MediaFormat format)
             throws IOException {
-        MediaCodec encoder = MediaCodec.createByCodecName(codecInfo.getName());
+        MediaCodec encoder = MediaCodec.createByCodecName(codecName);
         encoder.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
         encoder.start();
         return encoder;
@@ -1126,28 +1130,4 @@
     private static String getMimeTypeFor(MediaFormat format) {
         return format.getString(MediaFormat.KEY_MIME);
     }
-
-    /**
-     * Returns the first codec capable of encoding the specified MIME type, or null if no match was
-     * found.
-     */
-    private static MediaCodecInfo selectCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return codecInfo;
-                }
-            }
-        }
-        return null;
-    }
-
 }
diff --git a/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java b/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
index d620995..cc28b86 100644
--- a/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/ImageReaderDecoderTest.java
@@ -21,13 +21,18 @@
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.cts.util.MediaUtils;
+import android.graphics.Rect;
 import android.graphics.ImageFormat;
+import android.media.cts.CodecUtils;
 import android.media.Image;
 import android.media.Image.Plane;
 import android.media.ImageReader;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.VideoCapabilities;
 import android.media.MediaCodecList;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
@@ -38,9 +43,15 @@
 import android.util.Log;
 import android.view.Surface;
 
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
+
+import java.io.File;
 import java.io.FileOutputStream;
+import java.io.InputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -62,12 +73,16 @@
     private static final long WAIT_FOR_IMAGE_TIMEOUT_MS = 1000;
     private static final String DEBUG_FILE_NAME_BASE = "/sdcard/";
     private static final int NUM_FRAME_DECODED = 100;
-    private static final int MAX_NUM_IMAGES = 3;
+    // video decoders only support a single outstanding image with the consumer
+    private static final int MAX_NUM_IMAGES = 1;
+    private static final float COLOR_STDEV_ALLOWANCE = 5f;
+    private static final float COLOR_DELTA_ALLOWANCE = 5f;
+
+    private final static int MODE_IMAGEREADER = 0;
+    private final static int MODE_IMAGE       = 1;
 
     private Resources mResources;
     private MediaCodec.BufferInfo mBufferInfo = new MediaCodec.BufferInfo();
-    private ByteBuffer[] mInputBuffers;
-    private ByteBuffer[] mOutputBuffers;
     private ImageReader mReader;
     private Surface mReaderSurface;
     private HandlerThread mHandlerThread;
@@ -95,31 +110,322 @@
         mHandler = null;
     }
 
-    /**
-     * Test ImageReader with 480x360 hw AVC decoding for flexible yuv format, which is mandatory
-     * to be supported by hw decoder.
-     */
-    public void testHwAVCDecode360pForFlexibleYuv() throws Exception {
-        try {
-            int format = ImageFormat.YUV_420_888;
-            videoDecodeToSurface(
-                    R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
-                    /* width */480, /* height */ 360, format, /* useHw */ true);
-        } finally {
-            closeImageReader();
+    static class MediaAsset {
+        public MediaAsset(int resource, int width, int height) {
+            mResource = resource;
+            mWidth = width;
+            mHeight = height;
+        }
+
+        public int getWidth() {
+            return mWidth;
+        }
+
+        public int getHeight() {
+            return mHeight;
+        }
+
+        public int getResource() {
+            return mResource;
+        }
+
+        private final int mResource;
+        private final int mWidth;
+        private final int mHeight;
+    }
+
+    static class MediaAssets {
+        public MediaAssets(String mime, MediaAsset... assets) {
+            mMime = mime;
+            mAssets = assets;
+        }
+
+        public String getMime() {
+            return mMime;
+        }
+
+        public MediaAsset[] getAssets() {
+            return mAssets;
+        }
+
+        private final String mMime;
+        private final MediaAsset[] mAssets;
+    }
+
+    private static MediaAssets H263_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_H263,
+            new MediaAsset(R.raw.swirl_176x144_h263, 176, 144),
+            new MediaAsset(R.raw.swirl_352x288_h263, 352, 288),
+            new MediaAsset(R.raw.swirl_128x96_h263, 128, 96));
+
+    private static MediaAssets MPEG4_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_MPEG4,
+            new MediaAsset(R.raw.swirl_128x128_mpeg4, 128, 128),
+            new MediaAsset(R.raw.swirl_144x136_mpeg4, 144, 136),
+            new MediaAsset(R.raw.swirl_136x144_mpeg4, 136, 144),
+            new MediaAsset(R.raw.swirl_132x130_mpeg4, 132, 130),
+            new MediaAsset(R.raw.swirl_130x132_mpeg4, 130, 132));
+
+    private static MediaAssets H264_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_AVC,
+            new MediaAsset(R.raw.swirl_128x128_h264, 128, 128),
+            new MediaAsset(R.raw.swirl_144x136_h264, 144, 136),
+            new MediaAsset(R.raw.swirl_136x144_h264, 136, 144),
+            new MediaAsset(R.raw.swirl_132x130_h264, 132, 130),
+            new MediaAsset(R.raw.swirl_130x132_h264, 130, 132));
+
+    private static MediaAssets H265_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_HEVC,
+            new MediaAsset(R.raw.swirl_128x128_h265, 128, 128),
+            new MediaAsset(R.raw.swirl_144x136_h265, 144, 136),
+            new MediaAsset(R.raw.swirl_136x144_h265, 136, 144),
+            new MediaAsset(R.raw.swirl_132x130_h265, 132, 130),
+            new MediaAsset(R.raw.swirl_130x132_h265, 130, 132));
+
+    private static MediaAssets VP8_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_VP8,
+            new MediaAsset(R.raw.swirl_128x128_vp8, 128, 128),
+            new MediaAsset(R.raw.swirl_144x136_vp8, 144, 136),
+            new MediaAsset(R.raw.swirl_136x144_vp8, 136, 144),
+            new MediaAsset(R.raw.swirl_132x130_vp8, 132, 130),
+            new MediaAsset(R.raw.swirl_130x132_vp8, 130, 132));
+
+    private static MediaAssets VP9_ASSETS = new MediaAssets(
+            MediaFormat.MIMETYPE_VIDEO_VP9,
+            new MediaAsset(R.raw.swirl_128x128_vp9, 128, 128),
+            new MediaAsset(R.raw.swirl_144x136_vp9, 144, 136),
+            new MediaAsset(R.raw.swirl_136x144_vp9, 136, 144),
+            new MediaAsset(R.raw.swirl_132x130_vp9, 132, 130),
+            new MediaAsset(R.raw.swirl_130x132_vp9, 130, 132));
+
+    static final float SWIRL_FPS = 12.f;
+
+    class Decoder {
+        final private String mName;
+        final private String mMime;
+        final private VideoCapabilities mCaps;
+        final private ArrayList<MediaAsset> mAssets;
+
+        boolean isFlexibleFormatSupported(CodecCapabilities caps) {
+            for (int c : caps.colorFormats) {
+                if (c == COLOR_FormatYUV420Flexible) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        Decoder(String name, MediaAssets assets, CodecCapabilities caps) {
+            mName = name;
+            mMime = assets.getMime();
+            mCaps = caps.getVideoCapabilities();
+            mAssets = new ArrayList<MediaAsset>();
+
+            for (MediaAsset asset : assets.getAssets()) {
+                if (mCaps.areSizeAndRateSupported(asset.getWidth(), asset.getHeight(), SWIRL_FPS)
+                        && isFlexibleFormatSupported(caps)) {
+                    mAssets.add(asset);
+                }
+            }
+        }
+
+        public boolean videoDecode(int mode, boolean checkSwirl) {
+            boolean skipped = true;
+            for (MediaAsset asset: mAssets) {
+                // TODO: loop over all supported image formats
+                int imageFormat = ImageFormat.YUV_420_888;
+                int colorFormat = COLOR_FormatYUV420Flexible;
+                videoDecode(asset, imageFormat, colorFormat, mode, checkSwirl);
+                skipped = false;
+            }
+            return skipped;
+        }
+
+        private void videoDecode(
+                MediaAsset asset, int imageFormat, int colorFormat, int mode, boolean checkSwirl) {
+            int video = asset.getResource();
+            int width = asset.getWidth();
+            int height = asset.getHeight();
+
+            if (DEBUG) Log.d(TAG, "videoDecode " + mName + " " + width + "x" + height);
+
+            MediaCodec decoder = null;
+            AssetFileDescriptor vidFD = null;
+
+            MediaExtractor extractor = null;
+            File tmpFile = null;
+            InputStream is = null;
+            FileOutputStream os = null;
+            MediaFormat mediaFormat = null;
+            try {
+                extractor = new MediaExtractor();
+
+                try {
+                    vidFD = mResources.openRawResourceFd(video);
+                    extractor.setDataSource(
+                            vidFD.getFileDescriptor(), vidFD.getStartOffset(), vidFD.getLength());
+                } catch (NotFoundException e) {
+                    // resource is compressed, uncompress locally
+                    String tmpName = "tempStream";
+                    tmpFile = File.createTempFile(tmpName, null, mContext.getCacheDir());
+                    is = mResources.openRawResource(video);
+                    os = new FileOutputStream(tmpFile);
+                    byte[] buf = new byte[1024];
+                    int len;
+                    while ((len = is.read(buf, 0, buf.length)) > 0) {
+                        os.write(buf, 0, len);
+                    }
+                    os.close();
+                    is.close();
+
+                    extractor.setDataSource(tmpFile.getAbsolutePath());
+                }
+
+                mediaFormat = extractor.getTrackFormat(0);
+                mediaFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+
+                // Create decoder
+                decoder = MediaCodec.createByCodecName(mName);
+                assertNotNull("couldn't create decoder" + mName, decoder);
+
+                decodeFramesToImage(
+                        decoder, extractor, mediaFormat,
+                        width, height, imageFormat, mode, checkSwirl);
+
+                decoder.stop();
+                if (vidFD != null) {
+                    vidFD.close();
+                }
+            } catch (Throwable e) {
+                throw new RuntimeException("while " + mName + " decoding "
+                        + mResources.getResourceEntryName(video) + ": " + mediaFormat, e);
+            } finally {
+                if (decoder != null) {
+                    decoder.release();
+                }
+                if (extractor != null) {
+                    extractor.release();
+                }
+                if (tmpFile != null) {
+                    tmpFile.delete();
+                }
+            }
         }
     }
 
+    private Decoder[] decoders(MediaAssets assets, boolean goog) {
+        String mime = assets.getMime();
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        ArrayList<Decoder> result = new ArrayList<Decoder>();
+
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (info.isEncoder()
+                    || info.getName().toLowerCase().startsWith("omx.google.") != goog) {
+                continue;
+            }
+            CodecCapabilities caps = null;
+            try {
+                caps = info.getCapabilitiesForType(mime);
+            } catch (IllegalArgumentException e) { // mime is not supported
+                continue;
+            }
+            assertNotNull(info.getName() + " capabilties for " + mime + " returned null", caps);
+            result.add(new Decoder(info.getName(), assets, caps));
+        }
+        return result.toArray(new Decoder[result.size()]);
+    }
+
+    private Decoder[] goog(MediaAssets assets) {
+        return decoders(assets, true /* goog */);
+    }
+
+    private Decoder[] other(MediaAssets assets) {
+        return decoders(assets, false /* goog */);
+    }
+
+    private Decoder[] googH265()  { return goog(H265_ASSETS); }
+    private Decoder[] googH264()  { return goog(H264_ASSETS); }
+    private Decoder[] googH263()  { return goog(H263_ASSETS); }
+    private Decoder[] googMpeg4() { return goog(MPEG4_ASSETS); }
+    private Decoder[] googVP8()   { return goog(VP8_ASSETS); }
+    private Decoder[] googVP9()   { return goog(VP9_ASSETS); }
+
+    private Decoder[] otherH265()  { return other(H265_ASSETS); }
+    private Decoder[] otherH264()  { return other(H264_ASSETS); }
+    private Decoder[] otherH263()  { return other(H263_ASSETS); }
+    private Decoder[] otherMpeg4() { return other(MPEG4_ASSETS); }
+    private Decoder[] otherVP8()   { return other(VP8_ASSETS); }
+    private Decoder[] otherVP9()   { return other(VP9_ASSETS); }
+
+    public void testGoogH265Image()   { swirlTest(googH265(),   MODE_IMAGE); }
+    public void testGoogH264Image()   { swirlTest(googH264(),   MODE_IMAGE); }
+    public void testGoogH263Image()   { swirlTest(googH263(),   MODE_IMAGE); }
+    public void testGoogMpeg4Image()  { swirlTest(googMpeg4(),  MODE_IMAGE); }
+    public void testGoogVP8Image()    { swirlTest(googVP8(),    MODE_IMAGE); }
+    public void testGoogVP9Image()    { swirlTest(googVP9(),    MODE_IMAGE); }
+
+    public void testOtherH265Image()  { swirlTest(otherH265(),  MODE_IMAGE); }
+    public void testOtherH264Image()  { swirlTest(otherH264(),  MODE_IMAGE); }
+    public void testOtherH263Image()  { swirlTest(otherH263(),  MODE_IMAGE); }
+    public void testOtherMpeg4Image() { swirlTest(otherMpeg4(), MODE_IMAGE); }
+    public void testOtherVP8Image()   { swirlTest(otherVP8(),   MODE_IMAGE); }
+    public void testOtherVP9Image()   { swirlTest(otherVP9(),   MODE_IMAGE); }
+
+    public void testGoogH265ImageReader()   { swirlTest(googH265(),   MODE_IMAGEREADER); }
+    public void testGoogH264ImageReader()   { swirlTest(googH264(),   MODE_IMAGEREADER); }
+    public void testGoogH263ImageReader()   { swirlTest(googH263(),   MODE_IMAGEREADER); }
+    public void testGoogMpeg4ImageReader()  { swirlTest(googMpeg4(),  MODE_IMAGEREADER); }
+    public void testGoogVP8ImageReader()    { swirlTest(googVP8(),    MODE_IMAGEREADER); }
+    public void testGoogVP9ImageReader()    { swirlTest(googVP9(),    MODE_IMAGEREADER); }
+
+    public void testOtherH265ImageReader()  { swirlTest(otherH265(),  MODE_IMAGEREADER); }
+    public void testOtherH264ImageReader()  { swirlTest(otherH264(),  MODE_IMAGEREADER); }
+    public void testOtherH263ImageReader()  { swirlTest(otherH263(),  MODE_IMAGEREADER); }
+    public void testOtherMpeg4ImageReader() { swirlTest(otherMpeg4(), MODE_IMAGEREADER); }
+    public void testOtherVP8ImageReader()   { swirlTest(otherVP8(),   MODE_IMAGEREADER); }
+    public void testOtherVP9ImageReader()   { swirlTest(otherVP9(),   MODE_IMAGEREADER); }
+
     /**
-     * Test ImageReader with 480x360 sw AVC decoding for flexible yuv format, which is mandatory
-     * to be supported by sw decoder.
+     * Test ImageReader with 480x360 non-google AVC decoding for flexible yuv format
+     */
+    public void testHwAVCDecode360pForFlexibleYuv() throws Exception {
+        Decoder[] decoders = other(new MediaAssets(
+                MediaFormat.MIMETYPE_VIDEO_AVC,
+                new MediaAsset(
+                        R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
+                        480 /* width */, 360 /* height */)));
+
+        decodeTest(decoders, MODE_IMAGEREADER, false /* checkSwirl */);
+    }
+
+    /**
+     * Test ImageReader with 480x360 google (SW) AVC decoding for flexible yuv format
      */
     public void testSwAVCDecode360pForFlexibleYuv() throws Exception {
+        Decoder[] decoders = goog(new MediaAssets(
+                MediaFormat.MIMETYPE_VIDEO_AVC,
+                new MediaAsset(
+                        R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
+                        480 /* width */, 360 /* height */)));
+
+        decodeTest(decoders, MODE_IMAGEREADER, false /* checkSwirl */);
+    }
+
+    private void swirlTest(Decoder[] decoders, int mode) {
+        decodeTest(decoders, mode, true /* checkSwirl */);
+    }
+
+    private void decodeTest(Decoder[] decoders, int mode, boolean checkSwirl) {
         try {
-            int format = ImageFormat.YUV_420_888;
-            videoDecodeToSurface(
-                    R.raw.video_480x360_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz,
-                    /* width */ 480, /* height */ 360, format, /* useHw */ false);
+            boolean skipped = true;
+            for (Decoder codec : decoders) {
+                if (codec.videoDecode(mode, checkSwirl)) {
+                    skipped = false;
+                }
+            }
+            if (skipped) {
+                MediaUtils.skipTest("decoder does not any of the input files");
+            }
         } finally {
             closeImageReader();
         }
@@ -152,57 +458,26 @@
         }
     }
 
-    private void videoDecodeToSurface(int video, int width,
-            int height, int imageFormat, boolean useHw) throws Exception {
-        MediaCodec decoder = null;
-        MediaExtractor extractor;
-        int outputBufferIndex;
-        ByteBuffer[] decoderInputBuffers;
-        ByteBuffer[] decoderOutputBuffers;
-
-        AssetFileDescriptor vidFD = mResources.openRawResourceFd(video);
-
-        extractor = new MediaExtractor();
-        extractor.setDataSource(vidFD.getFileDescriptor(), vidFD.getStartOffset(),
-                vidFD.getLength());
-
-        MediaFormat mediaFmt = extractor.getTrackFormat(0);
-        mediaFmt.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                            CodecCapabilities.COLOR_FormatYUV420Flexible);
-        String mime = mediaFmt.getString(MediaFormat.KEY_MIME);
-        try {
-            // Create decoder
-            decoder = createDecoder(mime, useHw);
-            assertNotNull("couldn't find decoder", decoder);
-            if (VERBOSE) Log.v(TAG, "using decoder: " + decoder.getName());
-
-            decodeFramesToImageReader(width, height, imageFormat, decoder,
-                    extractor, mediaFmt, mime);
-
-            decoder.stop();
-        } finally {
-            decoder.release();
-        }
-
-    }
-
     /**
      * Decode video frames to image reader.
      */
-    private void decodeFramesToImageReader(int width, int height, int imageFormat,
-            MediaCodec decoder, MediaExtractor extractor, MediaFormat mediaFmt, String mime)
-            throws Exception, InterruptedException {
+    private void decodeFramesToImage(
+            MediaCodec decoder, MediaExtractor extractor, MediaFormat mediaFormat,
+            int width, int height, int imageFormat, int mode, boolean checkSwirl)
+            throws InterruptedException {
         ByteBuffer[] decoderInputBuffers;
         ByteBuffer[] decoderOutputBuffers;
-        if (!imageFormatSupported(decoder, imageFormat, mime)) {
-            // TODO: SKIPPING TEST
-            return;
-        }
-        createImageReader(width, height, imageFormat, MAX_NUM_IMAGES, mImageListener);
 
         // Configure decoder.
-        if (VERBOSE) Log.v(TAG, "stream format: " + mediaFmt);
-        decoder.configure(mediaFmt, mReaderSurface, /*crypto*/null, /*flags*/0);
+        if (VERBOSE) Log.v(TAG, "stream format: " + mediaFormat);
+        if (mode == MODE_IMAGEREADER) {
+            createImageReader(width, height, imageFormat, MAX_NUM_IMAGES, mImageListener);
+            decoder.configure(mediaFormat, mReaderSurface, null /* crypto */, 0 /* flags */);
+        } else {
+            assertEquals(mode, MODE_IMAGE);
+            decoder.configure(mediaFormat, null /* surface */, null /* crypto */, 0 /* flags */);
+        }
+
         decoder.start();
         decoderInputBuffers = decoder.getInputBuffers();
         decoderOutputBuffers = decoder.getOutputBuffers();
@@ -267,65 +542,55 @@
                 // Should be decoding error.
                 fail("unexpected result from deocder.dequeueOutputBuffer: " + res);
             } else {
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    sawOutputEOS = true;
+                }
+
                 // res >= 0: normal decoding case, copy the output buffer.
                 // Will use it as reference to valid the ImageReader output
                 // Some decoders output a 0-sized buffer at the end. Ignore those.
-                outputFrameCount++;
                 boolean doRender = (info.size != 0);
 
-                decoder.releaseOutputBuffer(res, doRender);
                 if (doRender) {
-                    // Read image and verify
-                    Image image = mImageListener.getImage(WAIT_FOR_IMAGE_TIMEOUT_MS);
-                    Plane[] imagePlanes = image.getPlanes();
+                    outputFrameCount++;
+                    String fileName = DEBUG_FILE_NAME_BASE + MediaUtils.getTestName()
+                            + (mode == MODE_IMAGE ? "_image_" : "_reader_")
+                            + width + "x" + height + "_" + outputFrameCount + ".yuv";
 
-                    //Verify
-                    String fileName = DEBUG_FILE_NAME_BASE + width + "x" + height + "_"
-                            + outputFrameCount + ".yuv";
-                    validateImage(image, width, height, imageFormat, fileName);
+                    Image image = null;
+                    try {
+                        if (mode == MODE_IMAGE) {
+                            image = decoder.getOutputImage(res);
+                        } else {
+                            decoder.releaseOutputBuffer(res, doRender);
+                            res = -1;
+                            // Read image and verify
+                            image = mImageListener.getImage(WAIT_FOR_IMAGE_TIMEOUT_MS);
+                        }
+                        validateImage(image, width, height, imageFormat, fileName);
 
-                    if (VERBOSE) {
-                        Log.v(TAG, "Image " + outputFrameCount + " Info:");
-                        Log.v(TAG, "first plane pixelstride " + imagePlanes[0].getPixelStride());
-                        Log.v(TAG, "first plane rowstride " + imagePlanes[0].getRowStride());
-                        Log.v(TAG, "Image timestamp:" + image.getTimestamp());
+                        if (checkSwirl) {
+                            try {
+                                validateSwirl(image);
+                            } catch (Throwable e) {
+                                dumpFile(fileName, getDataFromImage(image));
+                                throw e;
+                            }
+                        }
+                    } finally {
+                        if (image != null) {
+                            image.close();
+                        }
                     }
-                    image.close();
+                }
+
+                if (res >= 0) {
+                    decoder.releaseOutputBuffer(res, false /* render */);
                 }
             }
         }
     }
 
-    private boolean imageFormatSupported(MediaCodec decoder, int imageFormat, String mime) {
-        MediaCodecInfo codecInfo = decoder.getCodecInfo();
-        if (codecInfo == null) {
-            return false;
-        }
-        MediaCodecInfo.CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mime);
-        for (int colorFormat : capabilities.colorFormats) {
-            if (colorFormat == CodecCapabilities.COLOR_FormatYUV420Flexible
-                    && imageFormat == ImageFormat.YUV_420_888) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private MediaCodec createDecoder(String mime, boolean useHw) throws Exception {
-        if (!useHw) {
-            if (mime.contains("avc")) {
-                return MediaCodec.createByCodecName("OMX.google.h264.decoder");
-            } else if (mime.contains("3gpp")) {
-                return MediaCodec.createByCodecName("OMX.google.h263.decoder");
-            } else if (mime.contains("mp4v")) {
-                return MediaCodec.createByCodecName("OMX.google.mpeg4.decoder");
-            } else if (mime.contains("vp8")) {
-                return MediaCodec.createByCodecName("OMX.google.vpx.decoder");
-            }
-        }
-        return MediaCodec.createDecoderByType(mime);
-    }
-
     /**
      * Validate image based on format and size.
      *
@@ -335,22 +600,103 @@
      * @param format The image format.
      * @param filePath The debug dump file path, null if don't want to dump to file.
      */
-    public static void validateImage(Image image, int width, int height, int format,
-            String filePath) {
+    public static void validateImage(
+            Image image, int width, int height, int format, String filePath) {
+        if (VERBOSE) {
+            Plane[] imagePlanes = image.getPlanes();
+            Log.v(TAG, "Image " + filePath + " Info:");
+            Log.v(TAG, "first plane pixelstride " + imagePlanes[0].getPixelStride());
+            Log.v(TAG, "first plane rowstride " + imagePlanes[0].getRowStride());
+            Log.v(TAG, "Image timestamp:" + image.getTimestamp());
+        }
+
         assertNotNull("Input image is invalid", image);
         assertEquals("Format doesn't match", format, image.getFormat());
-        assertEquals("Width doesn't match", width, image.getWidth());
-        assertEquals("Height doesn't match", height, image.getHeight());
+        assertEquals("Width doesn't match", width, image.getCropRect().width());
+        assertEquals("Height doesn't match", height, image.getCropRect().height());
 
         if(VERBOSE) Log.v(TAG, "validating Image");
         byte[] data = getDataFromImage(image);
         assertTrue("Invalid image data", data != null && data.length > 0);
 
-        validateYuvData(data, width, height, format, image.getTimestamp(), filePath);
+        validateYuvData(data, width, height, format, image.getTimestamp());
+
+        if (VERBOSE && filePath != null) {
+            dumpFile(filePath, data);
+        }
+    }
+
+    private static void validateSwirl(Image image) {
+        Rect crop = image.getCropRect();
+        final int NUM_SIDES = 4;
+        final int step = 8;      // the width of the layers
+        long[][] rawStats = new long[NUM_SIDES][10];
+        int[][] colors = new int[][] {
+            { 111, 96, 204 }, { 178, 27, 174 }, { 100, 192, 92 }, { 106, 117, 62 }
+        };
+
+        // successively accumulate statistics for each layer of the swirl
+        // by using overlapping rectangles, and the observation that
+        // layer_i = rectangle_i - rectangle_(i+1)
+        int lastLayer = 0;
+        int layer = 0;
+        boolean lastLayerValid = false;
+        for (int pos = 0; ; pos += step) {
+            Rect area = new Rect(pos - step, pos, crop.width() / 2, crop.height() + 2 * step - pos);
+            if (area.isEmpty()) {
+                break;
+            }
+            area.offset(crop.left, crop.top);
+            area.intersect(crop);
+            for (int lr = 0; lr < 2; ++lr) {
+                long[] oneStat = CodecUtils.getRawStats(image, area);
+                if (VERBOSE) Log.v(TAG, "area=" + area + ", layer=" + layer + ", last="
+                                    + lastLayer + ": " + Arrays.toString(oneStat));
+                for (int i = 0; i < oneStat.length; i++) {
+                    rawStats[layer][i] += oneStat[i];
+                    if (lastLayerValid) {
+                        rawStats[lastLayer][i] -= oneStat[i];
+                    }
+                }
+                if (VERBOSE && lastLayerValid) {
+                    Log.v(TAG, "layer-" + lastLayer + ": " + Arrays.toString(rawStats[lastLayer]));
+                    Log.v(TAG, Arrays.toString(CodecUtils.Raw2YUVStats(rawStats[lastLayer])));
+                }
+                // switch to the opposite side
+                layer ^= 2;      // NUM_SIDES / 2
+                lastLayer ^= 2;  // NUM_SIDES / 2
+                area.offset(crop.centerX() - area.left, 2 * (crop.centerY() - area.centerY()));
+            }
+
+            lastLayer = layer;
+            lastLayerValid = true;
+            layer = (layer + 1) % NUM_SIDES;
+        }
+
+        for (layer = 0; layer < NUM_SIDES; ++layer) {
+            float[] stats = CodecUtils.Raw2YUVStats(rawStats[layer]);
+            if (DEBUG) Log.d(TAG, "layer-" + layer + ": " + Arrays.toString(stats));
+            if (VERBOSE) Log.v(TAG, Arrays.toString(rawStats[layer]));
+
+            // check layer uniformity
+            for (int i = 0; i < 3; i++) {
+                assertTrue("color of layer-" + layer + " is not uniform: "
+                        + Arrays.toString(stats),
+                        stats[3 + i] < COLOR_STDEV_ALLOWANCE);
+            }
+
+            // check layer color
+            for (int i = 0; i < 3; i++) {
+                assertTrue("color of layer-" + layer + " mismatches target "
+                        + Arrays.toString(colors[layer]) + " vs "
+                        + Arrays.toString(Arrays.copyOf(stats, 3)),
+                        Math.abs(stats[i] - colors[layer][i]) < COLOR_DELTA_ALLOWANCE);
+            }
+        }
     }
 
     private static void validateYuvData(byte[] yuvData, int width, int height, int format,
-            long ts, String fileName) {
+            long ts) {
 
         assertTrue("YUV format must be one of the YUV_420_888, NV21, or YV12",
                 format == ImageFormat.YUV_420_888 ||
@@ -360,10 +706,6 @@
         if (VERBOSE) Log.v(TAG, "Validating YUV data");
         int expectedSize = width * height * ImageFormat.getBitsPerPixel(format) / 8;
         assertEquals("Yuv data doesn't match", expectedSize, yuvData.length);
-
-        if (DEBUG && fileName != null) {
-            dumpFile(fileName, yuvData);
-        }
     }
 
     private static void checkYuvFormat(int format) {
@@ -408,9 +750,10 @@
      */
     private static byte[] getDataFromImage(Image image) {
         assertNotNull("Invalid image:", image);
+        Rect crop = image.getCropRect();
         int format = image.getFormat();
-        int width = image.getWidth();
-        int height = image.getHeight();
+        int width = crop.width();
+        int height = crop.height();
         int rowStride, pixelStride;
         byte[] data = null;
 
@@ -428,6 +771,7 @@
         byte[] rowData = new byte[planes[0].getRowStride()];
         if(VERBOSE) Log.v(TAG, "get data from " + planes.length + " planes");
         for (int i = 0; i < planes.length; i++) {
+            int shift = (i == 0) ? 0 : 1;
             buffer = planes[i].getBuffer();
             assertNotNull("Fail to get bytebuffer from plane", buffer);
             rowStride = planes[i].getRowStride();
@@ -440,27 +784,32 @@
                 Log.v(TAG, "height " + height);
             }
             // For multi-planar yuv images, assuming yuv420 with 2x2 chroma subsampling.
-            int w = (i == 0) ? width : width / 2;
-            int h = (i == 0) ? height : height / 2;
+            int w = crop.width() >> shift;
+            int h = crop.height() >> shift;
+            buffer.position(rowStride * (crop.top >> shift) + pixelStride * (crop.left >> shift));
             assertTrue("rowStride " + rowStride + " should be >= width " + w , rowStride >= w);
             for (int row = 0; row < h; row++) {
                 int bytesPerPixel = ImageFormat.getBitsPerPixel(format) / 8;
+                int length;
                 if (pixelStride == bytesPerPixel) {
                     // Special case: optimized read of the entire row
-                    int length = w * bytesPerPixel;
+                    length = w * bytesPerPixel;
                     buffer.get(data, offset, length);
-                    // Advance buffer the remainder of the row stride
-                    buffer.position(buffer.position() + rowStride - length);
                     offset += length;
                 } else {
                     // Generic case: should work for any pixelStride but slower.
                     // Use intermediate buffer to avoid read byte-by-byte from
                     // DirectByteBuffer, which is very bad for performance
-                    buffer.get(rowData, 0, rowStride);
+                    length = (w - 1) * pixelStride + bytesPerPixel;
+                    buffer.get(rowData, 0, length);
                     for (int col = 0; col < w; col++) {
                         data[offset++] = rowData[col * pixelStride];
                     }
                 }
+                // Advance buffer the remainder of the row stride
+                if (row < h - 1) {
+                    buffer.position(buffer.position() + rowStride - length);
+                }
             }
             if (VERBOSE) Log.v(TAG, "Finished reading data from plane " + i);
         }
@@ -487,8 +836,9 @@
         }
     }
 
-    private void createImageReader(int width, int height, int format, int maxNumImages,
-            ImageReader.OnImageAvailableListener listener) throws Exception {
+    private void createImageReader(
+            int width, int height, int format, int maxNumImages,
+            ImageReader.OnImageAvailableListener listener)  {
         closeImageReader();
 
         mReader = ImageReader.newInstance(width, height, format, maxNumImages);
diff --git a/tests/tests/media/src/android/media/cts/LoudnessEnhancerTest.java b/tests/tests/media/src/android/media/cts/LoudnessEnhancerTest.java
new file mode 100644
index 0000000..be5099e
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/LoudnessEnhancerTest.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import com.android.cts.media.R;
+
+import android.content.Context;
+import android.media.audiofx.AudioEffect;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import android.media.audiofx.LoudnessEnhancer;
+import android.os.Looper;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+public class LoudnessEnhancerTest extends PostProcTestBase {
+
+    private String TAG = "LoudnessEnhancerTest";
+    private LoudnessEnhancer mLE;
+
+    //-----------------------------------------------------------------
+    // LOUDNESS ENHANCER TESTS:
+    //----------------------------------
+
+    //-----------------------------------------------------------------
+    // 0 - constructor
+    //----------------------------------
+
+    //Test case 0.0: test constructor and release
+    public void test0_0ConstructorAndRelease() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        assertNotNull("null AudioManager", am);
+        getLoudnessEnhancer(0);
+        releaseLoudnessEnhancer();
+
+        int session = am.generateAudioSessionId();
+        assertTrue("cannot generate new session", session != AudioManager.ERROR);
+        getLoudnessEnhancer(session);
+        releaseLoudnessEnhancer();
+    }
+
+    //-----------------------------------------------------------------
+    // 1 - get/set parameters
+    //----------------------------------
+
+    //Test case 1.0: test set/get target gain
+    public void test1_0TargetGain() throws Exception {
+        if (!hasAudioOutput()) {
+            return;
+        }
+        getLoudnessEnhancer(0);
+        try {
+            mLE.setTargetGain(0);
+            assertEquals("target gain differs from value set", 0.0f, mLE.getTargetGain());
+            mLE.setTargetGain(800);
+            assertEquals("target gain differs from value set", 800.0f, mLE.getTargetGain());
+        } catch (IllegalArgumentException e) {
+            fail("target gain illegal argument");
+        } catch (UnsupportedOperationException e) {
+            fail("target gain unsupported operation");
+        } catch (IllegalStateException e) {
+            fail("target gain operation called in wrong state");
+        } finally {
+            releaseLoudnessEnhancer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // private methods
+    //----------------------------------
+    private void getLoudnessEnhancer(int session) {
+        releaseLoudnessEnhancer();
+        try {
+            mLE = new LoudnessEnhancer(session);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "getLoudnessEnhancer() LoudnessEnhancer not found exception: ", e);
+        } catch (UnsupportedOperationException e) {
+            Log.e(TAG, "getLoudnessEnhancer() Effect library not loaded exception: ", e);
+        }
+        assertNotNull("could not create LoudnessEnhancer", mLE);
+    }
+
+    private void releaseLoudnessEnhancer() {
+        if (mLE != null) {
+            mLE.release();
+            mLE = null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index 08e6212..4c07482 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -15,227 +15,516 @@
  */
 package android.media.cts;
 
+import android.content.pm.PackageManager;
+import android.cts.util.MediaUtils;
+import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaCodecInfo.CodecProfileLevel;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import static android.media.MediaCodecInfo.CodecProfileLevel.*;
 import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_AVC;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_H263;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_HEVC;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_MPEG4;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_VP8;
+import static android.media.MediaFormat.MIMETYPE_VIDEO_VP9;
 import android.media.MediaPlayer;
-
 import android.os.Build;
-
 import android.util.Log;
 
+import java.io.IOException;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Arrays;
+
 /**
  * Basic sanity test of data returned by MediaCodeCapabilities.
  */
 public class MediaCodecCapabilitiesTest extends MediaPlayerTestBase {
 
     private static final String TAG = "MediaCodecCapabilitiesTest";
-    private static final String AVC_MIME = "video/avc";
-    private static final String HEVC_MIME = "video/hevc";
     private static final int PLAY_TIME_MS = 30000;
+    private static final int TIMEOUT_US = 1000000;  // 1 sec
+    private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
+
+    private final MediaCodecList mRegularCodecs =
+            new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+    private final MediaCodecList mAllCodecs =
+            new MediaCodecList(MediaCodecList.ALL_CODECS);
+    private final MediaCodecInfo[] mRegularInfos =
+            mRegularCodecs.getCodecInfos();
+    private final MediaCodecInfo[] mAllInfos =
+            mAllCodecs.getCodecInfos();
+
+    // Android device implementations with H.264 encoders, MUST support Baseline Profile Level 3.
+    // SHOULD support Main Profile/ Level 4, if supported the device must also support Main
+    // Profile/Level 4 decoding.
+    public void testH264EncoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkEncoder(MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.264 must support Baseline Profile Level 3",
+                hasEncoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3));
+
+        if (hasEncoder(MIMETYPE_VIDEO_AVC, AVCProfileMain, AVCLevel4)) {
+            assertTrue(
+                    "H.264 decoder must support Main Profile Level 4 if it can encode it",
+                    hasDecoder(MIMETYPE_VIDEO_AVC, AVCProfileMain, AVCLevel4));
+        }
+    }
+
+    // Android device implementations with H.264 decoders, MUST support Baseline Profile Level 3.
+    // Android Television Devices MUST support High Profile Level 4.2.
+    public void testH264DecoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.264 must support Baseline Profile Level 3",
+                hasDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3));
+
+        if (isTv()) {
+            assertTrue(
+                    "H.264 must support High Profile Level 4.2 on TV",
+                    checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel42));
+        }
+    }
+
+    // Android device implementations with H.263 encoders, MUST support Level 45.
+    public void testH263EncoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkEncoder(MIMETYPE_VIDEO_H263)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.263 must support Level 45",
+                hasEncoder(MIMETYPE_VIDEO_H263, MPEG4ProfileSimple, H263Level45));
+    }
+
+    // Android device implementations with H.263 decoders, MUST support Level 30.
+    public void testH263DecoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_H263)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.263 must support Level 30",
+                hasDecoder(MIMETYPE_VIDEO_H263, MPEG4ProfileSimple, H263Level30));
+    }
+
+    // Android device implementations with MPEG-4 decoders, MUST support Simple Profile Level 3.
+    public void testMpeg4DecoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_MPEG4)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "MPEG-4 must support Simple Profile Level 3",
+                hasDecoder(MIMETYPE_VIDEO_MPEG4, MPEG4ProfileSimple, MPEG4Level3));
+    }
+
+    // Android device implementations, when supporting H.265 codec MUST support the Main Profile
+    // Level 3 Main tier.
+    // Android Television Devices MUST support the Main Profile Level 4.1 Main tier.
+    // When the UHD video decoding profile is supported, it MUST support Main10 Level 5 Main
+    // Tier profile.
+    public void testH265DecoderProfileAndLevel() throws Exception {
+        if (!MediaUtils.checkDecoder(MIMETYPE_VIDEO_HEVC)) {
+            return; // skip
+        }
+
+        assertTrue(
+                "H.265 must support Main Profile Main Tier Level 3",
+                hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel3));
+
+        if (isTv()) {
+            assertTrue(
+                    "H.265 must support Main Profile Main Tier Level 4.1 on TV",
+                    hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel41));
+        }
+
+        if (isTv() && MediaUtils.canDecodeVideo(MIMETYPE_VIDEO_HEVC, 3840, 2160, 30)) {
+            assertTrue(
+                    "H.265 must support Main10 Profile Main Tier Level 5 if UHD is supported",
+                    hasDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain10, HEVCMainTierLevel5));
+        }
+    }
 
     public void testAvcBaseline1() throws Exception {
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline)) {
-          return;
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel1)) {
+            return; // skip
         }
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline,
-                CodecProfileLevel.AVCLevel1)) {
-            throw new RuntimeException("AVCLevel1 support is required by CDD");
-        }
-        // We don't have a test stream, but at least we're testing
-        // that supports() returns true for something.
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testAvcBaseline12() throws Exception {
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline)) {
-            return;
-        }
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline,
-                CodecProfileLevel.AVCLevel12)) {
-            Log.i(TAG, "AvcBaseline12 not supported");
-            return;  // TODO: Can we make this mandatory?
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel12)) {
+            return; // skip
         }
         playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
                 + "&itag=160&source=youtube&user=android-device-test"
                 + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=999999999999999999"
-                + "&signature=341692D20FACCAE25B90EA2C131EA6ADCD8E2384."
-                + "9EB08C174BE401AAD20FB85EE4DBA51A2882BB60"
-                + "&key=test_key1", 256, 144, PLAY_TIME_MS);
+                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
+                + "&signature=9EDCA0B395B8A949C511FD5E59B9F805CFF797FD."
+                + "702DE9BA7AF96785FD6930AD2DD693A0486C880E"
+                + "&key=ik0", 256, 144, PLAY_TIME_MS);
     }
 
     public void testAvcBaseline30() throws Exception {
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline)) {
-            return;
-        }
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileBaseline,
-                CodecProfileLevel.AVCLevel3)) {
-            Log.i(TAG, "AvcBaseline30 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3)) {
+            return; // skip
         }
         playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
                 + "&itag=18&source=youtube&user=android-device-test"
                 + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=999999999999999999"
-                + "&signature=8701A45F6422229D46ABB25A22E2C00C94024606."
-                + "08BCDF16C3F744C49D4C8A8AD1C38B3DC1810918"
-                + "&key=test_key1", 640, 360, PLAY_TIME_MS);
+                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
+                + "&signature=7DCDE3A6594D0B91A27676A3CDC3A87B149F82EA."
+                + "7A83031734CB1EDCE06766B6228842F954927960"
+                + "&key=ik0", 640, 360, PLAY_TIME_MS);
     }
 
     public void testAvcHigh31() throws Exception {
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileHigh)) {
-            return;
-        }
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileHigh,
-                CodecProfileLevel.AVCLevel31)) {
-            Log.i(TAG, "AvcHigh31 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel31)) {
+            return; // skip
         }
         playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
                 + "&itag=22&source=youtube&user=android-device-test"
                 + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=999999999999999999"
-                + "&signature=42969CA8F7FFAE432B7135BC811F96F7C4172C3F."
-                + "1A8A92EA714C1B7C98A05DDF2DE90854CDD7638B"
-                + "&key=test_key1", 1280, 720, PLAY_TIME_MS);
-
+                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
+                + "&signature=179525311196616BD8E1381759B0E5F81A9E91B5."
+                + "C4A50E44059FEBCC6BBC78E3B3A4E0E0065777"
+                + "&key=ik0", 1280, 720, PLAY_TIME_MS);
     }
 
     public void testAvcHigh40() throws Exception {
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileHigh)) {
-            return;
-        }
-        if (!supports(AVC_MIME, CodecProfileLevel.AVCProfileHigh,
-                CodecProfileLevel.AVCLevel4)) {
-            Log.i(TAG, "AvcHigh40 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel4)) {
+            return; // skip
         }
         if (Build.VERSION.SDK_INT < 18) {
-            Log.i(TAG, "fragmented mp4 not supported");
+            MediaUtils.skipTest(TAG, "fragmented mp4 not supported");
             return;
         }
         playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
                 + "&itag=137&source=youtube&user=android-device-test"
                 + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=999999999999999999"
-                + "&signature=2C836E04C4DDC98649CD44C8B91813D98342D1D1."
-                + "870A848D54CA08C197E5FDC34ED45E6ED7DB5CDA"
-                + "&key=test_key1", 1920, 1080, PLAY_TIME_MS);
+                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
+                + "&signature=B0976085596DD42DEA3F08307F76587241CB132B."
+                + "043B719C039E8B92F45391ADC0BE3665E2332930"
+                + "&key=ik0", 1920, 1080, PLAY_TIME_MS);
     }
 
     public void testHevcMain1() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel1)) {
-            throw new RuntimeException("HECLevel1 support is required by CDD");
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel1)) {
+            return; // skip
         }
-        // We don't have a test stream, but at least we're testing
-        // that supports() returns true for something.
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
+
     public void testHevcMain2() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel2)) {
-            Log.i(TAG, "HevcMain2 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel2)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testHevcMain21() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel21)) {
-            Log.i(TAG, "HevcMain21 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel21)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testHevcMain3() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel3)) {
-            Log.i(TAG, "HevcMain3 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel3)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testHevcMain31() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel31)) {
-            Log.i(TAG, "HevcMain31 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel31)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testHevcMain4() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel4)) {
-            Log.i(TAG, "HevcMain4 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel4)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testHevcMain41() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel41)) {
-            Log.i(TAG, "HevcMain41 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel41)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testHevcMain5() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel5)) {
-            Log.i(TAG, "HevcMain5 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel5)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
     public void testHevcMain51() throws Exception {
-        if (!supports(HEVC_MIME, CodecProfileLevel.HEVCProfileMain,
-                CodecProfileLevel.HEVCMainTierLevel51)) {
-            Log.i(TAG, "HevcMain51 not supported");
-            return;
+        if (!checkDecoder(MIMETYPE_VIDEO_HEVC, HEVCProfileMain, HEVCMainTierLevel51)) {
+            return; // skip
         }
+
+        // TODO: add a test stream
+        MediaUtils.skipTest(TAG, "no test stream");
     }
 
-    private boolean supports(String mimeType, int profile) {
-        return supports(mimeType, profile, 0, false);
+    private boolean checkDecoder(String mime, int profile, int level) {
+        if (!hasDecoder(mime, profile, level)) {
+            MediaUtils.skipTest(TAG, "no " + mime + " decoder for profile "
+                    + profile + " and level " + level);
+            return false;
+        }
+        return true;
     }
 
-    private boolean supports(String mimeType, int profile, int level) {
-        return supports(mimeType, profile, level, true);
+    private boolean hasDecoder(String mime, int profile, int level) {
+        return supports(mime, false /* isEncoder */, profile, level);
     }
 
-    private boolean supports(String mimeType, int profile, int level, boolean testLevel) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-            if (codecInfo.isEncoder()) {
+    private boolean hasEncoder(String mime, int profile, int level) {
+        return supports(mime, true /* isEncoder */, profile, level);
+    }
+
+    private boolean supports(
+            String mime, boolean isEncoder, int profile, int level) {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (isEncoder != info.isEncoder()) {
                 continue;
             }
+            try {
+                CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                for (CodecProfileLevel pl : caps.profileLevels) {
+                    if (pl.profile != profile) {
+                        continue;
+                    }
 
-            if (!supportsMimeType(codecInfo, mimeType)) {
+                    // H.263 levels are not completely ordered:
+                    // Level45 support only implies Level10 support
+                    if (mime.equalsIgnoreCase(MIMETYPE_VIDEO_H263)) {
+                        if (pl.level != level && pl.level == H263Level45 && level > H263Level10) {
+                            continue;
+                        }
+                    }
+                    if (pl.level >= level) {
+                        return true;
+                    }
+                }
+            } catch (IllegalArgumentException e) {
+            }
+        }
+        return false;
+    }
+
+    private boolean isVideoMime(String mime) {
+        return mime.toLowerCase().startsWith("video/");
+    }
+
+    private Set<String> requiredAdaptiveFormats() {
+        Set<String> adaptiveFormats = new HashSet<String>();
+        adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_AVC);
+        adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
+        adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_VP8);
+        adaptiveFormats.add(MediaFormat.MIMETYPE_VIDEO_VP9);
+        return adaptiveFormats;
+    }
+
+    public void testHaveAdaptiveVideoDecoderForAllSupportedFormats() {
+        Set<String> supportedFormats = new HashSet<String>();
+        boolean skipped = true;
+
+        // gather all supported video formats
+        for (MediaCodecInfo info : mAllInfos) {
+            if (info.isEncoder()) {
                 continue;
             }
-
-            CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(mimeType);
-            for (CodecProfileLevel profileLevel : capabilities.profileLevels) {
-                if (profileLevel.profile == profile
-                        && (!testLevel || profileLevel.level >= level)) {
-                    return true;
+            for (String mime : info.getSupportedTypes()) {
+                if (isVideoMime(mime)) {
+                    supportedFormats.add(mime);
                 }
             }
         }
 
-        return false;
+        // limit to CDD-required formats for now
+        supportedFormats.retainAll(requiredAdaptiveFormats());
+
+        // check if there is an adaptive decoder for each
+        for (String mime : supportedFormats) {
+            skipped = false;
+            // implicit assumption that QVGA video is always valid.
+            MediaFormat format = MediaFormat.createVideoFormat(mime, 176, 144);
+            format.setFeatureEnabled(CodecCapabilities.FEATURE_AdaptivePlayback, true);
+            String codec = mAllCodecs.findDecoderForFormat(format);
+            assertTrue(
+                    "could not find adaptive decoder for " + mime, codec != null);
+        }
+        if (skipped) {
+            MediaUtils.skipTest("no video decoders that are required to be adaptive found");
+        }
     }
 
-    private static boolean supportsMimeType(MediaCodecInfo codecInfo, String mimeType) {
-        String[] supportedMimeTypes = codecInfo.getSupportedTypes();
-        for (String supportedMimeType : supportedMimeTypes) {
-            if (mimeType.equalsIgnoreCase(supportedMimeType)) {
-                return true;
+    public void testAllVideoDecodersAreAdaptive() {
+        Set<String> adaptiveFormats = requiredAdaptiveFormats();
+        boolean skipped = true;
+        for (MediaCodecInfo info : mAllInfos) {
+            if (info.isEncoder()) {
+                continue;
+            }
+            for (String mime : info.getSupportedTypes()) {
+                if (!isVideoMime(mime)
+                        // limit to CDD-required formats for now
+                        || !adaptiveFormats.contains(mime)) {
+                    continue;
+                }
+                skipped = false;
+                CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                assertTrue(
+                    info.getName() + " is not adaptive for " + mime,
+                    caps.isFeatureSupported(CodecCapabilities.FEATURE_AdaptivePlayback));
             }
         }
-        return false;
+        if (skipped) {
+            MediaUtils.skipTest("no video decoders that are required to be adaptive found");
+        }
     }
 
+    private MediaFormat createReasonableVideoFormat(
+            CodecCapabilities caps, String mime, boolean encoder, int width, int height) {
+        VideoCapabilities vidCaps = caps.getVideoCapabilities();
+        MediaFormat format = MediaFormat.createVideoFormat(mime, width, height);
+        if (encoder) {
+            // bitrate
+            int maxWidth = vidCaps.getSupportedWidths().getUpper();
+            int maxHeight = vidCaps.getSupportedHeightsFor(width).getUpper();
+            int maxRate = vidCaps.getSupportedFrameRatesFor(width, height).getUpper().intValue();
+            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);
+            format.setInteger(format.KEY_BIT_RATE, bitrate);
+            format.setInteger(format.KEY_FRAME_RATE, maxRate);
+            format.setInteger(format.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
+        }
+        return format;
+    }
+
+    public void testSecureCodecsAdvertiseSecurePlayback() throws IOException {
+        boolean skipped = true;
+        for (MediaCodecInfo info : mAllInfos) {
+            boolean isEncoder = info.isEncoder();
+            if (isEncoder || !info.getName().endsWith(".secure")) {
+                continue;
+            }
+            for (String mime : info.getSupportedTypes()) {
+                if (!isVideoMime(mime)) {
+                    continue;
+                }
+                skipped = false;
+                CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                assertTrue(
+                        info.getName() + " does not advertise secure playback",
+                        caps.isFeatureSupported(CodecCapabilities.FEATURE_SecurePlayback));
+            }
+        }
+        if (skipped) {
+            MediaUtils.skipTest("no video decoders found ending in .secure");
+        }
+    }
+
+    public void testAllNonTunneledVideoCodecsSupportFlexibleYUV() throws IOException {
+        boolean skipped = true;
+        for (MediaCodecInfo info : mAllInfos) {
+            boolean isEncoder = info.isEncoder();
+            for (String mime: info.getSupportedTypes()) {
+                if (!isVideoMime(mime)) {
+                    continue;
+                }
+                CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                if (caps.isFeatureRequired(CodecCapabilities.FEATURE_TunneledPlayback)
+                        || caps.isFeatureRequired(CodecCapabilities.FEATURE_SecurePlayback)) {
+                    continue;
+                }
+                skipped = false;
+                boolean found = false;
+                for (int c : caps.colorFormats) {
+                    if (c == caps.COLOR_FormatYUV420Flexible) {
+                        found = true;
+                        break;
+                    }
+                }
+                assertTrue(
+                    info.getName() + " does not advertise COLOR_FormatYUV420Flexible",
+                    found);
+
+                MediaCodec codec = null;
+                MediaFormat format = null;
+                try {
+                    codec = MediaCodec.createByCodecName(info.getName());
+                    // implicit assumption that QVGA video is always valid.
+                    format = createReasonableVideoFormat(caps, mime, isEncoder, 176, 144);
+                    format.setInteger(
+                            MediaFormat.KEY_COLOR_FORMAT,
+                            caps.COLOR_FormatYUV420Flexible);
+
+                    codec.configure(format, null /* surface */, null /* crypto */,
+                            isEncoder ? codec.CONFIGURE_FLAG_ENCODE : 0);
+                    MediaFormat configuredFormat =
+                            isEncoder ? codec.getInputFormat() : codec.getOutputFormat();
+                    Log.d(TAG, "color format is " + configuredFormat.getInteger(
+                            MediaFormat.KEY_COLOR_FORMAT));
+                    if (isEncoder) {
+                        codec.start();
+                        int ix = codec.dequeueInputBuffer(TIMEOUT_US);
+                        assertNotNull(
+                                info.getName() + " encoder has non-flexYUV input buffer #" + ix,
+                                codec.getInputImage(ix));
+                    } else {
+                        // TODO: test these on various decoders (need test streams)
+                    }
+                } finally {
+                    if (codec != null) {
+                        codec.release();
+                    }
+                }
+            }
+        }
+        if (skipped) {
+            MediaUtils.skipTest("no non-tunneled/non-secure video decoders found");
+        }
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCencPlayer.java b/tests/tests/media/src/android/media/cts/MediaCodecCencPlayer.java
index 90696ff..b96d38c 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCencPlayer.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCencPlayer.java
@@ -15,6 +15,7 @@
  */
 package android.media.cts;
 
+import android.media.AudioManager;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecList;
@@ -40,7 +41,7 @@
  * {@link MediaDrm} can be used to obtain keys for decrypting protected media streams,
  * in conjunction with MediaCrypto.
  */
-public class MediaCodecCencPlayer {
+public class MediaCodecCencPlayer implements MediaTimeProvider {
     private static final String TAG = MediaCodecCencPlayer.class.getSimpleName();
 
     private static final int STATE_IDLE = 1;
@@ -300,10 +301,14 @@
 
         CodecState state;
         if (isVideo) {
-            state = new CodecState(this, mVideoExtractor, trackIndex, format, codec, true);
+            state = new CodecState((MediaTimeProvider)this, mVideoExtractor,
+                            trackIndex, format, codec, true, false, 
+                            AudioManager.AUDIO_SESSION_ID_GENERATE);
             mVideoCodecStates.put(Integer.valueOf(trackIndex), state);
         } else {
-            state = new CodecState(this, mAudioExtractor, trackIndex, format, codec, true);
+            state = new CodecState((MediaTimeProvider)this, mAudioExtractor,
+                            trackIndex, format, codec, true, false,
+                            AudioManager.AUDIO_SESSION_ID_GENERATE);
             mAudioCodecStates.put(Integer.valueOf(trackIndex), state);
         }
 
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecListTest.java b/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
index 865780e..9442d09 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
@@ -50,9 +50,10 @@
             mAllCodecs.getCodecInfos();
 
     class CodecType {
-        CodecType(String type, boolean isEncoder) {
+        CodecType(String type, boolean isEncoder, MediaFormat sampleFormat) {
             mMimeTypeName = type;
             mIsEncoder = isEncoder;
+            mSampleFormat = sampleFormat;
         }
 
         boolean equals(CodecType codecType) {
@@ -60,10 +61,35 @@
                     mIsEncoder == codecType.mIsEncoder;
         }
 
-        String mMimeTypeName;
-        boolean mIsEncoder;
+        boolean canBeFound() {
+            return codecCanBeFound(mIsEncoder, mSampleFormat);
+        }
+
+        @Override
+        public String toString() {
+            return mMimeTypeName + (mIsEncoder ? " encoder" : " decoder") + " for " + mSampleFormat;
+        }
+
+        private String mMimeTypeName;
+        private boolean mIsEncoder;
+        private MediaFormat mSampleFormat;
     };
 
+    class AudioCodec extends CodecType {
+        AudioCodec(String mime, boolean isEncoder, int sampleRate) {
+            super(mime, isEncoder, MediaFormat.createAudioFormat(
+                    mime, sampleRate, 1 /* channelCount */));
+        }
+    }
+
+    class VideoCodec extends CodecType {
+        VideoCodec(String mime, boolean isEncoder) {
+            // implicit assumption that QVGA video is always valid
+            super(mime, isEncoder, MediaFormat.createVideoFormat(
+                    mime, 176 /* width */, 144 /* height */));
+        }
+    }
+
     public static void testMediaCodecXmlFileExist() {
         File file = new File(MEDIA_CODEC_XML_FILE);
         assertTrue("/etc/media_codecs.xml does not exist", file.exists());
@@ -241,6 +267,9 @@
         List<CodecType> requiredList = getRequiredCodecTypes();
         List<CodecType> supportedList = getSupportedCodecTypes();
         assertTrue(areRequiredCodecTypesSupported(requiredList, supportedList));
+        for (CodecType type : requiredList) {
+            assertTrue("cannot find " + type, type.canBeFound());
+        }
     }
 
     private boolean hasCamera() {
@@ -249,80 +278,27 @@
                 pm.hasSystemFeature(pm.FEATURE_CAMERA);
     }
 
-    // H263 baseline profile must be supported
-    public void testIsH263BaselineProfileSupported() {
-        if (!hasCamera()) {
-            Log.d(TAG, "not required without camera");
-            return;
-        }
-
-        int profile = CodecProfileLevel.H263ProfileBaseline;
-        assertTrue(checkProfileSupported("video/3gpp", false, profile));
-        assertTrue(checkProfileSupported("video/3gpp", true, profile));
+    private boolean hasMicrophone() {
+        PackageManager pm = getContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_MICROPHONE);
     }
 
-    // AVC baseline profile must be supported
-    public void testIsAVCBaselineProfileSupported() {
-        int profile = CodecProfileLevel.AVCProfileBaseline;
-        assertTrue(checkProfileSupported("video/avc", false, profile));
-        assertTrue(checkProfileSupported("video/avc", true, profile));
+    private boolean isWatch() {
+        PackageManager pm = getContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_WATCH);
     }
 
-    // HEVC main profile must be supported
-    public void testIsHEVCMainProfileSupported() {
-        int profile = CodecProfileLevel.HEVCProfileMain;
-        assertTrue(checkProfileSupported("video/hevc", false, profile));
-    }
-
-    // MPEG4 simple profile must be supported
-    public void testIsM4VSimpleProfileSupported() {
-        if (!hasCamera()) {
-            Log.d(TAG, "not required without camera");
-            return;
-        }
-
-        int profile = CodecProfileLevel.MPEG4ProfileSimple;
-        assertTrue(checkProfileSupported("video/mp4v-es", false, profile));
-
-        // FIXME: no support for M4v simple profile video encoder
-        // assertTrue(checkProfileSupported("video/mp4v-es", true, profile));
-    }
-
-    /*
-     * Find whether the given codec is supported
-     */
-    private boolean checkProfileSupported(
-            String mime, boolean isEncoder, int profile) {
-        return profileIsListed(mime, isEncoder, profile) &&
-                codecCanBeFound(mime, isEncoder);
-    }
-
-    private boolean profileIsListed(
-        String mime, boolean isEncoder, int profile) {
-
-        for (MediaCodecInfo info : mRegularInfos) {
-            if (isEncoder != info.isEncoder()) {
-                continue;
-            }
-
-            for (String type : info.getSupportedTypes()) {
-                if (type.equalsIgnoreCase(mime)) {
-                    CodecCapabilities cap = info.getCapabilitiesForType(type);
-                    for (CodecProfileLevel pl : cap.profileLevels) {
-                        if (pl.profile == profile) {
-                            return true;
-                        }
-                    }
-                }
-            }
-        }
-        return false;
+    private boolean isHandheld() {
+        // handheld nature is not exposed to package manager, for now
+        // we check for touchscreen and NOT watch and NOT tv
+        PackageManager pm = getContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_TOUCHSCREEN)
+                && !pm.hasSystemFeature(pm.FEATURE_WATCH)
+                && !pm.hasSystemFeature(pm.FEATURE_TELEVISION);
     }
 
     // Find whether the given codec can be found using MediaCodecList.find methods.
-    private boolean codecCanBeFound(String mime, boolean isEncoder) {
-        // implicit assumption that QVGA video is always valid.
-        MediaFormat format = MediaFormat.createVideoFormat(mime, 176, 144);
+    private boolean codecCanBeFound(boolean isEncoder, MediaFormat format) {
         String codecName = isEncoder
                 ? mRegularCodecs.findEncoderForFormat(format)
                 : mRegularCodecs.findDecoderForFormat(format);
@@ -361,7 +337,7 @@
             assertTrue("Unexpected number of supported types", types.length > 0);
             boolean isEncoder = info.isEncoder();
             for (int j = 0; j < types.length; ++j) {
-                supportedList.add(new CodecType(types[j], isEncoder));
+                supportedList.add(new CodecType(types[j], isEncoder, null /* sampleFormat */));
             }
         }
         return supportedList;
@@ -374,30 +350,53 @@
     private List<CodecType> getRequiredCodecTypes() {
         List<CodecType> list = new ArrayList<CodecType>(16);
 
-        // Mandatory audio codecs
-        list.add(new CodecType("audio/amr-wb", false));         // amrwb decoder
-        list.add(new CodecType("audio/amr-wb", true));          // amrwb encoder
+        // Mandatory audio decoders
 
         // flac decoder is not omx-based yet
-        // list.add(new CodecType("audio/flac", false));        // flac decoder
-        list.add(new CodecType("audio/flac", true));            // flac encoder
-        list.add(new CodecType("audio/mpeg", false));           // mp3 decoder
-        list.add(new CodecType("audio/mp4a-latm", false));      // aac decoder
-        list.add(new CodecType("audio/mp4a-latm", true));       // aac encoder
-        list.add(new CodecType("audio/vorbis", false));         // vorbis decoder
-        list.add(new CodecType("audio/3gpp", false));           // amrnb decoder
-        list.add(new CodecType("audio/3gpp", true));            // amrnb encoder
+        // list.add(new CodecType(MediaFormat.MIMETYPE_AUDIO_FLAC, false, 8000));
+        // list.add(new CodecType(MediaFormat.MIMETYPE_AUDIO_FLAC, false, 48000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_MPEG, false, 8000));  // mp3
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_MPEG, false, 48000)); // mp3
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_VORBIS, false, 8000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_VORBIS, false, 48000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, false, 8000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, false, 48000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_RAW, false, 8000));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_RAW, false, 44100));
+        list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_OPUS, false, 48000));
 
-        // Mandatory video codecs
-        list.add(new CodecType("video/avc", false));            // avc decoder
-        list.add(new CodecType("video/avc", true));             // avc encoder
-        list.add(new CodecType("video/hevc", false));           // hevc decoder
-        list.add(new CodecType("video/3gpp", false));           // h263 decoder
-        list.add(new CodecType("video/3gpp", true));            // h263 encoder
-        list.add(new CodecType("video/mp4v-es", false));        // m4v decoder
-        list.add(new CodecType("video/x-vnd.on2.vp8", false));  // vp8 decoder
-        list.add(new CodecType("video/x-vnd.on2.vp8", true));   // vp8 encoder
-        list.add(new CodecType("video/x-vnd.on2.vp9", false));  // vp9 decoder
+        // Mandatory audio encoders (for non-watch devices with camera)
+
+        if (hasMicrophone() && !isWatch()) {
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, true, 8000));
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AAC, true, 48000));
+            // flac encoder is not required
+            // list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_FLAC, true));  // encoder
+        }
+
+        // Mandatory audio encoders for handheld devices
+        if (isHandheld()) {
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_NB, false, 8000));  // decoder
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_NB, true,  8000));  // encoder
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_WB, false, 16000)); // decoder
+            list.add(new AudioCodec(MediaFormat.MIMETYPE_AUDIO_AMR_WB, true,  16000)); // encoder
+        }
+
+        // Mandatory video codecs (for non-watch devices)
+
+        if (!isWatch()) {
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_AVC, false));   // avc decoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_AVC, true));    // avc encoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP8, false));   // vp8 decoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP8, true));    // vp8 encoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_VP9, false));   // vp9 decoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_HEVC, false));  // hevc decoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_MPEG4, false)); // m4v decoder
+            list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_H263, false));  // h263 decoder
+            if (hasCamera()) {
+                list.add(new VideoCodec(MediaFormat.MIMETYPE_VIDEO_H263, true)); // h263 encoder
+            }
+        }
 
         return list;
     }
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTest.java b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
index f72e3a0..494e823 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecTest.java
@@ -19,6 +19,7 @@
 import com.android.cts.media.R;
 
 import android.content.res.AssetFileDescriptor;
+import android.cts.util.MediaUtils;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecList;
@@ -35,7 +36,6 @@
 import java.nio.ByteBuffer;
 import java.util.concurrent.atomic.AtomicBoolean;
 
-
 /**
  * General MediaCodec tests.
  *
@@ -50,14 +50,15 @@
     private static final boolean VERBOSE = false;           // lots of logging
 
     // parameters for the video encoder
-    private static final String MIME_TYPE = "video/avc";    // H.264 Advanced Video Coding
+                                                            // H.264 Advanced Video Coding
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
     private static final int BIT_RATE = 2000000;            // 2Mbps
     private static final int FRAME_RATE = 15;               // 15fps
     private static final int IFRAME_INTERVAL = 10;          // 10 seconds between I-frames
     private static final int WIDTH = 1280;
     private static final int HEIGHT = 720;
     // parameters for the audio encoder
-    private static final String MIME_TYPE_AUDIO = "audio/mp4a-latm";
+    private static final String MIME_TYPE_AUDIO = MediaFormat.MIMETYPE_AUDIO_AAC;
     private static final int AUDIO_SAMPLE_RATE = 44100;
     private static final int AUDIO_AAC_PROFILE = 2; /* OMX_AUDIO_AACObjectLC */
     private static final int AUDIO_CHANNEL_COUNT = 2; // mono
@@ -79,21 +80,28 @@
      * methods when called in incorrect operational states.
      */
     public void testException() throws Exception {
-        MediaFormat[] formatList = new MediaFormat[2];
+        boolean tested = false;
+        // audio decoder (MP3 should be present on all Android devices)
+        MediaFormat format = MediaFormat.createAudioFormat(
+                MediaFormat.MIMETYPE_AUDIO_MPEG, 44100 /* sampleRate */, 2 /* channelCount */);
+        tested = verifyException(format, false /* isEncoder */) || tested;
 
-        // use audio format
-        formatList[0] = new MediaFormat();
-        formatList[0].setString(MediaFormat.KEY_MIME, "audio/amr-wb");
-        formatList[0].setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000);
-        formatList[0].setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
-        formatList[0].setInteger(MediaFormat.KEY_BIT_RATE, 19850);
+        // audio encoder (AMR-WB may not be present on some Android devices)
+        format = MediaFormat.createAudioFormat(
+                MediaFormat.MIMETYPE_AUDIO_AMR_WB, 16000 /* sampleRate */, 1 /* channelCount */);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, 19850);
+        tested = verifyException(format, true /* isEncoder */) || tested;
 
-        // use video format
-        formatList[1] = createMediaFormat();
+        // video decoder (H.264/AVC may not be present on some Android devices)
+        format = createMediaFormat();
+        tested = verifyException(format, false /* isEncoder */) || tested;
 
-        for (MediaFormat format : formatList) {
-            verifyIllegalStateException(format, false);
-            verifyIllegalStateException(format, true);
+        // video encoder (H.264/AVC may not be present on some Android devices)
+        tested = verifyException(format, true /* isEncoder */) || tested;
+
+        // signal test is skipped due to no device media codecs.
+        if (!tested) {
+            MediaUtils.skipTest(TAG, "cannot find any compatible device codecs");
         }
     }
 
@@ -116,11 +124,17 @@
         }
     }
 
-    private static void verifyIllegalStateException(MediaFormat format, boolean isEncoder)
+    private static boolean verifyException(MediaFormat format, boolean isEncoder)
             throws IOException {
-        MediaCodec codec;
+        String mimeType = format.getString(MediaFormat.KEY_MIME);
+        if (!supportsCodec(mimeType, isEncoder)) {
+            Log.i(TAG, "No " + (isEncoder ? "encoder" : "decoder")
+                    + " found for mimeType= " + mimeType);
+            return false;
+        }
 
         // create codec (enter Initialized State)
+        MediaCodec codec;
 
         // create improperly
         final String methodName = isEncoder ? "createEncoderByType" : "createDecoderByType";
@@ -263,6 +277,7 @@
             fail("stop should not return MediaCodec.CodecException on wrong state");
         } catch (IllegalStateException e) { // expected
         }
+        return true;
     }
 
     /**
@@ -272,6 +287,11 @@
      * <br> calling createInputSurface() with a non-Surface color format throws exception
      */
     public void testCreateInputSurfaceErrors() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
         MediaFormat format = createMediaFormat();
         MediaCodec encoder = null;
         Surface surface = null;
@@ -326,6 +346,11 @@
      * <br> submitting a frame after EOS throws exception [TODO]
      */
     public void testSignalSurfaceEOS() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
         MediaFormat format = createMediaFormat();
         MediaCodec encoder = null;
         InputSurface inputSurface = null;
@@ -378,6 +403,11 @@
      * <br> stopping with buffers in flight doesn't crash or hang
      */
     public void testAbruptStop() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
         // There appears to be a race, so run it several times with a short delay between runs
         // to allow any previous activity to shut down.
         for (int i = 0; i < 50; i++) {
@@ -432,6 +462,11 @@
      * <br> dequeueInputBuffer() fails when encoder configured with an input Surface
      */
     public void testDequeueSurface() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
         MediaFormat format = createMediaFormat();
         MediaCodec encoder = null;
         Surface surface = null;
@@ -470,6 +505,11 @@
      * <br> sending EOS with signalEndOfInputStream on non-Surface encoder fails
      */
     public void testReconfigureWithoutSurface() {
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
         MediaFormat format = createMediaFormat();
         MediaCodec encoder = null;
         Surface surface = null;
@@ -553,8 +593,13 @@
             mediaExtractor = getMediaExtractorForMimeType(inputResourceId, "video/");
             MediaFormat mediaFormat =
                     mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
+            String mimeType = mediaFormat.getString(MediaFormat.KEY_MIME);
+            if (!supportsCodec(mimeType, false)) {
+                Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE);
+                return true;
+            }
             mediaCodec =
-                    MediaCodec.createDecoderByType(mediaFormat.getString(MediaFormat.KEY_MIME));
+                    MediaCodec.createDecoderByType(mimeType);
             mediaCodec.configure(mediaFormat, outputSurface.getSurface(), null, 0);
             mediaCodec.start();
             boolean eos = false;
@@ -669,6 +714,16 @@
      * Tests creating an encoder and decoder for {@link #MIME_TYPE_AUDIO} at the same time.
      */
     public void testCreateAudioDecoderAndEncoder() {
+        if (!supportsCodec(MIME_TYPE_AUDIO, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE_AUDIO);
+            return;
+        }
+
+        if (!supportsCodec(MIME_TYPE_AUDIO, false)) {
+            Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE_AUDIO);
+            return;
+        }
+
         final MediaFormat encoderFormat = MediaFormat.createAudioFormat(
                 MIME_TYPE_AUDIO, AUDIO_SAMPLE_RATE, AUDIO_CHANNEL_COUNT);
         encoderFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, AUDIO_AAC_PROFILE);
@@ -716,6 +771,16 @@
     }
 
     public void testConcurrentAudioVideoEncodings() throws InterruptedException {
+        if (!supportsCodec(MIME_TYPE_AUDIO, true)) {
+            Log.i(TAG, "No encoder found for mimeType= " + MIME_TYPE_AUDIO);
+            return;
+        }
+
+        if (!supportsCodec(MIME_TYPE, true)) {
+            Log.i(TAG, "No decoder found for mimeType= " + MIME_TYPE);
+            return;
+        }
+
         final int VIDEO_NUM_SWAPS = 100;
         // audio only checks this and stop
         mVideoEncodingOngoing = true;
@@ -948,18 +1013,17 @@
      * match was found.
      */
     private static MediaCodecInfo selectCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
+        // FIXME: select codecs based on the complete use-case, not just the mime
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (!info.isEncoder()) {
                 continue;
             }
 
-            String[] types = codecInfo.getSupportedTypes();
+            String[] types = info.getSupportedTypes();
             for (int j = 0; j < types.length; j++) {
                 if (types[j].equalsIgnoreCase(mimeType)) {
-                    return codecInfo;
+                    return info;
                 }
             }
         }
@@ -1006,4 +1070,23 @@
 
         return mediaExtractor;
     }
+
+    private static boolean supportsCodec(String mimeType, boolean encoder) {
+        MediaCodecList list = new MediaCodecList(MediaCodecList.ALL_CODECS);
+        for (MediaCodecInfo info : list.getCodecInfos()) {
+            if (encoder && !info.isEncoder()) {
+                continue;
+            }
+            if (!encoder && info.isEncoder()) {
+                continue;
+            }
+            
+            for (String type : info.getSupportedTypes()) {
+                if (type.equalsIgnoreCase(mimeType)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecTunneledPlayer.java b/tests/tests/media/src/android/media/cts/MediaCodecTunneledPlayer.java
new file mode 100644
index 0000000..411cd14
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/MediaCodecTunneledPlayer.java
@@ -0,0 +1,506 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.net.Uri;
+import android.util.Log;
+import android.view.SurfaceHolder;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * JB(API 21) introduces {@link MediaCodec} tunneled mode API.  It allows apps
+ * to use MediaCodec to delegate their Audio/Video rendering to a vendor provided
+ * Codec component.
+ */
+public class MediaCodecTunneledPlayer implements MediaTimeProvider {
+    private static final String TAG = MediaCodecTunneledPlayer.class.getSimpleName();
+
+    private static final int STATE_IDLE = 1;
+    private static final int STATE_PREPARING = 2;
+    private static final int STATE_PLAYING = 3;
+    private static final int STATE_PAUSED = 4;
+
+    private Boolean mThreadStarted = false;
+    private byte[] mSessionId;
+    private CodecState mAudioTrackState;
+    private int mMediaFormatHeight;
+    private int mMediaFormatWidth;
+    private Integer mState;
+    private long mDeltaTimeUs;
+    private long mDurationUs;
+    private Map<Integer, CodecState> mAudioCodecStates;
+    private Map<Integer, CodecState> mVideoCodecStates;
+    private Map<String, String> mAudioHeaders;
+    private Map<String, String> mVideoHeaders;
+    private MediaExtractor mAudioExtractor;
+    private MediaExtractor mVideoExtractor;
+    private SurfaceHolder mSurfaceHolder;
+    private Thread mThread;
+    private Uri mAudioUri;
+    private Uri mVideoUri;
+    private boolean mTunneled;
+    private int mAudioSessionId;
+
+    /*
+     * Media player class to playback video using tunneled MediaCodec.
+     */
+    public MediaCodecTunneledPlayer(SurfaceHolder holder, boolean tunneled, int AudioSessionId) {
+        mSurfaceHolder = holder;
+        mTunneled = tunneled;
+        mAudioTrackState = null;
+        mState = STATE_IDLE;
+        mAudioSessionId = AudioSessionId;
+        mThread = new Thread(new Runnable() {
+            @Override
+            public void run() {
+                while (true) {
+                    synchronized (mThreadStarted) {
+                        if (mThreadStarted == false) {
+                            break;
+                        }
+                    }
+                    synchronized (mState) {
+                        if (mState == STATE_PLAYING) {
+                            doSomeWork();
+                            if (mAudioTrackState != null) {
+                                mAudioTrackState.process();
+                            }
+                        }
+                    }
+                    try {
+                        Thread.sleep(5);
+                    } catch (InterruptedException ex) {
+                        Log.d(TAG, "Thread interrupted");
+                    }
+                }
+            }
+        });
+    }
+
+    public void setAudioDataSource(Uri uri, Map<String, String> headers) {
+        mAudioUri = uri;
+        mAudioHeaders = headers;
+    }
+
+    public void setVideoDataSource(Uri uri, Map<String, String> headers) {
+        mVideoUri = uri;
+        mVideoHeaders = headers;
+    }
+
+    public final int getMediaFormatHeight() {
+        return mMediaFormatHeight;
+    }
+
+    public final int getMediaFormatWidth() {
+        return mMediaFormatWidth;
+    }
+
+    private boolean prepareAudio() throws IOException {
+        for (int i = mAudioExtractor.getTrackCount(); i-- > 0;) {
+            MediaFormat format = mAudioExtractor.getTrackFormat(i);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+
+            if (!mime.startsWith("audio/")) {
+                continue;
+            }
+
+            Log.d(TAG, "audio track #" + i + " " + format + " " + mime +
+                  " Is ADTS:" + getMediaFormatInteger(format, MediaFormat.KEY_IS_ADTS) +
+                  " Sample rate:" + getMediaFormatInteger(format, MediaFormat.KEY_SAMPLE_RATE) +
+                  " Channel count:" +
+                  getMediaFormatInteger(format, MediaFormat.KEY_CHANNEL_COUNT));
+
+            mAudioExtractor.selectTrack(i);
+            if (!addTrack(i, format)) {
+                Log.e(TAG, "prepareAudio - addTrack() failed!");
+                return false;
+            }
+
+            if (format.containsKey(MediaFormat.KEY_DURATION)) {
+                long durationUs = format.getLong(MediaFormat.KEY_DURATION);
+
+                if (durationUs > mDurationUs) {
+                    mDurationUs = durationUs;
+                }
+                Log.d(TAG, "audio track format #" + i +
+                        " Duration:" + mDurationUs + " microseconds");
+            }
+        }
+        return true;
+    }
+
+    private boolean prepareVideo() throws IOException {
+        for (int i = mVideoExtractor.getTrackCount(); i-- > 0;) {
+            MediaFormat format = mVideoExtractor.getTrackFormat(i);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+
+            if (!mime.startsWith("video/")) {
+                continue;
+            }
+
+            mMediaFormatHeight = getMediaFormatInteger(format, MediaFormat.KEY_HEIGHT);
+            mMediaFormatWidth = getMediaFormatInteger(format, MediaFormat.KEY_WIDTH);
+            Log.d(TAG, "video track #" + i + " " + format + " " + mime +
+                  " Width:" + mMediaFormatWidth + ", Height:" + mMediaFormatHeight);
+
+            mVideoExtractor.selectTrack(i);
+            if (!addTrack(i, format)) {
+                Log.e(TAG, "prepareVideo - addTrack() failed!");
+                return false;
+            }
+
+            if (format.containsKey(MediaFormat.KEY_DURATION)) {
+                long durationUs = format.getLong(MediaFormat.KEY_DURATION);
+
+                if (durationUs > mDurationUs) {
+                    mDurationUs = durationUs;
+                }
+                Log.d(TAG, "track format #" + i + " Duration:" +
+                        mDurationUs + " microseconds");
+            }
+        }
+        return true;
+    }
+
+    public boolean prepare() throws IOException {
+        if (null == mAudioExtractor) {
+            mAudioExtractor = new MediaExtractor();
+            if (null == mAudioExtractor) {
+                Log.e(TAG, "prepare - Cannot create Audio extractor.");
+                return false;
+            }
+        }
+
+        if (null == mVideoExtractor){
+            mVideoExtractor = new MediaExtractor();
+            if (null == mVideoExtractor) {
+                Log.e(TAG, "prepare - Cannot create Video extractor.");
+                return false;
+            }
+        }
+
+        mAudioExtractor.setDataSource(mAudioUri.toString(), mAudioHeaders);
+        mVideoExtractor.setDataSource(mVideoUri.toString(), mVideoHeaders);
+
+        if (null == mVideoCodecStates) {
+            mVideoCodecStates = new HashMap<Integer, CodecState>();
+        } else {
+            mVideoCodecStates.clear();
+        }
+
+        if (null == mAudioCodecStates) {
+            mAudioCodecStates = new HashMap<Integer, CodecState>();
+        } else {
+            mAudioCodecStates.clear();
+        }
+
+        if (!prepareAudio()) {
+            Log.e(TAG,"prepare - prepareAudio() failed!");
+            return false;
+        }
+        if (!prepareVideo()) {
+            Log.e(TAG,"prepare - prepareVideo() failed!");
+            return false;
+        }
+
+        synchronized (mState) {
+            mState = STATE_PAUSED;
+        }
+        return true;
+    }
+
+    private boolean addTrack(int trackIndex, MediaFormat format) throws IOException {
+        String mime = format.getString(MediaFormat.KEY_MIME);
+        boolean isVideo = mime.startsWith("video/");
+        boolean isAudio = mime.startsWith("audio/");
+        MediaCodec codec;
+
+        // setup tunneled video codec if needed
+        if (isVideo && mTunneled) {
+            format.setFeatureEnabled(MediaCodecInfo.CodecCapabilities.FEATURE_TunneledPlayback,
+                        true);
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.ALL_CODECS);
+            String codecName = mcl.findDecoderForFormat(format);
+            if (codecName == null) {
+                Log.e(TAG,"addTrack - Could not find Tunneled playback codec for "+mime+
+                        " format!");
+                return false;
+            }
+
+            codec = MediaCodec.createByCodecName(codecName);
+            if (codec == null) {
+                Log.e(TAG, "addTrack - Could not create Tunneled playback codec "+
+                        codecName+"!");
+                return false;
+            }
+
+            if (mAudioTrackState != null) {
+                format.setInteger(MediaFormat.KEY_AUDIO_SESSION_ID, mAudioSessionId);
+            }
+        }
+        else {
+            codec = MediaCodec.createDecoderByType(mime);
+            if (codec == null) {
+                Log.e(TAG, "addTrack - Could not create regular playback codec for mime "+
+                        mime+"!");
+                return false;
+            }
+        }
+        codec.configure(
+                format,
+                isVideo ? mSurfaceHolder.getSurface() : null, null, 0);
+
+        CodecState state;
+        if (isVideo) {
+            state = new CodecState((MediaTimeProvider)this, mVideoExtractor,
+                            trackIndex, format, codec, true, mTunneled, mAudioSessionId);
+            mVideoCodecStates.put(Integer.valueOf(trackIndex), state);
+        } else {
+            state = new CodecState((MediaTimeProvider)this, mAudioExtractor,
+                            trackIndex, format, codec, true, mTunneled, mAudioSessionId);
+            mAudioCodecStates.put(Integer.valueOf(trackIndex), state);
+        }
+
+        if (isAudio) {
+            mAudioTrackState = state;
+        }
+
+        return true;
+    }
+
+    protected int getMediaFormatInteger(MediaFormat format, String key) {
+        return format.containsKey(key) ? format.getInteger(key) : 0;
+    }
+
+    public boolean start() {
+        Log.d(TAG, "start");
+
+        synchronized (mState) {
+            if (mState == STATE_PLAYING || mState == STATE_PREPARING) {
+                return true;
+            } else if (mState == STATE_IDLE) {
+                mState = STATE_PREPARING;
+                return true;
+            } else if (mState != STATE_PAUSED) {
+                throw new IllegalStateException();
+            }
+
+            for (CodecState state : mVideoCodecStates.values()) {
+                state.start();
+            }
+
+            for (CodecState state : mAudioCodecStates.values()) {
+                state.start();
+            }
+
+            mDeltaTimeUs = -1;
+            mState = STATE_PLAYING;
+        }
+        return false;
+    }
+
+    public void startWork() throws IOException, Exception {
+        try {
+            // Just change state from STATE_IDLE to STATE_PREPARING.
+            start();
+            // Extract media information from uri asset, and change state to STATE_PAUSED.
+            prepare();
+            // Start CodecState, and change from STATE_PAUSED to STATE_PLAYING.
+            start();
+        } catch (IOException e) {
+            throw e;
+        }
+
+        synchronized (mThreadStarted) {
+            mThreadStarted = true;
+            mThread.start();
+        }
+    }
+
+    public void startThread() {
+        start();
+        synchronized (mThreadStarted) {
+            mThreadStarted = true;
+            mThread.start();
+        }
+    }
+
+    public void pause() {
+        Log.d(TAG, "pause");
+
+        synchronized (mState) {
+            if (mState == STATE_PAUSED) {
+                return;
+            } else if (mState != STATE_PLAYING) {
+                throw new IllegalStateException();
+            }
+
+            for (CodecState state : mVideoCodecStates.values()) {
+                state.pause();
+            }
+
+            for (CodecState state : mAudioCodecStates.values()) {
+                state.pause();
+            }
+
+            mState = STATE_PAUSED;
+        }
+    }
+
+    public void flush() {
+        Log.d(TAG, "flush");
+
+        synchronized (mState) {
+            if (mState == STATE_PLAYING || mState == STATE_PREPARING) {
+                return;
+            }
+
+            for (CodecState state : mAudioCodecStates.values()) {
+                state.flush();
+            }
+
+            for (CodecState state : mVideoCodecStates.values()) {
+                state.flush();
+            }
+        }
+    }
+
+    public void reset() {
+        synchronized (mState) {
+            if (mState == STATE_PLAYING) {
+                pause();
+            }
+            if (mVideoCodecStates != null) {
+                for (CodecState state : mVideoCodecStates.values()) {
+                    state.release();
+                }
+                mVideoCodecStates = null;
+            }
+
+            if (mAudioCodecStates != null) {
+                for (CodecState state : mAudioCodecStates.values()) {
+                    state.release();
+                }
+                mAudioCodecStates = null;
+            }
+
+            if (mAudioExtractor != null) {
+                mAudioExtractor.release();
+                mAudioExtractor = null;
+            }
+
+            if (mVideoExtractor != null) {
+                mVideoExtractor.release();
+                mVideoExtractor = null;
+            }
+
+            mDurationUs = -1;
+            mState = STATE_IDLE;
+        }
+        synchronized (mThreadStarted) {
+            mThreadStarted = false;
+        }
+        try {
+            mThread.join();
+        } catch (InterruptedException ex) {
+            Log.d(TAG, "mThread.join " + ex);
+        }
+    }
+
+    public boolean isEnded() {
+        for (CodecState state : mVideoCodecStates.values()) {
+          if (!state.isEnded()) {
+            return false;
+          }
+        }
+
+        for (CodecState state : mAudioCodecStates.values()) {
+            if (!state.isEnded()) {
+              return false;
+            }
+        }
+
+        return true;
+    }
+
+    private void doSomeWork() {
+        try {
+            for (CodecState state : mVideoCodecStates.values()) {
+                state.doSomeWork();
+            }
+        } catch (IllegalStateException e) {
+            throw new Error("Video CodecState.doSomeWork" + e);
+        }
+
+        try {
+            for (CodecState state : mAudioCodecStates.values()) {
+                state.doSomeWork();
+            }
+        } catch (IllegalStateException e) {
+            throw new Error("Audio CodecState.doSomeWork" + e);
+        }
+
+    }
+
+    public long getNowUs() {
+        if (mAudioTrackState == null) {
+            return System.currentTimeMillis() * 1000;
+        }
+
+        return mAudioTrackState.getAudioTimeUs();
+    }
+
+    public long getRealTimeUsForMediaTime(long mediaTimeUs) {
+        if (mDeltaTimeUs == -1) {
+            long nowUs = getNowUs();
+            mDeltaTimeUs = nowUs - mediaTimeUs;
+        }
+
+        return mDeltaTimeUs + mediaTimeUs;
+    }
+
+    public int getDuration() {
+        return (int)((mDurationUs + 500) / 1000);
+    }
+
+    public int getCurrentPosition() {
+        if (mVideoCodecStates == null) {
+                return 0;
+        }
+
+        long positionUs = 0;
+
+        for (CodecState state : mVideoCodecStates.values()) {
+            long trackPositionUs = state.getCurrentPositionUs();
+
+            if (trackPositionUs > positionUs) {
+                positionUs = trackPositionUs;
+            }
+        }
+        return (int)((positionUs + 500) / 1000);
+    }
+
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
index 6591555..67eeca0 100644
--- a/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMuxerTest.java
@@ -97,7 +97,7 @@
 
         // Throws exception b/c start() is not called.
         muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        muxer.addTrack(MediaFormat.createVideoFormat("video/avc", 480, 320));
+        muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
 
         try {
             muxer.stop();
@@ -108,10 +108,10 @@
 
         // Throws exception b/c 2 video tracks were added.
         muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        muxer.addTrack(MediaFormat.createVideoFormat("video/avc", 480, 320));
+        muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
 
         try {
-            muxer.addTrack(MediaFormat.createVideoFormat("video/avc", 480, 320));
+            muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
             fail("should throw IllegalStateException.");
         } catch (IllegalStateException e) {
             // expected
@@ -119,9 +119,9 @@
 
         // Throws exception b/c 2 audio tracks were added.
         muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        muxer.addTrack(MediaFormat.createAudioFormat("audio/mp4a-latm", 48000, 1));
+        muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1));
         try {
-            muxer.addTrack(MediaFormat.createAudioFormat("audio/mp4a-latm", 48000, 1));
+            muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1));
             fail("should throw IllegalStateException.");
         } catch (IllegalStateException e) {
             // expected
@@ -129,11 +129,11 @@
 
         // Throws exception b/c 3 tracks were added.
         muxer = new MediaMuxer(outputFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
-        muxer.addTrack(MediaFormat.createVideoFormat("video/avc", 480, 320));
-        muxer.addTrack(MediaFormat.createAudioFormat("audio/mp4a-latm", 48000, 1));
+        muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
+        muxer.addTrack(MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 48000, 1));
         try {
 
-            muxer.addTrack(MediaFormat.createVideoFormat("video/avc", 480, 320));
+            muxer.addTrack(MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 480, 320));
             fail("should throw IllegalStateException.");
         } catch (IllegalStateException e) {
             // expected
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java
index 8063cbb..32fbfb5 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerFlakyNetworkTest.java
@@ -15,6 +15,7 @@
  */
 package android.media.cts;
 
+import android.cts.util.MediaUtils;
 import android.media.MediaPlayer;
 import android.os.Handler;
 import android.os.Looper;
@@ -34,6 +35,7 @@
 
 import java.net.Socket;
 import java.util.Random;
+import java.util.Vector;
 import java.util.concurrent.Callable;
 import java.util.concurrent.FutureTask;
 
@@ -42,6 +44,7 @@
  * from an HTTP server over a simulated "flaky" network.
  */
 public class MediaPlayerFlakyNetworkTest extends MediaPlayerTestBase {
+    private static final String PKG = "com.android.cts.media";
 
     private static final String[] TEST_VIDEOS = {
         "raw/video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz",
@@ -92,21 +95,37 @@
         doPlayStreams(6, 0.00002f);
     }
 
-   private void doPlayStreams(int seed, float probability) throws Throwable {
+    private String[] getSupportedVideos() {
+        Vector<String> supported = new Vector<String>();
+        for (String video : TEST_VIDEOS) {
+            if (MediaUtils.hasCodecsForPath(mContext, "android.resource://" + PKG + "/" + video)) {
+                supported.add(video);
+            }
+        }
+        return supported.toArray(new String[supported.size()]);
+    }
+
+    private void doPlayStreams(int seed, float probability) throws Throwable {
+        String[] supported = getSupportedVideos();
+        if (supported.length == 0) {
+            MediaUtils.skipTest("no codec found");
+            return;
+        }
+
         Random random = new Random(seed);
         createHttpServer(seed, probability);
         for (int i = 0; i < 10; i++) {
-            String video = getRandomTestVideo(random);
+            String video = getRandomTestVideo(random, supported);
             doPlayMp4Stream(video, 20000, 5000);
             doAsyncPrepareAndRelease(video);
             doRandomOperations(video);
         }
-        doPlayMp4Stream(getRandomTestVideo(random), 30000, 20000);
+        doPlayMp4Stream(getRandomTestVideo(random, supported), 30000, 20000);
         releaseHttpServer();
     }
 
-    private String getRandomTestVideo(Random random) {
-        return TEST_VIDEOS[random.nextInt(TEST_VIDEOS.length)];
+    private String getRandomTestVideo(Random random, String[] videos) {
+        return videos[random.nextInt(videos.length)];
     }
 
     private void doPlayMp4Stream(String video, int millisToPrepare, int millisToPlay)
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index 78ba149..e058981 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -20,7 +20,11 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
+import android.cts.util.MediaUtils;
 import android.media.AudioManager;
+import android.media.MediaCodec;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
 import android.media.MediaPlayer;
 import android.media.MediaPlayer.OnErrorListener;
 import android.media.MediaRecorder;
@@ -239,6 +243,46 @@
         }
     }
 
+    public void testPlayMidi() throws Exception {
+        final int resid = R.raw.midi8sec;
+        final int midiDuration = 8000;
+        final int tolerance = 70;
+        final int seekDuration = 1000;
+
+        MediaPlayer mp = MediaPlayer.create(mContext, resid);
+        try {
+            mp.setAudioStreamType(AudioManager.STREAM_MUSIC);
+            mp.setWakeMode(mContext, PowerManager.PARTIAL_WAKE_LOCK);
+
+            mp.start();
+
+            assertFalse(mp.isLooping());
+            mp.setLooping(true);
+            assertTrue(mp.isLooping());
+
+            assertEquals(midiDuration, mp.getDuration(), tolerance);
+            int pos = mp.getCurrentPosition();
+            assertTrue(pos >= 0);
+            assertTrue(pos < midiDuration - seekDuration);
+
+            mp.seekTo(pos + seekDuration);
+            assertEquals(pos + seekDuration, mp.getCurrentPosition(), tolerance);
+
+            // test stop and restart
+            mp.stop();
+            mp.reset();
+            AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
+            mp.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+            afd.close();
+            mp.prepare();
+            mp.start();
+
+            Thread.sleep(SLEEP_TIME);
+        } finally {
+            mp.release();
+        }
+    }
+
     static class OutputListener {
         int mSession;
         AudioEffect mVc;
@@ -304,6 +348,11 @@
     }
 
     public void testPlayAudioTwice() throws Exception {
+        if (!hasAudioOutput()) {
+            Log.i(LOG_TAG, "SKIPPING testPlayAudioTwice(). No audio output.");
+            return;
+        }
+
         final int resid = R.raw.camera_click;
 
         MediaPlayer mp = MediaPlayer.create(mContext, resid);
@@ -550,6 +599,10 @@
     }
 
     private void testGapless(int resid1, int resid2) throws Exception {
+        if (!hasAudioOutput()) {
+            Log.i(LOG_TAG, "SKIPPING testPlayAudioTwice(). No audio output.");
+            return;
+        }
 
         MediaPlayer mp1 = new MediaPlayer();
         mp1.setAudioStreamType(AudioManager.STREAM_MUSIC);
@@ -660,7 +713,9 @@
             }
         });
 
-        loadResource(R.raw.testvideo);
+        if (!checkLoadResource(R.raw.testvideo)) {
+            return; // skip;
+        }
         playLoadedVideo(352, 288, -1);
 
         Thread.sleep(SLEEP_TIME);
@@ -1011,7 +1066,9 @@
     }
 
     public void testDeselectTrack() throws Throwable {
-        loadResource(R.raw.testvideo_with_2_subtitles);
+        if (!checkLoadResource(R.raw.testvideo_with_2_subtitles)) {
+            return; // skip;
+        }
         runTestOnUiThread(new Runnable() {
             public void run() {
                 try {
@@ -1082,7 +1139,9 @@
     }
 
     public void testChangeSubtitleTrack() throws Throwable {
-        loadResource(R.raw.testvideo_with_2_subtitles);
+        if (!checkLoadResource(R.raw.testvideo_with_2_subtitles)) {
+            return; // skip;
+        }
 
         mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
         mMediaPlayer.setScreenOnWhilePlaying(true);
@@ -1170,7 +1229,9 @@
     }
 
     public void testGetTrackInfo() throws Throwable {
-        loadResource(R.raw.testvideo_with_2_subtitles);
+        if (!checkLoadResource(R.raw.testvideo_with_2_subtitles)) {
+            return; // skip;
+        }
         runTestOnUiThread(new Runnable() {
             public void run() {
                 try {
@@ -1212,17 +1273,23 @@
      *  The ones being used here are 10 seconds long.
      */
     public void testResumeAtEnd() throws Throwable {
-        testResumeAtEnd(R.raw.loudsoftmp3);
-        testResumeAtEnd(R.raw.loudsoftwav);
-        testResumeAtEnd(R.raw.loudsoftogg);
-        testResumeAtEnd(R.raw.loudsoftitunes);
-        testResumeAtEnd(R.raw.loudsoftfaac);
-        testResumeAtEnd(R.raw.loudsoftaac);
+        int testsRun =
+            testResumeAtEnd(R.raw.loudsoftmp3) +
+            testResumeAtEnd(R.raw.loudsoftwav) +
+            testResumeAtEnd(R.raw.loudsoftogg) +
+            testResumeAtEnd(R.raw.loudsoftitunes) +
+            testResumeAtEnd(R.raw.loudsoftfaac) +
+            testResumeAtEnd(R.raw.loudsoftaac);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no decoder found");
+        }
     }
 
-    private void testResumeAtEnd(int res) throws Throwable {
-
-        loadResource(res);
+    // returns 1 if test was run, 0 otherwise
+    private int testResumeAtEnd(int res) throws Throwable {
+        if (!loadResource(res)) {
+            return 0; // skip
+        }
         mMediaPlayer.prepare();
         mOnCompletionCalled.reset();
         mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
@@ -1240,12 +1307,16 @@
         assertTrue("MediaPlayer should still be playing", mMediaPlayer.isPlaying());
         mMediaPlayer.reset();
         assertEquals("wrong number of repetitions", 1, mOnCompletionCalled.getNumSignal());
+        return 1;
     }
 
     public void testCallback() throws Throwable {
         final int mp4Duration = 8484;
 
-        loadResource(R.raw.testvideo);
+        if (!checkLoadResource(R.raw.testvideo)) {
+            return; // skip;
+        }
+
         mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
         mMediaPlayer.setScreenOnWhilePlaying(true);
 
@@ -1317,8 +1388,13 @@
 
     public void testRecordAndPlay() throws Exception {
         if (!hasMicrophone()) {
+            MediaUtils.skipTest(LOG_TAG, "no microphone");
             return;
         }
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB)
+                || !MediaUtils.checkEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
+            return; // skip
+        }
         File outputFile = new File(Environment.getExternalStorageDirectory(),
                 "record_and_play.3gp");
         String outputFileLocation = outputFile.getAbsolutePath();
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java b/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java
index 61d8792..d089658 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTestBase.java
@@ -16,8 +16,10 @@
 package android.media.cts;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
+import android.cts.util.MediaUtils;
 import android.media.MediaPlayer;
 import android.test.ActivityInstrumentationTestCase2;
 
@@ -142,7 +144,12 @@
         super.tearDown();
     }
 
-    protected void loadResource(int resid) throws Exception {
+    // returns true on success
+    protected boolean loadResource(int resid) throws Exception {
+        if (!MediaUtils.hasCodecsForResource(mContext, resid)) {
+            return false;
+        }
+
         AssetFileDescriptor afd = mResources.openRawResourceFd(resid);
         try {
             mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(),
@@ -159,6 +166,11 @@
             afd.close();
         }
         sUseScaleToFitMode = !sUseScaleToFitMode;  // Alternate the scaling mode
+        return true;
+    }
+
+    protected boolean checkLoadResource(int resid) throws Exception {
+        return MediaUtils.check(loadResource(resid), "no decoder found");
     }
 
     protected void loadSubtitleSource(int resid) throws Exception {
@@ -197,7 +209,10 @@
     }
 
     protected void playVideoTest(int resid, int width, int height) throws Exception {
-        loadResource(resid);
+        if (!checkLoadResource(resid)) {
+            return; // skip
+        }
+
         playLoadedVideo(width, height, 0);
     }
 
@@ -278,4 +293,19 @@
     }
 
     private static class PrepareFailedException extends Exception {}
+
+    public boolean hasAudioOutput() {
+        return getInstrumentation().getTargetContext().getPackageManager()
+            .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+    }
+
+    public boolean isTv() {
+        PackageManager pm = getInstrumentation().getTargetContext().getPackageManager();
+        return pm.hasSystemFeature(pm.FEATURE_TELEVISION)
+                && pm.hasSystemFeature(pm.FEATURE_LEANBACK);
+    }
+
+    public boolean checkTv() {
+        return MediaUtils.check(isTv(), "not a TV");
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
index 3cc71fa..78b5cfd 100644
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
@@ -17,7 +17,9 @@
 
 
 import android.content.pm.PackageManager;
+import android.cts.util.MediaUtils;
 import android.hardware.Camera;
+import android.media.MediaFormat;
 import android.media.MediaMetadataRetriever;
 import android.media.MediaRecorder;
 import android.media.MediaRecorder.OnErrorListener;
@@ -306,30 +308,40 @@
     }
 
     public void testRecordingAudioInRawFormats() throws Exception {
-        testRecordAudioInRawFormat(
-                MediaRecorder.OutputFormat.AMR_NB,
-                MediaRecorder.AudioEncoder.AMR_NB);
+        int testsRun = 0;
+        if (hasAmrNb()) {
+            testsRun += testRecordAudioInRawFormat(
+                    MediaRecorder.OutputFormat.AMR_NB,
+                    MediaRecorder.AudioEncoder.AMR_NB);
+        }
 
-        testRecordAudioInRawFormat(
-                MediaRecorder.OutputFormat.AMR_WB,
-                MediaRecorder.AudioEncoder.AMR_WB);
+        if (hasAmrWb()) {
+            testsRun += testRecordAudioInRawFormat(
+                    MediaRecorder.OutputFormat.AMR_WB,
+                    MediaRecorder.AudioEncoder.AMR_WB);
+        }
 
-        testRecordAudioInRawFormat(
-                MediaRecorder.OutputFormat.AAC_ADTS,
-                MediaRecorder.AudioEncoder.AAC);
+        if (hasAac()) {
+            testsRun += testRecordAudioInRawFormat(
+                    MediaRecorder.OutputFormat.AAC_ADTS,
+                    MediaRecorder.AudioEncoder.AAC);
+        }
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no audio codecs or microphone");
+        }
     }
 
-    private void testRecordAudioInRawFormat(
+    private int testRecordAudioInRawFormat(
             int fileFormat, int codec) throws Exception {
-
         if (!hasMicrophone()) {
-            return;
+            return 0; // skip
         }
         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
         mMediaRecorder.setOutputFormat(fileFormat);
         mMediaRecorder.setOutputFile(OUTPUT_PATH);
         mMediaRecorder.setAudioEncoder(codec);
         recordMedia(MAX_FILE_SIZE, mOutFile);
+        return 1;
     }
 
     public void testGetAudioSourceMax() throws Exception {
@@ -345,7 +357,8 @@
     }
 
     public void testRecorderAudio() throws Exception {
-        if (!hasMicrophone()) {
+        if (!hasMicrophone() || !hasAmrNb()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
             return;
         }
         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
@@ -360,7 +373,8 @@
     }
 
     public void testOnInfoListener() throws Exception {
-        if (!hasMicrophone()) {
+        if (!hasMicrophone() || !hasAmrNb()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
             return;
         }
         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
@@ -374,7 +388,8 @@
     }
 
     public void testSetMaxDuration() throws Exception {
-        if (!hasMicrophone()) {
+        if (!hasMicrophone() || !hasAmrNb()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
             return;
         }
         testSetMaxDuration(RECORD_TIME_LONG_MS, RECORDED_DUR_TOLERANCE_MS);
@@ -412,14 +427,15 @@
     }
 
     public void testSetMaxFileSize() throws Exception {
-        if (!hasMicrophone() || !hasCamera()) {
-            return;
-        }
         testSetMaxFileSize(512 * 1024, 50 * 1024);
     }
 
     private void testSetMaxFileSize(
             long fileSize, long tolerance) throws Exception {
+        if (!hasMicrophone() || !hasCamera() || !hasAmrNb() || !hasH264()) {
+            MediaUtils.skipTest("no microphone, camera, or codecs");
+            return;
+        }
         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
@@ -448,7 +464,8 @@
     }
 
     public void testOnErrorListener() throws Exception {
-        if (!hasMicrophone()) {
+        if (!hasMicrophone() || !hasAmrNb()) {
+            MediaUtils.skipTest("no audio codecs or microphone");
             return;
         }
         mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
@@ -483,4 +500,20 @@
         return mActivity.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_MICROPHONE);
     }
+
+    private static boolean hasAmrNb() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_NB);
+    }
+
+    private static boolean hasAmrWb() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AMR_WB);
+    }
+
+    private static boolean hasAac() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_AUDIO_AAC);
+    }
+
+    private static boolean hasH264() {
+        return MediaUtils.hasEncoder(MediaFormat.MIMETYPE_VIDEO_AVC);
+    }
 }
diff --git a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java b/tests/tests/media/src/android/media/cts/MediaTimeProvider.java
similarity index 71%
copy from common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
copy to tests/tests/media/src/android/media/cts/MediaTimeProvider.java
index a376373..4f6837e 100644
--- a/common/util/tests/src/com/android/compatibility/common/util/CommonUtilTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaTimeProvider.java
@@ -13,13 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+package android.media.cts;
 
-package com.android.compatibility.common.util;
-
-import junit.framework.TestCase;
-
-public class CommonUtilTest extends TestCase {
-
-    // TODO(stuartscott): Add tests when there is something to test.
-
+/*
+ * Interface used by CodecState to retrieve Media timing info from parent Player
+ */
+public interface MediaTimeProvider {
+    public long getNowUs();
+    public long getRealTimeUsForMediaTime(long mediaTimeUs);
 }
diff --git a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
index fc27dfa..e7b08e9 100644
--- a/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/NativeDecoderTest.java
@@ -20,6 +20,7 @@
 
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources;
+import android.cts.util.MediaUtils;
 import android.media.MediaCodec;
 import android.media.MediaCodec.BufferInfo;
 import android.media.MediaCodecInfo;
@@ -91,7 +92,7 @@
 
         testExtractor(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
         testExtractor(R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz);
-        testExtractor(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz);
+        testExtractor(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz);
         testExtractor(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz);
         testExtractor(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
 
@@ -183,22 +184,29 @@
 
 
     public void testDecoder() throws Exception {
-        testDecoder(R.raw.sinesweepogg);
-        testDecoder(R.raw.sinesweepmp3lame);
-        testDecoder(R.raw.sinesweepmp3smpb);
-        testDecoder(R.raw.sinesweepm4a);
-        testDecoder(R.raw.sinesweepflac);
-        testDecoder(R.raw.sinesweepwav);
+        int testsRun =
+            testDecoder(R.raw.sinesweepogg) +
+            testDecoder(R.raw.sinesweepmp3lame) +
+            testDecoder(R.raw.sinesweepmp3smpb) +
+            testDecoder(R.raw.sinesweepm4a) +
+            testDecoder(R.raw.sinesweepflac) +
+            testDecoder(R.raw.sinesweepwav) +
 
-        testDecoder(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
-        testDecoder(R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz);
-        testDecoder(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz);
-        testDecoder(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz);
-        testDecoder(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
-
+            testDecoder(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz) +
+            testDecoder(R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz) +
+            testDecoder(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz) +
+            testDecoder(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz) +
+            testDecoder(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no decoders found");
+        }
     }
 
-    private void testDecoder(int res) throws Exception {
+    private int testDecoder(int res) throws Exception {
+        if (!MediaUtils.hasCodecsForResource(mContext, res)) {
+            return 0; // skip
+        }
+
         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
 
         int[] jdata = getDecodedData(
@@ -211,6 +219,7 @@
         Log.i("@@@", Arrays.toString(ndata));
         assertEquals("number of samples differs", jdata.length, ndata.length);
         assertTrue("different decoded data", Arrays.equals(jdata, ndata));
+        return 1;
     }
 
     private static int[] getDecodedData(FileDescriptor fd, long offset, long size)
@@ -331,6 +340,10 @@
             }
         }
 
+        for (int i = 0; i < codec.length; i++) {
+            codec[i].release();
+        }
+
         return trackbytes;
     }
 
@@ -374,19 +387,33 @@
             throws IOException;
 
     public void testVideoPlayback() throws Exception {
-        testVideoPlayback(R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz);
-        testVideoPlayback(R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz);
-        testVideoPlayback(R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_44100hz);
-        testVideoPlayback(R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz);
-        testVideoPlayback(R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
+        int testsRun =
+            testVideoPlayback(
+                    R.raw.video_1280x720_mp4_h264_1000kbps_25fps_aac_stereo_128kbps_44100hz) +
+            testVideoPlayback(
+                    R.raw.video_1280x720_webm_vp8_333kbps_25fps_vorbis_stereo_128kbps_44100hz) +
+            testVideoPlayback(
+                    R.raw.video_1280x720_webm_vp9_309kbps_25fps_vorbis_stereo_128kbps_48000hz) +
+            testVideoPlayback(
+                    R.raw.video_176x144_3gp_h263_300kbps_12fps_aac_mono_24kbps_11025hz) +
+            testVideoPlayback(
+                    R.raw.video_480x360_mp4_mpeg4_860kbps_25fps_aac_stereo_128kbps_44100hz);
+        if (testsRun == 0) {
+            MediaUtils.skipTest("no decoders found");
+        }
     }
 
-    private void testVideoPlayback(int res) throws Exception {
+    private int testVideoPlayback(int res) throws Exception {
+        if (!MediaUtils.checkCodecsForResource(mContext, res)) {
+            return 0; // skip
+        }
+
         AssetFileDescriptor fd = mResources.openRawResourceFd(res);
 
         boolean ret = testPlaybackNative(mActivity.getSurfaceHolder().getSurface(),
                 fd.getParcelFileDescriptor().getFd(), fd.getStartOffset(), fd.getLength());
         assertTrue("native playback error", ret);
+        return 1;
     }
 
     private static native boolean testPlaybackNative(Surface surface,
@@ -397,6 +424,10 @@
     }
 
     private void testMuxer(int res, boolean webm) throws Exception {
+        if (!MediaUtils.checkCodecsForResource(mContext, res)) {
+            return; // skip
+        }
+
         AssetFileDescriptor infd = mResources.openRawResourceFd(res);
 
         File base = mContext.getExternalFilesDir(null);
diff --git a/tests/tests/media/src/android/media/cts/NonBlockingAudioTrack.java b/tests/tests/media/src/android/media/cts/NonBlockingAudioTrack.java
index 3ba1ce8..20c1dff 100644
--- a/tests/tests/media/src/android/media/cts/NonBlockingAudioTrack.java
+++ b/tests/tests/media/src/android/media/cts/NonBlockingAudioTrack.java
@@ -18,6 +18,7 @@
 import android.media.AudioFormat;
 import android.media.AudioManager;
 import android.media.AudioTrack;
+import android.media.AudioAttributes;
 import android.util.Log;
 
 import java.util.LinkedList;
@@ -46,7 +47,8 @@
     private int mNumBytesQueued = 0;
     private LinkedList<QueueElem> mQueue = new LinkedList<QueueElem>();
 
-    public NonBlockingAudioTrack(int sampleRate, int channelCount) {
+    public NonBlockingAudioTrack(int sampleRate, int channelCount, boolean hwAvSync,
+                    int audioSessionId) {
         int channelConfig;
         switch (channelCount) {
             case 1:
@@ -70,13 +72,29 @@
 
         int bufferSize = 2 * minBufferSize;
 
-        mAudioTrack = new AudioTrack(
-                AudioManager.STREAM_MUSIC,
-                sampleRate,
-                channelConfig,
-                AudioFormat.ENCODING_PCM_16BIT,
-                bufferSize,
-                AudioTrack.MODE_STREAM);
+        if (!hwAvSync) {
+            mAudioTrack = new AudioTrack(
+                    AudioManager.STREAM_MUSIC,
+                    sampleRate,
+                    channelConfig,
+                    AudioFormat.ENCODING_PCM_16BIT,
+                    bufferSize,
+                    AudioTrack.MODE_STREAM);
+        }
+        else {
+            // build AudioTrack using Audio Attributes and FLAG_HW_AV_SYNC
+            AudioAttributes audioAttributes = (new AudioAttributes.Builder())
+                            .setLegacyStreamType(AudioManager.STREAM_MUSIC)
+                            .setFlags(AudioAttributes.FLAG_HW_AV_SYNC)
+                            .build();
+            AudioFormat audioFormat = (new AudioFormat.Builder())
+                            .setChannelMask(channelConfig)
+                            .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                            .setSampleRate(sampleRate)
+                            .build();
+             mAudioTrack = new AudioTrack(audioAttributes, audioFormat, bufferSize,
+                                    AudioTrack.MODE_STREAM, audioSessionId);
+        }
 
         mSampleRate = sampleRate;
         mFrameSize = 2 * channelCount;
@@ -113,6 +131,16 @@
         mAudioTrack.pause();
     }
 
+    public void flush() {
+        if (mAudioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) {
+            return;
+        }
+        mAudioTrack.flush();
+        mNumFramesSubmitted = 0;
+        mQueue.clear();
+        mNumBytesQueued = 0;
+    }
+
     public void release() {
         cancelWriteMore();
 
diff --git a/tests/tests/media/src/android/media/cts/OutputSurface.java b/tests/tests/media/src/android/media/cts/OutputSurface.java
index fc8ad9c..c87326d 100644
--- a/tests/tests/media/src/android/media/cts/OutputSurface.java
+++ b/tests/tests/media/src/android/media/cts/OutputSurface.java
@@ -70,7 +70,7 @@
         eglSetup(width, height);
         makeCurrent();
 
-        setup();
+        setup(this);
     }
 
     /**
@@ -78,14 +78,18 @@
      * new one).  Creates a Surface that can be passed to MediaCodec.configure().
      */
     public OutputSurface() {
-        setup();
+        setup(this);
+    }
+
+    public OutputSurface(final SurfaceTexture.OnFrameAvailableListener listener) {
+        setup(listener);
     }
 
     /**
      * Creates instances of TextureRender and SurfaceTexture, and a Surface associated
      * with the SurfaceTexture.
      */
-    private void setup() {
+    private void setup(SurfaceTexture.OnFrameAvailableListener listener) {
         mTextureRender = new TextureRender();
         mTextureRender.surfaceCreated();
 
@@ -107,7 +111,7 @@
         //
         // Java language note: passing "this" out of a constructor is generally unwise,
         // but we should be able to get away with it here.
-        mSurfaceTexture.setOnFrameAvailableListener(this);
+        mSurfaceTexture.setOnFrameAvailableListener(listener);
 
         mSurface = new Surface(mSurfaceTexture);
     }
@@ -285,6 +289,11 @@
         mTextureRender.drawFrame(mSurfaceTexture);
     }
 
+    public void latchImage() {
+        mTextureRender.checkGlError("before updateTexImage");
+        mSurfaceTexture.updateTexImage();
+    }
+
     @Override
     public void onFrameAvailable(SurfaceTexture st) {
         if (VERBOSE) Log.d(TAG, "new frame available");
diff --git a/tests/tests/media/src/android/media/cts/PostProcTestBase.java b/tests/tests/media/src/android/media/cts/PostProcTestBase.java
index b034763..ef87662 100644
--- a/tests/tests/media/src/android/media/cts/PostProcTestBase.java
+++ b/tests/tests/media/src/android/media/cts/PostProcTestBase.java
@@ -16,7 +16,9 @@
 
 package android.media.cts;
 
+import android.content.Context;
 import android.content.pm.PackageManager;
+import android.media.AudioManager;
 import android.media.audiofx.AudioEffect;
 import android.os.Looper;
 import android.test.AndroidTestCase;
@@ -29,6 +31,8 @@
     protected Looper mLooper = null;
     protected final Object mLock = new Object();
     protected int mChangedParameter = -1;
+    protected final static String BUNDLE_VOLUME_EFFECT_UUID =
+            "119341a0-8469-11df-81f9-0002a5d5c51b";
 
     protected boolean hasAudioOutput() {
         return getContext().getPackageManager().hasSystemFeature(
@@ -62,4 +66,13 @@
         }
         return AudioEffect.isEffectTypeAvailable(AudioEffect.EFFECT_TYPE_ENV_REVERB);
     }
+
+    protected int getSessionId() {
+        AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+        assertNotNull("could not get AudioManager", am);
+        int sessionId = am.generateAudioSessionId();
+        assertTrue("Could not generate session id", sessionId>0);
+        return sessionId;
+    }
+
 }
diff --git a/tests/tests/media/src/android/media/cts/PresentationSyncTest.java b/tests/tests/media/src/android/media/cts/PresentationSyncTest.java
index ac86f1e..efa2e0a 100644
--- a/tests/tests/media/src/android/media/cts/PresentationSyncTest.java
+++ b/tests/tests/media/src/android/media/cts/PresentationSyncTest.java
@@ -109,7 +109,20 @@
         CpuWaster cpuWaster = new CpuWaster();
         try {
             cpuWaster.start();
-            runThroughputTest(output, refreshNsec, 0.75f);
+            // Tests with mult < 1.0f are flaky, for two reasons:
+            //
+            // (a) They assume that the GPU can render the test scene in less than mult*refreshNsec.
+            //     It's a simple scene, but CTS/CDD don't currently require being able to do more
+            //     than a full-screen clear in refreshNsec.
+            //
+            // (b) More importantly, it assumes that the only rate-limiting happening is
+            //     backpressure from the buffer queue. If the EGL implementation is doing its own
+            //     rate-limiting (to limit the amount of work queued to the GPU at any time), then
+            //     depending on how that's implemented the buffer queue may not have two frames
+            //     pending very often. So the consumer won't be able to drop many frames, and the
+            //     throughput won't be much better than with mult=1.0.
+            //
+            // runThroughputTest(output, refreshNsec, 0.75f);
             cpuWaster.stop();
             runThroughputTest(output, refreshNsec, 1.0f);
             runThroughputTest(output, refreshNsec, 2.0f);
diff --git a/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java b/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java
index dfaabb8..bf47a27 100644
--- a/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java
+++ b/tests/tests/media/src/android/media/cts/RingtoneManagerTest.java
@@ -21,6 +21,7 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.database.Cursor;
 import android.media.AudioManager;
 import android.media.Ringtone;
@@ -28,11 +29,13 @@
 import android.net.Uri;
 import android.provider.Settings;
 import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
 
 public class RingtoneManagerTest
         extends ActivityInstrumentationTestCase2<RingtonePickerActivity> {
 
     private static final String PKG = "com.android.cts.media";
+    private static final String TAG = "RingtoneManagerTest";
 
     private RingtonePickerActivity mActivity;
     private Instrumentation mInstrumentation;
@@ -74,12 +77,21 @@
         super.tearDown();
     }
 
+    private boolean hasAudioOutput() {
+        return mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+    }
+
     public void testConstructors() {
         new RingtoneManager(mActivity);
         new RingtoneManager(mContext);
     }
 
     public void testAccessMethods() {
+        if (!hasAudioOutput()) {
+            Log.i(TAG, "Skipping testAccessMethods(): device doesn't have audio output.");
+            return;
+        }
+
         Cursor c = mRingtoneManager.getCursor();
         assertTrue("Must have at least one ring tone available", c.getCount() > 0);
 
@@ -115,6 +127,11 @@
     }
 
     public void testStopPreviousRingtone() {
+        if (!hasAudioOutput()) {
+            Log.i(TAG, "Skipping testStopPreviousRingtone(): device doesn't have audio output.");
+            return;
+        }
+
         Cursor c = mRingtoneManager.getCursor();
         assertTrue("Must have at least one ring tone available", c.getCount() > 0);
 
diff --git a/tests/tests/media/src/android/media/cts/RingtoneTest.java b/tests/tests/media/src/android/media/cts/RingtoneTest.java
index 6e3a1e9..f5218e3 100644
--- a/tests/tests/media/src/android/media/cts/RingtoneTest.java
+++ b/tests/tests/media/src/android/media/cts/RingtoneTest.java
@@ -16,16 +16,18 @@
 
 package android.media.cts;
 
-
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.media.AudioManager;
 import android.media.Ringtone;
 import android.media.RingtoneManager;
 import android.net.Uri;
 import android.provider.Settings;
 import android.test.AndroidTestCase;
+import android.util.Log;
 
 public class RingtoneTest extends AndroidTestCase {
+    private static final String TAG = "RingtoneTest";
 
     private Context mContext;
     private Ringtone mRingtone;
@@ -73,7 +75,16 @@
         super.tearDown();
     }
 
+    private boolean hasAudioOutput() {
+        return getContext().getPackageManager()
+            .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
+    }
+
     public void testRingtone() {
+        if (!hasAudioOutput()) {
+            Log.i(TAG, "Skipping testRingtone(): device doesn't have audio output.");
+            return;
+        }
 
         assertNotNull(mRingtone.getTitle(mContext));
         assertTrue(mOriginalStreamType >= 0);
diff --git a/tests/tests/media/src/android/media/cts/SoundPoolMidiTest.java b/tests/tests/media/src/android/media/cts/SoundPoolMidiTest.java
new file mode 100644
index 0000000..a8c640e
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/SoundPoolMidiTest.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.media.cts;
+
+import com.android.cts.media.R;
+
+public class SoundPoolMidiTest extends SoundPoolTest {
+
+    @Override
+    protected int getSoundA() {
+        return R.raw.midi_a;
+    }
+
+    @Override
+    protected int getSoundCs() {
+        return R.raw.midi_cs;
+    }
+
+    @Override
+    protected int getSoundE() {
+        return R.raw.midi_e;
+    }
+
+    @Override
+    protected int getSoundB() {
+        return R.raw.midi_b;
+    }
+
+    @Override
+    protected int getSoundGs() {
+        return R.raw.midi_gs;
+    }
+
+    @Override
+    protected String getFileName() {
+        return "midi_a.mid";
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
index 2b93064..dd7c1f6 100644
--- a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
@@ -15,6 +15,8 @@
  */
 package android.media.cts;
 
+import android.cts.util.MediaUtils;
+import android.media.MediaFormat;
 import android.media.MediaPlayer;
 import android.os.Looper;
 import android.os.SystemClock;
@@ -23,11 +25,12 @@
 
 import java.io.IOException;
 
-
 /**
  * Tests of MediaPlayer streaming capabilities.
  */
 public class StreamingMediaPlayerTest extends MediaPlayerTestBase {
+    private static final String TAG = "StreamingMediaPlayerTest";
+
     private CtsTestServer mServer;
 
 /* RTSP tests are more flaky and vulnerable to network condition.
@@ -62,58 +65,86 @@
 */
     // Streaming HTTP video from YouTube
     public void testHTTP_H263_AMR_Video1() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263, MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
+            return; // skip
+        }
+
         playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
                 + "&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
                 + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=6F5AEC448AAF88466D7A10EBB76020745405D33F."
-                + "5050D35AE997E1535FE828B0DE99EF31A699D9D0"
-                + "&key=test_key1&user=android-device-test", 176, 144);
+                + "&signature=5729247E22691EBB3E804DDD523EC42DC17DD8CE"
+                + ".443B81C1E8E6D64E4E1555F568BA46C206507D78"
+                + "&key=ik0&user=android-device-test", 176, 144);
     }
     public void testHTTP_H263_AMR_Video2() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263, MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
+            return; // skip
+        }
+
         playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
                 + "&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
                 + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=71749754E28115FD1C233E3BE96CDDC3F430CB74."
-                + "49D1506DE694CC8FCEE63CB4F3AD41EB76C198CE"
-                + "&key=test_key1&user=android-device-test", 176, 144);
+                + "&signature=508D82AB36939345BF6B8D0623CB6CABDD9C64C3"
+                + ".9B3336A96846DF38E5343C46AA57F6CF2956E427"
+                + "&key=ik0&user=android-device-test", 176, 144);
     }
 
     public void testHTTP_MPEG4SP_AAC_Video1() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+            return; // skip
+        }
+
         playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
                 + "&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
                 + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=197A9742C1EFCA95725F2F26DFFD512FC48C149F."
-                + "A59B42FD490F6B591B292F3B2659A9723B980351"
-                + "&key=test_key1&user=android-device-test", 176, 144);
+                + "&signature=837198AAADF6F36BA6B2D324F690A7C5B7AFE3FF"
+                + ".7138CE5E36D718220726C1FC305497FF2D082249"
+                + "&key=ik0&user=android-device-test", 176, 144);
     }
     public void testHTTP_MPEG4SP_AAC_Video2() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
+            return; // skip
+        }
+
         playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
                 + "&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
                 + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=0F84740A7E06F884127E78A6D7DE6DEA8F4B8BFD."
-                + "248DF1E90B8137C30769C79BF23147F6BB3DFCDF"
-                + "&key=test_key1&user=android-device-test", 176, 144);
+                + "&signature=70E979A621001201BC18622BDBF914FA870BDA40"
+                + ".6E78890B80F4A33A18835F775B1FF64F0A4D0003"
+                + "&key=ik0&user=android-device-test", 176, 144);
     }
 
     public void testHTTP_H264Base_AAC_Video1() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
         playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
                 + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
                 + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=3CFCAFB87EB9FC943FACDC54FEC8C725A801642C."
-                + "7D77ACBC4CAF40349BF093E302B635757E45F345"
-                + "&key=test_key1&user=android-device-test", 640, 360);
+                + "&signature=667AEEF54639926662CE62361400B8F8C1753B3F"
+                + ".15F46C382C68A9F121BA17BF1F56BEDEB4B06091"
+                + "&key=ik0&user=android-device-test", 640, 360);
     }
     public void testHTTP_H264Base_AAC_Video2() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
         playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
                 + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
                 + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=A11D8BA0AA67A27F1409BE0C0B96B756625DB88B."
-                + "9BF4C93A130583ADBDF2B953AD5A8A58F518B012"
-                + "&key=test_key1&user=android-device-test", 640, 360);
+                + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26"
+                + ".49582D382B4A9AFAA163DED38D2AE531D85603C0"
+                + "&key=ik0&user=android-device-test", 640, 360);
     }
 
     // Streaming HLS video from YouTube
     public void testHLS() throws Exception {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
+
         // Play stream for 60 seconds
         playLiveVideoTest("http://www.youtube.com/api/manifest/hls_variant/id/"
                 + "0168724d02bd9945/itag/5/source/youtube/playlist_type/DVR/ip/"
@@ -165,6 +196,10 @@
                 stream_url = stream_url + "?" + CtsTestServer.NOLENGTH_POSTFIX;
             }
 
+            if (!MediaUtils.checkCodecsForPath(mContext, stream_url)) {
+                return; // skip
+            }
+
             mMediaPlayer.setDataSource(stream_url);
 
             mMediaPlayer.setDisplay(getActivity().getSurfaceHolder());
@@ -252,14 +287,23 @@
     }
 
     public void testPlayHlsStream() throws Throwable {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
         localHlsTest("hls.m3u8", false, false);
     }
 
     public void testPlayHlsStreamWithQueryString() throws Throwable {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
         localHlsTest("hls.m3u8", true, false);
     }
 
     public void testPlayHlsStreamWithRedirect() throws Throwable {
+        if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+            return; // skip
+        }
         localHlsTest("hls.m3u8", false, true);
     }
 
diff --git a/tests/tests/media/src/android/media/cts/VideoEncoderTest.java b/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
new file mode 100644
index 0000000..858e47c
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
@@ -0,0 +1,1606 @@
+/*
+ * Copyright 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import com.android.cts.media.R;
+
+import android.media.cts.CodecUtils;
+
+import android.cts.util.MediaUtils;
+import android.graphics.ImageFormat;
+import android.graphics.SurfaceTexture;
+import android.media.Image;
+import android.media.MediaCodec;
+import android.media.MediaCodec.BufferInfo;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecInfo.VideoCapabilities;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.net.Uri;
+import android.util.Log;
+import android.util.Pair;
+import android.util.Range;
+import android.util.Size;
+import android.view.Surface;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+
+public class VideoEncoderTest extends MediaPlayerTestBase {
+    private static final int MAX_SAMPLE_SIZE = 256 * 1024;
+    private static final String TAG = "VideoEncoderTest";
+    private static final long FRAME_TIMEOUT_MS = 1000;
+
+    private static final String SOURCE_URL =
+        "android.resource://com.android.cts.media/raw/video_480x360_mp4_h264_871kbps_30fps";
+
+    private final boolean DEBUG = false;
+
+    class VideoStorage {
+        private LinkedList<Pair<ByteBuffer, BufferInfo>> mStream;
+        private MediaFormat mFormat;
+        private int mInputBufferSize;
+
+        public VideoStorage() {
+            mStream = new LinkedList<Pair<ByteBuffer, BufferInfo>>();
+        }
+
+        public void setFormat(MediaFormat format) {
+            mFormat = format;
+        }
+
+        public void addBuffer(ByteBuffer buffer, BufferInfo info) {
+            ByteBuffer savedBuffer = ByteBuffer.allocate(info.size);
+            savedBuffer.put(buffer);
+            if (info.size > mInputBufferSize) {
+                mInputBufferSize = info.size;
+            }
+            BufferInfo savedInfo = new BufferInfo();
+            savedInfo.set(0, savedBuffer.position(), info.presentationTimeUs, info.flags);
+            mStream.addLast(Pair.create(savedBuffer, savedInfo));
+        }
+
+        private void play(MediaCodec decoder, Surface surface) {
+            decoder.reset();
+            final Object condition = new Object();
+            final Iterator<Pair<ByteBuffer, BufferInfo>> it = mStream.iterator();
+            decoder.setCallback(new MediaCodec.Callback() {
+                public void onOutputBufferAvailable(MediaCodec codec, int ix, BufferInfo info) {
+                    codec.releaseOutputBuffer(ix, info.size > 0);
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        synchronized (condition) {
+                            condition.notifyAll();
+                        }
+                    }
+                }
+                public void onInputBufferAvailable(MediaCodec codec, int ix) {
+                    if (it.hasNext()) {
+                        Pair<ByteBuffer, BufferInfo> el = it.next();
+                        el.first.clear();
+                        try {
+                            codec.getInputBuffer(ix).put(el.first);
+                        } catch (java.nio.BufferOverflowException e) {
+                            Log.e(TAG, "cannot fit " + el.first.limit()
+                                    + "-byte encoded buffer into "
+                                    + codec.getInputBuffer(ix).remaining()
+                                    + "-byte input buffer of " + codec.getName()
+                                    + " configured for " + codec.getInputFormat());
+                            throw e;
+                        }
+                        BufferInfo info = el.second;
+                        codec.queueInputBuffer(
+                                ix, 0, info.size, info.presentationTimeUs, info.flags);
+                    }
+                }
+                public void onError(MediaCodec codec, MediaCodec.CodecException e) {
+                    Log.i(TAG, "got codec exception", e);
+                    fail("received codec error during decode" + e);
+                }
+                public void onOutputFormatChanged(MediaCodec codec, MediaFormat format) {
+                    Log.i(TAG, "got output format " + format);
+                }
+            });
+            mFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, mInputBufferSize);
+            decoder.configure(mFormat, surface, null /* crypto */, 0 /* flags */);
+            decoder.start();
+            synchronized (condition) {
+                try {
+                    condition.wait();
+                } catch (InterruptedException e) {
+                    fail("playback interrupted");
+                }
+            }
+            decoder.stop();
+        }
+
+        public void playAll(Surface surface) {
+            if (mFormat == null) {
+                Log.i(TAG, "no stream to play");
+                return;
+            }
+            String mime = mFormat.getString(MediaFormat.KEY_MIME);
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            for (MediaCodecInfo info : mcl.getCodecInfos()) {
+                if (info.isEncoder()) {
+                    continue;
+                }
+                MediaCodec codec = null;
+                try {
+                    CodecCapabilities caps = info.getCapabilitiesForType(mime);
+                    if (!caps.isFormatSupported(mFormat)) {
+                        continue;
+                    }
+                    codec = MediaCodec.createByCodecName(info.getName());
+                } catch (IllegalArgumentException | IOException e) {
+                    continue;
+                }
+                play(codec, surface);
+                codec.release();
+            }
+        }
+    }
+
+    abstract class VideoProcessorBase extends MediaCodec.Callback {
+        private static final String TAG = "VideoProcessorBase";
+
+        private MediaExtractor mExtractor;
+        private ByteBuffer mBuffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
+        private int mTrackIndex = -1;
+        private boolean mSignaledDecoderEOS;
+
+        protected boolean mCompleted;
+        protected final Object mCondition = new Object();
+
+        protected MediaFormat mDecFormat;
+        protected MediaCodec mDecoder, mEncoder;
+
+        private VideoStorage mEncodedStream;
+        protected int mFrameRate = 0;
+        protected int mBitRate = 0;
+
+        protected void open(String path) throws IOException {
+            mExtractor = new MediaExtractor();
+            if (path.startsWith("android.resource://")) {
+                mExtractor.setDataSource(mContext, Uri.parse(path), null);
+            } else {
+                mExtractor.setDataSource(path);
+            }
+
+            for (int i = 0; i < mExtractor.getTrackCount(); i++) {
+                MediaFormat fmt = mExtractor.getTrackFormat(i);
+                String mime = fmt.getString(MediaFormat.KEY_MIME).toLowerCase();
+                if (mime.startsWith("video/")) {
+                    mTrackIndex = i;
+                    mDecFormat = fmt;
+                    mExtractor.selectTrack(i);
+                    break;
+                }
+            }
+            mEncodedStream = new VideoStorage();
+            assertTrue("file " + path + " has no video", mTrackIndex >= 0);
+        }
+
+        // returns true if encoder supports the size
+        protected boolean initCodecsAndConfigureEncoder(
+                String videoEncName, String outMime, int width, int height, int colorFormat)
+                        throws IOException {
+            mDecFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+
+            MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            String videoDecName = mcl.findDecoderForFormat(mDecFormat);
+            Log.i(TAG, "decoder for " + mDecFormat + " is " + videoDecName);
+            mDecoder = MediaCodec.createByCodecName(videoDecName);
+            mEncoder = MediaCodec.createByCodecName(videoEncName);
+
+            mDecoder.setCallback(this);
+            mEncoder.setCallback(this);
+
+            VideoCapabilities encCaps =
+                mEncoder.getCodecInfo().getCapabilitiesForType(outMime).getVideoCapabilities();
+            if (!encCaps.isSizeSupported(width, height)) {
+                Log.i(TAG, videoEncName + " does not support size: " + width + "x" + height);
+                return false;
+            }
+
+            MediaFormat outFmt = MediaFormat.createVideoFormat(outMime, width, height);
+
+            {
+                int maxWidth = encCaps.getSupportedWidths().getUpper();
+                int maxHeight = encCaps.getSupportedHeightsFor(maxWidth).getUpper();
+                int frameRate = mFrameRate;
+                if (frameRate <= 0) {
+                    int maxRate =
+                        encCaps.getSupportedFrameRatesFor(maxWidth, maxHeight)
+                        .getUpper().intValue();
+                    frameRate = Math.min(30, maxRate);
+                }
+                outFmt.setInteger(MediaFormat.KEY_FRAME_RATE, frameRate);
+
+                int bitRate = mBitRate;
+                if (bitRate <= 0) {
+                    bitRate = encCaps.getBitrateRange().clamp(
+                        (int)(encCaps.getBitrateRange().getUpper() /
+                                Math.sqrt((double)maxWidth * maxHeight / width / height)));
+                }
+                outFmt.setInteger(MediaFormat.KEY_BIT_RATE, bitRate);
+
+                Log.d(TAG, "frame rate = " + frameRate + ", bit rate = " + bitRate);
+            }
+            outFmt.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1);
+            outFmt.setInteger(MediaFormat.KEY_COLOR_FORMAT, colorFormat);
+            mEncoder.configure(outFmt, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            Log.i(TAG, "encoder input format " + mEncoder.getInputFormat() + " from " + outFmt);
+            return true;
+        }
+
+        protected void close() {
+            if (mDecoder != null) {
+                mDecoder.release();
+                mDecoder = null;
+            }
+            if (mEncoder != null) {
+                mEncoder.release();
+                mEncoder = null;
+            }
+            if (mExtractor != null) {
+                mExtractor.release();
+                mExtractor = null;
+            }
+        }
+
+        // returns true if filled buffer
+        protected boolean fillDecoderInputBuffer(int ix) {
+            if (DEBUG) Log.v(TAG, "decoder received input #" + ix);
+            while (!mSignaledDecoderEOS) {
+                int track = mExtractor.getSampleTrackIndex();
+                if (track >= 0 && track != mTrackIndex) {
+                    mExtractor.advance();
+                    continue;
+                }
+                int size = mExtractor.readSampleData(mBuffer, 0);
+                if (size < 0) {
+                    // queue decoder input EOS
+                    if (DEBUG) Log.v(TAG, "queuing decoder EOS");
+                    mDecoder.queueInputBuffer(
+                            ix, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                    mSignaledDecoderEOS = true;
+                } else {
+                    mBuffer.limit(size);
+                    mBuffer.position(0);
+                    BufferInfo info = new BufferInfo();
+                    info.set(
+                            0, mBuffer.limit(), mExtractor.getSampleTime(),
+                            mExtractor.getSampleFlags());
+                    mDecoder.getInputBuffer(ix).put(mBuffer);
+                    if (DEBUG) Log.v(TAG, "queing input #" + ix + " for decoder with timestamp "
+                            + info.presentationTimeUs);
+                    mDecoder.queueInputBuffer(
+                            ix, 0, mBuffer.limit(), info.presentationTimeUs, 0);
+                }
+                mExtractor.advance();
+                return true;
+            }
+            return false;
+        }
+
+        protected void emptyEncoderOutputBuffer(int ix, BufferInfo info) {
+            if (DEBUG) Log.v(TAG, "encoder received output #" + ix
+                     + " (sz=" + info.size + ", f=" + info.flags
+                     + ", ts=" + info.presentationTimeUs + ")");
+            mEncodedStream.addBuffer(mEncoder.getOutputBuffer(ix), info);
+            if (!mCompleted) {
+                mEncoder.releaseOutputBuffer(ix, false);
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    Log.d(TAG, "encoder received output EOS");
+                    synchronized(mCondition) {
+                        mCompleted = true;
+                        mCondition.notifyAll(); // condition is always satisfied
+                    }
+                }
+            }
+        }
+
+        protected void saveEncoderFormat(MediaFormat format) {
+            mEncodedStream.setFormat(format);
+        }
+
+        public void playBack(Surface surface) {
+            mEncodedStream.playAll(surface);
+        }
+
+        public void setFrameAndBitRates(int frameRate, int bitRate) {
+            mFrameRate = frameRate;
+            mBitRate = bitRate;
+        }
+
+        public abstract boolean processLoop(
+                String path, String outMime, String videoEncName,
+                int width, int height, boolean optional);
+    };
+
+    class VideoProcessor extends VideoProcessorBase {
+        private static final String TAG = "VideoProcessor";
+        private boolean mWorkInProgress;
+        private boolean mGotDecoderEOS;
+        private boolean mSignaledEncoderEOS;
+
+        private LinkedList<Pair<Integer, BufferInfo>> mBuffersToRender =
+            new LinkedList<Pair<Integer, BufferInfo>>();
+        private LinkedList<Integer> mEncInputBuffers = new LinkedList<Integer>();
+
+        private int mEncInputBufferSize = -1;
+
+        @Override
+        public boolean processLoop(
+                 String path, String outMime, String videoEncName,
+                 int width, int height, boolean optional) {
+            boolean skipped = true;
+            try {
+                open(path);
+                if (!initCodecsAndConfigureEncoder(
+                        videoEncName, outMime, width, height,
+                        CodecCapabilities.COLOR_FormatYUV420Flexible)) {
+                    assertTrue("could not configure encoder for supported size", optional);
+                    return !skipped;
+                }
+                skipped = false;
+
+                mDecoder.configure(mDecFormat, null /* surface */, null /* crypto */, 0);
+
+                mDecoder.start();
+                mEncoder.start();
+
+                // main loop - process GL ops as only main thread has GL context
+                while (!mCompleted) {
+                    Pair<Integer, BufferInfo> decBuffer = null;
+                    int encBuffer = -1;
+                    synchronized (mCondition) {
+                        try {
+                            // wait for an encoder input buffer and a decoder output buffer
+                            // Use a timeout to avoid stalling the test if it doesn't arrive.
+                            if (!haveBuffers() && !mCompleted) {
+                                mCondition.wait(FRAME_TIMEOUT_MS);
+                            }
+                        } catch (InterruptedException ie) {
+                            fail("wait interrupted");  // shouldn't happen
+                        }
+                        if (mCompleted) {
+                            break;
+                        }
+                        if (!haveBuffers()) {
+                            fail("timed out after " + mBuffersToRender.size()
+                                    + " decoder output and " + mEncInputBuffers.size()
+                                    + " encoder input buffers");
+                        }
+
+                        if (DEBUG) Log.v(TAG, "got image");
+                        decBuffer = mBuffersToRender.removeFirst();
+                        encBuffer = mEncInputBuffers.removeFirst();
+                        if (isEOSOnlyBuffer(decBuffer)) {
+                            queueEncoderEOS(decBuffer, encBuffer);
+                            continue;
+                        }
+                        mWorkInProgress = true;
+                    }
+
+                    if (mWorkInProgress) {
+                        renderDecodedBuffer(decBuffer, encBuffer);
+                        synchronized(mCondition) {
+                            mWorkInProgress = false;
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+                fail("received exception " + e);
+            } finally {
+                close();
+            }
+            return !skipped;
+        }
+
+        @Override
+        public void onInputBufferAvailable(MediaCodec mediaCodec, int ix) {
+            if (mediaCodec == mDecoder) {
+                // fill input buffer from extractor
+                fillDecoderInputBuffer(ix);
+            } else if (mediaCodec == mEncoder) {
+                synchronized(mCondition) {
+                    mEncInputBuffers.addLast(ix);
+                    tryToPropagateEOS();
+                    if (haveBuffers()) {
+                        mCondition.notifyAll();
+                    }
+                }
+            } else {
+                fail("received input buffer on " + mediaCodec.getName());
+            }
+        }
+
+        @Override
+        public void onOutputBufferAvailable(
+                MediaCodec mediaCodec, int ix, BufferInfo info) {
+            if (mediaCodec == mDecoder) {
+                if (DEBUG) Log.v(TAG, "decoder received output #" + ix
+                         + " (sz=" + info.size + ", f=" + info.flags
+                         + ", ts=" + info.presentationTimeUs + ")");
+                // render output buffer from decoder
+                if (!mGotDecoderEOS) {
+                    boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+                    // can release empty buffers now
+                    if (info.size == 0) {
+                        mDecoder.releaseOutputBuffer(ix, false /* render */);
+                        ix = -1; // dummy index used by render to not render
+                    }
+                    synchronized(mCondition) {
+                        if (ix < 0 && eos && mBuffersToRender.size() > 0) {
+                            // move lone EOS flag to last buffer to be rendered
+                            mBuffersToRender.peekLast().second.flags |=
+                                MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                        } else if (ix >= 0 || eos) {
+                            mBuffersToRender.addLast(Pair.create(ix, info));
+                        }
+                        if (eos) {
+                            tryToPropagateEOS();
+                            mGotDecoderEOS = true;
+                        }
+                        if (haveBuffers()) {
+                            mCondition.notifyAll();
+                        }
+                    }
+                }
+            } else if (mediaCodec == mEncoder) {
+                emptyEncoderOutputBuffer(ix, info);
+            } else {
+                fail("received output buffer on " + mediaCodec.getName());
+            }
+        }
+
+        private void renderDecodedBuffer(Pair<Integer, BufferInfo> decBuffer, int encBuffer) {
+            // process heavyweight actions under instance lock
+            Image encImage = mEncoder.getInputImage(encBuffer);
+            Image decImage = mDecoder.getOutputImage(decBuffer.first);
+            assertNotNull("could not get encoder image for " + mEncoder.getInputFormat(), encImage);
+            assertNotNull("could not get decoder image for " + mDecoder.getInputFormat(), decImage);
+            assertEquals("incorrect decoder format",decImage.getFormat(), ImageFormat.YUV_420_888);
+            assertEquals("incorrect encoder format", encImage.getFormat(), ImageFormat.YUV_420_888);
+
+            CodecUtils.copyFlexYUVImage(encImage, decImage);
+
+            // TRICKY: need this for queueBuffer
+            if (mEncInputBufferSize < 0) {
+                mEncInputBufferSize = mEncoder.getInputBuffer(encBuffer).capacity();
+            }
+            Log.d(TAG, "queuing output #" + encBuffer + " for encoder (sz="
+                    + mEncInputBufferSize + ", f=" + decBuffer.second.flags
+                    + ", ts=" + decBuffer.second.presentationTimeUs + ")");
+            mEncoder.queueInputBuffer(
+                    encBuffer, 0, mEncInputBufferSize, decBuffer.second.presentationTimeUs,
+                    decBuffer.second.flags);
+            if ((decBuffer.second.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                mSignaledEncoderEOS = true;
+            }
+            mDecoder.releaseOutputBuffer(decBuffer.first, false /* render */);
+        }
+
+        @Override
+        public void onError(MediaCodec mediaCodec, MediaCodec.CodecException e) {
+            fail("received error on " + mediaCodec.getName() + ": " + e);
+        }
+
+        @Override
+        public void onOutputFormatChanged(MediaCodec mediaCodec, MediaFormat mediaFormat) {
+            Log.i(TAG, mediaCodec.getName() + " got new output format " + mediaFormat);
+            if (mediaCodec == mEncoder) {
+                saveEncoderFormat(mediaFormat);
+            }
+        }
+
+        // next methods are synchronized on mCondition
+        private boolean haveBuffers() {
+            return mEncInputBuffers.size() > 0 && mBuffersToRender.size() > 0
+                    && !mSignaledEncoderEOS;
+        }
+
+        private boolean isEOSOnlyBuffer(Pair<Integer, BufferInfo> decBuffer) {
+            return decBuffer.first < 0 || decBuffer.second.size == 0;
+        }
+
+        protected void tryToPropagateEOS() {
+            if (!mWorkInProgress && haveBuffers() && isEOSOnlyBuffer(mBuffersToRender.getFirst())) {
+                Pair<Integer, BufferInfo> decBuffer = mBuffersToRender.removeFirst();
+                int encBuffer = mEncInputBuffers.removeFirst();
+                queueEncoderEOS(decBuffer, encBuffer);
+            }
+        }
+
+        void queueEncoderEOS(Pair<Integer, BufferInfo> decBuffer, int encBuffer) {
+            Log.d(TAG, "signaling encoder EOS");
+            mEncoder.queueInputBuffer(encBuffer, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+            mSignaledEncoderEOS = true;
+            if (decBuffer.first >= 0) {
+                mDecoder.releaseOutputBuffer(decBuffer.first, false /* render */);
+            }
+        }
+    }
+
+
+    class SurfaceVideoProcessor extends VideoProcessorBase
+            implements SurfaceTexture.OnFrameAvailableListener {
+        private static final String TAG = "SurfaceVideoProcessor";
+        private boolean mFrameAvailable;
+        private boolean mEncoderIsActive;
+        private boolean mGotDecoderEOS;
+        private boolean mSignaledEncoderEOS;
+
+        private InputSurface mEncSurface;
+        private OutputSurface mDecSurface;
+        private BufferInfo mInfoOnSurface;
+
+        private LinkedList<Pair<Integer, BufferInfo>> mBuffersToRender =
+            new LinkedList<Pair<Integer, BufferInfo>>();
+
+        @Override
+        public boolean processLoop(
+                String path, String outMime, String videoEncName,
+                int width, int height, boolean optional) {
+            boolean skipped = true;
+            try {
+                open(path);
+                if (!initCodecsAndConfigureEncoder(
+                        videoEncName, outMime, width, height,
+                        CodecCapabilities.COLOR_FormatSurface)) {
+                    assertTrue("could not configure encoder for supported size", optional);
+                    return !skipped;
+                }
+                skipped = false;
+
+                mEncSurface = new InputSurface(mEncoder.createInputSurface());
+                mEncSurface.makeCurrent();
+
+                mDecSurface = new OutputSurface(this);
+                //mDecSurface.changeFragmentShader(FRAGMENT_SHADER);
+                mDecoder.configure(mDecFormat, mDecSurface.getSurface(), null /* crypto */, 0);
+
+                mDecoder.start();
+                mEncoder.start();
+
+                // main loop - process GL ops as only main thread has GL context
+                while (!mCompleted) {
+                    BufferInfo info = null;
+                    synchronized (mCondition) {
+                        try {
+                            // wait for mFrameAvailable, which is set by onFrameAvailable().
+                            // Use a timeout to avoid stalling the test if it doesn't arrive.
+                            if (!mFrameAvailable && !mCompleted && !mEncoderIsActive) {
+                                mCondition.wait(FRAME_TIMEOUT_MS);
+                            }
+                        } catch (InterruptedException ie) {
+                            fail("wait interrupted");  // shouldn't happen
+                        }
+                        if (mCompleted) {
+                            break;
+                        }
+                        if (mEncoderIsActive) {
+                            mEncoderIsActive = false;
+                            if (DEBUG) Log.d(TAG, "encoder is still active, continue");
+                            continue;
+                        }
+                        assertTrue("still waiting for image", mFrameAvailable);
+                        if (DEBUG) Log.v(TAG, "got image");
+                        info = mInfoOnSurface;
+                    }
+                    if (info == null) {
+                        continue;
+                    }
+                    if (info.size > 0) {
+                        mDecSurface.latchImage();
+                        if (DEBUG) Log.v(TAG, "latched image");
+                        mFrameAvailable = false;
+
+                        mDecSurface.drawImage();
+                        Log.d(TAG, "encoding frame at " + info.presentationTimeUs * 1000);
+
+                        mEncSurface.setPresentationTime(info.presentationTimeUs * 1000);
+                        mEncSurface.swapBuffers();
+                    }
+                    if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                        mSignaledEncoderEOS = true;
+                        Log.d(TAG, "signaling encoder EOS");
+                        mEncoder.signalEndOfInputStream();
+                    }
+
+                    synchronized (mCondition) {
+                        mInfoOnSurface = null;
+                        if (mBuffersToRender.size() > 0 && mInfoOnSurface == null) {
+                            if (DEBUG) Log.v(TAG, "handling postponed frame");
+                            Pair<Integer, BufferInfo> nextBuffer = mBuffersToRender.removeFirst();
+                            renderDecodedBuffer(nextBuffer.first, nextBuffer.second);
+                        }
+                    }
+                }
+            } catch (IOException e) {
+                e.printStackTrace();
+                fail("received exception " + e);
+            } finally {
+                close();
+                if (mEncSurface != null) {
+                    mEncSurface.release();
+                    mEncSurface = null;
+                }
+                if (mDecSurface != null) {
+                    mDecSurface.release();
+                    mDecSurface = null;
+                }
+            }
+            return !skipped;
+        }
+
+        @Override
+        public void onFrameAvailable(SurfaceTexture st) {
+            if (DEBUG) Log.v(TAG, "new frame available");
+            synchronized (mCondition) {
+                assertFalse("mFrameAvailable already set, frame could be dropped", mFrameAvailable);
+                mFrameAvailable = true;
+                mCondition.notifyAll();
+            }
+        }
+
+        @Override
+        public void onInputBufferAvailable(MediaCodec mediaCodec, int ix) {
+            if (mediaCodec == mDecoder) {
+                // fill input buffer from extractor
+                fillDecoderInputBuffer(ix);
+            } else {
+                fail("received input buffer on " + mediaCodec.getName());
+            }
+        }
+
+        @Override
+        public void onOutputBufferAvailable(
+                MediaCodec mediaCodec, int ix, BufferInfo info) {
+            if (mediaCodec == mDecoder) {
+                if (DEBUG) Log.v(TAG, "decoder received output #" + ix
+                         + " (sz=" + info.size + ", f=" + info.flags
+                         + ", ts=" + info.presentationTimeUs + ")");
+                // render output buffer from decoder
+                if (!mGotDecoderEOS) {
+                    boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+                    if (eos) {
+                        mGotDecoderEOS = true;
+                    }
+                    // can release empty buffers now
+                    if (info.size == 0) {
+                        mDecoder.releaseOutputBuffer(ix, false /* render */);
+                        ix = -1; // dummy index used by render to not render
+                    }
+                    if (eos || info.size > 0) {
+                        synchronized(mCondition) {
+                            if (mInfoOnSurface != null || mBuffersToRender.size() > 0) {
+                                if (DEBUG) Log.v(TAG, "postponing render, surface busy");
+                                mBuffersToRender.addLast(Pair.create(ix, info));
+                            } else {
+                                renderDecodedBuffer(ix, info);
+                            }
+                        }
+                    }
+                }
+            } else if (mediaCodec == mEncoder) {
+                emptyEncoderOutputBuffer(ix, info);
+                synchronized(mCondition) {
+                    if (!mCompleted) {
+                        mEncoderIsActive = true;
+                        mCondition.notifyAll();
+                    }
+                }
+            } else {
+                fail("received output buffer on " + mediaCodec.getName());
+            }
+        }
+
+        private void renderDecodedBuffer(int ix, BufferInfo info) {
+            boolean eos = (info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0;
+            mInfoOnSurface = info;
+            if (info.size > 0) {
+                Log.d(TAG, "rendering frame #" + ix + " at " + info.presentationTimeUs * 1000
+                        + (eos ? " with EOS" : ""));
+                mDecoder.releaseOutputBuffer(ix, info.presentationTimeUs * 1000);
+            }
+
+            if (eos && info.size == 0) {
+                if (DEBUG) Log.v(TAG, "decoder output EOS available");
+                mFrameAvailable = true;
+                mCondition.notifyAll();
+            }
+        }
+
+        @Override
+        public void onError(MediaCodec mediaCodec, MediaCodec.CodecException e) {
+            fail("received error on " + mediaCodec.getName() + ": " + e);
+        }
+
+        @Override
+        public void onOutputFormatChanged(MediaCodec mediaCodec, MediaFormat mediaFormat) {
+            Log.i(TAG, mediaCodec.getName() + " got new output format " + mediaFormat);
+            if (mediaCodec == mEncoder) {
+                saveEncoderFormat(mediaFormat);
+            }
+        }
+    }
+
+    class Encoder {
+        final private String mName;
+        final private String mMime;
+        final private VideoCapabilities mCaps;
+
+        final private Map<Size, Set<Size>> mMinMax;     // extreme sizes
+        final private Map<Size, Set<Size>> mNearMinMax; // sizes near extreme
+        final private Set<Size> mArbitraryW;            // arbitrary widths in the middle
+        final private Set<Size> mArbitraryH;            // arbitrary heights in the middle
+        final private Set<Size> mSizes;                 // all non-specifically tested sizes
+
+        final private int xAlign;
+        final private int yAlign;
+
+        Encoder(String name, String mime, CodecCapabilities caps) {
+            mName = name;
+            mMime = mime;
+            mCaps = caps.getVideoCapabilities();
+
+            /* calculate min/max sizes */
+            mMinMax = new HashMap<Size, Set<Size>>();
+            mNearMinMax = new HashMap<Size, Set<Size>>();
+            mArbitraryW = new HashSet<Size>();
+            mArbitraryH = new HashSet<Size>();
+            mSizes = new HashSet<Size>();
+
+            xAlign = mCaps.getWidthAlignment();
+            yAlign = mCaps.getHeightAlignment();
+
+            initializeSizes();
+        }
+
+        private void initializeSizes() {
+            for (int x = 0; x < 2; ++x) {
+                for (int y = 0; y < 2; ++y) {
+                    addExtremeSizesFor(x, y);
+                }
+            }
+
+            // initialize arbitrary sizes
+            for (int i = 1; i <= 7; ++i) {
+                int j = ((7 * i) % 11) + 1;
+                int width, height;
+                try {
+                    width = alignedPointInRange(i * 0.125, xAlign, mCaps.getSupportedWidths());
+                    height = alignedPointInRange(
+                            j * 0.077, yAlign, mCaps.getSupportedHeightsFor(width));
+                    mArbitraryW.add(new Size(width, height));
+                } catch (IllegalArgumentException e) {
+                }
+
+                try {
+                    height = alignedPointInRange(i * 0.125, yAlign, mCaps.getSupportedHeights());
+                    width = alignedPointInRange(j * 0.077, xAlign, mCaps.getSupportedWidthsFor(height));
+                    mArbitraryH.add(new Size(width, height));
+                } catch (IllegalArgumentException e) {
+                }
+            }
+            mArbitraryW.removeAll(mArbitraryH);
+            mArbitraryW.removeAll(mSizes);
+            mSizes.addAll(mArbitraryW);
+            mArbitraryH.removeAll(mSizes);
+            mSizes.addAll(mArbitraryH);
+            if (DEBUG) Log.i(TAG, "arbitrary=" + mArbitraryW + "/" + mArbitraryH);
+        }
+
+        private void addExtremeSizesFor(int x, int y) {
+            Set<Size> minMax = new HashSet<Size>();
+            Set<Size> nearMinMax = new HashSet<Size>();
+
+            for (int dx = 0; dx <= xAlign; dx += xAlign) {
+                for (int dy = 0; dy <= yAlign; dy += yAlign) {
+                    Set<Size> bucket = (dx + dy == 0) ? minMax : nearMinMax;
+                    try {
+                        int width = getExtreme(mCaps.getSupportedWidths(), x, dx);
+                        int height = getExtreme(mCaps.getSupportedHeightsFor(width), y, dy);
+                        bucket.add(new Size(width, height));
+
+                        // try max max with more reasonable ratio if too skewed
+                        if (x + y == 2 && width >= 4 * height) {
+                            Size wideScreen = getLargestSizeForRatio(16, 9);
+                            width = getExtreme(
+                                    mCaps.getSupportedWidths()
+                                            .intersect(0, wideScreen.getWidth()), x, dx);
+                            height = getExtreme(mCaps.getSupportedHeightsFor(width), y, 0);
+                            bucket.add(new Size(width, height));
+                        }
+                    } catch (IllegalArgumentException e) {
+                    }
+
+                    try {
+                        int height = getExtreme(mCaps.getSupportedHeights(), y, dy);
+                        int width = getExtreme(mCaps.getSupportedWidthsFor(height), x, dx);
+                        bucket.add(new Size(width, height));
+
+                        // try max max with more reasonable ratio if too skewed
+                        if (x + y == 2 && height >= 4 * width) {
+                            Size wideScreen = getLargestSizeForRatio(9, 16);
+                            height = getExtreme(
+                                    mCaps.getSupportedHeights()
+                                            .intersect(0, wideScreen.getHeight()), y, dy);
+                            width = getExtreme(mCaps.getSupportedWidthsFor(height), x, dx);
+                            bucket.add(new Size(width, height));
+                        }
+                    } catch (IllegalArgumentException e) {
+                    }
+                }
+            }
+
+            // keep unique sizes
+            minMax.removeAll(mSizes);
+            mSizes.addAll(minMax);
+            nearMinMax.removeAll(mSizes);
+            mSizes.addAll(nearMinMax);
+
+            mMinMax.put(new Size(x, y), minMax);
+            mNearMinMax.put(new Size(x, y), nearMinMax);
+            if (DEBUG) Log.i(TAG, x + "x" + y + ": minMax=" + mMinMax + ", near=" + mNearMinMax);
+        }
+
+        private int alignInRange(double value, int align, Range<Integer> range) {
+            return range.clamp(align * (int)Math.round(value / align));
+        }
+
+        /* point should be between 0. and 1. */
+        private int alignedPointInRange(double point, int align, Range<Integer> range) {
+            return alignInRange(
+                    range.getLower() + point * (range.getUpper() - range.getLower()), align, range);
+        }
+
+        private int getExtreme(Range<Integer> range, int i, int delta) {
+            int dim = i == 1 ? range.getUpper() - delta : range.getLower() + delta;
+            if (delta == 0
+                    || (dim > range.getLower() && dim < range.getUpper())) {
+                return dim;
+            }
+            throw new IllegalArgumentException();
+        }
+
+        private Size getLargestSizeForRatio(int x, int y) {
+            Range<Integer> widthRange = mCaps.getSupportedWidths();
+            Range<Integer> heightRange = mCaps.getSupportedHeightsFor(widthRange.getUpper());
+            final int xAlign = mCaps.getWidthAlignment();
+            final int yAlign = mCaps.getHeightAlignment();
+
+            // scale by alignment
+            int width = alignInRange(
+                    Math.sqrt(widthRange.getUpper() * heightRange.getUpper() * (double)x / y),
+                    xAlign, widthRange);
+            int height = alignInRange(
+                    width * (double)y / x, yAlign, mCaps.getSupportedHeightsFor(width));
+            return new Size(width, height);
+        }
+
+
+        public boolean testExtreme(int x, int y, boolean flexYUV, boolean near) {
+            boolean skipped = true;
+            for (Size s : (near ? mNearMinMax : mMinMax).get(new Size(x, y))) {
+                if (test(s.getWidth(), s.getHeight(), false /* optional */, flexYUV)) {
+                    skipped = false;
+                }
+            }
+            return !skipped;
+        }
+
+        public boolean testArbitrary(boolean flexYUV, boolean widths) {
+            boolean skipped = true;
+            for (Size s : (widths ? mArbitraryW : mArbitraryH)) {
+                if (test(s.getWidth(), s.getHeight(), false /* optional */, flexYUV)) {
+                    skipped = false;
+                }
+            }
+            return !skipped;
+        }
+
+        public boolean testSpecific(int width, int height, boolean flexYUV) {
+            // already tested by one of the min/max tests
+            if (mSizes.contains(new Size(width, height))) {
+                return false;
+            }
+            return test(width, height, true /* optional */, flexYUV);
+        }
+
+        public boolean testDetailed(
+                int width, int height, int frameRate, int bitRate, boolean flexYUV) {
+            return test(width, height, frameRate, bitRate, true /* optional */, flexYUV);
+        }
+
+        public boolean testSupport(int width, int height, int frameRate, int bitRate) {
+            return mCaps.areSizeAndRateSupported(width, height, frameRate) &&
+                    mCaps.getBitrateRange().contains(bitRate);
+        }
+
+        private boolean test(int width, int height, boolean optional, boolean flexYUV) {
+            return test(width, height, 0 /* frameRate */, 0 /* bitRate */, optional, flexYUV);
+        }
+
+        private boolean test(int width, int height, int frameRate, int bitRate,
+                boolean optional, boolean flexYUV) {
+            Log.i(TAG, "testing " + mMime + " on " + mName + " for " + width + "x" + height
+                    + (flexYUV ? " flexYUV" : " surface"));
+
+            VideoProcessorBase processor =
+                flexYUV ? new VideoProcessor() : new SurfaceVideoProcessor();
+
+            processor.setFrameAndBitRates(frameRate, bitRate);
+
+            // We are using a resource URL as an example
+            boolean success = processor.processLoop(
+                    SOURCE_URL, mMime, mName, width, height, optional);
+            if (success) {
+                processor.playBack(getActivity().getSurfaceHolder().getSurface());
+            }
+            return success;
+        }
+    }
+
+    private Encoder[] googH265()  { return goog(MediaFormat.MIMETYPE_VIDEO_HEVC); }
+    private Encoder[] googH264()  { return goog(MediaFormat.MIMETYPE_VIDEO_AVC); }
+    private Encoder[] googH263()  { return goog(MediaFormat.MIMETYPE_VIDEO_H263); }
+    private Encoder[] googMpeg4() { return goog(MediaFormat.MIMETYPE_VIDEO_MPEG4); }
+    private Encoder[] googVP8()   { return goog(MediaFormat.MIMETYPE_VIDEO_VP8); }
+    private Encoder[] googVP9()   { return goog(MediaFormat.MIMETYPE_VIDEO_VP9); }
+
+    private Encoder[] otherH265()  { return other(MediaFormat.MIMETYPE_VIDEO_HEVC); }
+    private Encoder[] otherH264()  { return other(MediaFormat.MIMETYPE_VIDEO_AVC); }
+    private Encoder[] otherH263()  { return other(MediaFormat.MIMETYPE_VIDEO_H263); }
+    private Encoder[] otherMpeg4() { return other(MediaFormat.MIMETYPE_VIDEO_MPEG4); }
+    private Encoder[] otherVP8()   { return other(MediaFormat.MIMETYPE_VIDEO_VP8); }
+    private Encoder[] otherVP9()   { return other(MediaFormat.MIMETYPE_VIDEO_VP9); }
+
+    private Encoder[] goog(String mime) {
+        return encoders(mime, true /* goog */);
+    }
+
+    private Encoder[] other(String mime) {
+        return encoders(mime, false /* goog */);
+    }
+
+    private Encoder[] combineArray(Encoder[] a, Encoder[] b) {
+        Encoder[] all = new Encoder[a.length + b.length];
+        System.arraycopy(a, 0, all, 0, a.length);
+        System.arraycopy(b, 0, all, a.length, b.length);
+        return all;
+    }
+
+    private Encoder[] h264()  {
+        return combineArray(googH264(), otherH264());
+    }
+
+    private Encoder[] vp8()  {
+        return combineArray(googVP8(), otherVP8());
+    }
+
+    private Encoder[] encoders(String mime, boolean goog) {
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        ArrayList<Encoder> result = new ArrayList<Encoder>();
+
+        for (MediaCodecInfo info : mcl.getCodecInfos()) {
+            if (!info.isEncoder()
+                    || info.getName().toLowerCase().startsWith("omx.google.") != goog) {
+                continue;
+            }
+            CodecCapabilities caps = null;
+            try {
+                caps = info.getCapabilitiesForType(mime);
+            } catch (IllegalArgumentException e) { // mime is not supported
+                continue;
+            }
+            assertNotNull(info.getName() + " capabilties for " + mime + " returned null", caps);
+            result.add(new Encoder(info.getName(), mime, caps));
+        }
+        return result.toArray(new Encoder[result.size()]);
+    }
+
+    public void testGoogH265FlexMinMin()   { minmin(googH265(),   true /* flex */); }
+    public void testGoogH265SurfMinMin()   { minmin(googH265(),   false /* flex */); }
+    public void testGoogH264FlexMinMin()   { minmin(googH264(),   true /* flex */); }
+    public void testGoogH264SurfMinMin()   { minmin(googH264(),   false /* flex */); }
+    public void testGoogH263FlexMinMin()   { minmin(googH263(),   true /* flex */); }
+    public void testGoogH263SurfMinMin()   { minmin(googH263(),   false /* flex */); }
+    public void testGoogMpeg4FlexMinMin()  { minmin(googMpeg4(),  true /* flex */); }
+    public void testGoogMpeg4SurfMinMin()  { minmin(googMpeg4(),  false /* flex */); }
+    public void testGoogVP8FlexMinMin()    { minmin(googVP8(),    true /* flex */); }
+    public void testGoogVP8SurfMinMin()    { minmin(googVP8(),    false /* flex */); }
+    public void testGoogVP9FlexMinMin()    { minmin(googVP9(),    true /* flex */); }
+    public void testGoogVP9SurfMinMin()    { minmin(googVP9(),    false /* flex */); }
+
+    public void testOtherH265FlexMinMin()  { minmin(otherH265(),  true /* flex */); }
+    public void testOtherH265SurfMinMin()  { minmin(otherH265(),  false /* flex */); }
+    public void testOtherH264FlexMinMin()  { minmin(otherH264(),  true /* flex */); }
+    public void testOtherH264SurfMinMin()  { minmin(otherH264(),  false /* flex */); }
+    public void testOtherH263FlexMinMin()  { minmin(otherH263(),  true /* flex */); }
+    public void testOtherH263SurfMinMin()  { minmin(otherH263(),  false /* flex */); }
+    public void testOtherMpeg4FlexMinMin() { minmin(otherMpeg4(), true /* flex */); }
+    public void testOtherMpeg4SurfMinMin() { minmin(otherMpeg4(), false /* flex */); }
+    public void testOtherVP8FlexMinMin()   { minmin(otherVP8(),   true /* flex */); }
+    public void testOtherVP8SurfMinMin()   { minmin(otherVP8(),   false /* flex */); }
+    public void testOtherVP9FlexMinMin()   { minmin(otherVP9(),   true /* flex */); }
+    public void testOtherVP9SurfMinMin()   { minmin(otherVP9(),   false /* flex */); }
+
+    public void testGoogH265FlexMinMax()   { minmax(googH265(),   true /* flex */); }
+    public void testGoogH265SurfMinMax()   { minmax(googH265(),   false /* flex */); }
+    public void testGoogH264FlexMinMax()   { minmax(googH264(),   true /* flex */); }
+    public void testGoogH264SurfMinMax()   { minmax(googH264(),   false /* flex */); }
+    public void testGoogH263FlexMinMax()   { minmax(googH263(),   true /* flex */); }
+    public void testGoogH263SurfMinMax()   { minmax(googH263(),   false /* flex */); }
+    public void testGoogMpeg4FlexMinMax()  { minmax(googMpeg4(),  true /* flex */); }
+    public void testGoogMpeg4SurfMinMax()  { minmax(googMpeg4(),  false /* flex */); }
+    public void testGoogVP8FlexMinMax()    { minmax(googVP8(),    true /* flex */); }
+    public void testGoogVP8SurfMinMax()    { minmax(googVP8(),    false /* flex */); }
+    public void testGoogVP9FlexMinMax()    { minmax(googVP9(),    true /* flex */); }
+    public void testGoogVP9SurfMinMax()    { minmax(googVP9(),    false /* flex */); }
+
+    public void testOtherH265FlexMinMax()  { minmax(otherH265(),  true /* flex */); }
+    public void testOtherH265SurfMinMax()  { minmax(otherH265(),  false /* flex */); }
+    public void testOtherH264FlexMinMax()  { minmax(otherH264(),  true /* flex */); }
+    public void testOtherH264SurfMinMax()  { minmax(otherH264(),  false /* flex */); }
+    public void testOtherH263FlexMinMax()  { minmax(otherH263(),  true /* flex */); }
+    public void testOtherH263SurfMinMax()  { minmax(otherH263(),  false /* flex */); }
+    public void testOtherMpeg4FlexMinMax() { minmax(otherMpeg4(), true /* flex */); }
+    public void testOtherMpeg4SurfMinMax() { minmax(otherMpeg4(), false /* flex */); }
+    public void testOtherVP8FlexMinMax()   { minmax(otherVP8(),   true /* flex */); }
+    public void testOtherVP8SurfMinMax()   { minmax(otherVP8(),   false /* flex */); }
+    public void testOtherVP9FlexMinMax()   { minmax(otherVP9(),   true /* flex */); }
+    public void testOtherVP9SurfMinMax()   { minmax(otherVP9(),   false /* flex */); }
+
+    public void testGoogH265FlexMaxMin()   { maxmin(googH265(),   true /* flex */); }
+    public void testGoogH265SurfMaxMin()   { maxmin(googH265(),   false /* flex */); }
+    public void testGoogH264FlexMaxMin()   { maxmin(googH264(),   true /* flex */); }
+    public void testGoogH264SurfMaxMin()   { maxmin(googH264(),   false /* flex */); }
+    public void testGoogH263FlexMaxMin()   { maxmin(googH263(),   true /* flex */); }
+    public void testGoogH263SurfMaxMin()   { maxmin(googH263(),   false /* flex */); }
+    public void testGoogMpeg4FlexMaxMin()  { maxmin(googMpeg4(),  true /* flex */); }
+    public void testGoogMpeg4SurfMaxMin()  { maxmin(googMpeg4(),  false /* flex */); }
+    public void testGoogVP8FlexMaxMin()    { maxmin(googVP8(),    true /* flex */); }
+    public void testGoogVP8SurfMaxMin()    { maxmin(googVP8(),    false /* flex */); }
+    public void testGoogVP9FlexMaxMin()    { maxmin(googVP9(),    true /* flex */); }
+    public void testGoogVP9SurfMaxMin()    { maxmin(googVP9(),    false /* flex */); }
+
+    public void testOtherH265FlexMaxMin()  { maxmin(otherH265(),  true /* flex */); }
+    public void testOtherH265SurfMaxMin()  { maxmin(otherH265(),  false /* flex */); }
+    public void testOtherH264FlexMaxMin()  { maxmin(otherH264(),  true /* flex */); }
+    public void testOtherH264SurfMaxMin()  { maxmin(otherH264(),  false /* flex */); }
+    public void testOtherH263FlexMaxMin()  { maxmin(otherH263(),  true /* flex */); }
+    public void testOtherH263SurfMaxMin()  { maxmin(otherH263(),  false /* flex */); }
+    public void testOtherMpeg4FlexMaxMin() { maxmin(otherMpeg4(), true /* flex */); }
+    public void testOtherMpeg4SurfMaxMin() { maxmin(otherMpeg4(), false /* flex */); }
+    public void testOtherVP8FlexMaxMin()   { maxmin(otherVP8(),   true /* flex */); }
+    public void testOtherVP8SurfMaxMin()   { maxmin(otherVP8(),   false /* flex */); }
+    public void testOtherVP9FlexMaxMin()   { maxmin(otherVP9(),   true /* flex */); }
+    public void testOtherVP9SurfMaxMin()   { maxmin(otherVP9(),   false /* flex */); }
+
+    public void testGoogH265FlexMaxMax()   { maxmax(googH265(),   true /* flex */); }
+    public void testGoogH265SurfMaxMax()   { maxmax(googH265(),   false /* flex */); }
+    public void testGoogH264FlexMaxMax()   { maxmax(googH264(),   true /* flex */); }
+    public void testGoogH264SurfMaxMax()   { maxmax(googH264(),   false /* flex */); }
+    public void testGoogH263FlexMaxMax()   { maxmax(googH263(),   true /* flex */); }
+    public void testGoogH263SurfMaxMax()   { maxmax(googH263(),   false /* flex */); }
+    public void testGoogMpeg4FlexMaxMax()  { maxmax(googMpeg4(),  true /* flex */); }
+    public void testGoogMpeg4SurfMaxMax()  { maxmax(googMpeg4(),  false /* flex */); }
+    public void testGoogVP8FlexMaxMax()    { maxmax(googVP8(),    true /* flex */); }
+    public void testGoogVP8SurfMaxMax()    { maxmax(googVP8(),    false /* flex */); }
+    public void testGoogVP9FlexMaxMax()    { maxmax(googVP9(),    true /* flex */); }
+    public void testGoogVP9SurfMaxMax()    { maxmax(googVP9(),    false /* flex */); }
+
+    public void testOtherH265FlexMaxMax()  { maxmax(otherH265(),  true /* flex */); }
+    public void testOtherH265SurfMaxMax()  { maxmax(otherH265(),  false /* flex */); }
+    public void testOtherH264FlexMaxMax()  { maxmax(otherH264(),  true /* flex */); }
+    public void testOtherH264SurfMaxMax()  { maxmax(otherH264(),  false /* flex */); }
+    public void testOtherH263FlexMaxMax()  { maxmax(otherH263(),  true /* flex */); }
+    public void testOtherH263SurfMaxMax()  { maxmax(otherH263(),  false /* flex */); }
+    public void testOtherMpeg4FlexMaxMax() { maxmax(otherMpeg4(), true /* flex */); }
+    public void testOtherMpeg4SurfMaxMax() { maxmax(otherMpeg4(), false /* flex */); }
+    public void testOtherVP8FlexMaxMax()   { maxmax(otherVP8(),   true /* flex */); }
+    public void testOtherVP8SurfMaxMax()   { maxmax(otherVP8(),   false /* flex */); }
+    public void testOtherVP9FlexMaxMax()   { maxmax(otherVP9(),   true /* flex */); }
+    public void testOtherVP9SurfMaxMax()   { maxmax(otherVP9(),   false /* flex */); }
+
+    public void testGoogH265FlexNearMinMin()   { nearminmin(googH265(),   true /* flex */); }
+    public void testGoogH265SurfNearMinMin()   { nearminmin(googH265(),   false /* flex */); }
+    public void testGoogH264FlexNearMinMin()   { nearminmin(googH264(),   true /* flex */); }
+    public void testGoogH264SurfNearMinMin()   { nearminmin(googH264(),   false /* flex */); }
+    public void testGoogH263FlexNearMinMin()   { nearminmin(googH263(),   true /* flex */); }
+    public void testGoogH263SurfNearMinMin()   { nearminmin(googH263(),   false /* flex */); }
+    public void testGoogMpeg4FlexNearMinMin()  { nearminmin(googMpeg4(),  true /* flex */); }
+    public void testGoogMpeg4SurfNearMinMin()  { nearminmin(googMpeg4(),  false /* flex */); }
+    public void testGoogVP8FlexNearMinMin()    { nearminmin(googVP8(),    true /* flex */); }
+    public void testGoogVP8SurfNearMinMin()    { nearminmin(googVP8(),    false /* flex */); }
+    public void testGoogVP9FlexNearMinMin()    { nearminmin(googVP9(),    true /* flex */); }
+    public void testGoogVP9SurfNearMinMin()    { nearminmin(googVP9(),    false /* flex */); }
+
+    public void testOtherH265FlexNearMinMin()  { nearminmin(otherH265(),  true /* flex */); }
+    public void testOtherH265SurfNearMinMin()  { nearminmin(otherH265(),  false /* flex */); }
+    public void testOtherH264FlexNearMinMin()  { nearminmin(otherH264(),  true /* flex */); }
+    public void testOtherH264SurfNearMinMin()  { nearminmin(otherH264(),  false /* flex */); }
+    public void testOtherH263FlexNearMinMin()  { nearminmin(otherH263(),  true /* flex */); }
+    public void testOtherH263SurfNearMinMin()  { nearminmin(otherH263(),  false /* flex */); }
+    public void testOtherMpeg4FlexNearMinMin() { nearminmin(otherMpeg4(), true /* flex */); }
+    public void testOtherMpeg4SurfNearMinMin() { nearminmin(otherMpeg4(), false /* flex */); }
+    public void testOtherVP8FlexNearMinMin()   { nearminmin(otherVP8(),   true /* flex */); }
+    public void testOtherVP8SurfNearMinMin()   { nearminmin(otherVP8(),   false /* flex */); }
+    public void testOtherVP9FlexNearMinMin()   { nearminmin(otherVP9(),   true /* flex */); }
+    public void testOtherVP9SurfNearMinMin()   { nearminmin(otherVP9(),   false /* flex */); }
+
+    public void testGoogH265FlexNearMinMax()   { nearminmax(googH265(),   true /* flex */); }
+    public void testGoogH265SurfNearMinMax()   { nearminmax(googH265(),   false /* flex */); }
+    public void testGoogH264FlexNearMinMax()   { nearminmax(googH264(),   true /* flex */); }
+    public void testGoogH264SurfNearMinMax()   { nearminmax(googH264(),   false /* flex */); }
+    public void testGoogH263FlexNearMinMax()   { nearminmax(googH263(),   true /* flex */); }
+    public void testGoogH263SurfNearMinMax()   { nearminmax(googH263(),   false /* flex */); }
+    public void testGoogMpeg4FlexNearMinMax()  { nearminmax(googMpeg4(),  true /* flex */); }
+    public void testGoogMpeg4SurfNearMinMax()  { nearminmax(googMpeg4(),  false /* flex */); }
+    public void testGoogVP8FlexNearMinMax()    { nearminmax(googVP8(),    true /* flex */); }
+    public void testGoogVP8SurfNearMinMax()    { nearminmax(googVP8(),    false /* flex */); }
+    public void testGoogVP9FlexNearMinMax()    { nearminmax(googVP9(),    true /* flex */); }
+    public void testGoogVP9SurfNearMinMax()    { nearminmax(googVP9(),    false /* flex */); }
+
+    public void testOtherH265FlexNearMinMax()  { nearminmax(otherH265(),  true /* flex */); }
+    public void testOtherH265SurfNearMinMax()  { nearminmax(otherH265(),  false /* flex */); }
+    public void testOtherH264FlexNearMinMax()  { nearminmax(otherH264(),  true /* flex */); }
+    public void testOtherH264SurfNearMinMax()  { nearminmax(otherH264(),  false /* flex */); }
+    public void testOtherH263FlexNearMinMax()  { nearminmax(otherH263(),  true /* flex */); }
+    public void testOtherH263SurfNearMinMax()  { nearminmax(otherH263(),  false /* flex */); }
+    public void testOtherMpeg4FlexNearMinMax() { nearminmax(otherMpeg4(), true /* flex */); }
+    public void testOtherMpeg4SurfNearMinMax() { nearminmax(otherMpeg4(), false /* flex */); }
+    public void testOtherVP8FlexNearMinMax()   { nearminmax(otherVP8(),   true /* flex */); }
+    public void testOtherVP8SurfNearMinMax()   { nearminmax(otherVP8(),   false /* flex */); }
+    public void testOtherVP9FlexNearMinMax()   { nearminmax(otherVP9(),   true /* flex */); }
+    public void testOtherVP9SurfNearMinMax()   { nearminmax(otherVP9(),   false /* flex */); }
+
+    public void testGoogH265FlexNearMaxMin()   { nearmaxmin(googH265(),   true /* flex */); }
+    public void testGoogH265SurfNearMaxMin()   { nearmaxmin(googH265(),   false /* flex */); }
+    public void testGoogH264FlexNearMaxMin()   { nearmaxmin(googH264(),   true /* flex */); }
+    public void testGoogH264SurfNearMaxMin()   { nearmaxmin(googH264(),   false /* flex */); }
+    public void testGoogH263FlexNearMaxMin()   { nearmaxmin(googH263(),   true /* flex */); }
+    public void testGoogH263SurfNearMaxMin()   { nearmaxmin(googH263(),   false /* flex */); }
+    public void testGoogMpeg4FlexNearMaxMin()  { nearmaxmin(googMpeg4(),  true /* flex */); }
+    public void testGoogMpeg4SurfNearMaxMin()  { nearmaxmin(googMpeg4(),  false /* flex */); }
+    public void testGoogVP8FlexNearMaxMin()    { nearmaxmin(googVP8(),    true /* flex */); }
+    public void testGoogVP8SurfNearMaxMin()    { nearmaxmin(googVP8(),    false /* flex */); }
+    public void testGoogVP9FlexNearMaxMin()    { nearmaxmin(googVP9(),    true /* flex */); }
+    public void testGoogVP9SurfNearMaxMin()    { nearmaxmin(googVP9(),    false /* flex */); }
+
+    public void testOtherH265FlexNearMaxMin()  { nearmaxmin(otherH265(),  true /* flex */); }
+    public void testOtherH265SurfNearMaxMin()  { nearmaxmin(otherH265(),  false /* flex */); }
+    public void testOtherH264FlexNearMaxMin()  { nearmaxmin(otherH264(),  true /* flex */); }
+    public void testOtherH264SurfNearMaxMin()  { nearmaxmin(otherH264(),  false /* flex */); }
+    public void testOtherH263FlexNearMaxMin()  { nearmaxmin(otherH263(),  true /* flex */); }
+    public void testOtherH263SurfNearMaxMin()  { nearmaxmin(otherH263(),  false /* flex */); }
+    public void testOtherMpeg4FlexNearMaxMin() { nearmaxmin(otherMpeg4(), true /* flex */); }
+    public void testOtherMpeg4SurfNearMaxMin() { nearmaxmin(otherMpeg4(), false /* flex */); }
+    public void testOtherVP8FlexNearMaxMin()   { nearmaxmin(otherVP8(),   true /* flex */); }
+    public void testOtherVP8SurfNearMaxMin()   { nearmaxmin(otherVP8(),   false /* flex */); }
+    public void testOtherVP9FlexNearMaxMin()   { nearmaxmin(otherVP9(),   true /* flex */); }
+    public void testOtherVP9SurfNearMaxMin()   { nearmaxmin(otherVP9(),   false /* flex */); }
+
+    public void testGoogH265FlexNearMaxMax()   { nearmaxmax(googH265(),   true /* flex */); }
+    public void testGoogH265SurfNearMaxMax()   { nearmaxmax(googH265(),   false /* flex */); }
+    public void testGoogH264FlexNearMaxMax()   { nearmaxmax(googH264(),   true /* flex */); }
+    public void testGoogH264SurfNearMaxMax()   { nearmaxmax(googH264(),   false /* flex */); }
+    public void testGoogH263FlexNearMaxMax()   { nearmaxmax(googH263(),   true /* flex */); }
+    public void testGoogH263SurfNearMaxMax()   { nearmaxmax(googH263(),   false /* flex */); }
+    public void testGoogMpeg4FlexNearMaxMax()  { nearmaxmax(googMpeg4(),  true /* flex */); }
+    public void testGoogMpeg4SurfNearMaxMax()  { nearmaxmax(googMpeg4(),  false /* flex */); }
+    public void testGoogVP8FlexNearMaxMax()    { nearmaxmax(googVP8(),    true /* flex */); }
+    public void testGoogVP8SurfNearMaxMax()    { nearmaxmax(googVP8(),    false /* flex */); }
+    public void testGoogVP9FlexNearMaxMax()    { nearmaxmax(googVP9(),    true /* flex */); }
+    public void testGoogVP9SurfNearMaxMax()    { nearmaxmax(googVP9(),    false /* flex */); }
+
+    public void testOtherH265FlexNearMaxMax()  { nearmaxmax(otherH265(),  true /* flex */); }
+    public void testOtherH265SurfNearMaxMax()  { nearmaxmax(otherH265(),  false /* flex */); }
+    public void testOtherH264FlexNearMaxMax()  { nearmaxmax(otherH264(),  true /* flex */); }
+    public void testOtherH264SurfNearMaxMax()  { nearmaxmax(otherH264(),  false /* flex */); }
+    public void testOtherH263FlexNearMaxMax()  { nearmaxmax(otherH263(),  true /* flex */); }
+    public void testOtherH263SurfNearMaxMax()  { nearmaxmax(otherH263(),  false /* flex */); }
+    public void testOtherMpeg4FlexNearMaxMax() { nearmaxmax(otherMpeg4(), true /* flex */); }
+    public void testOtherMpeg4SurfNearMaxMax() { nearmaxmax(otherMpeg4(), false /* flex */); }
+    public void testOtherVP8FlexNearMaxMax()   { nearmaxmax(otherVP8(),   true /* flex */); }
+    public void testOtherVP8SurfNearMaxMax()   { nearmaxmax(otherVP8(),   false /* flex */); }
+    public void testOtherVP9FlexNearMaxMax()   { nearmaxmax(otherVP9(),   true /* flex */); }
+    public void testOtherVP9SurfNearMaxMax()   { nearmaxmax(otherVP9(),   false /* flex */); }
+
+    public void testGoogH265FlexArbitraryW()   { arbitraryw(googH265(),   true /* flex */); }
+    public void testGoogH265SurfArbitraryW()   { arbitraryw(googH265(),   false /* flex */); }
+    public void testGoogH264FlexArbitraryW()   { arbitraryw(googH264(),   true /* flex */); }
+    public void testGoogH264SurfArbitraryW()   { arbitraryw(googH264(),   false /* flex */); }
+    public void testGoogH263FlexArbitraryW()   { arbitraryw(googH263(),   true /* flex */); }
+    public void testGoogH263SurfArbitraryW()   { arbitraryw(googH263(),   false /* flex */); }
+    public void testGoogMpeg4FlexArbitraryW()  { arbitraryw(googMpeg4(),  true /* flex */); }
+    public void testGoogMpeg4SurfArbitraryW()  { arbitraryw(googMpeg4(),  false /* flex */); }
+    public void testGoogVP8FlexArbitraryW()    { arbitraryw(googVP8(),    true /* flex */); }
+    public void testGoogVP8SurfArbitraryW()    { arbitraryw(googVP8(),    false /* flex */); }
+    public void testGoogVP9FlexArbitraryW()    { arbitraryw(googVP9(),    true /* flex */); }
+    public void testGoogVP9SurfArbitraryW()    { arbitraryw(googVP9(),    false /* flex */); }
+
+    public void testOtherH265FlexArbitraryW()  { arbitraryw(otherH265(),  true /* flex */); }
+    public void testOtherH265SurfArbitraryW()  { arbitraryw(otherH265(),  false /* flex */); }
+    public void testOtherH264FlexArbitraryW()  { arbitraryw(otherH264(),  true /* flex */); }
+    public void testOtherH264SurfArbitraryW()  { arbitraryw(otherH264(),  false /* flex */); }
+    public void testOtherH263FlexArbitraryW()  { arbitraryw(otherH263(),  true /* flex */); }
+    public void testOtherH263SurfArbitraryW()  { arbitraryw(otherH263(),  false /* flex */); }
+    public void testOtherMpeg4FlexArbitraryW() { arbitraryw(otherMpeg4(), true /* flex */); }
+    public void testOtherMpeg4SurfArbitraryW() { arbitraryw(otherMpeg4(), false /* flex */); }
+    public void testOtherVP8FlexArbitraryW()   { arbitraryw(otherVP8(),   true /* flex */); }
+    public void testOtherVP8SurfArbitraryW()   { arbitraryw(otherVP8(),   false /* flex */); }
+    public void testOtherVP9FlexArbitraryW()   { arbitraryw(otherVP9(),   true /* flex */); }
+    public void testOtherVP9SurfArbitraryW()   { arbitraryw(otherVP9(),   false /* flex */); }
+
+    public void testGoogH265FlexArbitraryH()   { arbitraryh(googH265(),   true /* flex */); }
+    public void testGoogH265SurfArbitraryH()   { arbitraryh(googH265(),   false /* flex */); }
+    public void testGoogH264FlexArbitraryH()   { arbitraryh(googH264(),   true /* flex */); }
+    public void testGoogH264SurfArbitraryH()   { arbitraryh(googH264(),   false /* flex */); }
+    public void testGoogH263FlexArbitraryH()   { arbitraryh(googH263(),   true /* flex */); }
+    public void testGoogH263SurfArbitraryH()   { arbitraryh(googH263(),   false /* flex */); }
+    public void testGoogMpeg4FlexArbitraryH()  { arbitraryh(googMpeg4(),  true /* flex */); }
+    public void testGoogMpeg4SurfArbitraryH()  { arbitraryh(googMpeg4(),  false /* flex */); }
+    public void testGoogVP8FlexArbitraryH()    { arbitraryh(googVP8(),    true /* flex */); }
+    public void testGoogVP8SurfArbitraryH()    { arbitraryh(googVP8(),    false /* flex */); }
+    public void testGoogVP9FlexArbitraryH()    { arbitraryh(googVP9(),    true /* flex */); }
+    public void testGoogVP9SurfArbitraryH()    { arbitraryh(googVP9(),    false /* flex */); }
+
+    public void testOtherH265FlexArbitraryH()  { arbitraryh(otherH265(),  true /* flex */); }
+    public void testOtherH265SurfArbitraryH()  { arbitraryh(otherH265(),  false /* flex */); }
+    public void testOtherH264FlexArbitraryH()  { arbitraryh(otherH264(),  true /* flex */); }
+    public void testOtherH264SurfArbitraryH()  { arbitraryh(otherH264(),  false /* flex */); }
+    public void testOtherH263FlexArbitraryH()  { arbitraryh(otherH263(),  true /* flex */); }
+    public void testOtherH263SurfArbitraryH()  { arbitraryh(otherH263(),  false /* flex */); }
+    public void testOtherMpeg4FlexArbitraryH() { arbitraryh(otherMpeg4(), true /* flex */); }
+    public void testOtherMpeg4SurfArbitraryH() { arbitraryh(otherMpeg4(), false /* flex */); }
+    public void testOtherVP8FlexArbitraryH()   { arbitraryh(otherVP8(),   true /* flex */); }
+    public void testOtherVP8SurfArbitraryH()   { arbitraryh(otherVP8(),   false /* flex */); }
+    public void testOtherVP9FlexArbitraryH()   { arbitraryh(otherVP9(),   true /* flex */); }
+    public void testOtherVP9SurfArbitraryH()   { arbitraryh(otherVP9(),   false /* flex */); }
+
+    public void testGoogH265FlexQCIF()   { specific(googH265(),   176, 144, true /* flex */); }
+    public void testGoogH265SurfQCIF()   { specific(googH265(),   176, 144, false /* flex */); }
+    public void testGoogH264FlexQCIF()   { specific(googH264(),   176, 144, true /* flex */); }
+    public void testGoogH264SurfQCIF()   { specific(googH264(),   176, 144, false /* flex */); }
+    public void testGoogH263FlexQCIF()   { specific(googH263(),   176, 144, true /* flex */); }
+    public void testGoogH263SurfQCIF()   { specific(googH263(),   176, 144, false /* flex */); }
+    public void testGoogMpeg4FlexQCIF()  { specific(googMpeg4(),  176, 144, true /* flex */); }
+    public void testGoogMpeg4SurfQCIF()  { specific(googMpeg4(),  176, 144, false /* flex */); }
+    public void testGoogVP8FlexQCIF()    { specific(googVP8(),    176, 144, true /* flex */); }
+    public void testGoogVP8SurfQCIF()    { specific(googVP8(),    176, 144, false /* flex */); }
+    public void testGoogVP9FlexQCIF()    { specific(googVP9(),    176, 144, true /* flex */); }
+    public void testGoogVP9SurfQCIF()    { specific(googVP9(),    176, 144, false /* flex */); }
+
+    public void testOtherH265FlexQCIF()  { specific(otherH265(),  176, 144, true /* flex */); }
+    public void testOtherH265SurfQCIF()  { specific(otherH265(),  176, 144, false /* flex */); }
+    public void testOtherH264FlexQCIF()  { specific(otherH264(),  176, 144, true /* flex */); }
+    public void testOtherH264SurfQCIF()  { specific(otherH264(),  176, 144, false /* flex */); }
+    public void testOtherH263FlexQCIF()  { specific(otherH263(),  176, 144, true /* flex */); }
+    public void testOtherH263SurfQCIF()  { specific(otherH263(),  176, 144, false /* flex */); }
+    public void testOtherMpeg4FlexQCIF() { specific(otherMpeg4(), 176, 144, true /* flex */); }
+    public void testOtherMpeg4SurfQCIF() { specific(otherMpeg4(), 176, 144, false /* flex */); }
+    public void testOtherVP8FlexQCIF()   { specific(otherVP8(),   176, 144, true /* flex */); }
+    public void testOtherVP8SurfQCIF()   { specific(otherVP8(),   176, 144, false /* flex */); }
+    public void testOtherVP9FlexQCIF()   { specific(otherVP9(),   176, 144, true /* flex */); }
+    public void testOtherVP9SurfQCIF()   { specific(otherVP9(),   176, 144, false /* flex */); }
+
+    public void testGoogH265Flex480p()   { specific(googH265(),   720, 480, true /* flex */); }
+    public void testGoogH265Surf480p()   { specific(googH265(),   720, 480, false /* flex */); }
+    public void testGoogH264Flex480p()   { specific(googH264(),   720, 480, true /* flex */); }
+    public void testGoogH264Surf480p()   { specific(googH264(),   720, 480, false /* flex */); }
+    public void testGoogH263Flex480p()   { specific(googH263(),   720, 480, true /* flex */); }
+    public void testGoogH263Surf480p()   { specific(googH263(),   720, 480, false /* flex */); }
+    public void testGoogMpeg4Flex480p()  { specific(googMpeg4(),  720, 480, true /* flex */); }
+    public void testGoogMpeg4Surf480p()  { specific(googMpeg4(),  720, 480, false /* flex */); }
+    public void testGoogVP8Flex480p()    { specific(googVP8(),    720, 480, true /* flex */); }
+    public void testGoogVP8Surf480p()    { specific(googVP8(),    720, 480, false /* flex */); }
+    public void testGoogVP9Flex480p()    { specific(googVP9(),    720, 480, true /* flex */); }
+    public void testGoogVP9Surf480p()    { specific(googVP9(),    720, 480, false /* flex */); }
+
+    public void testOtherH265Flex480p()  { specific(otherH265(),  720, 480, true /* flex */); }
+    public void testOtherH265Surf480p()  { specific(otherH265(),  720, 480, false /* flex */); }
+    public void testOtherH264Flex480p()  { specific(otherH264(),  720, 480, true /* flex */); }
+    public void testOtherH264Surf480p()  { specific(otherH264(),  720, 480, false /* flex */); }
+    public void testOtherH263Flex480p()  { specific(otherH263(),  720, 480, true /* flex */); }
+    public void testOtherH263Surf480p()  { specific(otherH263(),  720, 480, false /* flex */); }
+    public void testOtherMpeg4Flex480p() { specific(otherMpeg4(), 720, 480, true /* flex */); }
+    public void testOtherMpeg4Surf480p() { specific(otherMpeg4(), 720, 480, false /* flex */); }
+    public void testOtherVP8Flex480p()   { specific(otherVP8(),   720, 480, true /* flex */); }
+    public void testOtherVP8Surf480p()   { specific(otherVP8(),   720, 480, false /* flex */); }
+    public void testOtherVP9Flex480p()   { specific(otherVP9(),   720, 480, true /* flex */); }
+    public void testOtherVP9Surf480p()   { specific(otherVP9(),   720, 480, false /* flex */); }
+
+    // even though H.263 and MPEG-4 are not defined for 720p or 1080p
+    // test for it, in case device claims support for it.
+
+    public void testGoogH265Flex720p()   { specific(googH265(),   1280, 720, true /* flex */); }
+    public void testGoogH265Surf720p()   { specific(googH265(),   1280, 720, false /* flex */); }
+    public void testGoogH264Flex720p()   { specific(googH264(),   1280, 720, true /* flex */); }
+    public void testGoogH264Surf720p()   { specific(googH264(),   1280, 720, false /* flex */); }
+    public void testGoogH263Flex720p()   { specific(googH263(),   1280, 720, true /* flex */); }
+    public void testGoogH263Surf720p()   { specific(googH263(),   1280, 720, false /* flex */); }
+    public void testGoogMpeg4Flex720p()  { specific(googMpeg4(),  1280, 720, true /* flex */); }
+    public void testGoogMpeg4Surf720p()  { specific(googMpeg4(),  1280, 720, false /* flex */); }
+    public void testGoogVP8Flex720p()    { specific(googVP8(),    1280, 720, true /* flex */); }
+    public void testGoogVP8Surf720p()    { specific(googVP8(),    1280, 720, false /* flex */); }
+    public void testGoogVP9Flex720p()    { specific(googVP9(),    1280, 720, true /* flex */); }
+    public void testGoogVP9Surf720p()    { specific(googVP9(),    1280, 720, false /* flex */); }
+
+    public void testOtherH265Flex720p()  { specific(otherH265(),  1280, 720, true /* flex */); }
+    public void testOtherH265Surf720p()  { specific(otherH265(),  1280, 720, false /* flex */); }
+    public void testOtherH264Flex720p()  { specific(otherH264(),  1280, 720, true /* flex */); }
+    public void testOtherH264Surf720p()  { specific(otherH264(),  1280, 720, false /* flex */); }
+    public void testOtherH263Flex720p()  { specific(otherH263(),  1280, 720, true /* flex */); }
+    public void testOtherH263Surf720p()  { specific(otherH263(),  1280, 720, false /* flex */); }
+    public void testOtherMpeg4Flex720p() { specific(otherMpeg4(), 1280, 720, true /* flex */); }
+    public void testOtherMpeg4Surf720p() { specific(otherMpeg4(), 1280, 720, false /* flex */); }
+    public void testOtherVP8Flex720p()   { specific(otherVP8(),   1280, 720, true /* flex */); }
+    public void testOtherVP8Surf720p()   { specific(otherVP8(),   1280, 720, false /* flex */); }
+    public void testOtherVP9Flex720p()   { specific(otherVP9(),   1280, 720, true /* flex */); }
+    public void testOtherVP9Surf720p()   { specific(otherVP9(),   1280, 720, false /* flex */); }
+
+    public void testGoogH265Flex1080p()   { specific(googH265(),   1920, 1080, true /* flex */); }
+    public void testGoogH265Surf1080p()   { specific(googH265(),   1920, 1080, false /* flex */); }
+    public void testGoogH264Flex1080p()   { specific(googH264(),   1920, 1080, true /* flex */); }
+    public void testGoogH264Surf1080p()   { specific(googH264(),   1920, 1080, false /* flex */); }
+    public void testGoogH263Flex1080p()   { specific(googH263(),   1920, 1080, true /* flex */); }
+    public void testGoogH263Surf1080p()   { specific(googH263(),   1920, 1080, false /* flex */); }
+    public void testGoogMpeg4Flex1080p()  { specific(googMpeg4(),  1920, 1080, true /* flex */); }
+    public void testGoogMpeg4Surf1080p()  { specific(googMpeg4(),  1920, 1080, false /* flex */); }
+    public void testGoogVP8Flex1080p()    { specific(googVP8(),    1920, 1080, true /* flex */); }
+    public void testGoogVP8Surf1080p()    { specific(googVP8(),    1920, 1080, false /* flex */); }
+    public void testGoogVP9Flex1080p()    { specific(googVP9(),    1920, 1080, true /* flex */); }
+    public void testGoogVP9Surf1080p()    { specific(googVP9(),    1920, 1080, false /* flex */); }
+
+    public void testOtherH265Flex1080p()  { specific(otherH265(),  1920, 1080, true /* flex */); }
+    public void testOtherH265Surf1080p()  { specific(otherH265(),  1920, 1080, false /* flex */); }
+    public void testOtherH264Flex1080p()  { specific(otherH264(),  1920, 1080, true /* flex */); }
+    public void testOtherH264Surf1080p()  { specific(otherH264(),  1920, 1080, false /* flex */); }
+    public void testOtherH263Flex1080p()  { specific(otherH263(),  1920, 1080, true /* flex */); }
+    public void testOtherH263Surf1080p()  { specific(otherH263(),  1920, 1080, false /* flex */); }
+    public void testOtherMpeg4Flex1080p() { specific(otherMpeg4(), 1920, 1080, true /* flex */); }
+    public void testOtherMpeg4Surf1080p() { specific(otherMpeg4(), 1920, 1080, false /* flex */); }
+    public void testOtherVP8Flex1080p()   { specific(otherVP8(),   1920, 1080, true /* flex */); }
+    public void testOtherVP8Surf1080p()   { specific(otherVP8(),   1920, 1080, false /* flex */); }
+    public void testOtherVP9Flex1080p()   { specific(otherVP9(),   1920, 1080, true /* flex */); }
+    public void testOtherVP9Surf1080p()   { specific(otherVP9(),   1920, 1080, false /* flex */); }
+
+    // Tests encoder profiles required by CDD.
+    // H264
+    public void testH264LowQualitySDSupport()   {
+        support(h264(), 320, 240, 20, 384 * 1000);
+    }
+
+    public void testH264HighQualitySDSupport()   {
+        support(h264(), 720, 480, 30, 2 * 1000000);
+    }
+
+    public void testH264FlexQVGA20fps384kbps()   {
+        detailed(h264(), 320, 240, 20, 384 * 1000, true /* flex */);
+    }
+
+    public void testH264SurfQVGA20fps384kbps()   {
+        detailed(h264(), 320, 240, 20, 384 * 1000, false /* flex */);
+    }
+
+    public void testH264Flex480p30fps2Mbps()   {
+        detailed(h264(), 720, 480, 30, 2 * 1000000, true /* flex */);
+    }
+
+    public void testH264Surf480p30fps2Mbps()   {
+        detailed(h264(), 720, 480, 30, 2 * 1000000, false /* flex */);
+    }
+
+    public void testH264Flex720p30fps4Mbps()   {
+        detailed(h264(), 1280, 720, 30, 4 * 1000000, true /* flex */);
+    }
+
+    public void testH264Surf720p30fps4Mbps()   {
+        detailed(h264(), 1280, 720, 30, 4 * 1000000, false /* flex */);
+    }
+
+    public void testH264Flex1080p30fps10Mbps()   {
+        detailed(h264(), 1920, 1080, 30, 10 * 1000000, true /* flex */);
+    }
+
+    public void testH264Surf1080p30fps10Mbps()   {
+        detailed(h264(), 1920, 1080, 30, 10 * 1000000, false /* flex */);
+    }
+
+    // VP8
+    public void testVP8LowQualitySDSupport()   {
+        support(vp8(), 320, 180, 30, 800 * 1000);
+    }
+
+    public void testVP8HighQualitySDSupport()   {
+        support(vp8(), 640, 360, 30, 2 * 1000000);
+    }
+
+    public void testVP8Flex180p30fps800kbps()   {
+        detailed(vp8(), 320, 180, 30, 800 * 1000, true /* flex */);
+    }
+
+    public void testVP8Surf180p30fps800kbps()   {
+        detailed(vp8(), 320, 180, 30, 800 * 1000, false /* flex */);
+    }
+
+    public void testVP8Flex360p30fps2Mbps()   {
+        detailed(vp8(), 640, 360, 30, 2 * 1000000, true /* flex */);
+    }
+
+    public void testVP8Surf360p30fps2Mbps()   {
+        detailed(vp8(), 640, 360, 30, 2 * 1000000, false /* flex */);
+    }
+
+    public void testVP8Flex720p30fps4Mbps()   {
+        detailed(vp8(), 1280, 720, 30, 4 * 1000000, true /* flex */);
+    }
+
+    public void testVP8Surf720p30fps4Mbps()   {
+        detailed(vp8(), 1280, 720, 30, 4 * 1000000, false /* flex */);
+    }
+
+    public void testVP8Flex1080p30fps10Mbps()   {
+        detailed(vp8(), 1920, 1080, 30, 10 * 1000000, true /* flex */);
+    }
+
+    public void testVP8Surf1080p30fps10Mbps()   {
+        detailed(vp8(), 1920, 1080, 30, 10 * 1000000, false /* flex */);
+    }
+
+    private void minmin(Encoder[] encoders, boolean flexYUV) {
+        extreme(encoders, 0 /* x */, 0 /* y */, flexYUV, false /* near */);
+    }
+
+    private void minmax(Encoder[] encoders, boolean flexYUV) {
+        extreme(encoders, 0 /* x */, 1 /* y */, flexYUV, false /* near */);
+    }
+
+    private void maxmin(Encoder[] encoders, boolean flexYUV) {
+        extreme(encoders, 1 /* x */, 0 /* y */, flexYUV, false /* near */);
+    }
+
+    private void maxmax(Encoder[] encoders, boolean flexYUV) {
+        extreme(encoders, 1 /* x */, 1 /* y */, flexYUV, false /* near */);
+    }
+
+    private void nearminmin(Encoder[] encoders, boolean flexYUV) {
+        extreme(encoders, 0 /* x */, 0 /* y */, flexYUV, true /* near */);
+    }
+
+    private void nearminmax(Encoder[] encoders, boolean flexYUV) {
+        extreme(encoders, 0 /* x */, 1 /* y */, flexYUV, true /* near */);
+    }
+
+    private void nearmaxmin(Encoder[] encoders, boolean flexYUV) {
+        extreme(encoders, 1 /* x */, 0 /* y */, flexYUV, true /* near */);
+    }
+
+    private void nearmaxmax(Encoder[] encoders, boolean flexYUV) {
+        extreme(encoders, 1 /* x */, 1 /* y */, flexYUV, true /* near */);
+    }
+
+    private void extreme(Encoder[] encoders, int x, int y, boolean flexYUV, boolean near) {
+        boolean skipped = true;
+        if (encoders.length == 0) {
+            MediaUtils.skipTest("no such encoder present");
+            return;
+        }
+        for (Encoder encoder: encoders) {
+            if (encoder.testExtreme(x, y, flexYUV, near)) {
+                skipped = false;
+            }
+        }
+        if (skipped) {
+            MediaUtils.skipTest("duplicate resolution extreme");
+        }
+    }
+
+    private void arbitrary(Encoder[] encoders, boolean flexYUV, boolean widths) {
+        boolean skipped = true;
+        if (encoders.length == 0) {
+            MediaUtils.skipTest("no such encoder present");
+            return;
+        }
+        for (Encoder encoder: encoders) {
+            if (encoder.testArbitrary(flexYUV, widths)) {
+                skipped = false;
+            }
+        }
+        if (skipped) {
+            MediaUtils.skipTest("duplicate resolution");
+        }
+    }
+
+    private void arbitraryw(Encoder[] encoders, boolean flexYUV) {
+        arbitrary(encoders, flexYUV, true /* widths */);
+    }
+
+    private void arbitraryh(Encoder[] encoders, boolean flexYUV) {
+        arbitrary(encoders, flexYUV, false /* widths */);
+    }
+
+    /* test specific size */
+    private void specific(Encoder[] encoders, int width, int height, boolean flexYUV) {
+        boolean skipped = true;
+        if (encoders.length == 0) {
+            MediaUtils.skipTest("no such encoder present");
+            return;
+        }
+        for (Encoder encoder : encoders) {
+            if (encoder.testSpecific(width, height, flexYUV)) {
+                skipped = false;
+            }
+        }
+        if (skipped) {
+            MediaUtils.skipTest("duplicate or unsupported resolution");
+        }
+    }
+
+    /* test size, frame rate and bit rate */
+    private void detailed(
+            Encoder[] encoders, int width, int height, int frameRate, int bitRate,
+            boolean flexYUV) {
+        if (encoders.length == 0) {
+            MediaUtils.skipTest("no such encoder present");
+            return;
+        }
+        boolean skipped = true;
+        for (Encoder encoder : encoders) {
+            if (encoder.testSupport(width, height, frameRate, bitRate)) {
+                skipped = false;
+                encoder.testDetailed(width, height, frameRate, bitRate, flexYUV);
+            }
+        }
+        if (skipped) {
+            MediaUtils.skipTest("unsupported resolution and rate");
+        }
+    }
+
+    /* test size and rate are supported */
+    private void support(Encoder[] encoders, int width, int height, int frameRate, int bitRate) {
+        boolean supported = false;
+        if (encoders.length == 0) {
+            MediaUtils.skipTest("no such encoder present");
+            return;
+        }
+        for (Encoder encoder : encoders) {
+            if (encoder.testSupport(width, height, frameRate, bitRate)) {
+                supported = true;
+                break;
+            }
+        }
+        if (!supported) {
+            fail("unsupported format " + width + "x" + height + " " +
+                    frameRate + "fps " + bitRate + "bps");
+        }
+    }
+}
diff --git a/tests/tests/media/src/android/media/cts/VirtualizerTest.java b/tests/tests/media/src/android/media/cts/VirtualizerTest.java
index 51adf1d..ac8eb84 100644
--- a/tests/tests/media/src/android/media/cts/VirtualizerTest.java
+++ b/tests/tests/media/src/android/media/cts/VirtualizerTest.java
@@ -16,14 +16,18 @@
 
 package android.media.cts;
 
+import android.content.Context;
 import android.media.audiofx.AudioEffect;
 import android.media.AudioFormat;
-import android.media.AudioManager;
 import android.media.audiofx.Virtualizer;
 import android.os.Looper;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import com.android.cts.media.R;
+
+import java.util.Arrays;
+
 public class VirtualizerTest extends PostProcTestBase {
 
     private String TAG = "VirtualizerTest";
@@ -288,6 +292,256 @@
     }
 
     //-----------------------------------------------------------------
+    // 4 virtualizer capabilities
+    //----------------------------------
+
+    //Test case 4.0: test virtualization format / mode query: at least one of the following
+    //   combinations must be supported, otherwise the effect doesn't really qualify as
+    //   a virtualizer: AudioFormat.CHANNEL_OUT_STEREO or the quad and 5.1 side/back variants,
+    //   in VIRTUALIZATION_MODE_BINAURAL or VIRTUALIZATION_MODE_TRANSAURAL
+    public void test4_0FormatModeQuery() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+        getVirtualizer(getSessionId());
+        try {
+            boolean isAtLeastOneConfigSupported = false;
+            boolean isConfigSupported = false;
+
+            // testing combinations of input channel mask and virtualization mode
+            for (int m = 0 ; m < VIRTUALIZATION_MODES.length ; m++) {
+                for (int i = 0 ; i < CHANNEL_MASKS.length ; i++) {
+                    isConfigSupported = mVirtualizer.canVirtualize(CHANNEL_MASKS[i],
+                            VIRTUALIZATION_MODES[m]);
+                    isAtLeastOneConfigSupported |= isConfigSupported;
+                    // optional logging
+                    String channelMask = Integer.toHexString(CHANNEL_MASKS[i]).toUpperCase();
+                    String nativeChannelMask =
+                            Integer.toHexString(CHANNEL_MASKS[i] >> 2).toUpperCase();
+                    Log.d(TAG, "content channel mask: 0x" + channelMask + " (native 0x"
+                            + nativeChannelMask
+                            + ") mode: " + VIRTUALIZATION_MODES[m]
+                            + " supported=" + isConfigSupported);
+                }
+            }
+
+            assertTrue("no valid configuration supported", isAtLeastOneConfigSupported);
+        } catch (IllegalArgumentException e) {
+            fail("bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("command not supported");
+        } catch (IllegalStateException e) {
+            fail("command called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 4.1: test that the capabilities reported by Virtualizer.canVirtualize(int,int)
+    //   matches those returned by Virtualizer.getSpeakerAngles(int, int, int[])
+    public void test4_1SpeakerAnglesCapaMatchesFormatModeCapa() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+        getVirtualizer(getSessionId());
+        try {
+            // 3: see size requirement in Virtualizer.getSpeakerAngles(int, int, int[])
+            // 6: for number of channels of 5.1 masks in CHANNEL_MASKS
+            int[] angles = new int[3*6];
+            for (int m = 0 ; m < VIRTUALIZATION_MODES.length ; m++) {
+                for (int i = 0 ; i < CHANNEL_MASKS.length ; i++) {
+                    Arrays.fill(angles,AudioFormat.CHANNEL_INVALID);
+                    boolean canVirtualize = mVirtualizer.canVirtualize(CHANNEL_MASKS[i],
+                            VIRTUALIZATION_MODES[m]);
+                    boolean canGetAngles = mVirtualizer.getSpeakerAngles(CHANNEL_MASKS[i],
+                            VIRTUALIZATION_MODES[m], angles);
+                    assertTrue("mismatch capability between canVirtualize() and getSpeakerAngles()",
+                            canVirtualize == canGetAngles);
+                    if(canGetAngles) {
+                        //check if the number of angles matched the expected number of channels for
+                        //each CHANNEL_MASKS
+                        int expectedChannelCount = CHANNEL_MASKS_CHANNEL_COUNT[i];
+                        for(int k=0; k<expectedChannelCount; k++) {
+                            int speakerIdentification = angles[k*3];
+                            assertTrue("found unexpected speaker identification or channel count",
+                                    speakerIdentification !=AudioFormat.CHANNEL_INVALID );
+                        }
+                    }
+                }
+            }
+        } catch (IllegalArgumentException e) {
+            fail("bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("command not supported");
+        } catch (IllegalStateException e) {
+            fail("command called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 4.2: test forcing virtualization mode: at least binaural or transaural must be
+    //   supported
+    public void test4_2VirtualizationMode() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+        getVirtualizer(getSessionId());
+        try {
+            mVirtualizer.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mVirtualizer.getEnabled());
+            // testing binaural
+            boolean binauralSupported =
+                    mVirtualizer.forceVirtualizationMode(Virtualizer.VIRTUALIZATION_MODE_BINAURAL);
+            // testing transaural
+            boolean transauralSupported = mVirtualizer
+                    .forceVirtualizationMode(Virtualizer.VIRTUALIZATION_MODE_TRANSAURAL);
+            // testing at least one supported
+            assertTrue("doesn't support binaural nor transaural",
+                    binauralSupported || transauralSupported);
+        } catch (IllegalArgumentException e) {
+            fail("bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("command not supported");
+        } catch (IllegalStateException e) {
+            fail("command called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 4.3: test disabling virtualization maps to VIRTUALIZATION_MODE_OFF
+    public void test4_3DisablingVirtualizationOff() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+        getVirtualizer(getSessionId());
+        try {
+            mVirtualizer.setEnabled(false);
+            assertFalse("invalid state from getEnabled", mVirtualizer.getEnabled());
+            int virtMode = mVirtualizer.getVirtualizationMode();
+            assertTrue("disabled virtualization isn't reported as OFF",
+                    virtMode == Virtualizer.VIRTUALIZATION_MODE_OFF);
+        } catch (IllegalArgumentException e) {
+            fail("bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("command not supported");
+        } catch (IllegalStateException e) {
+            fail("command called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 4.4: test forcing virtualization mode to AUTO
+    public void test4_4VirtualizationModeAuto() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+        getVirtualizer(getSessionId());
+        try {
+            mVirtualizer.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mVirtualizer.getEnabled());
+            boolean forceRes =
+                    mVirtualizer.forceVirtualizationMode(Virtualizer.VIRTUALIZATION_MODE_AUTO);
+            assertTrue("can't set virtualization to AUTO", forceRes);
+
+        } catch (IllegalArgumentException e) {
+            fail("bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("command not supported");
+        } catch (IllegalStateException e) {
+            fail("command called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //Test case 4.5: test for consistent capabilities if virtualizer is enabled or disabled
+    public void test4_5ConsistentCapabilitiesWithEnabledDisabled() throws Exception {
+        if (!isVirtualizerAvailable()) {
+            return;
+        }
+        getVirtualizer(getSessionId());
+        try {
+            // 3: see size requirement in Virtualizer.getSpeakerAngles(int, int, int[])
+            // 6: for number of channels of 5.1 masks in CHANNEL_MASKS
+            int[] angles = new int[3*6];
+            boolean[][] values = new boolean[VIRTUALIZATION_MODES.length][CHANNEL_MASKS.length];
+            mVirtualizer.setEnabled(true);
+            assertTrue("invalid state from getEnabled", mVirtualizer.getEnabled());
+            for (int m = 0 ; m < VIRTUALIZATION_MODES.length ; m++) {
+                for (int i = 0 ; i < CHANNEL_MASKS.length ; i++) {
+                    Arrays.fill(angles,AudioFormat.CHANNEL_INVALID);
+                    boolean canVirtualize = mVirtualizer.canVirtualize(CHANNEL_MASKS[i],
+                            VIRTUALIZATION_MODES[m]);
+                    boolean canGetAngles = mVirtualizer.getSpeakerAngles(CHANNEL_MASKS[i],
+                            VIRTUALIZATION_MODES[m], angles);
+                    assertTrue("mismatch capability between canVirtualize() and getSpeakerAngles()",
+                            canVirtualize == canGetAngles);
+                    values[m][i] = canVirtualize;
+                }
+            }
+
+            mVirtualizer.setEnabled(false);
+            assertTrue("invalid state from getEnabled", !mVirtualizer.getEnabled());
+            for (int m = 0 ; m < VIRTUALIZATION_MODES.length ; m++) {
+                for (int i = 0 ; i < CHANNEL_MASKS.length ; i++) {
+                    Arrays.fill(angles,AudioFormat.CHANNEL_INVALID);
+                    boolean canVirtualize = mVirtualizer.canVirtualize(CHANNEL_MASKS[i],
+                            VIRTUALIZATION_MODES[m]);
+                    boolean canGetAngles = mVirtualizer.getSpeakerAngles(CHANNEL_MASKS[i],
+                            VIRTUALIZATION_MODES[m], angles);
+                    assertTrue("mismatch capability between canVirtualize() and getSpeakerAngles()",
+                            canVirtualize == canGetAngles);
+                    assertTrue("mismatch capability between enabled and disabled virtualizer",
+                            canVirtualize == values[m][i]);
+                }
+            }
+
+        } catch (IllegalArgumentException e) {
+            fail("bad parameter value");
+        } catch (UnsupportedOperationException e) {
+            fail("command not supported");
+        } catch (IllegalStateException e) {
+            fail("command called in wrong state");
+        } finally {
+            releaseVirtualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // private data
+    //----------------------------------
+    // channel masks to test at input of virtualizer
+    private static final int[] CHANNEL_MASKS = {
+            AudioFormat.CHANNEL_OUT_STEREO, //2 channels
+            AudioFormat.CHANNEL_OUT_QUAD, //4 channels
+            //AudioFormat.CHANNEL_OUT_QUAD_SIDE (definition is not public)
+            (AudioFormat.CHANNEL_OUT_FRONT_LEFT | AudioFormat.CHANNEL_OUT_FRONT_RIGHT |
+                    AudioFormat.CHANNEL_OUT_SIDE_LEFT | AudioFormat.CHANNEL_OUT_SIDE_RIGHT), //4 channels
+            AudioFormat.CHANNEL_OUT_5POINT1, //6 channels
+            //AudioFormat.CHANNEL_OUT_5POINT1_SIDE (definition is not public)
+            (AudioFormat.CHANNEL_OUT_FRONT_LEFT | AudioFormat.CHANNEL_OUT_FRONT_RIGHT |
+                    AudioFormat.CHANNEL_OUT_FRONT_CENTER |
+                    AudioFormat.CHANNEL_OUT_LOW_FREQUENCY |
+                    AudioFormat.CHANNEL_OUT_SIDE_LEFT | AudioFormat.CHANNEL_OUT_SIDE_RIGHT) //6 channels
+    };
+
+    private static final int[] CHANNEL_MASKS_CHANNEL_COUNT = {
+        2,
+        4,
+        4,
+        6,
+        6
+    };
+
+    private static final int[] VIRTUALIZATION_MODES = {
+            Virtualizer.VIRTUALIZATION_MODE_BINAURAL,
+            Virtualizer.VIRTUALIZATION_MODE_TRANSAURAL
+    };
+
+    //-----------------------------------------------------------------
     // private methods
     //----------------------------------
 
@@ -362,7 +616,6 @@
                 mLooper = Looper.myLooper();
 
                 mVirtualizer2 = new Virtualizer(0, 0);
-                assertNotNull("could not create virtualizer2", mVirtualizer2);
 
                 synchronized(mLock) {
                     if (mControl) {
@@ -436,4 +689,4 @@
         }
     }
 
-}
\ No newline at end of file
+}
diff --git a/tests/tests/media/src/android/media/cts/VisualizerTest.java b/tests/tests/media/src/android/media/cts/VisualizerTest.java
index 8c91e9b..ea7a13f 100644
--- a/tests/tests/media/src/android/media/cts/VisualizerTest.java
+++ b/tests/tests/media/src/android/media/cts/VisualizerTest.java
@@ -16,12 +16,18 @@
 
 package android.media.cts;
 
+import com.android.cts.media.R;
+
+import android.content.Context;
 import android.media.audiofx.AudioEffect;
 import android.media.AudioFormat;
 import android.media.AudioManager;
+import android.media.MediaPlayer;
 import android.media.audiofx.Visualizer;
+import android.media.audiofx.Visualizer.MeasurementPeakRms;
 import android.os.Looper;
 import android.test.AndroidTestCase;
+import java.util.UUID;
 import android.util.Log;
 
 public class VisualizerTest extends PostProcTestBase {
@@ -126,7 +132,7 @@
     // 2 - check capture
     //----------------------------------
 
-    //Test case 2.0: test cature in polling mode
+    //Test case 2.0: test capture in polling mode
     public void test2_0PollingCapture() throws Exception {
         if (!hasAudioOutput()) {
             return;
@@ -217,6 +223,203 @@
     }
 
     //-----------------------------------------------------------------
+    // 3 - check measurement mode MEASUREMENT_MODE_NONE
+    //----------------------------------
+
+    //Test case 3.0: test setting NONE measurement mode
+    public void test3_0MeasurementModeNone() throws Exception {
+        try {
+            getVisualizer(0);
+            mVisualizer.setEnabled(true);
+            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
+            Thread.sleep(100);
+
+            int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_NONE);
+            assertEquals("setMeasurementMode for NONE doesn't report success",
+                    Visualizer.SUCCESS, status);
+
+            int mode = mVisualizer.getMeasurementMode();
+            assertEquals("getMeasurementMode reports NONE",
+                    Visualizer.MEASUREMENT_MODE_NONE, mode);
+
+        } catch (IllegalStateException e) {
+            fail("method called in wrong state");
+        } catch (InterruptedException e) {
+            fail("sleep() interrupted");
+        } finally {
+            releaseVisualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
+    // 4 - check measurement mode MEASUREMENT_MODE_PEAK_RMS
+    //----------------------------------
+
+    //Test case 4.0: test setting peak / RMS measurement mode
+    public void test4_0MeasurementModePeakRms() throws Exception {
+        try {
+            getVisualizer(0);
+            mVisualizer.setEnabled(true);
+            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
+            Thread.sleep(100);
+
+            int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS);
+            assertEquals("setMeasurementMode for PEAK_RMS doesn't report success",
+                    Visualizer.SUCCESS, status);
+
+            int mode = mVisualizer.getMeasurementMode();
+            assertEquals("getMeasurementMode doesn't report PEAK_RMS",
+                    Visualizer.MEASUREMENT_MODE_PEAK_RMS, mode);
+
+        } catch (IllegalStateException e) {
+            fail("method called in wrong state");
+        } catch (InterruptedException e) {
+            fail("sleep() interrupted");
+        } finally {
+            releaseVisualizer();
+        }
+    }
+
+    //Test case 4.1: test measurement of peak / RMS
+    public void test4_1MeasurePeakRms() throws Exception {
+        AudioEffect vc = null;
+        try {
+            // this test will play a 1kHz sine wave with peaks at -40dB
+            MediaPlayer mp = MediaPlayer.create(getContext(), R.raw.sine1khzm40db);
+            final int EXPECTED_PEAK_MB = -4015;
+            final int EXPECTED_RMS_MB =  -4300;
+            final int MAX_MEASUREMENT_ERROR_MB = 2000;
+            assertNotNull("null MediaPlayer", mp);
+
+            // creating a volume controller on output mix ensures that ro.audio.silent mutes
+            // audio after the effects and not before
+            vc = new AudioEffect(
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    UUID.fromString(BUNDLE_VOLUME_EFFECT_UUID),
+                    0,
+                    mp.getAudioSessionId());
+            vc.setEnabled(true);
+
+            AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+            assertNotNull("null AudioManager", am);
+            int originalVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
+            am.setStreamVolume(AudioManager.STREAM_MUSIC,
+                    am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
+            getVisualizer(mp.getAudioSessionId());
+            mp.setLooping(true);
+            mp.start();
+
+            mVisualizer.setEnabled(true);
+            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
+            Thread.sleep(100);
+            int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS);
+            assertEquals("setMeasurementMode() for PEAK_RMS doesn't report success",
+                    Visualizer.SUCCESS, status);
+            // make sure we're playing long enough so the measurement is valid
+            int currentPosition = mp.getCurrentPosition();
+            final int maxTry = 100;
+            int tryCount = 0;
+            while (currentPosition < 200 && tryCount < maxTry) {
+                Thread.sleep(50);
+                currentPosition = mp.getCurrentPosition();
+                tryCount++;
+            }
+            assertTrue("MediaPlayer not ready", tryCount < maxTry);
+
+            MeasurementPeakRms measurement = new MeasurementPeakRms();
+            status = mVisualizer.getMeasurementPeakRms(measurement);
+            mp.stop();
+            mp.release();
+            am.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0);
+            assertEquals("getMeasurementPeakRms() reports failure",
+                    Visualizer.SUCCESS, status);
+            Log.i("VisTest", "peak="+measurement.mPeak+"  rms="+measurement.mRms);
+            int deltaPeak = Math.abs(measurement.mPeak - EXPECTED_PEAK_MB);
+            int deltaRms =  Math.abs(measurement.mRms - EXPECTED_RMS_MB);
+            assertTrue("peak deviation in mB=" + deltaPeak, deltaPeak < MAX_MEASUREMENT_ERROR_MB);
+            assertTrue("RMS deviation in mB=" + deltaRms, deltaRms < MAX_MEASUREMENT_ERROR_MB);
+
+        } catch (IllegalStateException e) {
+            fail("method called in wrong state");
+        } catch (InterruptedException e) {
+            fail("sleep() interrupted");
+        } finally {
+            if (vc != null)
+                vc.release();
+            releaseVisualizer();
+        }
+    }
+
+    //Test case 4.2: test measurement of peak / RMS in Long MP3
+    public void test4_2MeasurePeakRmsLongMP3() throws Exception {
+        AudioEffect vc = null;
+        try {
+            // this test will play a 1kHz sine wave with peaks at -40dB
+            MediaPlayer mp = MediaPlayer.create(getContext(), R.raw.sine1khzs40dblong);
+            final int EXPECTED_PEAK_MB = -4015;
+            final int EXPECTED_RMS_MB =  -4300;
+            final int MAX_MEASUREMENT_ERROR_MB = 2000;
+            assertNotNull("null MediaPlayer", mp);
+
+            // creating a volume controller on output mix ensures that ro.audio.silent mutes
+            // audio after the effects and not before
+            vc = new AudioEffect(
+                    AudioEffect.EFFECT_TYPE_NULL,
+                    UUID.fromString(BUNDLE_VOLUME_EFFECT_UUID),
+                    0,
+                    mp.getAudioSessionId());
+            vc.setEnabled(true);
+
+            AudioManager am = (AudioManager) getContext().getSystemService(Context.AUDIO_SERVICE);
+            assertNotNull("null AudioManager", am);
+            int originalVolume = am.getStreamVolume(AudioManager.STREAM_MUSIC);
+            am.setStreamVolume(AudioManager.STREAM_MUSIC,
+                    am.getStreamMaxVolume(AudioManager.STREAM_MUSIC), 0);
+            getVisualizer(mp.getAudioSessionId());
+            mp.start();
+
+            mVisualizer.setEnabled(true);
+            assertTrue("visualizer not enabled", mVisualizer.getEnabled());
+            Thread.sleep(100);
+            int status = mVisualizer.setMeasurementMode(Visualizer.MEASUREMENT_MODE_PEAK_RMS);
+            assertEquals("setMeasurementMode() for PEAK_RMS doesn't report success",
+                    Visualizer.SUCCESS, status);
+            // make sure we're playing long enough so the measurement is valid
+            int currentPosition = mp.getCurrentPosition();
+            final int maxTry = 100;
+            int tryCount = 0;
+            while (currentPosition < 400 && tryCount < maxTry) {
+                Thread.sleep(50);
+                currentPosition = mp.getCurrentPosition();
+                tryCount++;
+            }
+            assertTrue("MediaPlayer not ready", tryCount < maxTry);
+
+            MeasurementPeakRms measurement = new MeasurementPeakRms();
+            status = mVisualizer.getMeasurementPeakRms(measurement);
+            mp.stop();
+            mp.release();
+            am.setStreamVolume(AudioManager.STREAM_MUSIC, originalVolume, 0);
+            assertEquals("getMeasurementPeakRms() reports failure",
+                    Visualizer.SUCCESS, status);
+            Log.i("VisTest", "peak="+measurement.mPeak+"  rms="+measurement.mRms);
+            int deltaPeak = Math.abs(measurement.mPeak - EXPECTED_PEAK_MB);
+            int deltaRms =  Math.abs(measurement.mRms - EXPECTED_RMS_MB);
+            assertTrue("peak deviation in mB=" + deltaPeak, deltaPeak < MAX_MEASUREMENT_ERROR_MB);
+            assertTrue("RMS deviation in mB=" + deltaRms, deltaRms < MAX_MEASUREMENT_ERROR_MB);
+
+        } catch (IllegalStateException e) {
+            fail("method called in wrong state");
+        } catch (InterruptedException e) {
+            fail("sleep() interrupted");
+        } finally {
+            if (vc != null)
+                vc.release();
+            releaseVisualizer();
+        }
+    }
+
+    //-----------------------------------------------------------------
     // private methods
     //----------------------------------
 
diff --git a/tests/tests/media/src/android/media/cts/Vp8CodecTestBase.java b/tests/tests/media/src/android/media/cts/Vp8CodecTestBase.java
index 586b9ef..133b91d 100644
--- a/tests/tests/media/src/android/media/cts/Vp8CodecTestBase.java
+++ b/tests/tests/media/src/android/media/cts/Vp8CodecTestBase.java
@@ -53,10 +53,8 @@
 public class Vp8CodecTestBase extends AndroidTestCase {
 
     protected static final String TAG = "VP8CodecTestBase";
-    protected static final String VP8_MIME = "video/x-vnd.on2.vp8";
-    private static final String VPX_SW_DECODER_NAME = "OMX.google.vp8.decoder";
-    private static final String VPX_SW_ENCODER_NAME = "OMX.google.vp8.encoder";
-    private static final String OMX_SW_CODEC_PREFIX = "OMX.google";
+    protected static final String VP8_MIME = MediaFormat.MIMETYPE_VIDEO_VP8;
+    private static final String GOOGLE_CODEC_PREFIX = "omx.google.";
     protected static final String SDCARD_DIR =
             Environment.getExternalStorageDirectory().getAbsolutePath();
 
@@ -93,29 +91,6 @@
     }
 
     /**
-     * Returns the first codec capable of encoding the specified MIME type, or null if no
-     * match was found.
-     */
-    protected static MediaCodecInfo selectCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return codecInfo;
-                }
-            }
-        }
-        return null;
-    }
-
-    /**
      *  VP8 codec properties generated by getVp8CodecProperties() function.
      */
     private class CodecProperties {
@@ -123,8 +98,8 @@
             this.codecName = codecName;
             this.colorFormat = colorFormat;
         }
-        public boolean  isGoogleSwCodec() {
-            return codecName.startsWith(OMX_SW_CODEC_PREFIX);
+        public boolean  isGoogleCodec() {
+            return codecName.toLowerCase().startsWith(GOOGLE_CODEC_PREFIX);
         }
 
         public final String codecName; // OpenMax component name for VP8 codec.
@@ -135,76 +110,75 @@
      * Function to find VP8 codec.
      *
      * Iterates through the list of available codecs and tries to find
-     * VP8 codec, which can support either YUV420 planar or NV12 color formats.
-     * If forceSwGoogleCodec parameter set to true the function always returns
-     * Google sw VP8 codec.
-     * If forceSwGoogleCodec parameter set to false the functions looks for platform
-     * specific VP8 codec first. If no platform specific codec exist, falls back to
-     * Google sw VP8 codec.
+     * VPX codec, which can support either YUV420 planar or NV12 color formats.
+     * If forceGoogleCodec parameter set to true the function always returns
+     * Google VPX codec.
+     * If forceGoogleCodec parameter set to false the functions looks for platform
+     * specific VPX codec first. If no platform specific codec exist, falls back to
+     * Google VPX codec.
      *
      * @param isEncoder     Flag if encoder is requested.
-     * @param forceSwGoogleCodec  Forces to use Google sw codec.
+     * @param forceGoogleCodec  Forces to use Google codec.
      */
-    private CodecProperties getVp8CodecProperties(boolean isEncoder,
-            boolean forceSwGoogleCodec) throws Exception {
+    private CodecProperties getVpxCodecProperties(
+            boolean isEncoder,
+            MediaFormat format,
+            boolean forceGoogleCodec) throws Exception {
         CodecProperties codecProperties = null;
+        String mime = format.getString(MediaFormat.KEY_MIME);
 
-        if (!forceSwGoogleCodec) {
-            // Loop through the list of omx components in case platform specific codec
-            // is requested.
-            for (int i = 0; i < MediaCodecList.getCodecCount(); i++) {
-                MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-                if (isEncoder != codecInfo.isEncoder()) {
+        // Loop through the list of omx components in case platform specific codec
+        // is requested.
+        MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        for (MediaCodecInfo codecInfo : mcl.getCodecInfos()) {
+            if (isEncoder != codecInfo.isEncoder()) {
+                continue;
+            }
+            Log.v(TAG, codecInfo.getName());
+            // TODO: remove dependence of Google from the test
+            // Check if this is Google codec - we should ignore it.
+            boolean isGoogleCodec =
+                codecInfo.getName().toLowerCase().startsWith(GOOGLE_CODEC_PREFIX);
+            if (!isGoogleCodec && forceGoogleCodec) {
+                continue;
+            }
+
+            for (String type : codecInfo.getSupportedTypes()) {
+                if (!type.equalsIgnoreCase(mime)) {
                     continue;
                 }
-                Log.v(TAG, codecInfo.getName());
-                // Check if this is sw Google codec - we should ignore it.
-                boolean isGoogleSwCodec = codecInfo.getName().startsWith(OMX_SW_CODEC_PREFIX);
-                if (isGoogleSwCodec) {
+                CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(type);
+                if (!capabilities.isFormatSupported(format)) {
                     continue;
                 }
 
-                for (String type : codecInfo.getSupportedTypes()) {
-                    if (!type.equalsIgnoreCase(VP8_MIME)) {
-                        continue;
-                    }
-                    CodecCapabilities capabilities = codecInfo.getCapabilitiesForType(VP8_MIME);
+                // Get candidate codec properties.
+                Log.v(TAG, "Found candidate codec " + codecInfo.getName());
+                for (int colorFormat: capabilities.colorFormats) {
+                    Log.v(TAG, "   Color: 0x" + Integer.toHexString(colorFormat));
+                }
 
-                    // Get candidate codec properties.
-                    Log.v(TAG, "Found candidate codec " + codecInfo.getName());
-                    for (int colorFormat : capabilities.colorFormats) {
-                        Log.v(TAG, "   Color: 0x" + Integer.toHexString(colorFormat));
-                    }
-
-                    // Check supported color formats.
-                    for (int supportedColorFormat : mSupportedColorList) {
-                        for (int codecColorFormat : capabilities.colorFormats) {
-                            if (codecColorFormat == supportedColorFormat) {
-                                codecProperties = new CodecProperties(codecInfo.getName(),
-                                        codecColorFormat);
-                                Log.v(TAG, "Found target codec " + codecProperties.codecName +
-                                        ". Color: 0x" + Integer.toHexString(codecColorFormat));
+                // Check supported color formats.
+                for (int supportedColorFormat : mSupportedColorList) {
+                    for (int codecColorFormat : capabilities.colorFormats) {
+                        if (codecColorFormat == supportedColorFormat) {
+                            codecProperties = new CodecProperties(codecInfo.getName(),
+                                    codecColorFormat);
+                            Log.v(TAG, "Found target codec " + codecProperties.codecName +
+                                    ". Color: 0x" + Integer.toHexString(codecColorFormat));
+                            // return first HW codec found
+                            if (!isGoogleCodec) {
                                 return codecProperties;
                             }
                         }
                     }
-                    // HW codec we found does not support one of necessary color formats.
-                    throw new RuntimeException("No hw codec with YUV420 or NV12 color formats");
                 }
             }
         }
-        // If no hw vp8 codec exist or sw codec is requested use default Google sw codec.
         if (codecProperties == null) {
-            Log.v(TAG, "Use SW VP8 codec");
-            if (isEncoder) {
-                codecProperties = new CodecProperties(VPX_SW_ENCODER_NAME,
-                        CodecCapabilities.COLOR_FormatYUV420Planar);
-            } else {
-                codecProperties = new CodecProperties(VPX_SW_DECODER_NAME,
-                        CodecCapabilities.COLOR_FormatYUV420Planar);
-            }
+            Log.i(TAG, "no suitable " + (forceGoogleCodec ? "google " : "")
+                    + (isEncoder ? "encoder " : "decoder ") + "found for " + format);
         }
-
         return codecProperties;
     }
 
@@ -223,8 +197,8 @@
         int inputResourceId;
         // Name of the IVF file to write encoded bitsream
         public String outputIvfFilename;
-        // Force to use Google SW VP8 encoder.
-        boolean forceSwEncoder;
+        // Force to use Google VP8 encoder.
+        boolean forceGoogleEncoder;
         // Number of frames to encode.
         int frameCount;
         // Frame rate of input file in frames per second.
@@ -286,7 +260,7 @@
             params.inputResourceId = R.raw.football_qvga;
             params.outputIvfFilename = SDCARD_DIR + File.separator +
                     outputIvfBaseName + resolutionScales[i] + ".ivf";
-            params.forceSwEncoder = false;
+            params.forceGoogleEncoder = false;
             params.frameCount = encodeSeconds * frameRate;
             params.frameRate = frameRate;
             params.frameWidth = Math.min(frameWidth * resolutionScales[i], 1280);
@@ -532,15 +506,15 @@
      * @param inputIvfFilename  The name of the IVF file containing encoded bitsream.
      * @param outputYuvFilename The name of the output YUV file (optional).
      * @param frameRate         Frame rate of input file in frames per second
-     * @param forceSwDecoder    Force to use Googlw sw VP8 decoder.
+     * @param forceGoogleDecoder    Force to use Google VP8 decoder.
      */
     protected ArrayList<MediaCodec.BufferInfo> decode(
             String inputIvfFilename,
             String outputYuvFilename,
             int frameRate,
-            boolean forceSwDecoder) throws Exception {
+            boolean forceGoogleDecoder) throws Exception {
         ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
-        CodecProperties properties = getVp8CodecProperties(false, forceSwDecoder);
+
         // Open input/output.
         IvfReader ivf = new IvfReader(inputIvfFilename);
         int frameWidth = ivf.getWidth();
@@ -548,21 +522,27 @@
         int frameCount = ivf.getFrameCount();
         int frameStride = frameWidth;
         int frameSliceHeight = frameHeight;
-        int frameColorFormat = properties.colorFormat;
         assertTrue(frameWidth > 0);
         assertTrue(frameHeight > 0);
         assertTrue(frameCount > 0);
 
+        // Create decoder.
+        MediaFormat format = MediaFormat.createVideoFormat(
+                VP8_MIME, ivf.getWidth(), ivf.getHeight());
+        CodecProperties properties = getVpxCodecProperties(
+                false /* encoder */, format, forceGoogleDecoder);
+        if (properties == null) {
+            ivf.close();
+            return null;
+        }
+        int frameColorFormat = properties.colorFormat;
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
+
         FileOutputStream yuv = null;
         if (outputYuvFilename != null) {
             yuv = new FileOutputStream(outputYuvFilename, false);
         }
 
-        // Create decoder.
-        MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME,
-                                                           ivf.getWidth(),
-                                                           ivf.getHeight());
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, properties.colorFormat);
         Log.d(TAG, "Creating decoder " + properties.codecName +
                 ". Color format: 0x" + Integer.toHexString(frameColorFormat) +
                 ". " + frameWidth + " x " + frameHeight);
@@ -1282,11 +1262,20 @@
             EncoderOutputStreamParameters streamParams) throws Exception {
 
         ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
-        CodecProperties properties = getVp8CodecProperties(true, streamParams.forceSwEncoder);
-        Log.d(TAG, "Source reslution: " + streamParams.frameWidth + " x " +
+        Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
                 streamParams.frameHeight);
         int bitrate = streamParams.bitrateSet[0];
 
+        // Create minimal media format signifying desired output.
+        MediaFormat format = MediaFormat.createVideoFormat(
+                VP8_MIME, streamParams.frameWidth, streamParams.frameHeight);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+        CodecProperties properties = getVpxCodecProperties(
+                true, format, streamParams.forceGoogleEncoder);
+        if (properties == null) {
+            return null;
+        }
+
         // Open input/output
         InputStream yuvStream = OpenFileOrResourceId(
                 streamParams.inputYuvFilename, streamParams.inputResourceId);
@@ -1294,9 +1283,6 @@
                 streamParams.outputIvfFilename, streamParams.frameWidth, streamParams.frameHeight);
 
         // Create a media format signifying desired output.
-        MediaFormat format = MediaFormat.createVideoFormat(
-                VP8_MIME, streamParams.frameWidth, streamParams.frameHeight);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
         if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
             format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
         }
@@ -1451,19 +1437,25 @@
         }
 
         ArrayList<MediaCodec.BufferInfo> bufferInfos = new ArrayList<MediaCodec.BufferInfo>();
-        CodecProperties properties = getVp8CodecProperties(true, streamParams.forceSwEncoder);
-        Log.d(TAG, "Source reslution: " + streamParams.frameWidth + " x " +
+        Log.d(TAG, "Source resolution: "+streamParams.frameWidth + " x " +
                 streamParams.frameHeight);
         int bitrate = streamParams.bitrateSet[0];
 
+        // Create minimal media format signifying desired output.
+        MediaFormat format = MediaFormat.createVideoFormat(
+                VP8_MIME, streamParams.frameWidth, streamParams.frameHeight);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+        CodecProperties properties = getVpxCodecProperties(
+                true, format, streamParams.forceGoogleEncoder);
+        if (properties == null) {
+            return null;
+        }
+
         // Open input/output
         IvfWriter ivf = new IvfWriter(
                 streamParams.outputIvfFilename, streamParams.frameWidth, streamParams.frameHeight);
 
         // Create a media format signifying desired output.
-        MediaFormat format = MediaFormat.createVideoFormat(
-                VP8_MIME, streamParams.frameWidth, streamParams.frameHeight);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
         if (streamParams.bitrateType == VIDEO_ControlRateConstant) {
             format.setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
         }
@@ -1546,9 +1538,22 @@
         boolean bufferConsumedTotal = false;
         CodecProperties[] codecProperties = new CodecProperties[numEncoders];
 
-        for (int i = 0; i < numEncoders; i++) {
-            EncoderOutputStreamParameters params = encodingParams.get(i);
-            CodecProperties properties = getVp8CodecProperties(true, params.forceSwEncoder);
+        numEncoders = 0;
+        for (EncoderOutputStreamParameters params : encodingParams) {
+            int i = numEncoders;
+            Log.d(TAG, "Source resolution: " + params.frameWidth + " x " +
+                    params.frameHeight);
+            int bitrate = params.bitrateSet[0];
+
+            // Create minimal media format signifying desired output.
+            format[i] = MediaFormat.createVideoFormat(VP8_MIME,
+                    params.frameWidth, params.frameHeight);
+            format[i].setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+            CodecProperties properties = getVpxCodecProperties(
+                    true, format[i], params.forceGoogleEncoder);
+            if (properties == null) {
+                continue;
+            }
 
             // Check if scaled image was created
             int scale = params.frameWidth / srcFrameWidth;
@@ -1574,10 +1579,6 @@
             srcFrame[i] = new byte[frameSize];
 
             // Create a media format signifying desired output.
-            int bitrate = params.bitrateSet[0];
-            format[i] = MediaFormat.createVideoFormat(VP8_MIME,
-                    params.frameWidth, params.frameHeight);
-            format[i].setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
             if (params.bitrateType == VIDEO_ControlRateConstant) {
                 format[i].setInteger("bitrate-mode", VIDEO_ControlRateConstant); // set CBR
             }
@@ -1607,6 +1608,11 @@
             codecProperties[i] = new CodecProperties(properties.codecName, properties.colorFormat);
 
             inputConsumed[i] = true;
+            ++numEncoders;
+        }
+        if (numEncoders == 0) {
+            Log.i(TAG, "no suitable encoders found for any of the streams");
+            return null;
         }
 
         while (!sawOutputEOSTotal) {
@@ -1644,7 +1650,8 @@
                     // Convert YUV420 to NV12 if necessary
                     if (codecProperties[i].colorFormat !=
                             CodecCapabilities.COLOR_FormatYUV420Planar) {
-                        srcFrame[i] = YUV420ToNV(params.frameWidth, params.frameHeight, srcFrame[i]);
+                        srcFrame[i] =
+                            YUV420ToNV(params.frameWidth, params.frameHeight, srcFrame[i]);
                     }
                 }
 
diff --git a/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
index 57e397f..be7e721 100644
--- a/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/Vp8EncoderTest.java
@@ -18,6 +18,8 @@
 
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
 import android.util.Log;
 import com.android.cts.media.R;
 
@@ -52,13 +54,13 @@
     private static final int[] TEST_BITRATES_SET = { 300000, 500000, 700000, 900000 };
     // Maximum allowed bitrate variation from the target value.
     private static final double MAX_BITRATE_VARIATION = 0.2;
-    // Average PSNR values for reference SW VP8 codec for the above bitrates.
+    // Average PSNR values for reference Google VP8 codec for the above bitrates.
     private static final double[] REFERENCE_AVERAGE_PSNR = { 33.1, 35.2, 36.6, 37.8 };
-    // Minimum PSNR values for reference SW VP8 codec for the above bitrates.
+    // Minimum PSNR values for reference Google VP8 codec for the above bitrates.
     private static final double[] REFERENCE_MINIMUM_PSNR = { 25.9, 27.5, 28.4, 30.3 };
-    // Maximum allowed average PSNR difference of HW encoder comparing to reference SW encoder.
+    // Maximum allowed average PSNR difference of encoder comparing to reference Google encoder.
     private static final double MAX_AVERAGE_PSNR_DIFFERENCE = 2;
-    // Maximum allowed minimum PSNR difference of HW encoder comparing to reference SW encoder.
+    // Maximum allowed minimum PSNR difference of encoder comparing to reference Google encoder.
     private static final double MAX_MINIMUM_PSNR_DIFFERENCE = 4;
     // Maximum allowed average PSNR difference of the encoder running in a looper thread with 0 ms
     // buffer dequeue timeout comparing to the encoder running in a callee's thread with 100 ms
@@ -80,13 +82,8 @@
      * Also checks the average bitrate is within MAX_BITRATE_VARIATION of the target value.
      */
     public void testBasic() throws Exception {
-        MediaCodecInfo codecInfo = selectCodec(VP8_MIME);
-        if (codecInfo == null) {
-            Log.w(TAG, "Codec " + VP8_MIME + " not supported. Return from testBasic.");
-            return;
-        }
-
         int encodeSeconds = 9;
+        boolean skipped = true;
 
         for (int targetBitrate : TEST_BITRATES_SET) {
             EncoderOutputStreamParameters params = getDefaultEncodingParameters(
@@ -100,6 +97,11 @@
                     targetBitrate,
                     true);
             ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+            if (bufInfo == null) {
+                continue;
+            }
+            skipped = false;
+
             Vp8EncodingStatistics statistics = computeEncodingStatistics(bufInfo);
 
             assertEquals("Stream bitrate " + statistics.mAverageBitrate +
@@ -107,7 +109,11 @@
                     targetBitrate, statistics.mAverageBitrate,
                     MAX_BITRATE_VARIATION * targetBitrate);
 
-            decode(params.outputIvfFilename, null, FPS, params.forceSwEncoder);
+            decode(params.outputIvfFilename, null, FPS, params.forceGoogleEncoder);
+        }
+
+        if (skipped) {
+            Log.i(TAG, "SKIPPING testBasic(): codec is not supported");
         }
     }
 
@@ -119,12 +125,6 @@
      * does not change much for two different ways of the encoder call.
      */
     public void testAsyncEncoding() throws Exception {
-        MediaCodecInfo codecInfo = selectCodec(VP8_MIME);
-        if (codecInfo == null) {
-            Log.w(TAG, "Codec " + VP8_MIME + " not supported. Return from testAsyncEncoding.");
-            return;
-        }
-
         int encodeSeconds = 9;
 
         // First test the encoder running in a looper thread with buffer callbacks enabled.
@@ -140,8 +140,12 @@
                 BITRATE,
                 syncEncoding);
         ArrayList<MediaCodec.BufferInfo> bufInfos = encodeAsync(params);
+        if (bufInfos == null) {
+            Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
+            return;
+        }
         computeEncodingStatistics(bufInfos);
-        decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceSwEncoder);
+        decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceGoogleEncoder);
         Vp8DecodingStatistics statisticsAsync = computeDecodingStatistics(
                 params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
                 params.frameWidth, params.frameHeight);
@@ -160,8 +164,12 @@
                 BITRATE,
                 syncEncoding);
         bufInfos = encode(params);
+        if (bufInfos == null) {
+            Log.i(TAG, "SKIPPING testAsyncEncoding(): no suitable encoder found");
+            return;
+        }
         computeEncodingStatistics(bufInfos);
-        decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceSwEncoder);
+        decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceGoogleEncoder);
         Vp8DecodingStatistics statisticsSync = computeDecodingStatistics(
                 params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
                 params.frameWidth, params.frameHeight);
@@ -186,12 +194,6 @@
      * The test does not verify the output stream.
      */
     public void testSyncFrame() throws Exception {
-        MediaCodecInfo codecInfo = selectCodec(VP8_MIME);
-        if (codecInfo == null) {
-            Log.w(TAG, "Codec " + VP8_MIME + " not supported. Return from testSyncFrame.");
-            return;
-        }
-
         int encodeSeconds = 9;
 
         EncoderOutputStreamParameters params = getDefaultEncodingParameters(
@@ -207,6 +209,11 @@
         params.syncFrameInterval = encodeSeconds * FPS;
         params.syncForceFrameInterval = FPS;
         ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+        if (bufInfo == null) {
+            Log.i(TAG, "SKIPPING testSyncFrame(): no suitable encoder found");
+            return;
+        }
+
         Vp8EncodingStatistics statistics = computeEncodingStatistics(bufInfo);
 
         // First check if we got expected number of key frames.
@@ -236,12 +243,6 @@
      * bitrate after 6 seconds and ensure the encoder responds.
      */
     public void testDynamicBitrateChange() throws Exception {
-        MediaCodecInfo codecInfo = selectCodec(VP8_MIME);
-        if (codecInfo == null) {
-            Log.w(TAG, "Codec " + VP8_MIME + " not supported. Return from testDynamicBitrateChange.");
-            return;
-        }
-
         int encodeSeconds = 12;    // Encoding sequence duration in seconds.
         int[] bitrateTargetValues = { 400000, 800000 };  // List of bitrates to test.
 
@@ -268,6 +269,11 @@
         }
 
         ArrayList<MediaCodec.BufferInfo> bufInfo = encode(params);
+        if (bufInfo == null) {
+            Log.i(TAG, "SKIPPING testDynamicBitrateChange(): no suitable encoder found");
+            return;
+        }
+
         Vp8EncodingStatistics statistics = computeEncodingStatistics(bufInfo);
 
         // Calculate actual average bitrates  for every [stepSeconds] second.
@@ -304,10 +310,12 @@
       * Compares average bitrate and PSNR for sequential and parallel runs.
       */
      public void testParallelEncodingAndDecoding() throws Exception {
-         MediaCodecInfo codecInfo = selectCodec(VP8_MIME);
-         if (codecInfo == null) {
-             Log.w(TAG, "Codec " + VP8_MIME + " not supported. "
-                     + "Return from testParallelEncodingAndDecoding.");
+         // check for encoder up front, as by the time we detect lack of
+         // encoder support, we may have already started decoding.
+         MediaCodecList mcl = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+         MediaFormat format = MediaFormat.createVideoFormat(VP8_MIME, WIDTH, HEIGHT);
+         if (mcl.findEncoderForFormat(format) == null) {
+             Log.i(TAG, "SKIPPING testParallelEncodingAndDecoding(): no suitable encoder found");
              return;
          }
 
@@ -343,7 +351,7 @@
          Runnable runDecoder = new Runnable() {
              public void run() {
                  try {
-                     decode(inputIvfFilename, OUTPUT_YUV, FPS, params.forceSwEncoder);
+                     decode(inputIvfFilename, OUTPUT_YUV, FPS, params.forceGoogleEncoder);
                      Vp8DecodingStatistics statistics = computeDecodingStatistics(
                             params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
                             params.frameWidth, params.frameHeight);
@@ -400,21 +408,17 @@
      * Run the the encoder for 9 seconds for each bitrate and calculate PSNR
      * for each encoded stream.
      * Video streams with higher bitrates should have higher PSNRs.
-     * Also compares average and minimum PSNR of HW codec with PSNR values of reference SW codec.
+     * Also compares average and minimum PSNR of codec with PSNR values of reference Google codec.
      */
     public void testEncoderQuality() throws Exception {
-        MediaCodecInfo codecInfo = selectCodec(VP8_MIME);
-        if (codecInfo == null) {
-            Log.w(TAG, "Codec " + VP8_MIME + " not supported. Return from testEncoderQuality.");
-            return;
-        }
-
         int encodeSeconds = 9;      // Encoding sequence duration in seconds for each bitrate.
         double[] psnrPlatformCodecAverage = new double[TEST_BITRATES_SET.length];
         double[] psnrPlatformCodecMin = new double[TEST_BITRATES_SET.length];
+        boolean[] completed = new boolean[TEST_BITRATES_SET.length];
+        boolean skipped = true;
 
         // Run platform specific encoder for different bitrates
-        // and compare PSNR of hw codec with PSNR of reference sw codec.
+        // and compare PSNR of codec with PSNR of reference Google codec.
         for (int i = 0; i < TEST_BITRATES_SET.length; i++) {
             EncoderOutputStreamParameters params = getDefaultEncodingParameters(
                     INPUT_YUV,
@@ -426,9 +430,15 @@
                     BITRATE_MODE,
                     TEST_BITRATES_SET[i],
                     true);
-            encode(params);
+            if (encode(params) == null) {
+                // parameters not supported, try other bitrates
+                completed[i] = false;
+                continue;
+            }
+            completed[i] = true;
+            skipped = false;
 
-            decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceSwEncoder);
+            decode(params.outputIvfFilename, OUTPUT_YUV, FPS, params.forceGoogleEncoder);
             Vp8DecodingStatistics statistics = computeDecodingStatistics(
                     params.inputYuvFilename, R.raw.football_qvga, OUTPUT_YUV,
                     params.frameWidth, params.frameHeight);
@@ -436,9 +446,20 @@
             psnrPlatformCodecMin[i] = statistics.mMinimumPSNR;
         }
 
+        if (skipped) {
+            Log.i(TAG, "SKIPPING testEncoderQuality(): no bitrates supported");
+            return;
+        }
+
         // First do a sanity check - higher bitrates should results in higher PSNR.
         for (int i = 1; i < TEST_BITRATES_SET.length ; i++) {
+            if (!completed[i]) {
+                continue;
+            }
             for (int j = 0; j < i; j++) {
+                if (!completed[j]) {
+                    continue;
+                }
                 double differenceBitrate = TEST_BITRATES_SET[i] - TEST_BITRATES_SET[j];
                 double differencePSNR = psnrPlatformCodecAverage[i] - psnrPlatformCodecAverage[j];
                 if (differenceBitrate * differencePSNR < 0) {
@@ -450,12 +471,16 @@
             }
         }
 
-        // Then compare average and minimum PSNR of platform codec with reference sw codec -
+        // Then compare average and minimum PSNR of platform codec with reference Google codec -
         // average PSNR for platform codec should be no more than 2 dB less than reference PSNR
         // and minumum PSNR - no more than 4 dB less than reference minimum PSNR.
         // These PSNR difference numbers are arbitrary for now, will need further estimation
-        // when more devices with hw VP8 codec will appear.
+        // when more devices with HW VP8 codec will appear.
         for (int i = 0; i < TEST_BITRATES_SET.length ; i++) {
+            if (!completed[i]) {
+                continue;
+            }
+
             Log.d(TAG, "Bitrate " + TEST_BITRATES_SET[i]);
             Log.d(TAG, "Reference: Average: " + REFERENCE_AVERAGE_PSNR[i] + ". Minimum: " +
                     REFERENCE_MINIMUM_PSNR[i]);
@@ -470,7 +495,7 @@
             if (psnrPlatformCodecMin[i] < REFERENCE_MINIMUM_PSNR[i] -
                     MAX_MINIMUM_PSNR_DIFFERENCE) {
                 throw new RuntimeException("Low minimum PSNR " + psnrPlatformCodecMin[i] +
-                        " comparing to sw PSNR " + REFERENCE_MINIMUM_PSNR[i] +
+                        " comparing to reference PSNR " + REFERENCE_MINIMUM_PSNR[i] +
                         " for bitrate " + TEST_BITRATES_SET[i]);
             }
         }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/H263QcifLongPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/H263QcifLongPlayerTest.java
index 482aec9..93dbdbd 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/H263QcifLongPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/H263QcifLongPlayerTest.java
@@ -16,6 +16,10 @@
 
 package android.mediastress.cts;
 
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder.AudioEncoder;
+import android.media.MediaRecorder.VideoEncoder;
+
 import com.android.cts.util.TimeoutReq;
 
 public class H263QcifLongPlayerTest extends MediaPlayerStressTest {
@@ -24,6 +28,10 @@
         "bbb_full.ffmpeg.176x144.3gp.h263_56kbps_12fps.libfaac_mono_24kbps_11025Hz.3gp"
     };
 
+    public H263QcifLongPlayerTest() {
+        super(CamcorderProfile.QUALITY_QCIF, VideoEncoder.H263, AudioEncoder.AAC);
+    }
+
     @TimeoutReq(minutes = 11)
     public void testPlay00() throws Exception {
         doTestVideoPlaybackLong(0);
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/H263QcifShortPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/H263QcifShortPlayerTest.java
index 2035869..392a2c8 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/H263QcifShortPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/H263QcifShortPlayerTest.java
@@ -16,6 +16,10 @@
 
 package android.mediastress.cts;
 
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder.AudioEncoder;
+import android.media.MediaRecorder.VideoEncoder;
+
 public class H263QcifShortPlayerTest extends MediaPlayerStressTest {
     private final static String VIDEO_PATH_MIDDLE = "bbb_short/176x144/3gp_h263_libfaac/";
     private final String[] mMedias = {
@@ -45,6 +49,10 @@
         "bbb_short.ffmpeg.176x144.3gp.h263_56kbps_25fps.libfaac_stereo_24kbps_22050Hz.3gp"
     };
 
+    public H263QcifShortPlayerTest() {
+        super(CamcorderProfile.QUALITY_QCIF, VideoEncoder.H263, AudioEncoder.AAC);
+    }
+
     public void testPlay00() throws Exception {
         doTestVideoPlaybackShort(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/H264R480x360AacShortPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/H264R480x360AacShortPlayerTest.java
index 12e8f6d..6d0afea 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/H264R480x360AacShortPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/H264R480x360AacShortPlayerTest.java
@@ -16,6 +16,10 @@
 
 package android.mediastress.cts;
 
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder.AudioEncoder;
+import android.media.MediaRecorder.VideoEncoder;
+
 public class H264R480x360AacShortPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_short/480x360/mp4_libx264_libfaac/";
     private final String[] mMedias = {
@@ -33,6 +37,10 @@
         "bbb_short.ffmpeg.480x360.mp4.libx264_500kbps_30fps.libfaac_stereo_192kbps_44100Hz.mp4"
     };
 
+    public H264R480x360AacShortPlayerTest() {
+        super(CamcorderProfile.QUALITY_480P, VideoEncoder.H264, AudioEncoder.AAC);
+    }
+
     public void testPlay00() throws Exception {
         doTestVideoPlaybackShort(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480x360AacShortPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480x360AacShortPlayerTest.java
index 78139ce..2099916 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480x360AacShortPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/HEVCR480x360AacShortPlayerTest.java
@@ -16,6 +16,10 @@
 
 package android.mediastress.cts;
 
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder.AudioEncoder;
+import android.media.MediaRecorder.VideoEncoder;
+
 public class HEVCR480x360AacShortPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_short/480x360/mp4_libx265_libfaac/";
     private final String[] mMedias = {
@@ -27,6 +31,10 @@
         "bbb_short.fmpeg.480x360.mp4.libx265_325kbps_30fps.libfaac_stereo_128kbps_48000hz.mp4"
     };
 
+    public HEVCR480x360AacShortPlayerTest() {
+        super(CamcorderProfile.QUALITY_480P, VideoEncoder.H264, AudioEncoder.AAC);
+    }
+
     public void testPlay00() throws Exception {
         doTestVideoPlaybackShort(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/MediaFrameworkTest.java b/tests/tests/mediastress/src/android/mediastress/cts/MediaFrameworkTest.java
index 09cc8b8..d39bc16 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/MediaFrameworkTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/MediaFrameworkTest.java
@@ -19,15 +19,13 @@
 import android.app.Activity;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Bitmap;
+import android.media.MediaFormat;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.PowerManager;
 import android.util.Log;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
-import android.view.ViewGroup;
-import android.widget.ImageView;
 
 import com.android.cts.mediastress.R;
 
@@ -85,7 +83,7 @@
     }
 
     public void startPlayback(String filename){
-      String mimetype = "audio/mpeg";
+      String mimetype = MediaFormat.MIMETYPE_AUDIO_MPEG;
       Uri path = Uri.parse(filename);
       Intent intent = new Intent(Intent.ACTION_VIEW);
       intent.setDataAndType(path, mimetype);
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
index d980e52..05bfb42 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
@@ -121,6 +121,7 @@
      */
     protected void doTestVideoPlayback(int mediaNumber, int repeatCounter) throws Exception {
         if (!mSupported) {
+            Log.i(TAG, "Not supported!");
             return;
         }
 
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/NativeMediaTest.java b/tests/tests/mediastress/src/android/mediastress/cts/NativeMediaTest.java
index 2119d75..05145f5 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/NativeMediaTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/NativeMediaTest.java
@@ -17,11 +17,11 @@
 
 import android.app.Instrumentation;
 import android.content.Intent;
+import android.cts.util.MediaUtils;
 import android.media.CamcorderProfile;
+import android.media.MediaFormat;
 import android.media.MediaRecorder.AudioEncoder;
 import android.media.MediaRecorder.VideoEncoder;
-import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
 import android.os.Environment;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
@@ -30,31 +30,11 @@
 
 public class NativeMediaTest extends ActivityInstrumentationTestCase2<NativeMediaActivity> {
     private static final String TAG = "NativeMediaTest";
-    private static final String MIME_TYPE = "video/h264";
+    private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
     private static final int VIDEO_CODEC = VideoEncoder.H264;
     private static final int NUMBER_PLAY_PAUSE_REPEATITIONS = 10;
     private static final long PLAY_WAIT_TIME_MS = 4000;
 
-    private static boolean hasCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     public NativeMediaTest() {
         super(NativeMediaActivity.class);
     }
@@ -77,9 +57,8 @@
 
     private void runPlayTest(int quality) throws InterruptedException {
         // Don't run the test if the codec isn't supported.
-        if (!hasCodec(MIME_TYPE)) {
-            Log.w(TAG, "Codec " + MIME_TYPE + " not supported.");
-            return;
+        if (!MediaUtils.checkDecoder(MIME_TYPE)) {
+            return; // skip
         }
         // Don't run the test if the quality level isn't supported.
         if (quality != 0) {
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360LongPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360LongPlayerTest.java
index 372f034..6b43558 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360LongPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360LongPlayerTest.java
@@ -16,12 +16,20 @@
 
 package android.mediastress.cts;
 
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder.AudioEncoder;
+import android.media.MediaRecorder.VideoEncoder;
+
 public class Vp8R480x360LongPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_full/480x360/webm_libvpx_libvorbis/";
     private final String[] mMedias = {
         "bbb_full.ffmpeg.480x360.webm.libvpx_500kbps_25fps.libvorbis_stereo_128kbps_44100Hz.webm"
     };
 
+    public Vp8R480x360LongPlayerTest() {
+        super(CamcorderProfile.QUALITY_480P, VideoEncoder.VP8, AudioEncoder.VORBIS);
+    }
+
     public void testPlay00() throws Exception {
         doTestVideoPlaybackLong(0);
     }
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360ShortPlayerTest.java b/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360ShortPlayerTest.java
index 30b4d2e..737032b 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360ShortPlayerTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/Vp8R480x360ShortPlayerTest.java
@@ -16,6 +16,10 @@
 
 package android.mediastress.cts;
 
+import android.media.CamcorderProfile;
+import android.media.MediaRecorder.AudioEncoder;
+import android.media.MediaRecorder.VideoEncoder;
+
 public class Vp8R480x360ShortPlayerTest extends MediaPlayerStressTest {
     private static final String VIDEO_PATH_MIDDLE = "bbb_short/480x360/webm_libvpx_libvorbis/";
     private final String[] mMedias = {
@@ -33,6 +37,10 @@
         "bbb_short.ffmpeg.480x360.webm.libvpx_500kbps_30fps.libvorbis_stereo_192kbps_44100Hz.webm"
     };
 
+    public Vp8R480x360ShortPlayerTest() {
+        super(CamcorderProfile.QUALITY_480P, VideoEncoder.VP8, AudioEncoder.VORBIS);
+    }
+
     public void testPlay00() throws Exception {
         doTestVideoPlaybackShort(0);
     }
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index d79ecdd..15d368f 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -197,7 +197,11 @@
     }
 
     private boolean isSupported(int networkType) {
-        return mNetworks.containsKey(networkType);
+        // Change-Id I02eb5f22737720095f646f8db5c87fd66da129d6 added VPN support
+        // to all devices directly in software, independent of any external
+        // configuration.
+        return mNetworks.containsKey(networkType) ||
+               (networkType == ConnectivityManager.TYPE_VPN);
     }
 
     // true if only the system can turn it on
diff --git a/tests/tests/net/src/android/net/cts/DnsTest.java b/tests/tests/net/src/android/net/cts/DnsTest.java
index 879a962..0377d04 100644
--- a/tests/tests/net/src/android/net/cts/DnsTest.java
+++ b/tests/tests/net/src/android/net/cts/DnsTest.java
@@ -16,6 +16,10 @@
 
 package android.net.cts;
 
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
 import android.os.SystemClock;
 import android.test.AndroidTestCase;
 import android.util.Log;
@@ -34,6 +38,7 @@
 
     private static final boolean DBG = false;
     private static final String TAG = "DnsTest";
+    private static final String PROXY_NETWORK_TYPE = "PROXY";
 
     /**
      * @return true on success
@@ -71,6 +76,14 @@
         // We should have at least one of the addresses to connect!
         assertTrue(foundV4 || foundV6);
 
+        // Skip the rest of the test if the active network for watch is PROXY.
+        // TODO: Check NetworkInfo type in addition to type name once ag/601257 is merged.
+        if (getContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
+                && activeNetworkInfoIsProxy()) {
+            Log.i(TAG, "Skipping test because the active network type name is PROXY.");
+            return;
+        }
+
         try {
             addrs = InetAddress.getAllByName("ipv6.google.com");
         } catch (UnknownHostException e) {}
@@ -241,4 +254,15 @@
             Log.e(TAG, "bad URL in testDnsPerf: " + e.toString());
         }
     }
+
+    private boolean activeNetworkInfoIsProxy() {
+        ConnectivityManager cm = (ConnectivityManager)
+                getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
+        NetworkInfo info = cm.getActiveNetworkInfo();
+        if (PROXY_NETWORK_TYPE.equals(info.getTypeName())) {
+            return true;
+        }
+
+        return false;
+    }
 }
diff --git a/tests/tests/net/src/android/net/ipv6/cts/PingTest.java b/tests/tests/net/src/android/net/ipv6/cts/PingTest.java
index 49fc59c..eddb416 100644
--- a/tests/tests/net/src/android/net/ipv6/cts/PingTest.java
+++ b/tests/tests/net/src/android/net/ipv6/cts/PingTest.java
@@ -53,6 +53,9 @@
     /** Maximum size of the packets we're using to test. */
     private static final int MAX_SIZE = 4096;
 
+    /** Size of the ICMPv6 header. */
+    private static final int ICMP_HEADER_SIZE = 8;
+
     /** Number of packets to test. */
     private static final int NUM_PACKETS = 10;
 
@@ -65,7 +68,7 @@
      * Returns a byte array containing an ICMPv6 echo request with the specified payload length.
      */
     private byte[] pingPacket(int payloadLength) {
-        byte[] packet = new byte[payloadLength + 8];
+        byte[] packet = new byte[payloadLength + ICMP_HEADER_SIZE];
         new Random().nextBytes(packet);
         System.arraycopy(PING_HEADER, 0, packet, 0, PING_HEADER.length);
         return packet;
@@ -130,6 +133,7 @@
 
         // Check the response is an echo reply.
         byte[] response = new byte[bytesRead];
+        responseBuffer.flip();
         responseBuffer.get(response, 0, bytesRead);
         assertEquals((byte) 0x81, response[0]);
 
@@ -154,7 +158,7 @@
         assertEquals("localhost/::1", ipv6Loopback.toString());
 
         for (int i = 0; i < NUM_PACKETS; i++) {
-            byte[] packet = pingPacket((int) (Math.random() * MAX_SIZE));
+            byte[] packet = pingPacket((int) (Math.random() * (MAX_SIZE - ICMP_HEADER_SIZE)));
             FileDescriptor s = createPingSocket();
             // Use both recvfrom and read().
             sendPing(s, ipv6Loopback, packet);
diff --git a/tests/tests/opengl/src/android/opengl/cts/ProgramTest.java b/tests/tests/opengl/src/android/opengl/cts/ProgramTest.java
index 85009d2..dab2ef1 100644
--- a/tests/tests/opengl/src/android/opengl/cts/ProgramTest.java
+++ b/tests/tests/opengl/src/android/opengl/cts/ProgramTest.java
@@ -32,7 +32,9 @@
         intent.putExtra(OpenGLES20NativeActivityOne.EXTRA_VIEW_TYPE, viewType);
         intent.putExtra(OpenGLES20NativeActivityOne.EXTRA_VIEW_INDEX, viewIndex);
         setActivityIntent(intent);
-        return getActivity();
+        OpenGLES20ActivityOne activity = getActivity();
+        assertTrue(activity.waitForFrameDrawn());
+        return activity;
     }
 
     public void test_glAttachShader_program() throws Throwable {
diff --git a/tests/tests/os/src/android/os/cts/BuildVersionTest.java b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
index e335901..3002ca3 100644
--- a/tests/tests/os/src/android/os/cts/BuildVersionTest.java
+++ b/tests/tests/os/src/android/os/cts/BuildVersionTest.java
@@ -29,7 +29,7 @@
 
     private static final String LOG_TAG = "BuildVersionTest";
     private static final Set<String> EXPECTED_RELEASES =
-            new HashSet<String>(Arrays.asList("5.0", "5.0.1", "5.0.2"));
+            new HashSet<String>(Arrays.asList("5.0.1", "5.0.2"));
     private static final int EXPECTED_SDK = 21;
     private static final String EXPECTED_BUILD_VARIANT = "user";
     private static final String EXPECTED_TAG = "release-keys";
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index c31c484..babc93f 100755
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -151,6 +151,14 @@
     }
 
     @MediumTest
+    public void testDevDiagSane() throws Exception {
+        File f = new File("/dev/diag");
+        assertFalse(f.canRead());
+        assertFalse(f.canWrite());
+        assertFalse(f.canExecute());
+    }
+
+    @MediumTest
     public void testDevMemSane() throws Exception {
         File f = new File("/dev/mem");
         assertFalse(f.canRead());
diff --git a/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java b/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
index 8979a07..7b3799d 100644
--- a/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/NoReadLogsPermissionTest.java
@@ -48,7 +48,7 @@
         BufferedReader reader = null;
         try {
             logcatProc = Runtime.getRuntime().exec(new String[]
-                    {"logcat", "-d", "ActivityManager:* *:S" });
+                    {"logcat", "-v", "brief", "-d", "ActivityManager:* *:S" });
 
             reader = new BufferedReader(new InputStreamReader(logcatProc.getInputStream()));
 
diff --git a/tests/tests/permission/src/android/permission/cts/TvPermissionTest.java b/tests/tests/permission/src/android/permission/cts/TvPermissionTest.java
new file mode 100644
index 0000000..0c648c5
--- /dev/null
+++ b/tests/tests/permission/src/android/permission/cts/TvPermissionTest.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.permission.cts;
+
+import android.content.ContentValues;
+import android.content.pm.PackageManager;
+import android.media.tv.TvContract;
+import android.net.Uri;
+import android.test.AndroidTestCase;
+
+/**
+ * Tests for TV API related permissions.
+ */
+public class TvPermissionTest extends AndroidTestCase {
+    private static final String DUMMY_INPUT_ID = "dummy";
+
+    private boolean mHasTvInputFramework;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mHasTvInputFramework = getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_LIVE_TV);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private void verifyQuery(Uri uri, final String[] projection,
+            String tableName) throws Exception {
+        try {
+            getContext().getContentResolver().query(uri, projection, null, null, null);
+            fail("Accessing " + tableName + " table should require READ_EPG_DATA permission.");
+        } catch (SecurityException e) {
+            // Expected exception
+        }
+    }
+
+    public void verifyInsert(Uri uri, String tableName) throws Exception {
+        try {
+            ContentValues values = new ContentValues();
+            getContext().getContentResolver().insert(uri, values);
+            fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
+        } catch (SecurityException e) {
+            // Expected exception
+        }
+    }
+
+    public void verifyUpdate(Uri uri, String tableName) throws Exception {
+        try {
+            ContentValues values = new ContentValues();
+            getContext().getContentResolver().update(uri, values, null, null);
+            fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
+        } catch (SecurityException e) {
+            // Expected exception
+        }
+    }
+
+    public void verifyDelete(Uri uri, String tableName) throws Exception {
+        try {
+            getContext().getContentResolver().delete(uri, null, null);
+            fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
+        } catch (SecurityException e) {
+            // Expected exception
+        }
+    }
+
+    public void testQueryChannels() throws Exception {
+        if (!mHasTvInputFramework) return;
+        final String[] projection = { TvContract.Channels._ID };
+        verifyQuery(TvContract.Channels.CONTENT_URI, projection, "channels");
+    }
+
+    public void testInsertChannels() throws Exception {
+        if (!mHasTvInputFramework) return;
+        verifyInsert(TvContract.Channels.CONTENT_URI, "channels");
+    }
+
+    public void testUpdateChannels() throws Exception {
+        if (!mHasTvInputFramework) return;
+        verifyUpdate(TvContract.Channels.CONTENT_URI, "channels");
+    }
+
+    public void testDeleteChannels() throws Exception {
+        if (!mHasTvInputFramework) return;
+        verifyDelete(TvContract.Channels.CONTENT_URI, "channels");
+    }
+
+    public void testQueryPrograms() throws Exception {
+        if (!mHasTvInputFramework) return;
+        final String[] projection = { TvContract.Programs._ID };
+        verifyQuery(TvContract.Programs.CONTENT_URI, projection, "programs");
+    }
+
+    public void testInsertPrograms() throws Exception {
+        if (!mHasTvInputFramework) return;
+        verifyInsert(TvContract.Programs.CONTENT_URI, "programs");
+    }
+
+    public void testUpdatePrograms() throws Exception {
+        if (!mHasTvInputFramework) return;
+        verifyUpdate(TvContract.Programs.CONTENT_URI, "programs");
+    }
+
+    public void testDeletePrograms() throws Exception {
+        if (!mHasTvInputFramework) return;
+        verifyDelete(TvContract.Programs.CONTENT_URI, "programs");
+    }
+}
diff --git a/tests/tests/print/src/android/print/cts/BasePrintTest.java b/tests/tests/print/src/android/print/cts/BasePrintTest.java
index 1493bc9..c73bb64 100644
--- a/tests/tests/print/src/android/print/cts/BasePrintTest.java
+++ b/tests/tests/print/src/android/print/cts/BasePrintTest.java
@@ -25,6 +25,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.pdf.PdfDocument;
@@ -458,4 +459,8 @@
             }
         }
     }
+
+    protected boolean supportsPrinting() {
+        return getActivity().getPackageManager().hasSystemFeature(PackageManager.FEATURE_PRINTING);
+    }
 }
diff --git a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
index 4952cbd..b9fd50a 100644
--- a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
+++ b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
@@ -62,6 +62,10 @@
     private static final String FIRST_PRINTER = "First printer";
 
     public void testAllPagesWantedAndAllPagesWritten() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
+
         // Create a callback for the target print service.
         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
             new Answer<PrinterDiscoverySessionCallbacks>() {
@@ -161,6 +165,10 @@
     }
 
     public void testSomePagesWantedAndAllPagesWritten() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
+
         // Create a callback for the target print service.
         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
             new Answer<PrinterDiscoverySessionCallbacks>() {
@@ -269,6 +277,10 @@
     }
 
     public void testSomePagesWantedAndSomeMorePagesWritten() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
+
         // Create a callback for the target print service.
         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
             new Answer<PrinterDiscoverySessionCallbacks>() {
@@ -393,6 +405,10 @@
     }
 
     public void testSomePagesWantedAndNotWritten() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
+
         // Create a callback for the target print service.
         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
             new Answer<PrinterDiscoverySessionCallbacks>() {
@@ -481,6 +497,10 @@
     }
 
     public void testWantedPagesAlreadyWrittenForPreview() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
+
         // Create a callback for the target print service.
         PrintServiceCallbacks firstServiceCallbacks = createMockPrintServiceCallbacks(
             new Answer<PrinterDiscoverySessionCallbacks>() {
diff --git a/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java b/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
index 516db56..538472e 100644
--- a/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
@@ -59,6 +59,9 @@
 public class PrintDocumentAdapterContractTest extends BasePrintTest {
 
     public void testNoPrintOptionsOrPrinterChange() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -175,6 +178,9 @@
     }
 
     public void testNoPrintOptionsOrPrinterChangeCanceled() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -265,6 +271,9 @@
     }
 
     public void testPrintOptionsChangeAndNoPrinterChange() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -439,6 +448,9 @@
     }
 
     public void testPrintOptionsChangeAndPrinterChange() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -594,6 +606,9 @@
 
     public void testPrintOptionsChangeAndNoPrinterChangeAndContentChange()
             throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -718,6 +733,9 @@
     }
 
     public void testNewPrinterSupportsSelectedPrintOptions() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -820,6 +838,9 @@
     }
 
     public void testNothingChangesAllPagesWrittenFirstTime() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -941,6 +962,9 @@
     }
 
     public void testCancelLongRunningLayout() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -1015,6 +1039,9 @@
     }
 
     public void testCancelLongRunningWrite() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -1111,6 +1138,9 @@
     }
 
     public void testFailedLayout() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -1177,6 +1207,9 @@
     }
 
     public void testFailedWrite() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -1259,6 +1292,9 @@
     }
 
     public void testRequestedPagesNotWritten() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -1348,6 +1384,9 @@
     }
 
     public void testLayoutCallbackNotCalled() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
@@ -1411,6 +1450,9 @@
     }
 
     public void testWriteCallbackNotCalled() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Configure the print services.
         FirstPrintService.setCallbacks(createFirstMockPrintServiceCallbacks());
         SecondPrintService.setCallbacks(createSecondMockPrintServiceCallbacks());
diff --git a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
index b092044..7ea09e5 100644
--- a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
@@ -61,6 +61,9 @@
     private static final String SECOND_PRINTER_LOCAL_ID = "second_printer";
 
     public void testNormalLifecycle() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Create the session callbacks that we will be checking.
         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
                 createFirstMockPrinterDiscoverySessionCallbacks();
@@ -155,6 +158,9 @@
     }
 
     public void testStartPrinterDiscoveryWithHistoricalPrinters() throws Exception {
+        if (!supportsPrinting()) {
+            return;
+        }
         // Create the session callbacks that we will be checking.
         final PrinterDiscoverySessionCallbacks firstSessionCallbacks =
                 createFirstMockPrinterDiscoverySessionCallbacks();
diff --git a/tests/tests/provider/src/android/provider/cts/CalendarTest.java b/tests/tests/provider/src/android/provider/cts/CalendarTest.java
index a8f547b..2f5edd0 100644
--- a/tests/tests/provider/src/android/provider/cts/CalendarTest.java
+++ b/tests/tests/provider/src/android/provider/cts/CalendarTest.java
@@ -352,6 +352,7 @@
             Events.SYNC_DATA2,
             Events.SYNC_DATA3,
             Events.SYNC_DATA4,
+            Events.MUTATORS,
         };
         // @formatter:on
 
@@ -3275,6 +3276,67 @@
         CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
     }
 
+    @MediumTest
+    public void testMutatorSetCorrectly() {
+        String account = "ec_account";
+        String packageName = "com.android.cts.provider";
+        int seed = 0;
+
+        // Clean up just in case
+        CalendarHelper.deleteCalendarByAccount(mContentResolver, account);
+
+        String mutator;
+        Cursor cursor;
+        ContentValues values = new ContentValues();
+        final long calendarId = createAndVerifyCalendar(account, seed++, null);
+
+        // Verify mutator is set to the package, via:
+        // Create:
+        final long eventId = createAndVerifyEvent(account, seed, calendarId, false, null);
+        final Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventId);
+        cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
+        cursor.moveToFirst();
+        mutator = cursor.getString(0);
+        cursor.close();
+        assertEquals(packageName, mutator);
+
+        // Edit:
+        // First clear the mutator column
+        values.clear();
+        values.putNull(Events.MUTATORS);
+        mContentResolver.update(asSyncAdapter(uri, account, CTS_TEST_TYPE), values, null, null);
+        cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
+        cursor.moveToFirst();
+        mutator = cursor.getString(0);
+        cursor.close();
+        assertNull(mutator);
+        // Now edit the event and verify the mutator column
+        values.clear();
+        values.put(Events.TITLE, "New title");
+        mContentResolver.update(uri, values, null, null);
+        cursor = mContentResolver.query(uri, new String[] {Events.MUTATORS}, null, null, null);
+        cursor.moveToFirst();
+        mutator = cursor.getString(0);
+        cursor.close();
+        assertEquals(packageName, mutator);
+
+        // Clean up the event
+        assertEquals(1, EventHelper.deleteEventAsSyncAdapter(mContentResolver, uri, account));
+
+        // Delete:
+        // First create as sync adapter
+        final long eventId2 = createAndVerifyEvent(account, seed, calendarId, true, null);
+        final Uri uri2 = ContentUris.withAppendedId(Events.CONTENT_URI, eventId2);
+        // Now delete the event and verify
+        values.clear();
+        values.put(Events.MUTATORS, packageName);
+        removeAndVerifyEvent(uri2, values, account);
+
+
+        // delete the calendar
+        removeAndVerifyCalendar(account, calendarId);
+    }
+
     /**
      * Acquires the set of instances that appear between the specified start and end points.
      *
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
index b6175be..974eeb7 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Video_ThumbnailsTest.java
@@ -22,6 +22,7 @@
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.cts.util.FileCopyHelper;
+import android.cts.util.MediaUtils;
 import android.database.Cursor;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecList;
@@ -38,32 +39,13 @@
 
 public class MediaStore_Video_ThumbnailsTest extends AndroidTestCase {
     private static final String TAG = "MediaStore_Video_ThumbnailsTest";
-    private static final String MIME_TYPE = "video/3gpp";
 
     private ContentResolver mResolver;
 
     private FileCopyHelper mFileHelper;
 
-    // TODO: Make a public method selectCodec() in common libraries (e.g. cts/libs/), to avoid
-    // redundant function definitions in this and other media related test files.
-    private static boolean hasCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return true;
-                }
-            }
-        }
-        return false;
+    private boolean hasCodec() {
+        return MediaUtils.hasCodecForResourceAndDomain(mContext, R.raw.testvideo, "video/");
     }
 
     @Override
@@ -98,10 +80,10 @@
         int count = getThumbnailCount(Thumbnails.EXTERNAL_CONTENT_URI);
 
         // Don't run the test if the codec isn't supported.
-        if (!hasCodec(MIME_TYPE)) {
+        if (!hasCodec()) {
             // Calling getThumbnail should not generate a new thumbnail.
             assertNull(Thumbnails.getThumbnail(mResolver, videoId, Thumbnails.MINI_KIND, null));
-            Log.w(TAG, "Codec " + MIME_TYPE + " not supported. Return from testGetThumbnail.");
+            Log.i(TAG, "SKIPPING testGetThumbnail(): codec not supported");
             return;
         }
 
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicConvolve3x3.java b/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicConvolve3x3.java
index 1880132..8faeb22 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicConvolve3x3.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicConvolve3x3.java
@@ -24,11 +24,17 @@
         float cf1[] = {0.f, 0.f, 0.f,  0.f, 1.f, 0.f,  0.f, 0.f, 0.f};
         float cf2[] = {0.f, -1.f, 0.f,  -1.f, 5.f, -1.f,  0.f, -1.f, 0.f};
 
+        float irCoeff1 = 3.1415927f;
+        float irCoeff2 = -irCoeff1;
+        float cf3[] = {0.f, irCoeff1, 0.f,  irCoeff2, 1.f, irCoeff2,  0.f, irCoeff1, 0.f};
+
         Element e = makeElement(dt, vecSize);
 
         System.gc();
         makeBuffers(w, h, e);
 
+        mVerify.set_gAllowedIntError(1);
+
         ScriptIntrinsicConvolve3x3 si = ScriptIntrinsicConvolve3x3.create(mRS, e);
         si.setCoefficients(cf1);
         si.setInput(mAllocSrc);
@@ -116,6 +122,44 @@
         }
         //android.util.Log.e("RSI test", "test convolve U8_" + vecSize + " 2 " + w + ", " + h);
         mVerify.invoke_verify(mAllocRef, mAllocDst, mAllocSrc);
+
+        si.setCoefficients(cf3);
+        sr.set_gCoeffs(cf3);
+        si.forEach(mAllocRef, sc);
+        if (dt == Element.DataType.UNSIGNED_8) {
+            switch(vecSize) {
+            case 4:
+                sr.forEach_convolve_U4(mAllocDst, sc);
+                break;
+            case 3:
+                sr.forEach_convolve_U3(mAllocDst, sc);
+                break;
+            case 2:
+                sr.forEach_convolve_U2(mAllocDst, sc);
+                break;
+            case 1:
+                sr.forEach_convolve_U1(mAllocDst, sc);
+                break;
+            }
+        } else {
+            switch(vecSize) {
+            case 4:
+                sr.forEach_convolve_F4(mAllocDst, sc);
+                break;
+            case 3:
+                sr.forEach_convolve_F3(mAllocDst, sc);
+                break;
+            case 2:
+                sr.forEach_convolve_F2(mAllocDst, sc);
+                break;
+            case 1:
+                sr.forEach_convolve_F1(mAllocDst, sc);
+                break;
+            }
+        }
+        //android.util.Log.e("RSI test", "test convolve U8_" + vecSize + " 2 " + w + ", " + h);
+        mVerify.invoke_verify(mAllocRef, mAllocDst, mAllocSrc);
+
         mRS.finish();
     }
 
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicConvolve5x5.java b/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicConvolve5x5.java
index ee92651..0753c62 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicConvolve5x5.java
+++ b/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicConvolve5x5.java
@@ -85,13 +85,24 @@
                        -1.f,  0.f,  0.f,  0.f, -1.f,
                        -1.f, -1.f, -1.f, -1.f, -1.f};
 
+        float irCoeff1 = 3.1415927f;
+        float irCoeff2 = -irCoeff1;
+        float cf3[] = {irCoeff1, -1.f, -1.f, -1.f, irCoeff2,
+                       irCoeff1,  0.f,  0.f,  0.f, irCoeff2,
+                       irCoeff1,  0.f,  7.f,  0.f, irCoeff2,
+                       irCoeff1,  0.f,  0.f,  0.f, irCoeff2,
+                       irCoeff1, -1.f, -1.f, -1.f, irCoeff2};
+
         Element e = makeElement(dt, vecSize);
         makeBuffers(w, h, e);
 
+        mVerify.set_gAllowedIntError(1);
+
         ScriptIntrinsicConvolve5x5 si = ScriptIntrinsicConvolve5x5.create(mRS, e);
         ScriptC_intrinsic_convolve5x5 sr = new ScriptC_intrinsic_convolve5x5(mRS);
         test5(sr, si, e, cf1, "test convolve", 1, w, h, sc);
         test5(sr, si, e, cf2, "test convolve", 2, w, h, sc);
+        test5(sr, si, e, cf3, "test convolve", 3, w, h, sc);
     }
 
     public void test_U8_4() {
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java b/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java
new file mode 100644
index 0000000..d593bff
--- /dev/null
+++ b/tests/tests/renderscript/src/android/renderscript/cts/IntrinsicResize.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.renderscript.cts;
+
+import android.renderscript.*;
+import android.util.Log;
+
+public class IntrinsicResize extends IntrinsicBase {
+
+    static final int inX = 307;
+    static final int inY = 157;
+
+    private void testReszie(int w, int h, Element.DataType dt, int vecSize, float scaleX, float scaleY) {
+
+        Element e = makeElement(dt, vecSize);
+
+        System.gc();
+        makeSource(w, h, e);
+
+        int outW = (int) (w*scaleX);
+        int outH = (int) (h*scaleY);
+        if (mAllocRef != null) {
+            mAllocRef.destroy();
+        }
+        if (mAllocDst != null) {
+            mAllocDst.destroy();
+        }
+        mAllocRef = makeAllocation(outW, outH, e);
+        mAllocDst = makeAllocation(outW, outH, e);
+
+        ScriptIntrinsicResize si = ScriptIntrinsicResize.create(mRS);
+        si.setInput(mAllocSrc);
+        si.forEach_bicubic(mAllocRef);
+
+        ScriptC_intrinsic_resize sr = new ScriptC_intrinsic_resize(mRS);
+        sr.set_scaleX((float)w/outW);
+        sr.set_scaleY((float)h/outH);
+        sr.set_gIn(mAllocSrc);
+        sr.set_gWidthIn(w);
+        sr.set_gHeightIn(h);
+        if (dt == Element.DataType.UNSIGNED_8) {
+            switch(vecSize) {
+            case 4:
+                sr.forEach_bicubic_U4(mAllocDst);
+                break;
+            case 3:
+                sr.forEach_bicubic_U3(mAllocDst);
+                break;
+            case 2:
+                sr.forEach_bicubic_U2(mAllocDst);
+                break;
+            case 1:
+                sr.forEach_bicubic_U1(mAllocDst);
+                break;
+            }
+        }
+
+        mVerify.invoke_verify(mAllocRef, mAllocDst, mAllocSrc);
+        if (outW == w && outH == h) {
+            //when scale = 1, check with the original.
+            mVerify.invoke_verify(mAllocRef, mAllocSrc, mAllocSrc);
+            mVerify.invoke_verify(mAllocDst, mAllocSrc, mAllocSrc);
+        }
+        mRS.finish();
+    }
+
+
+    public void test_U8_4_SCALE10_10_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f);
+        checkError();
+    }
+    public void test_U8_3_SCALE10_10_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f);
+        checkError();
+    }
+    public void test_U8_2_SCALE10_10_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f);
+        checkError();
+    }
+    public void test_U8_1_SCALE10_10_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE20_20_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f);
+        checkError();
+    }
+    public void test_U8_3_SCALE20_20_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f);
+        checkError();
+    }
+    public void test_U8_2_SCALE20_20_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f);
+        checkError();
+    }
+    public void test_U8_1_SCALE20_20_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE05_20_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f);
+        checkError();
+    }
+    public void test_U8_3_SCALE05_20_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f);
+        checkError();
+    }
+    public void test_U8_2_SCALE05_20_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f);
+        checkError();
+    }
+    public void test_U8_1_SCALE05_20_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE20_05_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f);
+        checkError();
+    }
+    public void test_U8_3_SCALE20_05_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f);
+        checkError();
+    }
+    public void test_U8_2_SCALE20_05_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f);
+        checkError();
+    }
+    public void test_U8_1_SCALE20_05_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE05_05_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f);
+        checkError();
+    }
+    public void test_U8_3_SCALE05_05_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f);
+        checkError();
+    }
+    public void test_U8_2_SCALE05_05_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f);
+        checkError();
+    }
+    public void test_U8_1_SCALE05_05_inSqure() {
+        testReszie(inX, inX, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE10_10_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 4, 1.f, 1.f);
+        checkError();
+    }
+    public void test_U8_3_SCALE10_10_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 3, 1.f, 1.f);
+        checkError();
+    }
+    public void test_U8_2_SCALE10_10_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 2, 1.f, 1.f);
+        checkError();
+    }
+    public void test_U8_1_SCALE10_10_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 1, 1.f, 1.f);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE20_20_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 2.f);
+        checkError();
+    }
+    public void test_U8_3_SCALE20_20_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 2.f);
+        checkError();
+    }
+    public void test_U8_2_SCALE20_20_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 2.f);
+        checkError();
+    }
+    public void test_U8_1_SCALE20_20_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 2.f);
+        checkError();
+    }
+
+    public void test_U8_4_SCALE05_20_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 2.f);
+        checkError();
+    }
+    public void test_U8_3_SCALE05_20_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 2.f);
+        checkError();
+    }
+    public void test_U8_2_SCALE05_20_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 2.f);
+        checkError();
+    }
+    public void test_U8_1_SCALE05_20_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 2.f);
+        checkError();
+    }
+    
+    public void test_U8_4_SCALE20_05_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 4, 2.f, 0.5f);
+        checkError();
+    }
+    public void test_U8_3_SCALE20_05_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 3, 2.f, 0.5f);
+        checkError();
+    }
+    public void test_U8_2_SCALE20_05_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 2, 2.f, 0.5f);
+        checkError();
+    }
+    public void test_U8_1_SCALE20_05_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 1, 2.f, 0.5f);
+        checkError();
+    }
+    
+    public void test_U8_4_SCALE05_05_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 4, 0.5f, 0.5f);
+        checkError();
+    }
+    public void test_U8_3_SCALE05_05_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 3, 0.5f, 0.5f);
+        checkError();
+    }
+    public void test_U8_2_SCALE05_05_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 2, 0.5f, 0.5f);
+        checkError();
+    }
+    public void test_U8_1_SCALE05_05_inRectangle() {
+        testReszie(inX, inY, Element.DataType.UNSIGNED_8, 1, 0.5f, 0.5f);
+        checkError();
+    }
+}
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/TestVLoad.java b/tests/tests/renderscript/src/android/renderscript/cts/TestVLoad.java
new file mode 100644
index 0000000..a2d22d9
--- /dev/null
+++ b/tests/tests/renderscript/src/android/renderscript/cts/TestVLoad.java
@@ -0,0 +1,371 @@
+ /*
+ * 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.renderscript.cts;
+
+import android.renderscript.*;
+
+public class TestVLoad extends RSBaseCompute {
+
+    private ScriptC_vload script;
+    private ScriptC_vload_relaxed scriptRelaxed;
+    Allocation walkAlloc;
+    Allocation inAlloc;
+    Allocation outAlloc;
+    private static java.util.Random random = new java.util.Random();
+
+    final int w = 253;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        random.setSeed(10);
+        script = new ScriptC_vload(mRS);
+        scriptRelaxed = new ScriptC_vload_relaxed(mRS);
+    }
+
+
+
+    protected void createWalk() {
+        int tmp[] = new int[w];
+        boolean b[] = new boolean[w];
+        int toCopy = w;
+        int i = 0;
+
+        while (toCopy > 0) {
+            int x = random.nextInt(w);
+
+            //android.util.Log.v("rs", "x " + x + ", y " + y + ", toCopy " + toCopy);
+            while ((x < w) && b[x]) {
+                x++;
+                if (x >= w) {
+                    x = 0;
+                }
+            }
+
+            int maxsize = 1;
+            b[x] = true;
+            if ((x+1 < w) && !b[x+1]) {
+                maxsize ++;
+                b[x+1] = true;
+                if ((x+2 < w) && !b[x+2]) {
+                    maxsize ++;
+                    b[x+2] = true;
+                    if ((x+3 < w) && !b[x+3]) {
+                        maxsize ++;
+                        b[x+3] = true;
+                    }
+                }
+            }
+
+            toCopy -= maxsize;
+            tmp[i] = x | (maxsize << 16);
+            android.util.Log.v("rs", "x " + x + ", vec " + maxsize);
+            i++;
+        }
+
+        walkAlloc = Allocation.createSized(mRS, Element.I32(mRS), i);
+        walkAlloc.copy1DRangeFrom(0, i, tmp);
+    }
+
+    private void testSetup(Type t) {
+        createWalk();
+
+        inAlloc = Allocation.createTyped(mRS, t);
+        outAlloc = Allocation.createTyped(mRS, t);
+        script.set_gAllocIn(inAlloc);
+        script.set_gAllocOut(outAlloc);
+        scriptRelaxed.set_gAllocIn(inAlloc);
+        scriptRelaxed.set_gAllocOut(outAlloc);
+    }
+
+    private void verify(byte[] a1, byte[] a2, String s) {
+        outAlloc.copyTo(a2);
+        for (int i=0; i < w; i++) {
+            if (a1[i] != a2[i]) {
+                throw new RSRuntimeException(s + a1[i] + ", " + a2[i] + ", at " + i);
+            }
+            a2[i] = 0;
+        }
+        outAlloc.copyFrom(a2);
+    }
+
+    private void verify(short[] a1, short[] a2, String s) {
+        outAlloc.copyTo(a2);
+        for (int i=0; i < w; i++) {
+            if (a1[i] != a2[i]) {
+                throw new RSRuntimeException(s + a1[i] + ", " + a2[i] + ", at " + i);
+            }
+            a2[i] = 0;
+        }
+        outAlloc.copyFrom(a2);
+    }
+
+    private void verify(int[] a1, int[] a2, String s) {
+        outAlloc.copyTo(a2);
+        for (int i=0; i < w; i++) {
+            if (a1[i] != a2[i]) {
+                throw new RSRuntimeException(s + a1[i] + ", " + a2[i] + ", at " + i);
+            }
+            a2[i] = 0;
+        }
+        outAlloc.copyFrom(a2);
+    }
+
+    private void verify(long[] a1, long[] a2, String s) {
+        outAlloc.copyTo(a2);
+        for (int i=0; i < w; i++) {
+            if (a1[i] != a2[i]) {
+                throw new RSRuntimeException(s + a1[i] + ", " + a2[i] + ", at " + i);
+            }
+            a2[i] = 0;
+        }
+        outAlloc.copyFrom(a2);
+    }
+
+    private void verify(float[] a1, float[] a2, String s) {
+        outAlloc.copyTo(a2);
+        for (int i=0; i < w; i++) {
+            if (a1[i] != a2[i]) {
+                throw new RSRuntimeException(s + a1[i] + ", " + a2[i] + ", at " + i);
+            }
+            a2[i] = 0;
+        }
+        outAlloc.copyFrom(a2);
+    }
+
+    private void verify(double[] a1, double[] a2, String s) {
+        outAlloc.copyTo(a2);
+        for (int i=0; i < w; i++) {
+            if (a1[i] != a2[i]) {
+                throw new RSRuntimeException(s + a1[i] + ", " + a2[i] + ", at " + i);
+            }
+            a2[i] = 0;
+        }
+        outAlloc.copyFrom(a2);
+    }
+
+    private byte[] randomByteArray(int len) {
+        byte t[] = new byte[len];
+        random.nextBytes(t);
+        inAlloc.copyFrom(t);
+        return t;
+    }
+
+    private short[] randomShortArray(int len) {
+        short t[] = new short[len];
+        for (int i = 0; i < t.length; i++) {
+            t[i] = (short)(random.nextInt() & 0xffff);
+        }
+        inAlloc.copyFrom(t);
+        return t;
+    }
+
+    private int[] randomIntArray(int len) {
+        int t[] = new int[len];
+        for (int i = 0; i < t.length; i++) {
+            t[i] = random.nextInt();
+        }
+        inAlloc.copyFrom(t);
+        return t;
+    }
+
+    private long[] randomLongArray(int len) {
+        long t[] = new long[len];
+        for (int i = 0; i < t.length; i++) {
+            t[i] = random.nextLong();
+        }
+        inAlloc.copyFrom(t);
+        return t;
+    }
+
+    public void testVload_char() {
+        testSetup(Type.createX(mRS, Element.I8(mRS), w));
+        byte tmp[] = randomByteArray(w);
+        byte tmp2[] = new byte[w];
+        script.forEach_copy2d_char(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch char: ");
+    }
+
+    public void testVload_uchar() {
+        testSetup(Type.createX(mRS, Element.I8(mRS), w));
+        byte tmp[] = randomByteArray(w);
+        byte tmp2[] = new byte[w];
+        script.forEach_copy2d_uchar(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch uchar: ");
+    }
+
+    public void testVload_char_relaxed() {
+        testSetup(Type.createX(mRS, Element.I8(mRS), w));
+        byte tmp[] = randomByteArray(w);
+        byte tmp2[] = new byte[w];
+        scriptRelaxed.forEach_copy2d_char(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch relaxed char: ");
+    }
+
+    public void testVload_uchar_relaxed() {
+        testSetup(Type.createX(mRS, Element.I8(mRS), w));
+        byte tmp[] = randomByteArray(w);
+        byte tmp2[] = new byte[w];
+        scriptRelaxed.forEach_copy2d_uchar(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch relaxed uchar: ");
+    }
+
+    public void testVload_short() {
+        testSetup(Type.createX(mRS, Element.I16(mRS), w));
+        short tmp[] = randomShortArray(w);
+        short tmp2[] = new short[w];
+        script.forEach_copy2d_short(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch short: ");
+    }
+
+    public void testVload_ushort() {
+        testSetup(Type.createX(mRS, Element.I16(mRS), w));
+        short tmp[] = randomShortArray(w);
+        short tmp2[] = new short[w];
+        script.forEach_copy2d_ushort(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch ushort: ");
+    }
+
+    public void testVload_short_relaxed() {
+        testSetup(Type.createX(mRS, Element.I16(mRS), w));
+        short tmp[] = randomShortArray(w);
+        short tmp2[] = new short[w];
+        scriptRelaxed.forEach_copy2d_short(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch relaxed short: ");
+    }
+
+    public void testVload_ushort_relaxed() {
+        testSetup(Type.createX(mRS, Element.I16(mRS), w));
+        short tmp[] = randomShortArray(w);
+        short tmp2[] = new short[w];
+        scriptRelaxed.forEach_copy2d_ushort(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch ushort: ");
+    }
+
+    public void testVload_int() {
+        testSetup(Type.createX(mRS, Element.I32(mRS), w));
+        int tmp[] = randomIntArray(w);
+        int tmp2[] = new int[w];
+        script.forEach_copy2d_int(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch int: ");
+    }
+
+    public void testVload_uint() {
+        testSetup(Type.createX(mRS, Element.I32(mRS), w));
+        int tmp[] = randomIntArray(w);
+        int tmp2[] = new int[w];
+        script.forEach_copy2d_uint(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch uint: ");
+    }
+
+    public void testVload_int_relaxed() {
+        testSetup(Type.createX(mRS, Element.I32(mRS), w));
+        int tmp[] = randomIntArray(w);
+        int tmp2[] = new int[w];
+        scriptRelaxed.forEach_copy2d_int(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch relaxed int: ");
+    }
+
+    public void testVload_uint_relaxed() {
+        testSetup(Type.createX(mRS, Element.I32(mRS), w));
+        int tmp[] = randomIntArray(w);
+        int tmp2[] = new int[w];
+        scriptRelaxed.forEach_copy2d_uint(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch uint: ");
+    }
+
+    public void testVload_long() {
+        testSetup(Type.createX(mRS, Element.I64(mRS), w));
+        long tmp[] = randomLongArray(w);
+        long tmp2[] = new long[w];
+        script.forEach_copy2d_long(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch long: ");
+    }
+
+    public void testVload_ulong() {
+        testSetup(Type.createX(mRS, Element.I64(mRS), w));
+        long tmp[] = randomLongArray(w);
+        long tmp2[] = new long[w];
+        script.forEach_copy2d_ulong(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch ulong: ");
+    }
+    public void testVload_long_relaxed() {
+        testSetup(Type.createX(mRS, Element.I64(mRS), w));
+        long tmp[] = randomLongArray(w);
+        long tmp2[] = new long[w];
+        scriptRelaxed.forEach_copy2d_long(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch relaxed long: ");
+    }
+    public void testVload_ulong_relaxed() {
+        testSetup(Type.createX(mRS, Element.I64(mRS), w));
+        long tmp[] = randomLongArray(w);
+        long tmp2[] = new long[w];
+        scriptRelaxed.forEach_copy2d_ulong(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch ulong: ");
+    }
+
+    public void testVload_float() {
+        testSetup(Type.createX(mRS, Element.F32(mRS), w));
+        float tmp[] = new float[w];
+        float tmp2[] = new float[w];
+        for (int i=0; i < w; i++) {
+            tmp[i] = random.nextFloat();
+        }
+        inAlloc.copyFrom(tmp);
+        script.forEach_copy2d_float(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch float: ");
+    }
+
+    public void testVload_float_relaxed() {
+        testSetup(Type.createX(mRS, Element.F32(mRS), w));
+        float tmp[] = new float[w];
+        float tmp2[] = new float[w];
+        for (int i=0; i < w; i++) {
+            tmp[i] = random.nextFloat();
+        }
+        inAlloc.copyFrom(tmp);
+        scriptRelaxed.forEach_copy2d_float(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch relaxed float: ");
+    }
+
+    public void testVload_double() {
+        testSetup(Type.createX(mRS, Element.F64(mRS), w));
+        double tmp[] = new double[w];
+        double tmp2[] = new double[w];
+        for (int i=0; i < w; i++) {
+            tmp[i] = random.nextDouble();
+        }
+        inAlloc.copyFrom(tmp);
+        script.forEach_copy2d_double(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch double: ");
+    }
+
+    public void testVload_double_relaxed() {
+        testSetup(Type.createX(mRS, Element.F64(mRS), w));
+        double tmp[] = new double[w];
+        double tmp2[] = new double[w];
+        for (int i=0; i < w; i++) {
+            tmp[i] = random.nextDouble();
+        }
+        inAlloc.copyFrom(tmp);
+        scriptRelaxed.forEach_copy2d_double(walkAlloc);
+        verify(tmp, tmp2, "Data mismatch relaxed double: ");
+    }
+
+}
+
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/intrinsic_convolve5x5.rs b/tests/tests/renderscript/src/android/renderscript/cts/intrinsic_convolve5x5.rs
index 9f9aa2b..966dbdd 100644
--- a/tests/tests/renderscript/src/android/renderscript/cts/intrinsic_convolve5x5.rs
+++ b/tests/tests/renderscript/src/android/renderscript/cts/intrinsic_convolve5x5.rs
@@ -66,7 +66,7 @@
               + convert_float4(rsGetElementAt_uchar4(gIn, x3, y4)) * gCoeffs[23]
               + convert_float4(rsGetElementAt_uchar4(gIn, x4, y4)) * gCoeffs[24];
 
-    p0 = clamp(p0 + p1 + p2 + p3 + p4, 0.f, 255.f);
+    p0 = clamp(p0 + p1 + p2 + p3 + p4 + 0.5f, 0.f, 255.f);
     return convert_uchar4(p0);
 }
 
@@ -113,7 +113,7 @@
               + convert_float3(rsGetElementAt_uchar3(gIn, x3, y4)) * gCoeffs[23]
               + convert_float3(rsGetElementAt_uchar3(gIn, x4, y4)) * gCoeffs[24];
 
-    p0 = clamp(p0 + p1 + p2 + p3 + p4, 0.f, 255.f);
+    p0 = clamp(p0 + p1 + p2 + p3 + p4 + 0.5f, 0.f, 255.f);
     return convert_uchar3(p0);
 }
 
@@ -160,7 +160,7 @@
               + convert_float2(rsGetElementAt_uchar2(gIn, x3, y4)) * gCoeffs[23]
               + convert_float2(rsGetElementAt_uchar2(gIn, x4, y4)) * gCoeffs[24];
 
-    p0 = clamp(p0 + p1 + p2 + p3 + p4, 0.f, 255.f);
+    p0 = clamp(p0 + p1 + p2 + p3 + p4 + 0.5f, 0.f, 255.f);
     return convert_uchar2(p0);
 }
 
@@ -207,7 +207,7 @@
              + (float)(rsGetElementAt_uchar(gIn, x3, y4)) * gCoeffs[23]
              + (float)(rsGetElementAt_uchar(gIn, x4, y4)) * gCoeffs[24];
 
-    return clamp(p0 + p1 + p2 + p3 + p4, 0.f, 255.f);
+    return clamp(p0 + p1 + p2 + p3 + p4 + 0.5f, 0.f, 255.f);
 }
 
 float4 __attribute__((kernel)) convolve_F4(uint32_t x, uint32_t y) {
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/intrinsic_resize.rs b/tests/tests/renderscript/src/android/renderscript/cts/intrinsic_resize.rs
new file mode 100644
index 0000000..fa8c8dd
--- /dev/null
+++ b/tests/tests/renderscript/src/android/renderscript/cts/intrinsic_resize.rs
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "shared.rsh"
+
+int32_t gWidthIn;
+int32_t gHeightIn;
+rs_allocation gIn;
+float scaleX;
+float scaleY;
+
+static float4 cubicInterpolate_F4 (float4 p0,float4 p1,float4 p2,float4 p3 , float x) {
+    return p1 + 0.5f * x * (p2 - p0 + x * (2.f * p0 - 5.f * p1 + 4.f * p2 - p3
+            + x * (3.f * (p1 - p2) + p3 - p0)));
+}
+
+static float3 cubicInterpolate_F3 (float3 p0,float3 p1,float3 p2,float3 p3 , float x) {
+    return p1 + 0.5f * x * (p2 - p0 + x * (2.f * p0 - 5.f * p1 + 4.f * p2 - p3
+            + x * (3.f * (p1 - p2) + p3 - p0)));
+}
+
+static float2 cubicInterpolate_F2 (float2 p0,float2 p1,float2 p2,float2 p3 , float x) {
+    return p1 + 0.5f * x * (p2 - p0 + x * (2.f * p0 - 5.f * p1 + 4.f * p2 - p3
+            + x * (3.f * (p1 - p2) + p3 - p0)));
+}
+
+static float cubicInterpolate_F1 (float p0,float p1,float p2,float p3 , float x) {
+    return p1 + 0.5f * x * (p2 - p0 + x * (2.f * p0 - 5.f * p1 + 4.f * p2 - p3
+            + x * (3.f * (p1 - p2) + p3 - p0)));
+}
+
+uchar4 __attribute__((kernel)) bicubic_U4(uint32_t x, uint32_t y) {
+    float xf = (x + 0.5f) * scaleX - 0.5f;
+    float yf = (y + 0.5f) * scaleY - 0.5f;
+
+    int startx = (int) floor(xf - 1);
+    int starty = (int) floor(yf - 1);
+    xf = xf - floor(xf);
+    yf = yf - floor(yf);
+    int maxx = gWidthIn - 1;
+    int maxy = gHeightIn - 1;
+
+    uint32_t xs0 = (uint32_t) max(0, startx + 0);
+    uint32_t xs1 = (uint32_t) max(0, startx + 1);
+    uint32_t xs2 = (uint32_t) min(maxx, startx + 2);
+    uint32_t xs3 = (uint32_t) min(maxx, startx + 3);
+
+    uint32_t ys0 = (uint32_t) max(0, starty + 0);
+    uint32_t ys1 = (uint32_t) max(0, starty + 1);
+    uint32_t ys2 = (uint32_t) min(maxy, starty + 2);
+    uint32_t ys3 = (uint32_t) min(maxy, starty + 3);
+
+    float4 p00 = convert_float4(rsGetElementAt_uchar4(gIn, xs0, ys0));
+    float4 p01 = convert_float4(rsGetElementAt_uchar4(gIn, xs1, ys0));
+    float4 p02 = convert_float4(rsGetElementAt_uchar4(gIn, xs2, ys0));
+    float4 p03 = convert_float4(rsGetElementAt_uchar4(gIn, xs3, ys0));
+    float4 p0  = cubicInterpolate_F4(p00, p01, p02, p03, xf);
+
+    float4 p10 = convert_float4(rsGetElementAt_uchar4(gIn, xs0, ys1));
+    float4 p11 = convert_float4(rsGetElementAt_uchar4(gIn, xs1, ys1));
+    float4 p12 = convert_float4(rsGetElementAt_uchar4(gIn, xs2, ys1));
+    float4 p13 = convert_float4(rsGetElementAt_uchar4(gIn, xs3, ys1));
+    float4 p1  = cubicInterpolate_F4(p10, p11, p12, p13, xf);
+
+    float4 p20 = convert_float4(rsGetElementAt_uchar4(gIn, xs0, ys2));
+    float4 p21 = convert_float4(rsGetElementAt_uchar4(gIn, xs1, ys2));
+    float4 p22 = convert_float4(rsGetElementAt_uchar4(gIn, xs2, ys2));
+    float4 p23 = convert_float4(rsGetElementAt_uchar4(gIn, xs3, ys2));
+    float4 p2  = cubicInterpolate_F4(p20, p21, p22, p23, xf);
+
+    float4 p30 = convert_float4(rsGetElementAt_uchar4(gIn, xs0, ys3));
+    float4 p31 = convert_float4(rsGetElementAt_uchar4(gIn, xs1, ys3));
+    float4 p32 = convert_float4(rsGetElementAt_uchar4(gIn, xs2, ys3));
+    float4 p33 = convert_float4(rsGetElementAt_uchar4(gIn, xs3, ys3));
+    float4 p3  = cubicInterpolate_F4(p30, p31, p32, p33, xf);
+
+    float4 p  = cubicInterpolate_F4(p0, p1, p2, p3, yf);
+    p = clamp(p + 0.5f, 0.f, 255.f);
+    return convert_uchar4(p);
+}
+
+uchar3 __attribute__((kernel)) bicubic_U3(uint32_t x, uint32_t y) {
+    float xf = (x + 0.5f) * scaleX - 0.5f;
+    float yf = (y + 0.5f) * scaleY - 0.5f;
+
+    int startx = (int) floor(xf - 1);
+    int starty = (int) floor(yf - 1);
+    xf = xf - floor(xf);
+    yf = yf - floor(yf);
+    int maxx = gWidthIn - 1;
+    int maxy = gHeightIn - 1;
+
+    uint32_t xs0 = (uint32_t) max(0, startx + 0);
+    uint32_t xs1 = (uint32_t) max(0, startx + 1);
+    uint32_t xs2 = (uint32_t) min(maxx, startx + 2);
+    uint32_t xs3 = (uint32_t) min(maxx, startx + 3);
+
+    uint32_t ys0 = (uint32_t) max(0, starty + 0);
+    uint32_t ys1 = (uint32_t) max(0, starty + 1);
+    uint32_t ys2 = (uint32_t) min(maxy, starty + 2);
+    uint32_t ys3 = (uint32_t) min(maxy, starty + 3);
+
+    float3 p00 = convert_float3(rsGetElementAt_uchar3(gIn, xs0, ys0));
+    float3 p01 = convert_float3(rsGetElementAt_uchar3(gIn, xs1, ys0));
+    float3 p02 = convert_float3(rsGetElementAt_uchar3(gIn, xs2, ys0));
+    float3 p03 = convert_float3(rsGetElementAt_uchar3(gIn, xs3, ys0));
+    float3 p0  = cubicInterpolate_F3(p00, p01, p02, p03, xf);
+
+    float3 p10 = convert_float3(rsGetElementAt_uchar3(gIn, xs0, ys1));
+    float3 p11 = convert_float3(rsGetElementAt_uchar3(gIn, xs1, ys1));
+    float3 p12 = convert_float3(rsGetElementAt_uchar3(gIn, xs2, ys1));
+    float3 p13 = convert_float3(rsGetElementAt_uchar3(gIn, xs3, ys1));
+    float3 p1  = cubicInterpolate_F3(p10, p11, p12, p13, xf);
+
+    float3 p20 = convert_float3(rsGetElementAt_uchar3(gIn, xs0, ys2));
+    float3 p21 = convert_float3(rsGetElementAt_uchar3(gIn, xs1, ys2));
+    float3 p22 = convert_float3(rsGetElementAt_uchar3(gIn, xs2, ys2));
+    float3 p23 = convert_float3(rsGetElementAt_uchar3(gIn, xs3, ys2));
+    float3 p2  = cubicInterpolate_F3(p20, p21, p22, p23, xf);
+
+    float3 p30 = convert_float3(rsGetElementAt_uchar3(gIn, xs0, ys3));
+    float3 p31 = convert_float3(rsGetElementAt_uchar3(gIn, xs1, ys3));
+    float3 p32 = convert_float3(rsGetElementAt_uchar3(gIn, xs2, ys3));
+    float3 p33 = convert_float3(rsGetElementAt_uchar3(gIn, xs3, ys3));
+    float3 p3  = cubicInterpolate_F3(p30, p31, p32, p33, xf);
+
+    float3 p  = cubicInterpolate_F3(p0, p1, p2, p3, yf);
+    p = clamp(p + 0.5f, 0.f, 255.f);
+    return convert_uchar3(p);
+}
+
+uchar2 __attribute__((kernel)) bicubic_U2(uint32_t x, uint32_t y) {
+    float xf = (x + 0.5f) * scaleX - 0.5f;
+    float yf = (y + 0.5f) * scaleY - 0.5f;
+
+    int startx = (int) floor(xf - 1);
+    int starty = (int) floor(yf - 1);
+    xf = xf - floor(xf);
+    yf = yf - floor(yf);
+    int maxx = gWidthIn - 1;
+    int maxy = gHeightIn - 1;
+
+    uint32_t xs0 = (uint32_t) max(0, startx + 0);
+    uint32_t xs1 = (uint32_t) max(0, startx + 1);
+    uint32_t xs2 = (uint32_t) min(maxx, startx + 2);
+    uint32_t xs3 = (uint32_t) min(maxx, startx + 3);
+
+    uint32_t ys0 = (uint32_t) max(0, starty + 0);
+    uint32_t ys1 = (uint32_t) max(0, starty + 1);
+    uint32_t ys2 = (uint32_t) min(maxy, starty + 2);
+    uint32_t ys3 = (uint32_t) min(maxy, starty + 3);
+
+    float2 p00 = convert_float2(rsGetElementAt_uchar2(gIn, xs0, ys0));
+    float2 p01 = convert_float2(rsGetElementAt_uchar2(gIn, xs1, ys0));
+    float2 p02 = convert_float2(rsGetElementAt_uchar2(gIn, xs2, ys0));
+    float2 p03 = convert_float2(rsGetElementAt_uchar2(gIn, xs3, ys0));
+    float2 p0  = cubicInterpolate_F2(p00, p01, p02, p03, xf);
+
+    float2 p10 = convert_float2(rsGetElementAt_uchar2(gIn, xs0, ys1));
+    float2 p11 = convert_float2(rsGetElementAt_uchar2(gIn, xs1, ys1));
+    float2 p12 = convert_float2(rsGetElementAt_uchar2(gIn, xs2, ys1));
+    float2 p13 = convert_float2(rsGetElementAt_uchar2(gIn, xs3, ys1));
+    float2 p1  = cubicInterpolate_F2(p10, p11, p12, p13, xf);
+
+    float2 p20 = convert_float2(rsGetElementAt_uchar2(gIn, xs0, ys2));
+    float2 p21 = convert_float2(rsGetElementAt_uchar2(gIn, xs1, ys2));
+    float2 p22 = convert_float2(rsGetElementAt_uchar2(gIn, xs2, ys2));
+    float2 p23 = convert_float2(rsGetElementAt_uchar2(gIn, xs3, ys2));
+    float2 p2  = cubicInterpolate_F2(p20, p21, p22, p23, xf);
+
+    float2 p30 = convert_float2(rsGetElementAt_uchar2(gIn, xs0, ys3));
+    float2 p31 = convert_float2(rsGetElementAt_uchar2(gIn, xs1, ys3));
+    float2 p32 = convert_float2(rsGetElementAt_uchar2(gIn, xs2, ys3));
+    float2 p33 = convert_float2(rsGetElementAt_uchar2(gIn, xs3, ys3));
+    float2 p3  = cubicInterpolate_F2(p30, p31, p32, p33, xf);
+
+    float2 p  = cubicInterpolate_F2(p0, p1, p2, p3, yf);
+    p = clamp(p + 0.5f, 0.f, 255.f);
+    return convert_uchar2(p);
+}
+
+uchar __attribute__((kernel)) bicubic_U1(uint32_t x, uint32_t y) {
+    float xf = (x + 0.5f) * scaleX - 0.5f;
+    float yf = (y + 0.5f) * scaleY - 0.5f;
+
+    int startx = (int) floor(xf - 1);
+    int starty = (int) floor(yf - 1);
+    xf = xf - floor(xf);
+    yf = yf - floor(yf);
+    int maxx = gWidthIn - 1;
+    int maxy = gHeightIn - 1;
+
+    uint32_t xs0 = (uint32_t) max(0, startx + 0);
+    uint32_t xs1 = (uint32_t) max(0, startx + 1);
+    uint32_t xs2 = (uint32_t) min(maxx, startx + 2);
+    uint32_t xs3 = (uint32_t) min(maxx, startx + 3);
+
+    uint32_t ys0 = (uint32_t) max(0, starty + 0);
+    uint32_t ys1 = (uint32_t) max(0, starty + 1);
+    uint32_t ys2 = (uint32_t) min(maxy, starty + 2);
+    uint32_t ys3 = (uint32_t) min(maxy, starty + 3);
+
+    float p00 = (float)(rsGetElementAt_uchar(gIn, xs0, ys0));
+    float p01 = (float)(rsGetElementAt_uchar(gIn, xs1, ys0));
+    float p02 = (float)(rsGetElementAt_uchar(gIn, xs2, ys0));
+    float p03 = (float)(rsGetElementAt_uchar(gIn, xs3, ys0));
+    float p0  = cubicInterpolate_F1(p00, p01, p02, p03, xf);
+
+    float p10 = (float)(rsGetElementAt_uchar(gIn, xs0, ys1));
+    float p11 = (float)(rsGetElementAt_uchar(gIn, xs1, ys1));
+    float p12 = (float)(rsGetElementAt_uchar(gIn, xs2, ys1));
+    float p13 = (float)(rsGetElementAt_uchar(gIn, xs3, ys1));
+    float p1  = cubicInterpolate_F1(p10, p11, p12, p13, xf);
+
+    float p20 = (float)(rsGetElementAt_uchar(gIn, xs0, ys2));
+    float p21 = (float)(rsGetElementAt_uchar(gIn, xs1, ys2));
+    float p22 = (float)(rsGetElementAt_uchar(gIn, xs2, ys2));
+    float p23 = (float)(rsGetElementAt_uchar(gIn, xs3, ys2));
+    float p2  = cubicInterpolate_F1(p20, p21, p22, p23, xf);
+
+    float p30 = (float)(rsGetElementAt_uchar(gIn, xs0, ys3));
+    float p31 = (float)(rsGetElementAt_uchar(gIn, xs1, ys3));
+    float p32 = (float)(rsGetElementAt_uchar(gIn, xs2, ys3));
+    float p33 = (float)(rsGetElementAt_uchar(gIn, xs3, ys3));
+    float p3  = cubicInterpolate_F1(p30, p31, p32, p33, xf);
+
+    float p  = cubicInterpolate_F1(p0, p1, p2, p3, yf);
+    p = clamp(p + 0.5f, 0.f, 255.f);
+    return (uchar)p;
+}
+
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/vload.rs b/tests/tests/renderscript/src/android/renderscript/cts/vload.rs
new file mode 100644
index 0000000..cdf5fd1
--- /dev/null
+++ b/tests/tests/renderscript/src/android/renderscript/cts/vload.rs
@@ -0,0 +1,60 @@
+ /*
+ * 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.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(android.renderscript.cts)
+
+rs_allocation gAllocIn;
+rs_allocation gAllocOut;
+
+
+
+#define COPY_2D(ty)                                                 \
+    void __attribute__((kernel)) copy2d_##ty(int xy_v) {            \
+        int lx = xy_v & 0xff;                                       \
+        int vecsize = (xy_v & 0xff0000) >> 16;                      \
+        switch(vecsize) {                                           \
+        case 1: {                                                   \
+                ty i = rsGetElementAt_##ty(gAllocIn, lx);           \
+                rsSetElementAt_##ty(gAllocOut, i, lx);              \
+            } break;                                                \
+        case 2: {                                                   \
+                ty##2 i = rsAllocationVLoadX_##ty##2(gAllocIn, lx); \
+                rsAllocationVStoreX_##ty##2(gAllocOut, i, lx);      \
+            } break;                                                \
+        case 3: {                                                   \
+                ty##3 i = rsAllocationVLoadX_##ty##3(gAllocIn, lx); \
+                rsAllocationVStoreX_##ty##3(gAllocOut, i, lx);      \
+            } break;                                                \
+        case 4: {                                                   \
+                ty##4 i = rsAllocationVLoadX_##ty##4(gAllocIn, lx); \
+                rsAllocationVStoreX_##ty##4(gAllocOut, i, lx);      \
+            } break;                                                \
+        }                                                           \
+    }
+
+COPY_2D(char)
+COPY_2D(uchar)
+COPY_2D(short)
+COPY_2D(ushort)
+COPY_2D(int)
+COPY_2D(uint)
+COPY_2D(long)
+COPY_2D(ulong)
+
+COPY_2D(float)
+COPY_2D(double)
+
diff --git a/tests/tests/renderscript/src/android/renderscript/cts/vload_relaxed.rs b/tests/tests/renderscript/src/android/renderscript/cts/vload_relaxed.rs
new file mode 100644
index 0000000..61940ba
--- /dev/null
+++ b/tests/tests/renderscript/src/android/renderscript/cts/vload_relaxed.rs
@@ -0,0 +1,62 @@
+ /*
+ * 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.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(android.renderscript.cts)
+
+#pragma rs_fp_relaxed
+
+rs_allocation gAllocIn;
+rs_allocation gAllocOut;
+
+
+
+#define COPY_2D(ty)                                                 \
+    void __attribute__((kernel)) copy2d_##ty(int xy_v) {            \
+        int lx = xy_v & 0xff;                                       \
+        int vecsize = (xy_v & 0xff0000) >> 16;                      \
+        switch(vecsize) {                                           \
+        case 1: {                                                   \
+                ty i = rsGetElementAt_##ty(gAllocIn, lx);           \
+                rsSetElementAt_##ty(gAllocOut, i, lx);              \
+            } break;                                                \
+        case 2: {                                                   \
+                ty##2 i = rsAllocationVLoadX_##ty##2(gAllocIn, lx); \
+                rsAllocationVStoreX_##ty##2(gAllocOut, i, lx);      \
+            } break;                                                \
+        case 3: {                                                   \
+                ty##3 i = rsAllocationVLoadX_##ty##3(gAllocIn, lx); \
+                rsAllocationVStoreX_##ty##3(gAllocOut, i, lx);      \
+            } break;                                                \
+        case 4: {                                                   \
+                ty##4 i = rsAllocationVLoadX_##ty##4(gAllocIn, lx); \
+                rsAllocationVStoreX_##ty##4(gAllocOut, i, lx);      \
+            } break;                                                \
+        }                                                           \
+    }
+
+COPY_2D(char)
+COPY_2D(uchar)
+COPY_2D(short)
+COPY_2D(ushort)
+COPY_2D(int)
+COPY_2D(uint)
+COPY_2D(long)
+COPY_2D(ulong)
+
+COPY_2D(float)
+COPY_2D(double)
+
diff --git a/tests/tests/security/Android.mk b/tests/tests/security/Android.mk
index de58783..c41ee58 100644
--- a/tests/tests/security/Android.mk
+++ b/tests/tests/security/Android.mk
@@ -34,22 +34,6 @@
 
 LOCAL_SDK_VERSION := current
 
-intermediates.COMMON := $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME),,COMMON)
-
-sepolicy_asset_dir := $(intermediates.COMMON)/assets
-
-LOCAL_ASSET_DIR := $(sepolicy_asset_dir)
-
 include $(BUILD_CTS_PACKAGE)
 
-selinux_policy.xml := $(sepolicy_asset_dir)/selinux_policy.xml
-selinux_policy_parser := cts/tools/selinux/src/gen_SELinux_CTS.py
-general_sepolicy_policy.conf := $(call intermediates-dir-for,ETC,general_sepolicy.conf)/general_sepolicy.conf
-$(selinux_policy.xml): PRIVATE_POLICY_PARSER := $(selinux_policy_parser)
-$(selinux_policy.xml): $(general_sepolicy_policy.conf) $(selinux_policy_parser)
-	mkdir -p $(dir $@)
-	$(PRIVATE_POLICY_PARSER) $< $@ neverallow_only=t
-
-$(R_file_stamp): $(selinux_policy.xml)
-
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/security/jni/Android.mk b/tests/tests/security/jni/Android.mk
index fa862c1..46d0868 100644
--- a/tests/tests/security/jni/Android.mk
+++ b/tests/tests/security/jni/Android.mk
@@ -31,7 +31,8 @@
 		android_security_cts_SeccompDeathTestService.cpp \
 		android_security_cts_SELinuxTest.cpp \
 		android_security_cts_MMapExecutableTest.cpp \
-		android_security_cts_NetlinkSocket.cpp
+		android_security_cts_NetlinkSocket.cpp \
+		android_security_cts_AudioPolicyBinderTest.cpp
 
 LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
 
diff --git a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
index 0e91b4e..ca8e841 100644
--- a/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
+++ b/tests/tests/security/jni/CtsSecurityJniOnLoad.cpp
@@ -26,6 +26,7 @@
 extern int register_android_security_cts_SeccompDeathTestService(JNIEnv*);
 extern int register_android_security_cts_SELinuxTest(JNIEnv*);
 extern int register_android_security_cts_MMapExecutableTest(JNIEnv* env);
+extern int register_android_security_cts_AudioPolicyBinderTest(JNIEnv* env);
 
 jint JNI_OnLoad(JavaVM *vm, void *reserved) {
     JNIEnv *env = NULL;
@@ -70,5 +71,9 @@
         return JNI_ERR;
     }
 
+    if (register_android_security_cts_AudioPolicyBinderTest(env)) {
+        return JNI_ERR;
+    }
+
     return JNI_VERSION_1_4;
 }
diff --git a/tests/tests/security/jni/android_security_cts_AudioPolicyBinderTest.cpp b/tests/tests/security/jni/android_security_cts_AudioPolicyBinderTest.cpp
new file mode 100644
index 0000000..d9c7ce7
--- /dev/null
+++ b/tests/tests/security/jni/android_security_cts_AudioPolicyBinderTest.cpp
@@ -0,0 +1,204 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "AudioPolicyBinderTest-JNI"
+
+#include <jni.h>
+#include <binder/IServiceManager.h>
+#include <media/IAudioPolicyService.h>
+#include <media/AudioSystem.h>
+#include <system/audio.h>
+#include <utils/Log.h>
+#include <utils/SystemClock.h>
+
+using namespace android;
+
+/*
+ * Native methods used by
+ * cts/tests/tests/security/src/android/security/cts/AudioPolicyBinderTest.java
+ */
+
+static bool init(sp<IAudioPolicyService>& aps, audio_io_handle_t *output, int *session)
+{
+    aps = 0;
+    if (output != NULL) {
+        *output = AUDIO_IO_HANDLE_NONE;
+    }
+    if (session != NULL) {
+        *session = AUDIO_UNIQUE_ID_ALLOCATE;
+    }
+
+    int64_t startTime = 0;
+    sp<IServiceManager> sm = defaultServiceManager();
+    while (aps == 0) {
+        sp<IBinder> binder = defaultServiceManager()->checkService(String16("media.audio_policy"));
+        if (binder == 0) {
+            if (startTime == 0) {
+                startTime = uptimeMillis();
+            } else if ((uptimeMillis()-startTime) > 10000) {
+                ALOGE("timeout while getting audio policy service");
+                return false;
+            }
+            sleep(1);
+        } else {
+            aps = interface_cast<IAudioPolicyService>(binder);
+        }
+    }
+
+    if (output != NULL) {
+        // get a valid output. Any use case will do.
+        for (int stream = AUDIO_STREAM_MIN; stream < AUDIO_STREAM_CNT; stream++) {
+            *output = AudioSystem::getOutput((audio_stream_type_t)stream);
+            if (*output != AUDIO_IO_HANDLE_NONE) {
+                break;
+            }
+        }
+        if (*output == AUDIO_IO_HANDLE_NONE) {
+            ALOGE("cannot get valid audio output");
+            return false;
+        }
+    }
+    if (session != NULL) {
+        //get a valid session
+        *session = AudioSystem::newAudioUniqueId();
+        if (*session == AUDIO_UNIQUE_ID_ALLOCATE) {
+            ALOGE("cannot get valid audio session");
+            return false;
+        }
+    }
+    return true;
+}
+
+/*
+ * Checks that IAudioPolicyService::startOutput() cannot be called with an
+ * invalid stream type.
+ */
+jboolean android_security_cts_AudioPolicy_test_startOutput(JNIEnv* env __unused,
+                                                           jobject thiz __unused)
+{
+    sp<IAudioPolicyService> aps;
+    audio_io_handle_t output;
+    int session;
+
+    if (!init(aps, &output, &session)) {
+        return false;
+    }
+
+    status_t status = aps->startOutput(output, (audio_stream_type_t)(AUDIO_STREAM_MIN -1),
+                                       (audio_session_t)session);
+    if (status == NO_ERROR) {
+        return false;
+    }
+    status = aps->startOutput(output, (audio_stream_type_t)AUDIO_STREAM_CNT,
+                              (audio_session_t)session);
+    if (status == NO_ERROR) {
+        return false;
+    }
+    return true;
+}
+
+/*
+ * Checks that IAudioPolicyService::stopOutput() cannot be called with an
+ * invalid stream type.
+ */
+jboolean android_security_cts_AudioPolicy_test_stopOutput(JNIEnv* env __unused,
+                                                           jobject thiz __unused)
+{
+    sp<IAudioPolicyService> aps;
+    audio_io_handle_t output;
+    int session;
+
+    if (!init(aps, &output, &session)) {
+        return false;
+    }
+
+    status_t status = aps->stopOutput(output, (audio_stream_type_t)(AUDIO_STREAM_MIN -1),
+                                      (audio_session_t)session);
+    if (status == NO_ERROR) {
+        return false;
+    }
+    status = aps->stopOutput(output, (audio_stream_type_t)AUDIO_STREAM_CNT,
+                             (audio_session_t)session);
+    if (status == NO_ERROR) {
+        return false;
+    }
+    return true;
+}
+
+/*
+ * Checks that IAudioPolicyService::isStreamActive() cannot be called with an
+ * invalid stream type.
+ */
+jboolean android_security_cts_AudioPolicy_test_isStreamActive(JNIEnv* env __unused,
+                                                           jobject thiz __unused)
+{
+    sp<IAudioPolicyService> aps;
+
+    if (!init(aps, NULL, NULL)) {
+        return false;
+    }
+
+    if (aps->isStreamActive((audio_stream_type_t)(AUDIO_STREAM_MIN -1), 0)) {
+        return false;
+    }
+
+    if (aps->isStreamActive((audio_stream_type_t)AUDIO_STREAM_CNT, 0)) {
+        return false;
+    }
+    return true;
+}
+
+/*
+ * Checks that IAudioPolicyService::isStreamActiveRemotely() cannot be called with an
+ * invalid stream type.
+ * Test with NUM_RANDOM_TESTS random values for stream type.
+ */
+jboolean android_security_cts_AudioPolicy_test_isStreamActiveRemotely(JNIEnv* env __unused,
+                                                           jobject thiz __unused)
+{
+    sp<IAudioPolicyService> aps;
+
+    if (!init(aps, NULL, NULL)) {
+        return false;
+    }
+
+    if (aps->isStreamActiveRemotely((audio_stream_type_t)(AUDIO_STREAM_MIN -1), 0)) {
+        return false;
+    }
+
+    if (aps->isStreamActiveRemotely((audio_stream_type_t)AUDIO_STREAM_CNT, 0)) {
+        return false;
+    }
+    return true;
+}
+
+static JNINativeMethod gMethods[] = {
+    {  "native_test_startOutput", "()Z",
+            (void *) android_security_cts_AudioPolicy_test_startOutput },
+    {  "native_test_stopOutput", "()Z",
+                (void *) android_security_cts_AudioPolicy_test_stopOutput },
+    {  "native_test_isStreamActive", "()Z",
+                (void *) android_security_cts_AudioPolicy_test_isStreamActive },
+    {  "native_test_isStreamActiveRemotely", "()Z",
+                (void *) android_security_cts_AudioPolicy_test_isStreamActiveRemotely },
+};
+
+int register_android_security_cts_AudioPolicyBinderTest(JNIEnv* env)
+{
+    jclass clazz = env->FindClass("android/security/cts/AudioPolicyBinderTest");
+    return env->RegisterNatives(clazz, gMethods,
+            sizeof(gMethods) / sizeof(JNINativeMethod));
+}
diff --git a/tests/tests/security/jni/android_security_cts_NetlinkSocket.cpp b/tests/tests/security/jni/android_security_cts_NetlinkSocket.cpp
index 2411f74..de315ea 100644
--- a/tests/tests/security/jni/android_security_cts_NetlinkSocket.cpp
+++ b/tests/tests/security/jni/android_security_cts_NetlinkSocket.cpp
@@ -32,7 +32,7 @@
     int sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
     if (sock == -1) {
         ALOGE("Can't create socket %s", strerror(errno));
-        jclass SocketException = env->FindClass("java/security/SocketException");
+        jclass SocketException = env->FindClass("java/net/SocketException");
         env->ThrowNew(SocketException, "Can't create socket");
         return;
     }
diff --git a/tests/tests/security/src/android/security/cts/AudioPolicyBinderTest.java b/tests/tests/security/src/android/security/cts/AudioPolicyBinderTest.java
new file mode 100644
index 0000000..b307247
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/AudioPolicyBinderTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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 junit.framework.TestCase;
+
+public class AudioPolicyBinderTest extends TestCase {
+
+    static {
+        System.loadLibrary("ctssecurity_jni");
+    }
+
+    /**
+     * Checks that IAudioPolicyService::startOutput() cannot be called with an
+     * invalid stream type.
+     */
+    public void test_startOutput() throws Exception {
+        assertTrue(native_test_startOutput());
+    }
+
+    /**
+     * Checks that IAudioPolicyService::stopOutput() cannot be called with an
+     * invalid stream type.
+     */
+    public void test_stopOutput() throws Exception {
+        assertTrue(native_test_stopOutput());
+    }
+
+    /**
+     * Checks that IAudioPolicyService::isStreamActive() cannot be called with an
+     * invalid stream type.
+     */
+    public void test_isStreamActive() throws Exception {
+        assertTrue(native_test_isStreamActive());
+    }
+
+    /**
+     * Checks that IAudioPolicyService::isStreamActiveRemotely() cannot be called with an
+     * invalid stream type.
+     */
+    public void test_isStreamActiveRemotely() throws Exception {
+        assertTrue(native_test_isStreamActiveRemotely());
+    }
+
+    private static native boolean native_test_startOutput();
+    private static native boolean native_test_stopOutput();
+    private static native boolean native_test_isStreamActive();
+    private static native boolean native_test_isStreamActiveRemotely();
+}
diff --git a/tests/tests/security/src/android/security/cts/BannedFilesTest.java b/tests/tests/security/src/android/security/cts/BannedFilesTest.java
index 8076f8e..00c4631 100644
--- a/tests/tests/security/src/android/security/cts/BannedFilesTest.java
+++ b/tests/tests/security/src/android/security/cts/BannedFilesTest.java
@@ -131,14 +131,6 @@
         assertNotSetugid("/vendor/bin/tcpdump-arm");
     }
 
-    /**
-     * Test if /dev/diag exists.
-     */
-    public void testNoDevDiag(){
-        File file = new File("/dev/diag");
-        assertFalse("File \"" + file.getAbsolutePath() + "\" exists", file.exists());
-    }
-
     private static void assertNotSetugid(String file) {
         FileUtils.FileStatus fs = new FileUtils.FileStatus();
         if (!FileUtils.getFileStatus(file, fs, false)) {
diff --git a/tests/tests/security/src/android/security/cts/CertificateData.java b/tests/tests/security/src/android/security/cts/CertificateData.java
index da098f3..de7c15e 100644
--- a/tests/tests/security/src/android/security/cts/CertificateData.java
+++ b/tests/tests/security/src/android/security/cts/CertificateData.java
@@ -28,9 +28,11 @@
       "91:C6:D6:EE:3E:8A:C8:63:84:E5:48:C2:99:29:5C:75:6C:81:7B:81",
       "4A:65:D5:F4:1D:EF:39:B8:B8:90:4A:4A:D3:64:81:33:CF:C7:A1:D1",
       "16:32:47:8D:89:F9:21:3A:92:00:85:63:F5:A4:A7:D3:12:40:8A:D6",
+      "D1:CB:CA:5D:B2:D5:2A:7F:69:3B:67:4D:E5:F0:5A:1D:0C:95:7D:F0",
       "4D:23:78:EC:91:95:39:B5:00:7F:75:8F:03:3B:21:1E:C5:4D:8B:CF",
       "E7:B4:F6:9D:61:EC:90:69:DB:7E:90:A7:40:1A:3C:F4:7D:4F:E8:EE",
       "DD:E1:D2:A9:01:80:2E:1D:87:5E:84:B3:80:7E:4B:B1:FD:99:41:34",
+      "69:69:56:2E:40:80:F4:24:A1:E7:19:9F:14:BA:F3:EE:58:AB:6A:BB",
       "92:5A:8F:8D:2C:6D:04:E0:66:5F:59:6A:FF:22:D8:63:E8:25:6F:3F",
       "75:E0:AB:B6:13:85:12:27:1C:04:F8:5F:DD:DE:38:E4:B7:24:2E:FE",
       "40:9D:4B:D9:17:B5:5C:27:B6:9B:64:CB:98:22:44:0D:CD:09:B8:89",
@@ -49,6 +51,7 @@
       "27:96:BA:E6:3F:18:01:E2:77:26:1B:A0:D7:77:70:02:8F:20:EE:E4",
       "AD:7E:1C:28:B0:64:EF:8F:60:03:40:20:14:C3:D0:E3:37:0E:B5:8A",
       "8D:17:84:D5:37:F3:03:7D:EC:70:FE:57:8B:51:9A:99:E6:10:D7:B0",
+      "1F:24:C6:30:CD:A4:18:EF:20:69:FF:AD:4F:DD:5F:46:3A:1B:69:AA",
       "AE:50:83:ED:7C:F4:5C:BC:8F:61:C6:21:FE:68:5D:79:42:21:15:6E",
       "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",
@@ -56,6 +59,7 @@
       "3E:2B:F7:F2:03:1B:96:F3:8C:E6:C4:D8:A8:5D:3E:2D:58:47:6A:0F",
       "A3:F1:33:3F:E2:42:BF:CF:C5:D1:4E:8F:39:42:98:40:68:10:D1:A0",
       "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",
       "A8:98:5D:3A:65:E5:E5:C4:B2:D7:D6:6D:40:C6:DD:2F:B1:9C:54:36",
       "59:22:A1:E1:5A:EA:16:35:21:F8:98:39:6A:46:46:B0:44:1B:0F:A9",
       "D4:DE:20:D0:5E:66:FC:53:FE:1A:50:88:2C:78:DB:28:52:CA:E4:74",
@@ -155,6 +159,7 @@
       "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",
       "AE:C5:FB:3F:C8:E1:BF:C4:E5:4F:03:07:5A:9A:E8:00:B7:F7:B6:FA",
+      "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",
       "2A:C8:D5:8B:57:CE:BF:2F:49:AF:F2:FC:76:8F:51:14:62:90:7A:41",
       "F1:7F:6F:B6:31:DC:99:E3:A3:C8:7F:FE:1C:F1:81:10:88:D9:60:33",
diff --git a/tests/tests/security/src/android/security/cts/NetlinkSocket.java b/tests/tests/security/src/android/security/cts/NetlinkSocket.java
index 1ea6d26..5ea80ca 100644
--- a/tests/tests/security/src/android/security/cts/NetlinkSocket.java
+++ b/tests/tests/security/src/android/security/cts/NetlinkSocket.java
@@ -18,6 +18,7 @@
 
 import java.io.FileDescriptor;
 import java.io.IOException;
+import java.net.SocketException;
 
 public class NetlinkSocket {
 
@@ -25,7 +26,7 @@
         System.loadLibrary("ctssecurity_jni");
     }
 
-    private static native void create_native(FileDescriptor fd);
+    private static native void create_native(FileDescriptor fd) throws SocketException;
     private static native int sendmsg(FileDescriptor fd, int pid, byte[] bytes);
 
     private FileDescriptor fd = new FileDescriptor();
@@ -33,7 +34,7 @@
     /** no public constructors */
     private NetlinkSocket() { }
 
-    public static NetlinkSocket create() {
+    public static NetlinkSocket create() throws SocketException {
         NetlinkSocket retval = new NetlinkSocket();
         create_native(retval.fd);
         return retval;
diff --git a/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java b/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java
index ee1b027..66054f9 100644
--- a/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java
+++ b/tests/tests/security/src/android/security/cts/SELinuxDomainTest.java
@@ -199,7 +199,7 @@
 
     /* drm server is always present */
     public void testDrmServerDomain() throws FileNotFoundException {
-        assertDomainOne("u:r:drmserver:s0", "/system/bin/drmserver");
+        assertDomainZeroOrOne("u:r:drmserver:s0", "/system/bin/drmserver");
     }
 
     /* Media server is always running */
diff --git a/tests/tests/security/src/android/security/cts/SELinuxPolicyRule.java b/tests/tests/security/src/android/security/cts/SELinuxPolicyRule.java
deleted file mode 100644
index d06fd75..0000000
--- a/tests/tests/security/src/android/security/cts/SELinuxPolicyRule.java
+++ /dev/null
@@ -1,162 +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.util.Xml;
-
-import com.google.common.collect.HashMultimap;
-import com.google.common.collect.Multimap;
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-
-import java.io.InputStream;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.HashMap;
-
-
-/**
- * A class for generating representations of SELinux avc rules parsed from an xml file.
- */
-public class SELinuxPolicyRule {
-    public final List<String> source_types;
-    public final List<String> target_types;
-    public final Multimap<String, String> obj_classes;
-    public final String name;
-    public final String type;
-
-    private SELinuxPolicyRule(List<String> source_types, List<String> target_types,
-            Multimap<String, String> obj_classes, String name, String type) {
-        this.source_types = source_types;
-        this.target_types = target_types;
-        this.obj_classes = obj_classes;
-        this.name = name;
-        this.type = type;
-    }
-
-    public static SELinuxPolicyRule readRule(XmlPullParser xpp) throws IOException, XmlPullParserException {
-        List<String> source_types = new ArrayList<String>();
-        List<String> target_types = new ArrayList<String>();
-        Multimap<String, String> obj_classes = HashMultimap.create();
-        xpp.require(XmlPullParser.START_TAG, null, "avc_rule");
-        String ruleName = xpp.getAttributeValue(null, "name");
-        String ruleType = xpp.getAttributeValue(null, "type");
-        while (xpp.next() != XmlPullParser.END_TAG) {
-            if (xpp.getEventType() != XmlPullParser.START_TAG) {
-                continue;
-            }
-            String name = xpp.getName();
-            if (name.equals("type")) {
-                if (xpp.getAttributeValue(null, "type").equals("source")) {
-                    source_types.add(readType(xpp));
-                } else if (xpp.getAttributeValue(null, "type").equals("target")) {
-                    target_types.add(readType(xpp));
-                } else {
-                    skip(xpp);
-                }
-            } else if (name.equals("obj_class")) {
-                String obj_name = xpp.getAttributeValue(null, "name");
-                List<String> perms = readObjClass(xpp);
-                obj_classes.putAll(obj_name, perms);
-            } else {
-                skip(xpp);
-            }
-        }
-        return new SELinuxPolicyRule(source_types, target_types, obj_classes, ruleName, ruleType);
-    }
-
-    public static List<SELinuxPolicyRule> readRulesFile(InputStream in) throws IOException, XmlPullParserException {
-        List<SELinuxPolicyRule> rules = new ArrayList<SELinuxPolicyRule>();
-        XmlPullParser xpp = Xml.newPullParser();
-        xpp.setInput(in, null);
-        xpp.nextTag();
-        xpp.require(XmlPullParser.START_TAG, null, "SELinux_AVC_Rules");
-
-        /* read rules */
-        while (xpp.next()  != XmlPullParser.END_TAG) {
-            if (xpp.getEventType() != XmlPullParser.START_TAG) {
-                continue;
-            }
-            String name = xpp.getName();
-            if (name.equals("avc_rule")) {
-                SELinuxPolicyRule r = readRule(xpp);
-                rules.add(r);
-            } else {
-                skip(xpp);
-            }
-        }
-        return rules;
-    }
-
-    private static List<String> readObjClass(XmlPullParser xpp) throws IOException, XmlPullParserException {
-        List<String> perms = new ArrayList<String>();
-        xpp.require(XmlPullParser.START_TAG, null, "obj_class");
-        while (xpp.next() != XmlPullParser.END_TAG) {
-        if (xpp.getEventType() != XmlPullParser.START_TAG) {
-                continue;
-            }
-            String name = xpp.getName();
-            if (name.equals("permission")) {
-                perms.add(readPermission(xpp));
-            } else {
-                skip(xpp);
-            }
-        }
-        return perms;
-    }
-
-    private static String readType(XmlPullParser xpp) throws IOException, XmlPullParserException {
-        xpp.require(XmlPullParser.START_TAG, null, "type");
-        String type = readText(xpp);
-        xpp.require(XmlPullParser.END_TAG, null, "type");
-        return type;
-    }
-
-    private static String readPermission(XmlPullParser xpp) throws IOException, XmlPullParserException {
-        xpp.require(XmlPullParser.START_TAG, null, "permission");
-        String permission = readText(xpp);
-        xpp.require(XmlPullParser.END_TAG, null, "permission");
-        return permission;
-    }
-
-    private static String readText(XmlPullParser xpp) throws IOException, XmlPullParserException {
-        String result = "";
-        if (xpp.next() == XmlPullParser.TEXT) {
-            result = xpp.getText();
-            xpp.nextTag();
-        }
-        return result;
-    }
-
-    public static void skip(XmlPullParser xpp) throws XmlPullParserException, IOException {
-        if (xpp.getEventType() != XmlPullParser.START_TAG) {
-            throw new IllegalStateException();
-        }
-        int depth = 1;
-        while (depth != 0) {
-            switch (xpp.next()) {
-            case XmlPullParser.END_TAG:
-                depth--;
-                break;
-            case XmlPullParser.START_TAG:
-                depth++;
-                break;
-            }
-        }
-    }
-}
diff --git a/tests/tests/security/src/android/security/cts/SELinuxTest.java b/tests/tests/security/src/android/security/cts/SELinuxTest.java
index 8e57037..711cb91 100644
--- a/tests/tests/security/src/android/security/cts/SELinuxTest.java
+++ b/tests/tests/security/src/android/security/cts/SELinuxTest.java
@@ -18,7 +18,6 @@
 
 import android.content.Context;
 import android.content.res.AssetManager;
-import android.security.cts.SELinuxPolicyRule;
 import android.test.AndroidTestCase;
 
 import junit.framework.TestCase;
@@ -82,130 +81,6 @@
         assertEquals(0, files.length);
     }
 
-    /**
-     * Verify all of the rules described by the selinux_policy.xml file are in effect.  Allow rules
-     * should return access granted, and Neverallow should return access denied.  All checks are run
-     * and then a list of specific failed checks is printed.
-     */
-    public void testSELinuxPolicyFile() throws IOException, XmlPullParserException {
-        List<String> failedChecks = new ArrayList<String>();
-        Map<String, Boolean> contextsCache = new HashMap<String, Boolean>();
-        int invalidContextsCount = 0;
-        int totalChecks = 0;
-        int totalFailedChecks = 0;
-        AssetManager assets = mContext.getAssets();
-        InputStream in = assets.open("selinux_policy.xml");
-        Collection<SELinuxPolicyRule> rules = SELinuxPolicyRule.readRulesFile(in);
-        for (SELinuxPolicyRule r : rules) {
-            PolicyFileTestResult result = runRuleChecks(r, contextsCache);
-            totalChecks += result.numTotalChecks;
-            if (result.numFailedChecks != 0) {
-                totalFailedChecks += result.numFailedChecks;
-
-                /* print failures to log, so as not to run OOM in the event of large policy mismatch,
-                   but record actual rule type and number */
-                failedChecks.add("SELinux avc rule " + r.type + r.name + " failed " + result.numFailedChecks +
-                        " out of " + result.numTotalChecks + " checks.");
-                for (String k : result.failedChecks) {
-                    System.out.println(r.type + r.name + " failed " + k);
-                }
-            }
-        }
-        if (totalFailedChecks != 0) {
-
-            /* print out failed rules, just the rule number and type */
-            for (String k : failedChecks) {
-                System.out.println(k);
-            }
-            System.out.println("Failed SELinux Policy Test: " + totalFailedChecks + " failed out of " + totalChecks);
-        }
-        for (String k : contextsCache.keySet()) {
-            if (!contextsCache.get(k)) {
-                invalidContextsCount++;
-                System.out.println("Invalid SELinux context encountered: " + k);
-            }
-        }
-        System.out.println("SELinuxPolicy Test Encountered: " + invalidContextsCount + " missing contexts out of " + contextsCache.size());
-        assertTrue(totalFailedChecks == 0);
-    }
-
-    /**
-     * A class for containing all of the results we care to know from checking each SELinux rule
-     */
-    private class PolicyFileTestResult {
-        private int numTotalChecks;
-        private int numFailedChecks;
-        private List<String> failedChecks = new ArrayList<String>();
-    }
-
-    private PolicyFileTestResult runRuleChecks(SELinuxPolicyRule r, Map<String, Boolean> contextsCache) {
-        PolicyFileTestResult result = new PolicyFileTestResult();
-
-        /* run checks by going through every possible 4-tuple specified by rule.  Start with class
-           and perm to allow early-exit based on context. */
-        for (String c : r.obj_classes.keySet()) {
-            for (String p : r.obj_classes.get(c)) {
-                for (String s : r.source_types) {
-
-                    /* check source context */
-                    String source_context = createAvcContext(s, false, c, p);
-                    if (!contextsCache.containsKey(source_context)) {
-                        contextsCache.put(source_context, checkSELinuxContext(source_context));
-                    }
-                    if (!contextsCache.get(source_context)) {
-                        continue;
-                    }
-                    for (String t : r.target_types) {
-                        if (t.equals("self")) {
-                            t = s;
-                        }
-
-                        /* check target context */
-                        String target_context = createAvcContext(t, true, c, p);
-                        if (!contextsCache.containsKey(target_context)) {
-                            contextsCache.put(target_context, checkSELinuxContext(target_context));
-                        }
-                        if (!contextsCache.get(target_context)) {
-                            continue;
-                        }
-                        boolean canAccess  = checkSELinuxAccess(source_context, target_context,
-                                c, p, "");
-                        result.numTotalChecks++;
-                        if ((r.type.equals("allow") && !canAccess)
-                                || (r.type.equals("neverallow") && canAccess)) {
-                            String failureNotice = s + ", " + t + ", " + c + ", " + p;
-                            result.numFailedChecks++;
-                            result.failedChecks.add(failureNotice);
-                        }
-                    }
-                }
-            }
-        }
-        return result;
-    }
-
-    /* createAvcContext - currently uses class type and perm to determine user, role and mls values.
-     *
-     * @param target - false if source domain, true if target.
-     */
-    private String createAvcContext(String domain, boolean target,
-            String obj_class, String perm) {
-        String usr = "u";
-        String role;
-
-        /* understand role labeling better */
-        if (obj_class.equals("filesystem") && perm.equals("associate")) {
-            role = "object_r";
-        } else if(obj_class.equals("process") || obj_class.endsWith("socket")) {
-            role = "r";
-        } else if (target) {
-            role = "object_r";
-        } else {
-            role = "r";
-        }
-        return String.format("%s:%s:%s:s0", usr, role, domain);
-    }
-
     private static native boolean checkSELinuxAccess(String scon, String tcon, String tclass, String perm, String extra);
 
     private static native boolean checkSELinuxContext(String con);
diff --git a/tests/tests/security/src/android/security/cts/VoldExploitTest.java b/tests/tests/security/src/android/security/cts/VoldExploitTest.java
index edaf82a..103158f 100644
--- a/tests/tests/security/src/android/security/cts/VoldExploitTest.java
+++ b/tests/tests/security/src/android/security/cts/VoldExploitTest.java
@@ -26,6 +26,7 @@
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.net.SocketException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -103,7 +104,13 @@
           return;
         }
 
-        NetlinkSocket ns = NetlinkSocket.create();
+        NetlinkSocket ns;
+        try {
+            ns = NetlinkSocket.create();
+        } catch (SocketException e) {
+            // Can't create netlink socket. Not vulnerable.
+            return;
+        }
         for (int i : pids) {
             for (String j : devices) {
                 doAttack(ns, i, j);
diff --git a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechTest.java b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechTest.java
index 799fd8d..69acdd0 100644
--- a/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechTest.java
+++ b/tests/tests/speech/src/android/speech/tts/cts/TextToSpeechTest.java
@@ -15,6 +15,7 @@
  */
 package android.speech.tts.cts;
 
+import android.content.pm.PackageManager;
 import android.os.Environment;
 import android.speech.tts.TextToSpeech;
 import android.test.AndroidTestCase;
@@ -39,6 +40,15 @@
     protected void setUp() throws Exception {
         super.setUp();
         mTts = TextToSpeechWrapper.createTextToSpeechWrapper(getContext());
+        if (mTts == null) {
+            PackageManager pm = getContext().getPackageManager();
+            if (!pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT)) {
+                // It is OK to have no TTS, when audio-out is not supported.
+                return;
+            } else {
+                fail("FEATURE_AUDIO_OUTPUT is set, but there is no TTS engine");
+            }
+        }
         assertNotNull(mTts);
         assertTrue(checkAndSetLanguageAvailable());
     }
@@ -46,7 +56,9 @@
     @Override
     protected void tearDown() throws Exception {
         super.tearDown();
-        mTts.shutdown();
+        if (mTts != null) {
+            mTts.shutdown();
+        }
     }
 
     private TextToSpeech getTts() {
@@ -83,6 +95,9 @@
     }
 
     public void testSynthesizeToFile() throws Exception {
+        if (mTts == null) {
+            return;
+        }
         File sampleFile = new File(Environment.getExternalStorageDirectory(), SAMPLE_FILE_NAME);
         try {
             assertFalse(sampleFile.exists());
@@ -101,18 +116,27 @@
     }
 
     public void testSpeak() throws Exception {
+        if (mTts == null) {
+            return;
+        }
         int result = getTts().speak(SAMPLE_TEXT, TextToSpeech.QUEUE_FLUSH, createParams());
         assertEquals("speak() failed", TextToSpeech.SUCCESS, result);
         assertTrue("speak() completion timeout", waitForUtterance());
     }
 
     public void testGetEnginesIncludesDefault() throws Exception {
+        if (mTts == null) {
+            return;
+        }
         List<TextToSpeech.EngineInfo> engines = getTts().getEngines();
         assertNotNull("getEngines() returned null", engines);
         assertContainsEngine(getTts().getDefaultEngine(), engines);
     }
 
     public void testGetEnginesIncludesMock() throws Exception {
+        if (mTts == null) {
+            return;
+        }
         List<TextToSpeech.EngineInfo> engines = getTts().getEngines();
         assertNotNull("getEngines() returned null", engines);
         assertContainsEngine(TextToSpeechWrapper.MOCK_TTS_ENGINE, engines);
diff --git a/tests/tests/telephony/AndroidManifest.xml b/tests/tests/telephony/AndroidManifest.xml
index b3ae1a3..31abf12 100644
--- a/tests/tests/telephony/AndroidManifest.xml
+++ b/tests/tests/telephony/AndroidManifest.xml
@@ -28,6 +28,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.BLUETOOTH" />
 
     <application>
         <uses-library android:name="android.test.runner" />
diff --git a/tests/tests/telephony/src/android/telephony/cts/SimRestrictedApisTest.java b/tests/tests/telephony/src/android/telephony/cts/SimRestrictedApisTest.java
new file mode 100644
index 0000000..ea0e80a
--- /dev/null
+++ b/tests/tests/telephony/src/android/telephony/cts/SimRestrictedApisTest.java
@@ -0,0 +1,268 @@
+/*
+ * 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.telephony.cts;
+
+import android.content.Context;
+import android.telephony.SmsManager;
+import android.telephony.TelephonyManager;
+import android.test.AndroidTestCase;
+
+public class SimRestrictedApisTest extends AndroidTestCase {
+    private static final byte[] TEST_PDU = {
+            0, 0 };
+    private TelephonyManager mTelephonyManager;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mTelephonyManager =
+                (TelephonyManager) getContext().getSystemService(Context.TELEPHONY_SERVICE);
+    }
+
+    private boolean isSimCardPresent() {
+        return mTelephonyManager.getSimState() != TelephonyManager.SIM_STATE_ABSENT;
+    }
+
+    /**
+     * Tests the SmsManager.injectSmsPdu() API. This makes a call to injectSmsPdu() API and expects
+     * a SecurityException since the test apk is not signed by a certificate on the SIM.
+     */
+    public void testInjectSmsPdu() {
+        try {
+            if (isSimCardPresent()) {
+                SmsManager.getDefault().injectSmsPdu(TEST_PDU, "3gpp", null);
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests the TelephonyManager.setLine1NumberForDisplay() API. This makes a call to
+     * setLine1NumberForDisplay() API and expects a SecurityException since the test apk is not
+     * signed by a certificate on the SIM.
+     */
+    public void testSetLine1NumberForDisplay() {
+        try {
+            if (isSimCardPresent()) {
+                TelephonyManager.getDefault().setLine1NumberForDisplay("", "");
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests the TelephonyManager.setLine1NumberForDisplay(long, string, string) API. This makes a
+     * call to setLine1NumberForDisplay() API and expects a SecurityException since the test apk is
+     * not signed by the certificate on the SIM.
+     */
+    public void testSetLine1NumberForDisplay2() {
+        try {
+            if (isSimCardPresent()) {
+                TelephonyManager.getDefault().setLine1NumberForDisplayForSubscriber(0, "", "");
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests the TelephonyManager.iccOpenLogicalChannel() API. This makes a call to
+     * iccOpenLogicalChannel() API and expects a SecurityException since the test apk is not signed
+     * by certificate on the SIM.
+     */
+    public void testIccOpenLogicalChannel() {
+        try {
+            if (isSimCardPresent()) {
+                TelephonyManager.getDefault().iccOpenLogicalChannel("");
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests the TelephonyManager.iccCloseLogicalChannel() API. This makes a call to
+     * iccCloseLogicalChannel() API and expects a SecurityException since the test apk is not signed
+     * by certificate on the SIM.
+     */
+    public void testIccCloseLogicalChannel() {
+        try {
+            if (isSimCardPresent()) {
+                TelephonyManager.getDefault().iccCloseLogicalChannel(0);
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests the TelephonyManager.iccTransmitApduLogicalChannel() API. This makes a call to
+     * iccTransmitApduLogicalChannel() API and expects a SecurityException since the test apk is not
+     * signed by a certificate on the SIM.
+     */
+    public void testIccTransmitApduLogicalChannel() {
+        try {
+            if (isSimCardPresent()) {
+                TelephonyManager.getDefault().iccTransmitApduLogicalChannel(0, 0, 0, 0, 0, 0, "");
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests the TelephonyManager.iccTransmitApduBasicChannel() API. This makes a call to
+     * iccTransmitApduBasicChannel() API and expects a SecurityException since the test apk is not
+     * signed by a certificate on the SIM.
+     */
+    public void testIccTransmitApduBasicChannel() {
+        try {
+            if (isSimCardPresent()) {
+                TelephonyManager.getDefault().iccTransmitApduBasicChannel(0, 0, 0, 0, 0, "");
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests the TelephonyManager.sendEnvelopeWithStatus() API. This makes a call to
+     * sendEnvelopeWithStatus() API and expects a SecurityException since the test apk is not signed
+     * by certificate on the SIM.
+     */
+    public void testSendEnvelopeWithStatus() {
+        try {
+            if (isSimCardPresent()) {
+                TelephonyManager.getDefault().sendEnvelopeWithStatus("");
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests the TelephonyManager.nvReadItem() API. This makes a call to nvReadItem() API and
+     * expects a SecurityException since the test apk is not signed by a certificate on the SIM.
+     */
+    public void testNvReadItem() {
+        try {
+            if (isSimCardPresent()) {
+                TelephonyManager.getDefault().nvReadItem(0);
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests the TelephonyManager.nvWriteItem() API. This makes a call to nvWriteItem() API and
+     * expects a SecurityException since the test apk is not signed by a certificate on the SIM.
+     */
+    public void testNvWriteItem() {
+        try {
+            if (isSimCardPresent()) {
+                TelephonyManager.getDefault().nvWriteItem(0, "");
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests the TelephonyManager.nvWriteCdmaPrl() API. This makes a call to nvWriteCdmaPrl() API
+     * and expects a SecurityException since the test apk is not signed by a certificate on the SIM.
+     */
+    public void testNvWriteCdmaPrl() {
+        try {
+            if (isSimCardPresent()) {
+                TelephonyManager.getDefault().nvWriteCdmaPrl(null);
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests the TelephonyManager.nvResetConfig() API. This makes a call to nvResetConfig() API and
+     * expects a SecurityException since the test apk is not signed by a certificate on the SIM.
+     */
+    public void testNvResetConfig() {
+        try {
+            if (isSimCardPresent()) {
+                TelephonyManager.getDefault().nvResetConfig(0);
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests the TelephonyManager.getPreferredNetworkType() API. This makes a call to
+     * getPreferredNetworkType() API and expects a SecurityException since the test apk is not
+     * signed by certificate on the SIM.
+     */
+    public void testGetPreferredNetworkType() {
+        try {
+            if (isSimCardPresent()) {
+                TelephonyManager.getDefault().getPreferredNetworkType();
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests the TelephonyManager.setPreferredNetworkTypeToGlobal() API. This makes a call to
+     * setPreferredNetworkTypeToGlobal() API and expects a SecurityException since the test apk is not
+     * signed by certificate on the SIM.
+     */
+    public void testSetPreferredNetworkTypeToGlobal() {
+        try {
+            if (isSimCardPresent()) {
+                TelephonyManager.getDefault().setPreferredNetworkTypeToGlobal();
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests that the test apk doesn't have carrier previliges.
+     */
+    public void testHasCarrierPrivileges() {
+        if (TelephonyManager.getDefault().hasCarrierPrivileges()) {
+            fail("App unexpectedly has carrier privileges");
+        }
+    }
+
+    /**
+     * Tests the TelephonyManager.setOperatorBrandOverride() API. This makes a call to
+     * setOperatorBrandOverride() API and expects a SecurityException since the test apk is not
+     * signed by certificate on the SIM.
+     */
+    public void testSetOperatorBrandOverride() {
+        try {
+            if (isSimCardPresent()) {
+                TelephonyManager.getDefault().setOperatorBrandOverride("");
+                fail("Expected SecurityException. App doesn't have carrier privileges.");
+            }
+        } catch (SecurityException expected) {
+        }
+    }
+}
diff --git a/tests/tests/tv/AndroidManifest.xml b/tests/tests/tv/AndroidManifest.xml
index dc5d30a..d2b3ddf 100644
--- a/tests/tests/tv/AndroidManifest.xml
+++ b/tests/tests/tv/AndroidManifest.xml
@@ -75,6 +75,16 @@
                        android:resource="@xml/stub_tv_input_service" />
         </service>
 
+        <service android:name="android.media.tv.cts.HardwareSessionTest$HardwareProxyTvInputService"
+                 android:permission="android.permission.BIND_TV_INPUT">
+            <intent-filter>
+                <action android:name="android.media.tv.TvInputService" />
+            </intent-filter>
+            <meta-data android:name="android.media.tv.input"
+                       android:resource="@xml/stub_tv_input_service" />
+        </service>
+
+
         <activity android:name="android.media.tv.cts.TvViewStubActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/tests/tests/tv/src/android/media/tv/cts/HardwareSessionTest.java b/tests/tests/tv/src/android/media/tv/cts/HardwareSessionTest.java
new file mode 100644
index 0000000..75cf28d
--- /dev/null
+++ b/tests/tests/tv/src/android/media/tv/cts/HardwareSessionTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.tv.cts;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.cts.util.PollingCheck;
+import android.media.tv.TvContract;
+import android.media.tv.TvInputInfo;
+import android.media.tv.TvInputManager;
+import android.media.tv.TvInputService;
+import android.media.tv.TvView;
+import android.media.tv.cts.HardwareSessionTest.HardwareProxyTvInputService.CountingSession;
+import android.net.Uri;
+import android.test.ActivityInstrumentationTestCase2;
+
+import com.android.cts.tv.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test {@link android.media.tv.TvInputService.HardwareSession}.
+ */
+public class HardwareSessionTest extends ActivityInstrumentationTestCase2<TvViewStubActivity> {
+    /** The maximum time to wait for an operation. */
+    private static final long TIME_OUT = 15000L;
+
+    private TvView mTvView;
+    private Activity mActivity;
+    private Instrumentation mInstrumentation;
+    private TvInputManager mManager;
+    private TvInputInfo mStubInfo;
+    private final List<TvInputInfo> mPassthroughInputList = new ArrayList<>();
+
+    public HardwareSessionTest() {
+        super(TvViewStubActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        if (!Utils.hasTvInputFramework(getActivity())) {
+            return;
+        }
+        mActivity = getActivity();
+        mInstrumentation = getInstrumentation();
+        mTvView = (TvView) mActivity.findViewById(R.id.tvview);
+        mManager = (TvInputManager) mActivity.getSystemService(Context.TV_INPUT_SERVICE);
+        for (TvInputInfo info : mManager.getTvInputList()) {
+            if (info.getServiceInfo().name.equals(HardwareProxyTvInputService.class.getName())) {
+                mStubInfo = info;
+            }
+            if (info.isPassthroughInput()) {
+                mPassthroughInputList.add(info);
+            }
+        }
+        assertNotNull(mStubInfo);
+    }
+
+    public void testHardwareProxyTvInputService() throws Throwable {
+        if (!Utils.hasTvInputFramework(getActivity())) {
+            return;
+        }
+        for (final TvInputInfo info : mPassthroughInputList) {
+            verifyCommandTuneAndHardwareVideoAvailable(info);
+        }
+    }
+
+    public void verifyCommandTuneAndHardwareVideoAvailable(TvInputInfo passthroughInfo) throws
+            Throwable {
+        HardwareProxyTvInputService.sHardwareInputId = passthroughInfo.getId();
+        Uri fakeChannelUri = TvContract.buildChannelUri(0);
+        mTvView.tune(mStubInfo.getId(), fakeChannelUri);
+        mInstrumentation.waitForIdleSync();
+        new PollingCheck(TIME_OUT) {
+            @Override
+            protected boolean check() {
+                CountingSession session = HardwareProxyTvInputService.sSession;
+                return session != null && session.mTuneCount > 0
+                        && session.mHardwareVideoAvailableCount > 0;
+            }
+        }.run();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mTvView.reset();
+            }
+        });
+        mInstrumentation.waitForIdleSync();
+        HardwareProxyTvInputService.sSession = null;
+    }
+
+    public static class HardwareProxyTvInputService extends TvInputService {
+        static String sHardwareInputId;
+        static CountingSession sSession;
+
+        @Override
+        public Session onCreateSession(String inputId) {
+            sSession = new CountingSession(this);
+            return sSession;
+        }
+
+        public static class CountingSession extends HardwareSession {
+            public volatile int mTuneCount;
+            public volatile int mHardwareVideoAvailableCount;
+
+            CountingSession(Context context) {
+                super(context);
+            }
+
+            @Override
+            public void onRelease() {
+            }
+
+            @Override
+            public void onSetCaptionEnabled(boolean enabled) {
+            }
+
+            @Override
+            public String getHardwareInputId() {
+                return sHardwareInputId;
+            }
+
+            @Override
+            public void onSetStreamVolume(float volume) {
+            }
+
+            @Override
+            public boolean onTune(Uri channelUri) {
+                mTuneCount++;
+                return true;
+            }
+
+            @Override
+            public void onHardwareVideoAvailable() {
+                mHardwareVideoAvailableCount++;
+            }
+        }
+    }
+}
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
index 651a199..017fadd 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvContractTest.java
@@ -395,4 +395,132 @@
         verifyOverlap(programEndMillis + hour, programEndMillis + hour * 2, 0,
                 channelId, channelUri);
     }
+
+    private void verifyQueryWithSortOrder(Uri uri, final String[] projection,
+            String sortOrder) throws Exception {
+        try {
+            getContext().getContentResolver().query(uri, projection, null, null, sortOrder);
+            fail("Setting sortOrder should fail without ACCESS_ALL_EPG_DATA permission for " + uri);
+        } catch (SecurityException e) {
+            // Expected exception
+        }
+    }
+
+    private void verifyQueryWithSelection(Uri uri, final String[] projection,
+            String selection) throws Exception {
+        try {
+            getContext().getContentResolver().query(uri, projection, selection, null, null);
+            fail("Setting selection should fail without ACCESS_ALL_EPG_DATA permission for " + uri);
+        } catch (SecurityException e) {
+            // Expected exception
+        }
+    }
+
+    private void verifyUpdateWithSelection(Uri uri, String selection) throws Exception {
+        try {
+            ContentValues values = new ContentValues();
+            getContext().getContentResolver().update(uri, values, selection, null);
+            fail("Setting selection should fail without ACCESS_ALL_EPG_DATA permission for " + uri);
+        } catch (SecurityException e) {
+            // Expected exception
+        }
+    }
+
+    private void verifyDeleteWithSelection(Uri uri, String selection) throws Exception {
+        try {
+            getContext().getContentResolver().delete(uri, selection, null);
+            fail("Setting selection should fail without ACCESS_ALL_EPG_DATA permission for " + uri);
+        } catch (SecurityException e) {
+            // Expected exception
+        }
+    }
+
+    public void testAllEpgPermissionBlocksSortOrderOnQuery_Channels() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        final String[] projection = { TvContract.Channels._ID };
+        verifyQueryWithSortOrder(TvContract.Channels.CONTENT_URI, projection,
+                TvContract.Channels._ID + " ASC");
+    }
+
+    public void testAllEpgPermissionBlocksSelectionOnQuery_Channels() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        final String[] projection = { TvContract.Channels._ID };
+        verifyQueryWithSelection(TvContract.Channels.CONTENT_URI, projection,
+                TvContract.Channels._ID + ">0");
+    }
+
+    public void testAllEpgPermissionBlocksSelectionOnUpdate_Channels() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        verifyUpdateWithSelection(TvContract.Channels.CONTENT_URI,
+                TvContract.Channels._ID + ">0");
+    }
+
+    public void testAllEpgPermissionBlocksSelectionOnDelete_Channels() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        verifyDeleteWithSelection(TvContract.Channels.CONTENT_URI,
+                TvContract.Channels._ID + ">0");
+    }
+
+    public void testAllEpgPermissionBlocksSortOrderOnQuery_Programs() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        final String[] projection = { TvContract.Programs._ID };
+        verifyQueryWithSortOrder(TvContract.Programs.CONTENT_URI, projection,
+                TvContract.Programs._ID + " ASC");
+    }
+
+    public void testAllEpgPermissionBlocksSelectionOnQuery_Programs() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        final String[] projection = { TvContract.Channels._ID };
+        verifyQueryWithSelection(TvContract.Programs.CONTENT_URI, projection,
+                TvContract.Programs._ID + ">0");
+    }
+
+    public void testAllEpgPermissionBlocksSelectionOnUpdate_Programs() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        verifyUpdateWithSelection(TvContract.Programs.CONTENT_URI,
+                TvContract.Programs._ID + ">0");
+    }
+
+    public void testAllEpgPermissionBlocksSelectionOnDelete_Programs() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        verifyDeleteWithSelection(TvContract.Programs.CONTENT_URI,
+                TvContract.Programs._ID + ">0");
+    }
+
+    public void testDefaultValues() throws Exception {
+        if (!Utils.hasTvInputFramework(getContext())) {
+            return;
+        }
+        ContentValues values = new ContentValues();
+        values.put(TvContract.Channels.COLUMN_INPUT_ID, mInputId);
+        Uri channelUri = mContentResolver.insert(mChannelsUri, values);
+        assertNotNull(channelUri);
+        long channelId = ContentUris.parseId(channelUri);
+        try (Cursor cursor = mContentResolver.query(
+                channelUri, CHANNELS_PROJECTION, null, null, null)) {
+            cursor.moveToNext();
+            assertEquals(TvContract.Channels.TYPE_OTHER,
+                    cursor.getString(cursor.getColumnIndex(TvContract.Channels.COLUMN_TYPE)));
+            assertEquals(TvContract.Channels.SERVICE_TYPE_AUDIO_VIDEO,
+                    cursor.getString(cursor.getColumnIndex(
+                            TvContract.Channels.COLUMN_SERVICE_TYPE)));
+        }
+        values.clear();
+    }
 }
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java b/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
index 6e30421..f0ee277 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvInputServiceTest.java
@@ -44,6 +44,10 @@
 public class TvInputServiceTest extends ActivityInstrumentationTestCase2<TvViewStubActivity> {
     /** The maximum time to wait for an operation. */
     private static final long TIME_OUT = 15000L;
+    private static final String mDummyTrackId = "dummyTrackId";
+    private static final TvTrackInfo mDummyTrack =
+            new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, mDummyTrackId)
+            .setLanguage("und").build();
 
     private TvView mTvView;
     private Activity mActivity;
@@ -250,7 +254,9 @@
     public void verifyCallbackTracksChanged() {
         CountingSession session = CountingTvInputService.sSession;
         assertNotNull(session);
-        session.notifyTracksChanged(new ArrayList<TvTrackInfo>());
+        ArrayList<TvTrackInfo> tracks = new ArrayList<>();
+        tracks.add(mDummyTrack);
+        session.notifyTracksChanged(tracks);
         new PollingCheck(TIME_OUT) {
             @Override
             protected boolean check() {
@@ -262,7 +268,7 @@
     public void verifyCallbackTrackSelected() {
         CountingSession session = CountingTvInputService.sSession;
         assertNotNull(session);
-        session.notifyTrackSelected(TvTrackInfo.TYPE_SUBTITLE, null);
+        session.notifyTrackSelected(mDummyTrack.getType(), mDummyTrack.getId());
         new PollingCheck(TIME_OUT) {
             @Override
             protected boolean check() {
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java b/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
index 8c95194..930dd6a 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvViewTest.java
@@ -224,17 +224,24 @@
 
     private void selectTrackAndVerify(final int type, final TvTrackInfo track,
             List<TvTrackInfo> tracks) {
+        String selectedTrackId = mTvView.getSelectedTrack(type);
         final int previousGeneration = mCallback.getSelectedTrackGeneration(
                 mStubInfo.getId(), type);
         mTvView.selectTrack(type, track == null ? null : track.getId());
-        new PollingCheck(TIME_OUT) {
-            @Override
-            protected boolean check() {
-                return mCallback.getSelectedTrackGeneration(
-                        mStubInfo.getId(), type) > previousGeneration;
-            }
-        }.run();
-        String selectedTrackId = mTvView.getSelectedTrack(type);
+
+        if ((track == null && selectedTrackId != null)
+                || (track != null && !track.getId().equals(selectedTrackId))) {
+            // Check generation change only if we're actually changing track.
+            new PollingCheck(TIME_OUT) {
+                @Override
+                protected boolean check() {
+                    return mCallback.getSelectedTrackGeneration(
+                            mStubInfo.getId(), type) > previousGeneration;
+                }
+            }.run();
+        }
+
+        selectedTrackId = mTvView.getSelectedTrack(type);
         assertEquals(selectedTrackId, track == null ? null : track.getId());
         if (selectedTrackId != null) {
             TvTrackInfo selectedTrack = null;
diff --git a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
index 6d80819..d267d63 100644
--- a/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
+++ b/tests/tests/uiautomation/src/android/app/uiautomation/cts/UiAutomationTest.java
@@ -272,7 +272,7 @@
             if (lastPresentedTimeNano == FrameStats.UNDEFINED_TIME_NANO) {
                 assertTrue(presentedTimeNano == FrameStats.UNDEFINED_TIME_NANO);
             } else if (presentedTimeNano != FrameStats.UNDEFINED_TIME_NANO) {
-                assertTrue(presentedTimeNano > lastPresentedTimeNano);
+                assertTrue(presentedTimeNano >= lastPresentedTimeNano);
             }
             lastPresentedTimeNano = presentedTimeNano;
         }
diff --git a/tests/tests/uirendering/res/layout/blue_padded_layout.xml b/tests/tests/uirendering/res/layout/blue_padded_layout.xml
index 1cd1b21..2bfd049 100644
--- a/tests/tests/uirendering/res/layout/blue_padded_layout.xml
+++ b/tests/tests/uirendering/res/layout/blue_padded_layout.xml
@@ -14,15 +14,15 @@
        limitations under the License.
   -->
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="200px"
-        android:layout_height="200px"
-        android:clipChildren="false">
+    android:layout_width="@dimen/test_width"
+    android:layout_height="@dimen/test_height"
+    android:clipChildren="false">
     <android.uirendering.cts.testclasses.view.UnclippedBlueView
-            android:id="@+id/child"
-            android:layout_width="100px"
-            android:layout_height="100px"
-            android:paddingLeft="15px"
-            android:paddingTop="16px"
-            android:paddingRight="17px"
-            android:paddingBottom="18px"/>
+        android:id="@+id/child"
+        android:layout_width="80px"
+        android:layout_height="80px"
+        android:paddingLeft="15px"
+        android:paddingTop="16px"
+        android:paddingRight="17px"
+        android:paddingBottom="18px"/>
 </FrameLayout>
diff --git a/tests/tests/uirendering/res/layout/blue_padded_square.xml b/tests/tests/uirendering/res/layout/blue_padded_square.xml
index 0f254d4..7d867d9 100644
--- a/tests/tests/uirendering/res/layout/blue_padded_square.xml
+++ b/tests/tests/uirendering/res/layout/blue_padded_square.xml
@@ -14,6 +14,6 @@
        limitations under the License.
   -->
 <View xmlns:android="http://schemas.android.com/apk/res/android"
-        android:layout_width="100px"
-        android:layout_height="100px"
-        android:background="@drawable/blue_padded_square"/>
+    android:layout_width="@dimen/test_width"
+    android:layout_height="@dimen/test_height"
+    android:background="@drawable/blue_padded_square"/>
diff --git a/tests/tests/uirendering/res/layout/simple_rect_layout.xml b/tests/tests/uirendering/res/layout/simple_rect_layout.xml
index 24c9b6b..7d6f928 100644
--- a/tests/tests/uirendering/res/layout/simple_rect_layout.xml
+++ b/tests/tests/uirendering/res/layout/simple_rect_layout.xml
@@ -13,15 +13,12 @@
        See the License for the specific language governing permissions and
        limitations under the License.
   -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    android:orientation="vertical"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
-    android:background="#f00">
-
-    <View android:layout_width="180px"
-        android:layout_height="120px"
-        android:background="#0f0" />
-
-</LinearLayout>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/test_width"
+    android:layout_height="@dimen/test_height">
+    <View android:layout_width="80px"
+        android:layout_height="80px"
+        android:translationX="5px"
+        android:translationY="5px"
+        android:background="#00f" />
+</FrameLayout>
diff --git a/tests/tests/uirendering/res/layout/simple_red_layout.xml b/tests/tests/uirendering/res/layout/simple_red_layout.xml
index 1ae3e38..2af8db6 100644
--- a/tests/tests/uirendering/res/layout/simple_red_layout.xml
+++ b/tests/tests/uirendering/res/layout/simple_red_layout.xml
@@ -14,6 +14,7 @@
        limitations under the License.
   -->
 <View xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent"
+    android:layout_width="@dimen/test_width"
+    android:layout_height="@dimen/test_height"
+    android:id="@+id/test_root"
     android:background="#f00" />
diff --git a/tests/tests/uirendering/res/layout/test_container.xml b/tests/tests/uirendering/res/layout/test_container.xml
new file mode 100644
index 0000000..94a8eab
--- /dev/null
+++ b/tests/tests/uirendering/res/layout/test_container.xml
@@ -0,0 +1,24 @@
+<!-- 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.
+  -->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/test_container"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+    <ViewStub
+        android:id="@+id/test_content_stub"
+        android:layout_gravity="center"
+        android:layout_width="@dimen/test_width"
+        android:layout_height="@dimen/test_height"/>
+</FrameLayout>
diff --git a/tests/tests/uirendering/res/layout/test_content_canvasclientview.xml b/tests/tests/uirendering/res/layout/test_content_canvasclientview.xml
new file mode 100644
index 0000000..9f8a139
--- /dev/null
+++ b/tests/tests/uirendering/res/layout/test_content_canvasclientview.xml
@@ -0,0 +1,18 @@
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+       Licensed under the Apache License, Version 2.0 (the "License");
+       you may not use this file except in compliance with the License.
+       You may obtain a copy of the License at
+
+            http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing, software
+       distributed under the License is distributed on an "AS IS" BASIS,
+       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+       See the License for the specific language governing permissions and
+       limitations under the License.
+  -->
+<android.uirendering.cts.testinfrastructure.CanvasClientView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/test_width"
+    android:layout_height="@dimen/test_height"/>
\ No newline at end of file
diff --git a/tests/tests/uirendering/res/layout/test_content_webview.xml b/tests/tests/uirendering/res/layout/test_content_webview.xml
new file mode 100644
index 0000000..8b03cab
--- /dev/null
+++ b/tests/tests/uirendering/res/layout/test_content_webview.xml
@@ -0,0 +1,18 @@
+<!-- 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.
+  -->
+<WebView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="@dimen/test_width"
+    android:layout_height="@dimen/test_height"/>
\ No newline at end of file
diff --git a/tests/tests/uirendering/res/values/dimens.xml b/tests/tests/uirendering/res/values/dimens.xml
new file mode 100644
index 0000000..1c304d3
--- /dev/null
+++ b/tests/tests/uirendering/res/values/dimens.xml
@@ -0,0 +1,18 @@
+<!-- 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.
+  -->
+<resources>
+    <dimen name="test_width">90px</dimen>
+    <dimen name="test_height">90px</dimen>
+</resources>
diff --git a/tests/tests/uirendering/res/values/themes.xml b/tests/tests/uirendering/res/values/themes.xml
index 751b7cb..f08f029 100644
--- a/tests/tests/uirendering/res/values/themes.xml
+++ b/tests/tests/uirendering/res/values/themes.xml
@@ -17,9 +17,9 @@
     <style name="WhiteBackgroundTheme" parent="@android:style/Theme.Holo.NoActionBar.Fullscreen">
         <item name="android:windowNoTitle">true</item>
         <item name="android:windowFullscreen">true</item>
+        <item name="android:windowOverscan">true</item>
         <item name="android:fadingEdge">none</item>
         <item name="android:windowBackground">@android:color/white</item>
-        <!--This shouldn't be necessary currently a hack for an existing bug with transitions-->
         <item name="android:windowContentTransitions">false</item>
         <item name="android:windowAnimationStyle">@null</item>
     </style>
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapcomparers/ExactComparer.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapcomparers/ExactComparer.java
index 978dc0b..36be5f0 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/bitmapcomparers/ExactComparer.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/bitmapcomparers/ExactComparer.java
@@ -41,9 +41,11 @@
             for (int x = 0 ; x < width ; x++) {
                 int index = indexFromXAndY(x, y, stride, offset);
                 if (ideal[index] != given[index]) {
-                    Log.d(TAG, "Failure on position x = " + x + " y = " + y);
-                    Log.d(TAG, "Expected color : " + Integer.toHexString(ideal[index]) +
-                            " given color : " + Integer.toHexString(given[index]));
+                    if (count < 50) {
+                        Log.d(TAG, "Failure on position x = " + x + " y = " + y);
+                        Log.d(TAG, "Expected color : " + Integer.toHexString(ideal[index]) +
+                                " given color : " + Integer.toHexString(given[index]));
+                    }
                     count++;
                 }
             }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/ColorVerifier.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/ColorVerifier.java
index ea836ea..7f62b3e 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/ColorVerifier.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/ColorVerifier.java
@@ -22,11 +22,16 @@
     private int mColor;
 
     public ColorVerifier(int color) {
-        this(color, 20);
+        this(color, DEFAULT_THRESHOLD);
     }
 
-    public ColorVerifier(int color, int threshold) {
-        super(threshold);
+    public ColorVerifier(int color, int colorTolerance) {
+        super(colorTolerance);
+        mColor = color;
+    }
+
+    public ColorVerifier(int color, int colorThreshold, float spatialTolerance) {
+        super(colorThreshold, spatialTolerance);
         mColor = color;
     }
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/GoldenImageVerifier.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/GoldenImageVerifier.java
index 672b3f6..d4a63de 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/GoldenImageVerifier.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/GoldenImageVerifier.java
@@ -18,6 +18,7 @@
 import android.graphics.Bitmap;
 import android.uirendering.cts.bitmapcomparers.BitmapComparer;
 import android.uirendering.cts.differencevisualizers.PassFailVisualizer;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 
 public class GoldenImageVerifier extends BitmapVerifier {
     private BitmapComparer mBitmapComparer;
@@ -26,7 +27,7 @@
     public GoldenImageVerifier(Bitmap goldenBitmap, BitmapComparer bitmapComparer) {
         mGoldenBitmapArray = new int[goldenBitmap.getWidth() * goldenBitmap.getHeight()];
         goldenBitmap.getPixels(mGoldenBitmapArray, 0, goldenBitmap.getWidth(), 0, 0,
-                goldenBitmap.getWidth(), goldenBitmap.getHeight());
+                ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
         mBitmapComparer = bitmapComparer;
     }
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/PerPixelBitmapVerifier.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/PerPixelBitmapVerifier.java
index ab809f4..2d00db5 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/PerPixelBitmapVerifier.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/PerPixelBitmapVerifier.java
@@ -26,45 +26,66 @@
  */
 public abstract class PerPixelBitmapVerifier extends BitmapVerifier {
     private static final String TAG = "PerPixelBitmapVerifer";
-    private int mTolerance;
+    public static final int DEFAULT_THRESHOLD = 48;
 
-    public PerPixelBitmapVerifier(int tolerance) {
-        mTolerance = tolerance;
+    // total color difference tolerated without the pixel failing
+    private int mColorTolerance;
+
+    // portion of bitmap allowed to fail pixel check
+    private float mSpatialTolerance;
+
+    public PerPixelBitmapVerifier() {
+        this(DEFAULT_THRESHOLD, 0);
+    }
+
+    public PerPixelBitmapVerifier(int colorTolerance) {
+        this(colorTolerance, 0);
+    }
+
+    public PerPixelBitmapVerifier(int colorTolerance, float spatialTolerance) {
+        mColorTolerance = colorTolerance;
+        mSpatialTolerance = spatialTolerance;
     }
 
     protected int getExpectedColor(int x, int y) {
         return Color.WHITE;
     }
 
-
     public boolean verify(int[] bitmap, int offset, int stride, int width, int height) {
-        boolean res = true;
+        int failures = 0;
         int[] differenceMap = new int[bitmap.length];
         for (int y = 0 ; y < height ; y++) {
             for (int x = 0 ; x < width ; x++) {
                 int index = indexFromXAndY(x, y, stride, offset);
-                int expectedColor = getExpectedColor(x, y);
-                if (!verifyPixel(bitmap[index], expectedColor)) {
-                    Log.d(TAG, "Expected : " + Integer.toHexString(expectedColor)
-                            + " received : " + Integer.toHexString(bitmap[index])
-                            + " at position (" + x + "," + y + ")");
-                    res = false;
+                if (!verifyPixel(x, y, bitmap[index])) {
+                    if (failures < 50) {
+                        Log.d(TAG, "Expected : " + Integer.toHexString(getExpectedColor(x, y))
+                                + " received : " + Integer.toHexString(bitmap[index])
+                                + " at position (" + x + "," + y + ")");
+                    }
+                    failures++;
                     differenceMap[index] = FAIL_COLOR;
                 } else {
                     differenceMap[index] = PASS_COLOR;
                 }
             }
         }
-        if (!res) {
+        int toleratedFailures = (int) (mSpatialTolerance * width * height);
+        boolean success = failures <= toleratedFailures;
+        Log.d(TAG, failures + " failures observed out of "
+                + toleratedFailures + " tolerated failures");
+        if (!success) {
             mDifferenceBitmap = Bitmap.createBitmap(ActivityTestBase.TEST_WIDTH,
                     ActivityTestBase.TEST_HEIGHT, Bitmap.Config.ARGB_8888);
             mDifferenceBitmap.setPixels(differenceMap, offset, stride, 0, 0,
                     ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
         }
-        return res;
+        return success;
     }
 
-    protected boolean verifyPixel(int color, int expectedColor) {
-        return CompareUtils.verifyPixelWithThreshold(color, expectedColor, mTolerance);
+
+    protected boolean verifyPixel(int x, int y, int observedColor) {
+        int expectedColor = getExpectedColor(x, y);
+        return CompareUtils.verifyPixelWithThreshold(observedColor, expectedColor, mColorTolerance);
     }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/RectVerifier.java b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/RectVerifier.java
index 06a430b..f4bece1 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/RectVerifier.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/bitmapverifiers/RectVerifier.java
@@ -26,7 +26,7 @@
     private Rect mInnerRect;
 
     public RectVerifier(int outerColor, int innerColor, Rect innerRect) {
-        this(outerColor, innerColor, innerRect, 20);
+        this(outerColor, innerColor, innerRect, DEFAULT_THRESHOLD);
     }
 
     public RectVerifier(int outerColor, int innerColor, Rect innerRect, int tolerance) {
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapFilterTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapFilterTests.java
index ddae100..117fe17 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapFilterTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/BitmapFilterTests.java
@@ -28,7 +28,6 @@
 import android.uirendering.cts.testinfrastructure.CanvasClient;
 
 public class BitmapFilterTests extends ActivityTestBase {
-    private static final int THRESHOLD = 20;
     private static final int WHITE_WEIGHT = 255 * 3;
     private enum FilterEnum {
         // Creates Paint object that will have bitmap filtering
@@ -40,72 +39,74 @@
         ADD_FILTER
     }
 
-    /**
-     * Verifies that a Bitmap only contains white and black, within a certain threshold
-     */
-    private static BitmapVerifier mBlackWhiteVerifier = new PerPixelBitmapVerifier(THRESHOLD) {
+    private static final BitmapVerifier BLACK_WHITE_ONLY_VERIFIER
+            = new PerPixelBitmapVerifier(PerPixelBitmapVerifier.DEFAULT_THRESHOLD, 0.99f) {
         @Override
-        protected boolean verifyPixel(int color, int expectedColor) {
+        protected boolean verifyPixel(int x, int y, int color) {
             int weight = Color.red(color) + Color.blue(color) + Color.green(color);
-            return weight < THRESHOLD // is approx Color.BLACK
-                    || weight > WHITE_WEIGHT - THRESHOLD; // is approx Color.WHITE
+            return weight < DEFAULT_THRESHOLD // is approx Color.BLACK
+                    || weight > WHITE_WEIGHT - DEFAULT_THRESHOLD; // is approx Color.WHITE
         }
     };
+    private static final BitmapVerifier GREY_ONLY_VERIFIER
+            = new ColorVerifier(Color.argb(255, 127, 127, 127),
+            PerPixelBitmapVerifier.DEFAULT_THRESHOLD);
+    private static final BitmapVerifier GREY_PARTIAL_VERIFIER
+            = new ColorVerifier(Color.argb(255, 127, 127, 127),
+            300, 0.8f); // content will be mostly grey, for a wide range of grey
+
 
     private static Bitmap createGridBitmap(int width, int height) {
         Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
         for (int i = 0 ; i < width ; i++) {
             for (int j = 0 ; j < height ; j++) {
-                bitmap.setPixel(i, j, ((i + j * width)) % 2 == 0 ?
-                        Color.WHITE : Color.BLACK);
+                boolean isWhite = (i + j * width) % 2 == 0;
+                bitmap.setPixel(i, j, isWhite ? Color.WHITE : Color.BLACK);
             }
         }
         return bitmap;
     }
 
     private static final int SMALL_GRID_SIZE = 5;
-    private static Bitmap mSmallGridBitmap = createGridBitmap(SMALL_GRID_SIZE, SMALL_GRID_SIZE);
+    private Bitmap mSmallGridBitmap = createGridBitmap(SMALL_GRID_SIZE, SMALL_GRID_SIZE);
     private static final int BIG_GRID_SIZE = 360;
-    private static Bitmap mBigGridBitmap = createGridBitmap(BIG_GRID_SIZE, BIG_GRID_SIZE);
-    private static final int HALFWAY_COLOR = Color.argb(255, 127, 127, 127);
+    private Bitmap mBigGridBitmap = createGridBitmap(BIG_GRID_SIZE, BIG_GRID_SIZE);
 
-    /* All of these tests seem to be broken.
-     * TODO: fix in L MR1
     @SmallTest
     public void testPaintFilterScaleUp() {
-        runScaleTest(FilterEnum.PAINT_FILTER, true, mBlackWhiteVerifier);
+        runScaleTest(FilterEnum.PAINT_FILTER, true);
     }
 
     @SmallTest
     public void testPaintFilterScaleDown() {
-        runScaleTest(FilterEnum.PAINT_FILTER, false, new ColorVerifier(HALFWAY_COLOR, 15));
+        runScaleTest(FilterEnum.PAINT_FILTER, false);
     }
 
     @SmallTest
     public void testDrawFilterRemoveFilterScaleUp() {
-        runScaleTest(FilterEnum.REMOVE_FILTER, true, mBlackWhiteVerifier);
+        runScaleTest(FilterEnum.REMOVE_FILTER, true);
     }
 
     @SmallTest
     public void testDrawFilterRemoveFilterScaleDown() {
-        runScaleTest(FilterEnum.REMOVE_FILTER, false, mBlackWhiteVerifier);
+        runScaleTest(FilterEnum.REMOVE_FILTER, false);
     }
 
     @SmallTest
     public void testDrawFilterScaleUp() {
-        runScaleTest(FilterEnum.ADD_FILTER, true, mBlackWhiteVerifier);
+        runScaleTest(FilterEnum.ADD_FILTER, true);
     }
 
     @SmallTest
     public void testDrawFilterScaleDown() {
-        runScaleTest(FilterEnum.ADD_FILTER, false, new ColorVerifier(HALFWAY_COLOR));
+        runScaleTest(FilterEnum.ADD_FILTER, false);
     }
-*/
-    private void runScaleTest(final FilterEnum filterEnum, final boolean scaleUp,
-            BitmapVerifier bitmapVerifier) {
+
+    private void runScaleTest(final FilterEnum filterEnum, final boolean scaleUp) {
         final int gridWidth = scaleUp ? SMALL_GRID_SIZE : BIG_GRID_SIZE;
         final Paint paint = new Paint(filterEnum.equals(FilterEnum.ADD_FILTER) ?
                 0 : Paint.FILTER_BITMAP_FLAG);
+
         CanvasClient canvasClient = new CanvasClient() {
             @Override
             public void draw(Canvas canvas, int width, int height) {
@@ -120,6 +121,16 @@
         };
         createTest()
                 .addCanvasClient(canvasClient)
-                .runWithVerifier(bitmapVerifier);
+                .runWithVerifier(getVerifierForTest(filterEnum, scaleUp));
+    }
+
+    private static BitmapVerifier getVerifierForTest(FilterEnum filterEnum, boolean scaleUp) {
+        if (filterEnum.equals(FilterEnum.REMOVE_FILTER)) {
+            // filtering disabled, so only black and white pixels will come through
+            return BLACK_WHITE_ONLY_VERIFIER;
+        }
+        // if scaling up, output pixels may have single source to sample from,
+        // will only be *mostly* grey.
+        return scaleUp ? GREY_PARTIAL_VERIFIER : GREY_ONLY_VERIFIER;
     }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
index 3088142..29755d8 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
@@ -16,8 +16,6 @@
 
 package android.uirendering.cts.testclasses;
 
-import com.android.cts.uirendering.R;
-
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
@@ -31,15 +29,14 @@
 import android.uirendering.cts.bitmapverifiers.RectVerifier;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 import android.uirendering.cts.testinfrastructure.CanvasClient;
-import android.uirendering.cts.testinfrastructure.ViewInitializer;
-import android.view.View;
+import com.android.cts.uirendering.R;
 
 public class ExactCanvasTests extends ActivityTestBase {
     private final BitmapComparer mExactComparer = new ExactComparer();
 
     @SmallTest
     public void testBlueRect() {
-        final Rect rect = new Rect(10, 10, 100, 100);
+        final Rect rect = new Rect(10, 10, 80, 80);
         createTest()
                 .addCanvasClient(new CanvasClient() {
                     @Override
@@ -61,8 +58,6 @@
                     public void draw(Canvas canvas, int width, int height) {
                         Paint p = new Paint();
                         p.setAntiAlias(false);
-                        p.setColor(Color.WHITE);
-                        canvas.drawRect(0, 0, 100, 100, p);
                         p.setStrokeWidth(1f);
                         p.setColor(Color.BLACK);
                         for (int i = 0; i < 10; i++) {
@@ -84,8 +79,8 @@
                         canvas.drawRect(0, 0, ActivityTestBase.TEST_WIDTH,
                                 ActivityTestBase.TEST_HEIGHT, p);
                         p.setColor(Color.BLACK);
-                        p.setStrokeWidth(10);
-                        canvas.drawRect(10, 10, 20, 20, p);
+                        p.setStrokeWidth(5);
+                        canvas.drawRect(10, 10, 80, 80, p);
                     }
                 })
                 .runWithComparer(mExactComparer);
@@ -97,10 +92,8 @@
                 .addCanvasClient(new CanvasClient() {
                     @Override
                     public void draw(Canvas canvas, int width, int height) {
+                        canvas.drawColor(Color.GREEN);
                         Paint p = new Paint();
-                        p.setColor(Color.GREEN);
-                        canvas.drawRect(0, 0, ActivityTestBase.TEST_WIDTH,
-                                ActivityTestBase.TEST_HEIGHT, p);
                         p.setColor(Color.BLACK);
                         p.setStrokeWidth(10);
                         canvas.drawLine(0, 0, 50, 0, p);
@@ -134,7 +127,7 @@
                         canvas.drawColor(Color.WHITE);
                         p.setColor(Color.BLACK);
                         float[] pts = {
-                                0, 0, 100, 100, 100, 0, 0, 100, 50, 50, 75, 75
+                                0, 0, 80, 80, 80, 0, 0, 80, 40, 50, 60, 50
                         };
                         canvas.drawLines(pts, p);
                     }
@@ -188,10 +181,10 @@
     public void testBluePaddedSquare() {
         final NinePatchDrawable ninePatchDrawable = (NinePatchDrawable)
             getActivity().getResources().getDrawable(R.drawable.blue_padded_square);
-        ninePatchDrawable.setBounds(0, 0, 100, 100);
+        ninePatchDrawable.setBounds(0, 0, 90, 90);
 
         BitmapVerifier verifier = new RectVerifier(Color.WHITE, Color.BLUE,
-                new Rect(10, 10, 90, 90));
+                new Rect(10, 10, 80, 80));
 
         createTest()
                 .addCanvasClient(new CanvasClient() {
@@ -200,7 +193,7 @@
                         canvas.drawColor(Color.WHITE);
                         Paint p = new Paint();
                         p.setColor(Color.BLUE);
-                        canvas.drawRect(10, 10, 90, 90, p);
+                        canvas.drawRect(10, 10, 80, 80, p);
                     }
                 })
                 .addCanvasClient(new CanvasClient() {
@@ -212,14 +205,4 @@
                 .addLayout(R.layout.blue_padded_square, null)
                 .runWithVerifier(verifier);
     }
-
-    @SmallTest
-    public void testClipping() {
-        createTest().addLayout(R.layout.simple_red_layout, new ViewInitializer() {
-            @Override
-            public void intializeView(View view) {
-                view.setClipBounds(new Rect(0, 0, 50, 50));
-            }
-        }).runWithComparer(mExactComparer);
-    }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java
index 6662226..c17e106 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/InfrastructureTests.java
@@ -17,7 +17,6 @@
 
 import com.android.cts.uirendering.R;
 
-import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Rect;
@@ -29,7 +28,6 @@
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 import android.uirendering.cts.testinfrastructure.CanvasClient;
 import android.uirendering.cts.testinfrastructure.ViewInitializer;
-import android.util.Log;
 import android.view.View;
 
 public class InfrastructureTests extends ActivityTestBase {
@@ -52,16 +50,17 @@
         CanvasClient canvasClient = new CanvasClient() {
             @Override
             public void draw(Canvas canvas, int width, int height) {
-                canvas.drawColor(canvas.isHardwareAccelerated() ? Color.WHITE : Color.BLACK);
+                canvas.drawColor(canvas.isHardwareAccelerated() ? Color.BLACK : Color.WHITE);
             }
         };
-        // This is considered a very high threshold and as such, the test should still fail because
-        // they are completely different images.
-        final float threshold = 0.1f;
         BitmapComparer inverseComparer = new BitmapComparer() {
             @Override
             public boolean verifySame(int[] ideal, int[] given, int offset, int stride, int width,
                     int height) {
+
+                // Return true if the images aren't even 10% similar. They should be completely
+                // different, since they should both be completely different colors.
+                final float threshold = 0.1f;
                 return !(new MSSIMComparer(threshold)).verifySame(ideal, given, offset, stride,
                         width, height);
             }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayoutTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayoutTests.java
index 0ba0f69..ff1e9db 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayoutTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/LayoutTests.java
@@ -15,32 +15,26 @@
  */
 package android.uirendering.cts.testclasses;
 
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
+import android.uirendering.cts.bitmapverifiers.RectVerifier;
 import com.android.cts.uirendering.R;
 
 import android.test.suitebuilder.annotation.SmallTest;
-import android.uirendering.cts.bitmapcomparers.BitmapComparer;
-import android.uirendering.cts.bitmapcomparers.ExactComparer;
 import android.uirendering.cts.testinfrastructure.ActivityTestBase;
 
-
-/**
- * Created to see how custom views made with XML and programatic code will work.
- */
 public class LayoutTests extends ActivityTestBase {
-    private BitmapComparer mBitmapComparer;
-
-    public LayoutTests() {
-        mBitmapComparer = new ExactComparer();
-    }
-
     @SmallTest
     public void testSimpleRedLayout() {
-        createTest().addLayout(R.layout.simple_red_layout, null).runWithComparer(mBitmapComparer);
+        createTest().addLayout(R.layout.simple_red_layout, null, false).runWithVerifier(
+                new ColorVerifier(Color.RED));
     }
 
     @SmallTest
     public void testSimpleRectLayout() {
-        createTest().addLayout(R.layout.simple_rect_layout, null).runWithComparer(mBitmapComparer);
+        createTest().addLayout(R.layout.simple_rect_layout, null, false).runWithVerifier(
+                new RectVerifier(Color.WHITE, Color.BLUE, new Rect(5, 5, 85, 85)));
     }
 }
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java
new file mode 100644
index 0000000..6911cf0
--- /dev/null
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/PathClippingTests.java
@@ -0,0 +1,146 @@
+package android.uirendering.cts.testclasses;
+
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Paint;
+import android.graphics.Path;
+import android.graphics.Point;
+import android.graphics.Typeface;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.uirendering.cts.bitmapcomparers.MSSIMComparer;
+import android.uirendering.cts.bitmapverifiers.SamplePointVerifier;
+import android.uirendering.cts.testinfrastructure.ActivityTestBase;
+import android.uirendering.cts.testinfrastructure.CanvasClient;
+import android.uirendering.cts.testinfrastructure.ViewInitializer;
+import android.view.View;
+import android.view.ViewGroup;
+import com.android.cts.uirendering.R;
+
+public class PathClippingTests extends ActivityTestBase {
+    // draw circle with hole in it, with stroked circle
+    static final CanvasClient sCircleDrawCanvasClient = new CanvasClient() {
+        @Override
+        public String getDebugString() {
+            return "StrokedCircleDraw";
+        }
+
+        @Override
+        public void draw(Canvas canvas, int width, int height) {
+            Paint paint = new Paint();
+            paint.setAntiAlias(false);
+            paint.setColor(Color.BLUE);
+            paint.setStyle(Paint.Style.STROKE);
+            paint.setStrokeWidth(20);
+            canvas.drawCircle(30, 30, 40, paint);
+        }
+    };
+
+    // draw circle with hole in it, by path operations + path clipping
+    static final CanvasClient sCircleClipCanvasClient = new CanvasClient() {
+        @Override
+        public String getDebugString() {
+            return "CircleClipDraw";
+        }
+
+        @Override
+        public void draw(Canvas canvas, int width, int height) {
+            canvas.save();
+
+            Path path = new Path();
+            path.addCircle(30, 30, 50, Path.Direction.CW);
+            path.addCircle(30, 30, 30, Path.Direction.CCW);
+
+            canvas.clipPath(path);
+            canvas.drawColor(Color.BLUE);
+
+            canvas.restore();
+        }
+    };
+
+    @SmallTest
+    public void testCircleWithCircle() {
+        createTest()
+                .addCanvasClient(sCircleDrawCanvasClient, false)
+                .addCanvasClient(sCircleClipCanvasClient)
+                .runWithComparer(new MSSIMComparer(0.90));
+    }
+
+    @SmallTest
+    public void testCircleWithPoints() {
+        createTest()
+                .addCanvasClient(sCircleClipCanvasClient)
+                .runWithVerifier(new SamplePointVerifier(
+                        new Point[] {
+                                // inside of circle
+                                new Point(30, 50),
+                                // on circle
+                                new Point(30 + 32, 30 + 32),
+                                // outside of circle
+                                new Point(30 + 38, 30 + 38),
+                                new Point(80, 80)
+                        },
+                        new int[] {
+                                Color.WHITE,
+                                Color.BLUE,
+                                Color.WHITE,
+                                Color.WHITE,
+                        }));
+    }
+
+    @SmallTest
+    public void testViewRotate() {
+        createTest()
+                .addLayout(R.layout.blue_padded_layout, new ViewInitializer() {
+                    @Override
+                    public void intializeView(View view) {
+                        ViewGroup rootView = (ViewGroup) view;
+                        rootView.setClipChildren(true);
+                        View childView = rootView.getChildAt(0);
+                        childView.setPivotX(40);
+                        childView.setPivotY(40);
+                        childView.setRotation(45f);
+
+                    }
+                })
+                .runWithVerifier(new SamplePointVerifier(
+                        new Point[] {
+                                // inside of rotated rect
+                                new Point(40, 40),
+                                new Point(40 + 25, 40 + 25),
+                                // outside of rotated rect
+                                new Point(40 + 31, 40 + 31),
+                                new Point(80, 80)
+                        },
+                        new int[] {
+                                Color.BLUE,
+                                Color.BLUE,
+                                Color.WHITE,
+                                Color.WHITE,
+                        }));
+    }
+
+    @SmallTest
+    public void testTextClip() {
+        createTest()
+                .addCanvasClient(new CanvasClient() {
+                    @Override
+                    public void draw(Canvas canvas, int width, int height) {
+                        canvas.save();
+
+                        Path path = new Path();
+                        path.addCircle(0, 45, 45, Path.Direction.CW);
+                        path.addCircle(90, 45, 45, Path.Direction.CW);
+                        canvas.clipPath(path);
+
+                        Paint paint = new Paint();
+                        paint.setAntiAlias(true);
+                        paint.setTextSize(90);
+                        paint.setTypeface(Typeface.defaultFromStyle(Typeface.BOLD));
+                        canvas.drawText("STRING", 0, 90, paint);
+
+                        canvas.restore();
+                    }
+                })
+                .runWithComparer(new MSSIMComparer(0.90));
+    }
+}
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SweepTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SweepTests.java
index 7947286..32ab0e4 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SweepTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/SweepTests.java
@@ -22,6 +22,7 @@
 import android.graphics.Paint;
 import android.graphics.Point;
 import android.graphics.PorterDuff;
+import android.graphics.PorterDuffXfermode;
 import android.graphics.Rect;
 import android.graphics.RectF;
 import android.graphics.Shader;
@@ -50,17 +51,11 @@
     public static final int MULTIPLY_COLOR = 0xFF668844;
     public static final int SCREEN_COLOR = 0xFFFFEEFF;
 
-    public static final int FILTER_COLOR = 0xFFBB0000;
-    public static final int RECT0_COLOR = 0x33808080;
-    public static final int RECT1_COLOR = 0x66808080;
-    public static final int RECT2_COLOR = 0x99808080;
-    public static final int RECT3_COLOR = 0xCC808080;
-
     // These points are in pairs, the first being the lower left corner, the second is only in the
     // Destination bitmap, the third is the intersection of the two bitmaps, and the fourth is in
     // the Source bitmap.
     private final static Point[] XFERMODE_TEST_POINTS = new Point[] {
-            new Point(1, 160), new Point(50, 50), new Point(70, 70), new Point(140, 140)
+            new Point(1, 80), new Point(25, 25), new Point(35, 35), new Point(70, 70)
     };
 
     /**
@@ -128,8 +123,8 @@
     };
 
     private final static DisplayModifier XFERMODE_MODIFIER = new DisplayModifier() {
-        private final RectF mSrcRect = new RectF(60, 60, 160, 160);
-        private final RectF mDstRect = new RectF(20, 20, 120, 120);
+        private final RectF mSrcRect = new RectF(30, 30, 80, 80);
+        private final RectF mDstRect = new RectF(10, 10, 60, 60);
         private final Bitmap mSrcBitmap = createSrc();
         private final Bitmap mDstBitmap = createDst();
 
@@ -144,8 +139,7 @@
         }
 
         private Bitmap createSrc() {
-            Bitmap srcB = Bitmap.createBitmap(MODIFIER_WIDTH, MODIFIER_HEIGHT,
-                    Bitmap.Config.ARGB_8888);
+            Bitmap srcB = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
             Canvas srcCanvas = new Canvas(srcB);
             Paint srcPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
             srcPaint.setColor(SRC_COLOR);
@@ -154,8 +148,7 @@
         }
 
         private Bitmap createDst() {
-            Bitmap dstB = Bitmap.createBitmap(MODIFIER_WIDTH, MODIFIER_HEIGHT,
-                    Bitmap.Config.ARGB_8888);
+            Bitmap dstB = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
             Canvas dstCanvas = new Canvas(dstB);
             Paint dstPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
             dstPaint.setColor(DST_COLOR);
@@ -164,18 +157,22 @@
         }
     };
 
-
     // We care about one point in each of the four rectangles of different alpha values, as well as
     // the area outside the rectangles
     private final static Point[] COLOR_FILTER_ALPHA_POINTS = new Point[] {
-            new Point(15, 90), new Point(45, 90), new Point(75, 90), new Point(105, 90),
-            new Point(135, 90)
+            new Point(9, 45),
+            new Point(27, 45),
+            new Point(45, 45),
+            new Point(63, 45),
+            new Point(81, 45)
     };
 
-    private final Map<PorterDuff.Mode, int[]> COLOR_FILTER_ALPHA_MAP = new LinkedHashMap<PorterDuff.Mode, int[]>() {
+    public static final int FILTER_COLOR = 0xFFBB0000;
+    private final Map<PorterDuff.Mode, int[]> COLOR_FILTER_ALPHA_MAP
+            = new LinkedHashMap<PorterDuff.Mode, int[]>() {
         {
             put(PorterDuff.Mode.SRC, new int[] {
-                FILTER_COLOR, FILTER_COLOR, FILTER_COLOR, FILTER_COLOR, FILTER_COLOR
+                    FILTER_COLOR, FILTER_COLOR, FILTER_COLOR, FILTER_COLOR, FILTER_COLOR
             });
 
             put(PorterDuff.Mode.DST, new int[] {
@@ -228,10 +225,17 @@
         }
     };
 
+    /**
+     * Draws 5 blocks of different color/opacity to be blended against
+     */
     private final static DisplayModifier COLOR_FILTER_ALPHA_MODIFIER = new DisplayModifier() {
-        private final static int mBlockWidths = 30;
-        private final int[] mColorValues = new int[] {RECT0_COLOR, RECT1_COLOR, RECT2_COLOR,
-                RECT3_COLOR};
+        private final int[] BLOCK_COLORS = new int[] {
+                0x33808080,
+                0x66808080,
+                0x99808080,
+                0xCC808080,
+                0x00000000
+        };
 
         private final Bitmap mBitmap = createQuadRectBitmap();
 
@@ -240,13 +244,15 @@
         }
 
         private Bitmap createQuadRectBitmap() {
-            Bitmap bitmap = Bitmap.createBitmap(MODIFIER_WIDTH, MODIFIER_HEIGHT,
-                    Bitmap.Config.ARGB_8888);
+            Bitmap bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
             Canvas canvas = new Canvas(bitmap);
             Paint paint = new Paint();
-            for (int i = 0 ; i < 4 ; i++) {
-                paint.setColor(mColorValues[i]);
-                canvas.drawRect(i * mBlockWidths, 0, (i + 1) * mBlockWidths, MODIFIER_HEIGHT, paint);
+            paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC));
+            final int blockCount = BLOCK_COLORS.length;
+            final int blockWidth = TEST_WIDTH / blockCount;
+            for (int i = 0 ; i < blockCount; i++) {
+                paint.setColor(BLOCK_COLORS[i]);
+                canvas.drawRect(i * blockWidth, 0, (i + 1) * blockWidth, TEST_HEIGHT, paint);
             }
             return bitmap;
         }
@@ -266,10 +272,9 @@
         }
 
         private Bitmap createGradient() {
-            LinearGradient gradient = new LinearGradient(30, 90, 150, 90, mColors, null,
+            LinearGradient gradient = new LinearGradient(15, 45, 75, 45, mColors, null,
                     Shader.TileMode.REPEAT);
-            Bitmap bitmap = Bitmap.createBitmap(MODIFIER_WIDTH, MODIFIER_HEIGHT,
-                    Bitmap.Config.ARGB_8888);
+            Bitmap bitmap = Bitmap.createBitmap(TEST_WIDTH, TEST_HEIGHT, Bitmap.Config.ARGB_8888);
             Paint p = new Paint();
             p.setShader(gradient);
             Canvas c = new Canvas(bitmap);
@@ -281,9 +286,7 @@
     public static final DisplayModifier mCircleDrawModifier = new DisplayModifier() {
         @Override
         public void modifyDrawing(Paint paint, Canvas canvas) {
-            canvas.drawCircle(ActivityTestBase.TEST_WIDTH / 2,
-                    ActivityTestBase.TEST_HEIGHT / 2,
-                    ActivityTestBase.TEST_HEIGHT / 2, paint);
+            canvas.drawCircle(TEST_WIDTH / 2, TEST_HEIGHT / 2, TEST_HEIGHT / 2, paint);
         }
     };
 
@@ -343,16 +346,15 @@
     }
 
     /*
-     * TODO: fix this test for L MR1
     @SmallTest
     public void testShaderSweeps() {
-        int mask = DisplayModifier.Accessor.AA_MASK |
-                DisplayModifier.Accessor.SHADER_MASK |
-                DisplayModifier.Accessor.XFERMODE_MASK |
-                DisplayModifier.Accessor.SHAPES_MASK;
+        int mask = DisplayModifier.Accessor.AA_MASK
+                | DisplayModifier.Accessor.SHADER_MASK
+                | DisplayModifier.Accessor.XFERMODE_MASK
+                | DisplayModifier.Accessor.SHAPES_MASK;
         sweepModifiersForMask(mask, null, DEFAULT_MSSIM_COMPARER, null);
     }
-     */
+    */
 
     protected void sweepModifiersForMask(int mask, final DisplayModifier drawOp,
             BitmapComparer[] bitmapComparers, BitmapVerifier[] bitmapVerifiers) {
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java
index 1acdc20..343228f 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ViewClippingTests.java
@@ -20,11 +20,11 @@
  * Since the layout is blue on a white background, this is always done with a RectVerifier.
  */
 public class ViewClippingTests extends ActivityTestBase {
-    final Rect FULL_RECT = new Rect(0, 0, 200, 200);
-    final Rect BOUNDS_RECT = new Rect(0, 0, 100, 100);
-    final Rect PADDED_RECT = new Rect(15, 16, 83, 82);
-    final Rect OUTLINE_RECT = new Rect(1, 2, 98, 99);
-    final Rect CLIP_BOUNDS_RECT = new Rect(10, 20, 70, 80);
+    final Rect FULL_RECT = new Rect(0, 0, 90, 90);
+    final Rect BOUNDS_RECT = new Rect(0, 0, 80, 80);
+    final Rect PADDED_RECT = new Rect(15, 16, 63, 62);
+    final Rect OUTLINE_RECT = new Rect(1, 2, 78, 79);
+    final Rect CLIP_BOUNDS_RECT = new Rect(10, 20, 50, 60);
 
     final ViewInitializer BOUNDS_CLIP_INIT = new ViewInitializer() {
         @Override
@@ -61,10 +61,9 @@
         }
     };
 
-    // TODO: attempt to reduce
-    static final int TOLERANCE = 10;
     static BitmapVerifier makeClipVerifier(Rect blueBoundsRect) {
-        return new RectVerifier(Color.WHITE, Color.BLUE, blueBoundsRect, TOLERANCE);
+        // very high error tolerance, since all these tests care about is clip alignment
+        return new RectVerifier(Color.WHITE, Color.BLUE, blueBoundsRect, 75);
     }
 
     public void testSimpleUnclipped() {
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/UnclippedBlueView.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/UnclippedBlueView.java
index e2037f7..7a16e3c 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/UnclippedBlueView.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/view/UnclippedBlueView.java
@@ -18,12 +18,12 @@
 
     public UnclippedBlueView(Context context, AttributeSet attrs, int defStyleAttr) {
         this(context, attrs, defStyleAttr, 0);
-        setWillNotDraw(false);
     }
 
     public UnclippedBlueView(Context context, AttributeSet attrs,
             int defStyleAttr, int defStyleRes) {
         super(context, attrs, defStyleAttr, defStyleRes);
+        setWillNotDraw(false);
     }
 
     @Override
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
index 052b251..e3ad5a6 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/ActivityTestBase.java
@@ -38,18 +38,19 @@
  */
 public abstract class ActivityTestBase extends
         ActivityInstrumentationTestCase2<DrawActivity> {
-    public static final String TAG_NAME = "ActivityTestBase";
+    public static final String TAG = "ActivityTestBase";
     public static final boolean DEBUG = false;
     public static final boolean USE_RS = false;
-    public static final int TEST_WIDTH = 180;
-    public static final int TEST_HEIGHT = 180; //The minimum height and width of a device
+
+    //The minimum height and width of a device
+    public static final int TEST_WIDTH = 90;
+    public static final int TEST_HEIGHT = 90;
+
     public static final int MAX_SCREEN_SHOTS = 100;
 
     private int[] mHardwareArray = new int[TEST_HEIGHT * TEST_WIDTH];
     private int[] mSoftwareArray = new int[TEST_HEIGHT * TEST_WIDTH];
     private DifferenceVisualizer mDifferenceVisualizer;
-    private Allocation mIdealAllocation;
-    private Allocation mGivenAllocation;
     private RenderScript mRenderScript;
     private TestCaseBuilder mTestCaseBuilder;
 
@@ -97,7 +98,7 @@
 
             for (TestCase testCase : testCases) {
                 if (!testCase.wasTestRan) {
-                    Log.w(TAG_NAME, getName() + " not all of the tests were ran");
+                    Log.w(TAG, getName() + " not all of the tests ran");
                     break;
                 }
             }
@@ -115,16 +116,31 @@
         getActivity().runOnUiThread(finishRunnable);
     }
 
+    static int[] getBitmapPixels(Bitmap bitmap) {
+        int[] pixels = new int[bitmap.getWidth() * bitmap.getHeight()];
+        bitmap.getPixels(pixels, 0, bitmap.getWidth(),
+                0, 0, bitmap.getWidth(), bitmap.getHeight());
+        return pixels;
+    }
+
+    private Bitmap takeScreenshotImpl() {
+        Bitmap source = getInstrumentation().getUiAutomation().takeScreenshot();
+        int x = (source.getWidth() - TEST_WIDTH) / 2;
+        int y = (source.getHeight() - TEST_HEIGHT) / 2;
+        return Bitmap.createBitmap(source, x, y, TEST_WIDTH, TEST_HEIGHT);
+    }
+
     public Bitmap takeScreenshot() {
         getInstrumentation().waitForIdleSync();
-        Bitmap bitmap1 = getInstrumentation().getUiAutomation().takeScreenshot();
+        Bitmap bitmap1 = takeScreenshotImpl();
         Bitmap bitmap2;
         int count = 0;
         do  {
             bitmap2 = bitmap1;
-            bitmap1 = getInstrumentation().getUiAutomation().takeScreenshot();
+            bitmap1 = takeScreenshotImpl();
             count++;
-        } while (count < MAX_SCREEN_SHOTS && !Arrays.equals(bitmap2.mBuffer, bitmap1.mBuffer));
+        } while (count < MAX_SCREEN_SHOTS &&
+                !Arrays.equals(getBitmapPixels(bitmap2), getBitmapPixels(bitmap1)));
         return bitmap1;
     }
 
@@ -154,12 +170,12 @@
         boolean success;
 
         if (USE_RS && comparer.supportsRenderScript()) {
-            mIdealAllocation = Allocation.createFromBitmap(mRenderScript, bitmap1,
+            Allocation idealAllocation = Allocation.createFromBitmap(mRenderScript, bitmap1,
                     Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
-            mGivenAllocation = Allocation.createFromBitmap(mRenderScript, bitmap2,
+            Allocation givenAllocation = Allocation.createFromBitmap(mRenderScript, bitmap2,
                     Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_SCRIPT);
-            success = comparer.verifySameRS(getActivity().getResources(), mIdealAllocation,
-                    mGivenAllocation, 0, TEST_WIDTH, TEST_WIDTH, TEST_HEIGHT, mRenderScript);
+            success = comparer.verifySameRS(getActivity().getResources(), idealAllocation,
+                    givenAllocation, 0, TEST_WIDTH, TEST_WIDTH, TEST_HEIGHT, mRenderScript);
         } else {
             bitmap1.getPixels(mSoftwareArray, 0, TEST_WIDTH, 0, 0, TEST_WIDTH, TEST_HEIGHT);
             bitmap2.getPixels(mHardwareArray, 0, TEST_WIDTH, 0, 0, TEST_WIDTH, TEST_HEIGHT);
@@ -213,6 +229,11 @@
          * every test case is tested against it.
          */
         public void runWithComparer(BitmapComparer bitmapComparer) {
+            if (getActivity().getOnWatch()) {
+                Log.d(TAG, getName() + "skipped");
+                return;
+            }
+
             if (mTestCases.size() == 0) {
                 throw new IllegalStateException("Need at least one test to run");
             }
@@ -231,6 +252,11 @@
          * the verifier given.
          */
         public void runWithVerifier(BitmapVerifier bitmapVerifier) {
+            if (getActivity().getOnWatch()) {
+                Log.d(TAG, getName() + "skipped");
+                return;
+            }
+
             if (mTestCases.size() == 0) {
                 throw new IllegalStateException("Need at least one test to run");
             }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
index 92242f0..60127ae 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/CanvasClientView.java
@@ -19,6 +19,7 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.util.AttributeSet;
 import android.view.View;
 
 /**
@@ -26,14 +27,21 @@
  */
 public class CanvasClientView extends View {
     private CanvasClient mCanvasClient;
-    private int mWidth;
-    private int mHeight;
 
-    public CanvasClientView(Context context, CanvasClient canvasClient, int width, int height) {
+    public CanvasClientView(Context context) {
         super(context);
+    }
+
+    public CanvasClientView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public CanvasClientView(Context context, AttributeSet attrs, int defStyleAttr) {
+        super(context, attrs, defStyleAttr);
+    }
+
+    public void setCanvasClient(CanvasClient canvasClient) {
         mCanvasClient = canvasClient;
-        mWidth = width;
-        mHeight = height;
     }
 
     @Override
@@ -45,11 +53,11 @@
             paint.setTextSize(20);
             canvas.drawText(s, 200, 200, paint);
         }
-        if (mCanvasClient != null) {
-            canvas.save();
-            canvas.clipRect(0, 0, mWidth, mHeight);
-            mCanvasClient.draw(canvas, mWidth, mHeight);
-            canvas.restore();
-        }
+        if (mCanvasClient == null) throw new IllegalStateException("Canvas client missing");
+
+        canvas.save();
+        canvas.clipRect(0, 0, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
+        mCanvasClient.draw(canvas, ActivityTestBase.TEST_WIDTH, ActivityTestBase.TEST_HEIGHT);
+        canvas.restore();
     }
 }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DisplayModifier.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DisplayModifier.java
index 684293d..b42ac88 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DisplayModifier.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DisplayModifier.java
@@ -32,17 +32,16 @@
  * Modifies the canvas and paint objects when called.
  */
 public abstract class DisplayModifier {
-    private static final RectF gRect = new RectF(0, 0, 100, 100);
-    private static final float[] gPts = new float[]{
-            0, 100, 100, 0, 100, 200, 200, 100
+    private static final RectF RECT = new RectF(0, 0, 100, 100);
+    private static final float[] POINTS = new float[]{
+            0, 40, 40, 0, 40, 80, 80, 40
     };
-    private static final float[] gTriPts = new float[]{
-            75, 0, 130, 130, 130, 130, 0, 130, 0, 130, 75, 0
+    private static final float[] TRIANGLE_POINTS = new float[]{
+            40, 0, 80, 80, 80, 80, 0, 80, 0, 80, 40, 0
     };
-    private static final int NUM_PARALLEL_LINES = 24;
-    private static final float[] gLinePts = new float[NUM_PARALLEL_LINES * 8 + gTriPts.length];
-    protected static final int MODIFIER_WIDTH = 180;
-    protected static final int MODIFIER_HEIGHT = 180;
+    private static final int NUM_PARALLEL_LINES = 10;
+    private static final float[] LINES = new float[NUM_PARALLEL_LINES * 8
+            + TRIANGLE_POINTS.length];
 
     public static final PorterDuff.Mode[] PORTERDUFF_MODES = new PorterDuff.Mode[] {
         PorterDuff.Mode.SRC, PorterDuff.Mode.DST, PorterDuff.Mode.SRC_OVER,
@@ -53,25 +52,23 @@
     };
 
     static {
-        int index;
-        for (index = 0; index < gTriPts.length; index++) {
-            gLinePts[index] = gTriPts[index];
-        }
+        System.arraycopy(TRIANGLE_POINTS, 0, LINES, 0, TRIANGLE_POINTS.length);
+        int index = TRIANGLE_POINTS.length;
         float val = 0;
         for (int i = 0; i < NUM_PARALLEL_LINES; i++) {
-            gLinePts[index + 0] = 150;
-            gLinePts[index + 1] = val;
-            gLinePts[index + 2] = 300;
-            gLinePts[index + 3] = val;
+            LINES[index + 0] = 40;
+            LINES[index + 1] = val;
+            LINES[index + 2] = 80;
+            LINES[index + 3] = val;
             index += 4;
             val += 8 + (2.0f / NUM_PARALLEL_LINES);
         }
         val = 0;
         for (int i = 0; i < NUM_PARALLEL_LINES; i++) {
-            gLinePts[index + 0] = val;
-            gLinePts[index + 1] = 150;
-            gLinePts[index + 2] = val;
-            gLinePts[index + 3] = 300;
+            LINES[index + 0] = val;
+            LINES[index + 1] = 40;
+            LINES[index + 2] = val;
+            LINES[index + 3] = 80;
             index += 4;
             val += 8 + (2.0f / NUM_PARALLEL_LINES);
         }
@@ -81,7 +78,7 @@
     // paint object, like anti-aliasing or drawing. Within those LinkedHashMaps are the various
     // options for that specific topic, which contains a displaymodifier which will affect the
     // given canvas and paint objects.
-    public static final LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>> sMaps =
+    public static final LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>> MAPS =
             new LinkedHashMap<String, LinkedHashMap<String,DisplayModifier>>() {
                 {
                     put("aa", new LinkedHashMap<String, DisplayModifier>() {
@@ -225,7 +222,7 @@
                                 @Override
                                 public void modifyDrawing(Paint paint, Canvas canvas) {
                                     canvas.rotate(90);
-                                    canvas.translate(0, -200);
+                                    canvas.translate(0, -100);
                                 }
                             });
                             put("scale2x2", new DisplayModifier() {
@@ -347,13 +344,13 @@
                             put("roundRect", new DisplayModifier() {
                                 @Override
                                 public void modifyDrawing(Paint paint, Canvas canvas) {
-                                    canvas.drawRoundRect(gRect, 20, 20, paint);
+                                    canvas.drawRoundRect(RECT, 20, 20, paint);
                                 }
                             });
                             put("rect", new DisplayModifier() {
                                 @Override
                                 public void modifyDrawing(Paint paint, Canvas canvas) {
-                                    canvas.drawRect(gRect, paint);
+                                    canvas.drawRect(RECT, paint);
                                 }
                             });
                             put("circle", new DisplayModifier() {
@@ -365,36 +362,32 @@
                             put("oval", new DisplayModifier() {
                                 @Override
                                 public void modifyDrawing(Paint paint, Canvas canvas) {
-                                    canvas.drawOval(gRect, paint);
+                                    canvas.drawOval(RECT, paint);
                                 }
                             });
                             put("lines", new DisplayModifier() {
                                 @Override
                                 public void modifyDrawing(Paint paint, Canvas canvas) {
-                                    canvas.drawLines(gLinePts, paint);
+                                    canvas.drawLines(LINES, paint);
                                 }
                             });
-                            /* drawPoints does not work with zero stroke width,
-                             * but it isn't a regression
-                             * TODO: fix hardware canvas so that drawPoints works
                             put("plusPoints", new DisplayModifier() {
                                 @Override
                                 public void modifyDrawing(Paint paint, Canvas canvas) {
-                                    canvas.drawPoints(gPts, paint);
+                                    canvas.drawPoints(POINTS, paint);
                                 }
                             });
-                             */
                             put("text", new DisplayModifier() {
                                 @Override
                                 public void modifyDrawing(Paint paint, Canvas canvas) {
-                                    paint.setTextSize(36);
+                                    paint.setTextSize(20);
                                     canvas.drawText("TEXTTEST", 0, 50, paint);
                                 }
                             });
                             put("shadowtext", new DisplayModifier() {
                                 @Override
                                 public void modifyDrawing(Paint paint, Canvas canvas) {
-                                    paint.setTextSize(36);
+                                    paint.setTextSize(20);
                                     paint.setShadowLayer(3.0f, 0.0f, 3.0f, 0xffff00ff);
                                     canvas.drawText("TEXTTEST", 0, 50, paint);
                                 }
@@ -410,13 +403,13 @@
                             put("arc", new DisplayModifier() {
                                 @Override
                                 public void modifyDrawing(Paint paint, Canvas canvas) {
-                                    canvas.drawArc(gRect, 260, 285, false, paint);
+                                    canvas.drawArc(RECT, 260, 285, false, paint);
                                 }
                             });
                             put("arcFromCenter", new DisplayModifier() {
                                 @Override
                                 public void modifyDrawing(Paint paint, Canvas canvas) {
-                                    canvas.drawArc(gRect, 260, 285, true, paint);
+                                    canvas.drawArc(RECT, 260, 285, true, paint);
                                 }
                             });
                         }
@@ -454,9 +447,9 @@
             // Create a Display Map of the valid indices
             mDisplayMap = new LinkedHashMap<String, LinkedHashMap<String, DisplayModifier>>();
             int index = 0;
-            for (String key : DisplayModifier.sMaps.keySet()) {
+            for (String key : DisplayModifier.MAPS.keySet()) {
                 if (validIndex(index)) {
-                    mDisplayMap.put(key, DisplayModifier.sMaps.get(key));
+                    mDisplayMap.put(key, DisplayModifier.MAPS.get(key));
                 }
                 index++;
             }
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
index 4d4a012..65ed7ee 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testinfrastructure/DrawActivity.java
@@ -17,15 +17,17 @@
 
 import android.annotation.Nullable;
 import android.app.Activity;
-import android.content.Context;
+import android.content.res.Configuration;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
-import android.util.AttributeSet;
 import android.view.View;
+import android.view.ViewStub;
 import android.view.ViewTreeObserver;
 import android.webkit.WebView;
 
+import com.android.cts.uirendering.R;
+
 /**
  * A generic activity that uses a view specified by the user.
  */
@@ -36,16 +38,19 @@
 
     private Handler mHandler;
     private View mView;
+    private boolean mOnWatch;
 
     public void onCreate(Bundle bundle){
         super.onCreate(bundle);
+        getWindow().getDecorView().setSystemUiVisibility(
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
         mHandler = new RenderSpecHandler();
+        int uiMode = getResources().getConfiguration().uiMode;
+        mOnWatch = (uiMode & Configuration.UI_MODE_TYPE_WATCH) == Configuration.UI_MODE_TYPE_WATCH;
     }
 
-    @Override
-    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
-        mView = parent;
-        return super.onCreateView(parent, name, context, attrs);
+    public boolean getOnWatch() {
+        return mOnWatch;
     }
 
     public void enqueueRenderSpecAndWait(int layoutId, CanvasClient canvasClient, String webViewUrl,
@@ -81,35 +86,40 @@
         }
 
         public void handleMessage(Message message) {
-            int webViewBuffer = 0;
+            int drawCountDelay = 0;
+            setContentView(R.layout.test_container);
+            ViewStub stub = (ViewStub) findViewById(R.id.test_content_stub);
             switch (message.what) {
                 case LAYOUT_MSG: {
-                    setContentView(message.arg1);
+                    stub.setLayoutResource(message.arg1);
+                    mView = stub.inflate();
                 } break;
 
                 case CANVAS_MSG: {
-                    mView = new CanvasClientView(getApplicationContext(),
-                            (CanvasClient) (message.obj), ActivityTestBase.TEST_WIDTH,
-                            ActivityTestBase.TEST_HEIGHT);
-                    setContentView(mView);
+                    stub.setLayoutResource(R.layout.test_content_canvasclientview);
+                    mView = stub.inflate();
+                    ((CanvasClientView) mView).setCanvasClient((CanvasClient) (message.obj));
                 } break;
 
                 case WEB_VIEW_MSG: {
-                    mView = new WebView(getApplicationContext());
+                    stub.setLayoutResource(R.layout.test_content_webview);
+                    mView = stub.inflate();
                     ((WebView) mView).loadUrl((String) message.obj);
                     ((WebView) mView).setInitialScale(100);
-                    setContentView(mView);
-                    webViewBuffer = 10;
+                    drawCountDelay = 10;
                 } break;
             }
 
+            if (mView == null) {
+                throw new IllegalStateException("failed to inflate test content");
+            }
+
             if (mViewInitializer != null) {
                 mViewInitializer.intializeView(mView);
             }
-
             mView.setLayerType(message.arg2, null);
 
-            DrawCounterListener onDrawListener = new DrawCounterListener(webViewBuffer);
+            DrawCounterListener onDrawListener = new DrawCounterListener(drawCountDelay);
 
             mView.getViewTreeObserver().addOnPreDrawListener(onDrawListener);
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java b/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java
index 41e255b..8dd98b0 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/util/BitmapDumper.java
@@ -99,9 +99,25 @@
         saveFile(className, testName, SINGULAR_FILE_NAME, bitmap);
     }
 
+    private static void logIfBitmapSolidColor(String bitmapName, Bitmap bitmap) {
+        int firstColor = bitmap.getPixel(0, 0);
+        for (int x = 0; x < bitmap.getWidth(); x++) {
+            for (int y = 0; y < bitmap.getHeight(); y++) {
+                if (bitmap.getPixel(x, y) != firstColor) {
+                    return;
+                }
+            }
+        }
+
+        Log.w(TAG, String.format("%s entire bitmap color is %x", bitmapName, firstColor));
+    }
+
     private static void saveFile(String className, String testName, String fileName, Bitmap bitmap) {
-        Log.d(TAG, "Saving file : " + testName + "_" + fileName + " in directory : " + className);
-        File file = new File(CAPTURE_SUB_DIRECTORY + className, testName + "_" + fileName);
+        String bitmapName = testName + "_" + fileName;
+        Log.d(TAG, "Saving file : " + bitmapName + " in directory : " + className);
+        logIfBitmapSolidColor(bitmapName, bitmap);
+
+        File file = new File(CAPTURE_SUB_DIRECTORY + className, bitmapName);
         FileOutputStream fileStream = null;
         try {
             fileStream = new FileOutputStream(file);
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 6806d29..a36e6fd 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -84,7 +84,7 @@
         </activity>
         
         <activity android:name="android.view.animation.cts.AnimationTestCtsActivity"
-            android:label="AnimationTestCtsActivity">
+            android:label="AnimationTestCtsActivity" android:configChanges="orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
diff --git a/tests/tests/view/res/anim-land/changing_reset_state_anim.xml b/tests/tests/view/res/anim-land/changing_reset_state_anim.xml
new file mode 100644
index 0000000..0b3279a
--- /dev/null
+++ b/tests/tests/view/res/anim-land/changing_reset_state_anim.xml
@@ -0,0 +1,20 @@
+<?xml version="1.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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="100dp" android:valueType="floatType"/>
+    <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="100dp" android:valueType="floatType"/>
+    <objectAnimator android:propertyName="z" android:duration="100" android:valueTo="100dp" android:valueType="floatType"/>
+</set>
\ No newline at end of file
diff --git a/tests/tests/view/res/anim-land/changing_state_list_animator.xml b/tests/tests/view/res/anim-land/changing_state_list_animator.xml
new file mode 100644
index 0000000..4c1c41e
--- /dev/null
+++ b/tests/tests/view/res/anim-land/changing_state_list_animator.xml
@@ -0,0 +1,24 @@
+<?xml version="1.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="10" android:valueType="floatType"/>
+    </item>
+    <!-- base state-->
+    <item>
+        <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="200dp" android:valueType="floatType"/>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/view/res/anim-land/changing_test_animator.xml b/tests/tests/view/res/anim-land/changing_test_animator.xml
new file mode 100644
index 0000000..d5c83cd
--- /dev/null
+++ b/tests/tests/view/res/anim-land/changing_test_animator.xml
@@ -0,0 +1,20 @@
+<?xml version="1.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.
+-->
+<!-- if you change this, you should also change AnimatorInflaterTest#testLoadAnimator-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+                android:propertyName="x" android:duration="100"
+                android:interpolator="@android:anim/bounce_interpolator"
+                android:valueTo="1" android:valueType="floatType"/>
\ No newline at end of file
diff --git a/tests/tests/view/res/anim/changing_reset_state_anim.xml b/tests/tests/view/res/anim/changing_reset_state_anim.xml
new file mode 100644
index 0000000..dd2b233
--- /dev/null
+++ b/tests/tests/view/res/anim/changing_reset_state_anim.xml
@@ -0,0 +1,20 @@
+<?xml version="1.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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="50dp" android:valueType="floatType"/>
+    <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="50dp" android:valueType="floatType"/>
+    <objectAnimator android:propertyName="z" android:duration="100" android:valueTo="50dp" android:valueType="floatType"/>
+</set>
\ No newline at end of file
diff --git a/tests/tests/view/res/anim/changing_state_list_animator.xml b/tests/tests/view/res/anim/changing_state_list_animator.xml
new file mode 100644
index 0000000..6bcc0d9
--- /dev/null
+++ b/tests/tests/view/res/anim/changing_state_list_animator.xml
@@ -0,0 +1,24 @@
+<?xml version="1.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="10" android:valueType="floatType"/>
+    </item>
+    <!-- base state-->
+    <item>
+        <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="100dp" android:valueType="floatType"/>
+    </item>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/view/res/anim/changing_test_animator.xml b/tests/tests/view/res/anim/changing_test_animator.xml
new file mode 100644
index 0000000..739a71b
--- /dev/null
+++ b/tests/tests/view/res/anim/changing_test_animator.xml
@@ -0,0 +1,20 @@
+<?xml version="1.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.
+-->
+<!-- if you change this, you should also change AnimatorInflaterTest#testLoadAnimator-->
+<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
+                android:propertyName="x" android:duration="100"
+                android:interpolator="@android:anim/accelerate_decelerate_interpolator"
+                android:valueTo="1" android:valueType="floatType"/>
\ No newline at end of file
diff --git a/tests/tests/view/res/anim/reset_state_anim.xml b/tests/tests/view/res/anim/reset_state_anim.xml
new file mode 100644
index 0000000..4bbbe62
--- /dev/null
+++ b/tests/tests/view/res/anim/reset_state_anim.xml
@@ -0,0 +1,20 @@
+<?xml version="1.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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
+    <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
+    <objectAnimator android:propertyName="z" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
+</set>
\ No newline at end of file
diff --git a/tests/tests/view/res/anim/test_animator.xml b/tests/tests/view/res/anim/test_animator.xml
new file mode 100644
index 0000000..94e9ec8
--- /dev/null
+++ b/tests/tests/view/res/anim/test_animator.xml
@@ -0,0 +1,21 @@
+<?xml version="1.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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+    <!-- if you change this, you should also change AnimatorInflaterTest#testLoadAnimator-->
+    <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="@dimen/test_animator_target_x" android:valueType="floatType"/>
+    <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="@dimen/test_animator_target_y" android:valueType="floatType"/>
+    <objectAnimator android:propertyName="left" android:duration="100" android:valueTo="2" android:valueType="intType"/>
+</set>
\ No newline at end of file
diff --git a/tests/tests/view/res/anim/test_state_list_animator.xml b/tests/tests/view/res/anim/test_state_list_animator.xml
new file mode 100644
index 0000000..b6a4822
--- /dev/null
+++ b/tests/tests/view/res/anim/test_state_list_animator.xml
@@ -0,0 +1,33 @@
+<?xml version="1.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <set>
+            <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="10" android:valueType="floatType"/>
+            <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="20" android:valueType="floatType"/>
+            <objectAnimator android:propertyName="z" android:duration="100" android:valueTo="20" android:valueType="floatType"/>
+        </set>
+    </item>
+    <item android:state_enabled="true" android:state_pressed="false">
+        <set>
+            <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
+            <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
+            <objectAnimator android:propertyName="z" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
+        </set>
+    </item>
+    <!-- base state-->
+    <item android:animation="@anim/reset_state_anim"/>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/view/res/anim/test_state_list_animator_2.xml b/tests/tests/view/res/anim/test_state_list_animator_2.xml
new file mode 100644
index 0000000..6aeb41f
--- /dev/null
+++ b/tests/tests/view/res/anim/test_state_list_animator_2.xml
@@ -0,0 +1,22 @@
+<?xml version="1.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:state_pressed="true">
+        <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="10" android:valueType="floatType"/>
+    </item>
+    <!-- base state-->
+    <item android:animation="@anim/changing_reset_state_anim"/>
+</selector>
\ No newline at end of file
diff --git a/tests/tests/view/res/values-land/dimens.xml b/tests/tests/view/res/values-land/dimens.xml
new file mode 100644
index 0000000..17b5395
--- /dev/null
+++ b/tests/tests/view/res/values-land/dimens.xml
@@ -0,0 +1,23 @@
+<?xml version="1.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.
+-->
+<resources>
+    <dimen name="test_animator_target_y">30dp</dimen>
+    <!-- this value is equal to the value in changing_reset_state_anim. It is NOT referenced
+    in the XML on purpose-->
+    <dimen name="reset_state_value">100dp</dimen>
+    <!-- this value is equal to the value in changing_state_list_animator. It is NOT referenced in the XML on purpose-->
+    <dimen name="changing_state_list_anim_target_x_value">200dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/tests/tests/view/res/values/dimens.xml b/tests/tests/view/res/values/dimens.xml
new file mode 100644
index 0000000..16e5084
--- /dev/null
+++ b/tests/tests/view/res/values/dimens.xml
@@ -0,0 +1,23 @@
+<?xml version="1.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.
+-->
+<resources>
+    <dimen name="test_animator_target_x">10dp</dimen>
+    <dimen name="test_animator_target_y">20dp</dimen>
+    <!-- this value is equal to the value in changing_reset_state_anim. It is NOT referenced in the XML on purpose-->
+    <dimen name="reset_state_value">50dp</dimen>
+    <!-- this value is equal to the value in changing_state_list_animator. It is NOT referenced in the XML on purpose-->
+    <dimen name="changing_state_list_anim_target_x_value">100dp</dimen>
+</resources>
\ No newline at end of file
diff --git a/tests/tests/view/src/android/view/animation/cts/AnimationTestCtsActivity.java b/tests/tests/view/src/android/view/animation/cts/AnimationTestCtsActivity.java
index 48692f1..1ef9e48 100644
--- a/tests/tests/view/src/android/view/animation/cts/AnimationTestCtsActivity.java
+++ b/tests/tests/view/src/android/view/animation/cts/AnimationTestCtsActivity.java
@@ -20,11 +20,41 @@
 
 import android.app.Activity;
 import android.os.Bundle;
+import android.util.Log;
+
+import java.util.concurrent.TimeUnit;
 
 public class AnimationTestCtsActivity extends Activity {
+    final static long VISIBLE_TIMEOUT = TimeUnit.SECONDS.toNanos(3);
+    private boolean mIsVisible;
+
+    public boolean isVisible() {
+        return mIsVisible;
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.anim_layout);
     }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mIsVisible = true;
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mIsVisible = false;
+    }
+
+    public boolean waitUntilVisible() throws InterruptedException {
+        long start = System.nanoTime();
+        while (!mIsVisible && (System.nanoTime() - start) < VISIBLE_TIMEOUT) {
+            Thread.sleep(100);
+        }
+        return mIsVisible;
+    }
 }
diff --git a/tests/tests/view/src/android/view/animation/cts/AnimatorInflaterTest.java b/tests/tests/view/src/android/view/animation/cts/AnimatorInflaterTest.java
new file mode 100644
index 0000000..cc8ada0
--- /dev/null
+++ b/tests/tests/view/src/android/view/animation/cts/AnimatorInflaterTest.java
@@ -0,0 +1,299 @@
+/*
+* 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.view.animation.cts;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.StateListAnimator;
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.util.Log;
+import android.test.ActivityInstrumentationTestCase2;
+import android.view.Display;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import com.android.cts.view.R;
+
+public class AnimatorInflaterTest
+        extends ActivityInstrumentationTestCase2<AnimationTestCtsActivity> {
+
+    private static final String TAG = "AnimatorInflaterTest";
+    Set<Integer> identityHashes = new HashSet<Integer>();
+
+    public AnimatorInflaterTest() {
+        super("com.android.cts.view", AnimationTestCtsActivity.class);
+    }
+
+    private void assertUnique(Object object) {
+        assertUnique(object, "");
+    }
+
+    private void assertUnique(Object object, String msg) {
+        final int code = System.identityHashCode(object);
+        assertTrue("object should be unique " + msg + ", obj:" + object, identityHashes.add(code));
+
+    }
+
+    public void testLoadAnimatorWithDifferentInterpolators() throws Throwable {
+        Animator anim1 = AnimatorInflater
+                .loadAnimator(getActivity(), R.anim.changing_test_animator);
+        if (!rotate()) {
+            return;//cancel test
+        }
+        Animator anim2 = AnimatorInflater
+                .loadAnimator(getActivity(), R.anim.changing_test_animator);
+        assertNotSame(anim1, anim2);
+        assertNotSame("interpolater is orientation dependent, should change",
+                anim1.getInterpolator(), anim2.getInterpolator());
+    }
+
+    /**
+     * Tests animators with dimension references.
+     */
+    public void testLoadAnimator() throws Throwable {
+        // to identify objects
+        Animator anim1 = AnimatorInflater.loadAnimator(getActivity(), R.anim.test_animator);
+        Animator anim2 = AnimatorInflater.loadAnimator(getActivity(), R.anim.test_animator);
+        assertNotSame("a different animation should be returned", anim1, anim2);
+        assertSame("interpolator should be shallow cloned", anim1.getInterpolator(),
+                anim2.getInterpolator());
+        for (int i = 0; i < 2; i++) {
+            float targetX = getActivity().getResources()
+                    .getDimension(R.dimen.test_animator_target_x);
+            // y value changes in landscape orientation
+            float targetY = getActivity().getResources()
+                    .getDimension(R.dimen.test_animator_target_y);
+            for (Animator anim : new Animator[]{anim1, anim2}) {
+                assertTrue(anim instanceof AnimatorSet);
+                assertUnique(anim);
+                AnimatorSet set = (AnimatorSet) anim;
+                assertEquals("should have 3 sub animations", 3, set.getChildAnimations().size());
+                for (Animator subAnim : set.getChildAnimations()) {
+                    assertUnique(subAnim);
+                    assertTrue(subAnim instanceof ObjectAnimator);
+                }
+                final ObjectAnimator child1 = (ObjectAnimator) set.getChildAnimations().get(0);
+                final ObjectAnimator child2 = (ObjectAnimator) set.getChildAnimations().get(1);
+                final DummyObject dummyObject = new DummyObject();
+                runTestOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        for (ObjectAnimator animator : new ObjectAnimator[]{child1, child2}) {
+                            animator.setTarget(dummyObject);
+                            animator.setupStartValues();
+                            animator.start();
+                            animator.end();
+                        }
+                    }
+                });
+                assertEquals(targetX, dummyObject.x);
+                assertEquals(targetY, dummyObject.y);
+            }
+            if (i == 0) {
+                if (!rotate()) {
+                    return;//cancel test
+                }
+            }
+            anim1 = AnimatorInflater.loadAnimator(getActivity(), R.anim.test_animator);
+            anim2 = AnimatorInflater.loadAnimator(getActivity(), R.anim.test_animator);
+
+        }
+    }
+
+    private boolean rotate() throws Throwable {
+        WindowManager mWindowManager = (WindowManager) getActivity()
+                .getSystemService(Context.WINDOW_SERVICE);
+        Display display = mWindowManager.getDefaultDisplay();
+
+        Instrumentation.ActivityMonitor monitor = new Instrumentation.ActivityMonitor(
+                getActivity().getClass().getName(), null, false);
+        getInstrumentation().addMonitor(monitor);
+        int nextRotation = 0;
+        switch (display.getRotation()) {
+            case Surface.ROTATION_0:
+            case Surface.ROTATION_180:
+                nextRotation = UiAutomation.ROTATION_FREEZE_90;
+                break;
+            case Surface.ROTATION_90:
+            case Surface.ROTATION_270:
+                nextRotation = UiAutomation.ROTATION_FREEZE_0;
+                break;
+            default:
+                Log.e(TAG, "Cannot get rotation, test is canceled");
+                return false;
+        }
+        boolean rotated = getInstrumentation().getUiAutomation().setRotation(nextRotation);
+        Thread.sleep(500);
+        if (!rotated) {
+            Log.e(TAG, "Rotation failed, test is canceled");
+        }
+        getInstrumentation().waitForIdleSync();
+        if (!getActivity().waitUntilVisible()) {
+            Log.e(TAG, "Activity failed to complete rotation, canceling test");
+            return false;
+        }
+        if (getActivity().getWindowManager().getDefaultDisplay().getRotation() != nextRotation) {
+            Log.e(TAG, "New activity orientation does not match. Canceling test");
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Simple state list animator test that checks for cloning
+     */
+    public void testLoadStateListAnimator() {
+        StateListAnimator sla1 = AnimatorInflater.loadStateListAnimator(getActivity(),
+                R.anim.test_state_list_animator);
+        StateListAnimator sla2 = AnimatorInflater.loadStateListAnimator(getActivity(),
+                R.anim.test_state_list_animator);
+        assertUnique(sla1);
+        assertUnique(sla2);
+    }
+
+    /**
+     * Tests a state list animator which has an @anim reference that has different xmls per
+     * orientation
+     */
+    public void testLoadStateListAnimatorWithChangingResetState() throws Throwable {
+        loadStateListAnimatorWithChangingResetStateTest();
+        if (!rotate()) {
+            return;//cancel test
+        }
+
+        loadStateListAnimatorWithChangingResetStateTest();
+    }
+
+    private void loadStateListAnimatorWithChangingResetStateTest() throws Throwable {
+        final StateListAnimator sla = AnimatorInflater.loadStateListAnimator(getActivity(),
+                R.anim.test_state_list_animator_2);
+        final View testView = getTestView();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                testView.setStateListAnimator(sla);
+                testView.jumpDrawablesToCurrentState();
+            }
+        });
+        float resetValue = getActivity().getResources().getDimension(R.dimen.reset_state_value);
+        getInstrumentation().waitForIdleSync();
+        assertEquals(resetValue, testView.getX());
+        assertEquals(resetValue, testView.getY());
+        assertEquals(resetValue, testView.getZ());
+    }
+
+    /**
+     * Tests a state list animator which has different xml descriptions per orientation.
+     */
+    public void testLoadChangingStateListAnimator() throws Throwable {
+        loadChangingStateListAnimatorTest();
+        if (!rotate()) {
+            return;//cancel test
+        }
+        loadChangingStateListAnimatorTest();
+    }
+
+    private void loadChangingStateListAnimatorTest() throws Throwable {
+        final StateListAnimator sla = AnimatorInflater.loadStateListAnimator(getActivity(),
+                R.anim.changing_state_list_animator);
+        final View testView = getTestView();
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                testView.setStateListAnimator(sla);
+                testView.jumpDrawablesToCurrentState();
+            }
+        });
+        float targetValue = getActivity().getResources()
+                .getDimension(R.dimen.changing_state_list_anim_target_x_value);
+        getInstrumentation().waitForIdleSync();
+        assertEquals(targetValue, testView.getX());
+    }
+
+    /**
+     * Tests that makes sure that reloaded animator is not affected by previous changes
+     */
+    public void testReloadedAnimatorIsNotModified() throws Throwable {
+        final Animator anim1 = AnimatorInflater.loadAnimator(getActivity(), R.anim.test_animator);
+        final CountDownLatch mStarted = new CountDownLatch(1);
+        final AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+            }
+
+            @Override
+            public void onAnimationStart(Animator animation) {
+                super.onAnimationStart(animation);
+                mStarted.countDown();
+            }
+        };
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                anim1.setTarget(getTestView());
+                anim1.addListener(listener);
+                anim1.start();
+            }
+        });
+        Animator anim2 = AnimatorInflater.loadAnimator(getActivity(), R.anim.test_animator);
+        assertTrue(anim1.isStarted());
+        assertFalse(anim2.isStarted());
+        assertFalse("anim2 should not include the listener",
+                anim2.getListeners() != null && anim2.getListeners().contains(listener));
+        assertTrue("animator should start", mStarted.await(10, TimeUnit.SECONDS));
+        assertFalse(anim2.isRunning());
+
+    }
+
+    public View getTestView() {
+        return getActivity().findViewById(R.id.anim_window);
+    }
+
+    class DummyObject {
+
+        float x;
+        float y;
+
+        public float getX() {
+            return x;
+        }
+
+        public void setX(float x) {
+            this.x = x;
+        }
+
+        public float getY() {
+            return y;
+        }
+
+        public void setY(float y) {
+            this.y = y;
+        }
+    }
+}
+
diff --git a/tests/tests/view/src/android/view/cts/MenuInflaterTest.java b/tests/tests/view/src/android/view/cts/MenuInflaterTest.java
index 40d1d3d..6007730 100644
--- a/tests/tests/view/src/android/view/cts/MenuInflaterTest.java
+++ b/tests/tests/view/src/android/view/cts/MenuInflaterTest.java
@@ -19,7 +19,6 @@
 import com.android.cts.view.R;
 import com.android.internal.view.menu.MenuBuilder;
 
-
 import android.app.Activity;
 import android.content.Context;
 import android.content.res.Resources;
@@ -28,6 +27,7 @@
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.BitmapDrawable;
 import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.SubMenu;
@@ -48,17 +48,22 @@
     protected void setUp() throws Exception {
         super.setUp();
         mActivity = getActivity();
-        mMenuInflater = mActivity.getMenuInflater();
     }
 
+    @UiThreadTest
     public void testConstructor() {
         new MenuInflater(mActivity);
     }
 
+    @UiThreadTest
     public void testInflate() {
         Menu menu = new MenuBuilder(mActivity);
         assertEquals(0, menu.size());
 
+        if (mMenuInflater == null) {
+            mMenuInflater = mActivity.getMenuInflater();
+        }
+
         mMenuInflater.inflate(com.android.cts.view.R.menu.browser, menu);
         assertNotNull(menu);
         assertEquals(1, menu.size());
@@ -77,7 +82,12 @@
     }
 
     // Check wheher the objects are created correctly from xml files
+    @UiThreadTest
     public void testInflateFromXml(){
+        if (mMenuInflater == null) {
+            mMenuInflater = mActivity.getMenuInflater();
+        }
+
         // the visibility and shortcut
         Menu menu = new MenuBuilder(mActivity);
         mMenuInflater.inflate(R.menu.visible_shortcut, menu);
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index e8da40e..ffbec1e 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -22,6 +22,7 @@
 import android.graphics.ColorFilter;
 import android.graphics.PorterDuff;
 
+import android.os.Bundle;
 import com.android.cts.view.R;
 import com.android.internal.view.menu.ContextMenuBuilder;
 import com.google.android.collect.Lists;
@@ -3667,6 +3668,11 @@
         public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
             return false;
         }
+
+        @Override
+        public boolean onNestedPrePerformAccessibilityAction(View target, int action, Bundle args) {
+            return false;
+        }
     }
 
     private final class OnCreateContextMenuListenerImpl implements OnCreateContextMenuListener {
diff --git a/tests/tests/view/src/android/view/cts/WindowTest.java b/tests/tests/view/src/android/view/cts/WindowTest.java
index ead4d5b..3c5386d 100644
--- a/tests/tests/view/src/android/view/cts/WindowTest.java
+++ b/tests/tests/view/src/android/view/cts/WindowTest.java
@@ -370,7 +370,9 @@
     public void testSetBackgroundDrawable() throws Throwable {
         // DecorView holds the background
         View decor = mWindow.getDecorView();
-        assertEquals(PixelFormat.OPAQUE, decor.getBackground().getOpacity());
+        if (!mWindow.hasFeature(Window.FEATURE_SWIPE_TO_DISMISS)) {
+            assertEquals(PixelFormat.OPAQUE, decor.getBackground().getOpacity());
+        }
         runTestOnUiThread(new Runnable() {
             public void run() {
                 // setBackgroundDrawableResource(int resId) has the same
diff --git a/tests/tests/webkit/src/android/webkit/cts/URLUtilTest.java b/tests/tests/webkit/src/android/webkit/cts/URLUtilTest.java
index fb44334..e8f0cab 100644
--- a/tests/tests/webkit/src/android/webkit/cts/URLUtilTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/URLUtilTest.java
@@ -144,7 +144,7 @@
 
     public void testGuessFileName() {
         String url = "ftp://example.url/test";
-        assertEquals("test.jpeg", URLUtil.guessFileName(url, null, "image/jpeg"));
+        assertEquals("test.jpg", URLUtil.guessFileName(url, null, "image/jpeg"));
 
         assertEquals("test.bin", URLUtil.guessFileName(url, null, "application/octet-stream"));
     }
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
index 378bf6e..dcdeead 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
@@ -703,6 +703,9 @@
     }
 
     public void testSecureServerRequestingClientCertDoesNotCancelRequest() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
         mWebServer = new CtsTestServer(getActivity(), CtsTestServer.SslMode.WANTS_CLIENT_AUTH);
         final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         final SslErrorWebViewClient webViewClient = new SslErrorWebViewClient(mOnUiThread);
@@ -716,6 +719,9 @@
     }
 
     public void testSecureServerRequiringClientCertDoesCancelRequest() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
         mWebServer = new CtsTestServer(getActivity(), CtsTestServer.SslMode.NEEDS_CLIENT_AUTH);
         final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         final SslErrorWebViewClient webViewClient = new SslErrorWebViewClient(mOnUiThread);
@@ -732,6 +738,9 @@
     }
 
     public void testProceedClientCertRequest() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
         mWebServer = new CtsTestServer(getActivity(), CtsTestServer.SslMode.NEEDS_CLIENT_AUTH);
         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         final ClientCertWebViewClient webViewClient = new ClientCertWebViewClient(mOnUiThread);
@@ -756,6 +765,9 @@
     }
 
     public void testIgnoreClientCertRequest() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
         mWebServer = new CtsTestServer(getActivity(), CtsTestServer.SslMode.NEEDS_CLIENT_AUTH);
         String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         final ClientCertWebViewClient webViewClient = new ClientCertWebViewClient(mOnUiThread);
@@ -784,6 +796,9 @@
     }
 
     public void testCancelClientCertRequest() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
         mWebServer = new CtsTestServer(getActivity(), CtsTestServer.SslMode.NEEDS_CLIENT_AUTH);
         final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         final ClientCertWebViewClient webViewClient = new ClientCertWebViewClient(mOnUiThread);
@@ -830,6 +845,9 @@
     }
 
     public void testClientCertIssuersReceivedCorrectly() throws Throwable {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
         mWebServer = new CtsTestServer(getActivity(), CtsTestServer.SslMode.NEEDS_CLIENT_AUTH,
                 new TrustManager());
         final String url = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
index ef64f4d..1e22acc 100755
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewTest.java
@@ -474,6 +474,9 @@
 
     @UiThreadTest
     public void testPostUrlWithNonNetworkUrl() throws Exception {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
         final String nonNetworkUrl = "file:///android_asset/" + TestHtmlConstants.HELLO_WORLD_URL;
 
         mOnUiThread.postUrlAndWaitForCompletion(nonNetworkUrl, new byte[1]);
@@ -484,6 +487,9 @@
 
     @UiThreadTest
     public void testPostUrlWithNetworkUrl() throws Exception {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
         startWebServer(false);
         final String networkUrl = mWebServer.getAssetUrl(TestHtmlConstants.HELLO_WORLD_URL);
         final String postDataString = "username=my_username&password=my_password";
@@ -565,6 +571,10 @@
     }
 
     public void testCanInjectHeaders() throws Exception {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
         final String X_FOO = "X-foo";
         final String X_FOO_VALUE = "test";
 
@@ -899,6 +909,9 @@
     }
 
     public void testAddJavascriptInterfaceExceptions() throws Exception {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
         WebSettings settings = mOnUiThread.getSettings();
         settings.setJavaScriptEnabled(true);
         settings.setJavaScriptCanOpenWindowsAutomatically(true);
diff --git a/tests/tests/widget/res/layout/textview_layout.xml b/tests/tests/widget/res/layout/textview_layout.xml
index 419bbf9..bf7f757 100644
--- a/tests/tests/widget/res/layout/textview_layout.xml
+++ b/tests/tests/widget/res/layout/textview_layout.xml
@@ -27,6 +27,7 @@
                 android:layout_height="match_parent">
 
             <TextView android:id="@+id/textview_textAttr"
+                    android:fontFamily="@null"
                     android:text="@string/text_view_hello"
                     android:textColor="@drawable/black"
                     android:textColorHighlight="@drawable/yellow"
diff --git a/tests/tests/widget/src/android/widget/cts/MockPopupWindowCtsActivity.java b/tests/tests/widget/src/android/widget/cts/MockPopupWindowCtsActivity.java
index a68286a..9589fec 100644
--- a/tests/tests/widget/src/android/widget/cts/MockPopupWindowCtsActivity.java
+++ b/tests/tests/widget/src/android/widget/cts/MockPopupWindowCtsActivity.java
@@ -21,6 +21,10 @@
 import android.app.Activity;
 import android.os.Bundle;
 import android.widget.PopupWindow;
+import android.view.View;
+import android.view.View.OnApplyWindowInsetsListener;
+import android.view.Window;
+import android.view.WindowInsets;
 
 /**
  * Stub activity for testing {@link PopupWindow}
diff --git a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
index e1742c8..c14bb03 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
@@ -273,18 +273,21 @@
     }
 
     public void testShowAtLocation() {
-        int[] viewInWindowXY = new int[2];
-        int[] viewOnScreenXY = new int[2];
+        int[] popupContentViewInWindowXY = new int[2];
+        int[] popupContentViewOnScreenXY = new int[2];
 
         mPopupWindow = createPopupWindow(createPopupContent());
+        // Do not attach within the decor; we will be measuring location
+        // with regard to screen coordinates.
+        mPopupWindow.setAttachedInDecor(false);
         final View upperAnchor = mActivity.findViewById(R.id.anchor_upper);
 
         final int xOff = 10;
         final int yOff = 21;
         assertFalse(mPopupWindow.isShowing());
-        mPopupWindow.getContentView().getLocationInWindow(viewInWindowXY);
-        assertEquals(0, viewInWindowXY[0]);
-        assertEquals(0, viewInWindowXY[1]);
+        mPopupWindow.getContentView().getLocationInWindow(popupContentViewInWindowXY);
+        assertEquals(0, popupContentViewInWindowXY[0]);
+        assertEquals(0, popupContentViewInWindowXY[1]);
 
         mInstrumentation.runOnMainSync(new Runnable() {
             public void run() {
@@ -294,12 +297,12 @@
         mInstrumentation.waitForIdleSync();
 
         assertTrue(mPopupWindow.isShowing());
-        mPopupWindow.getContentView().getLocationInWindow(viewInWindowXY);
-        mPopupWindow.getContentView().getLocationOnScreen(viewOnScreenXY);
-        assertTrue(viewInWindowXY[0] >= 0);
-        assertTrue(viewInWindowXY[1] >= 0);
-        assertEquals(viewInWindowXY[0] + xOff, viewOnScreenXY[0]);
-        assertEquals(viewInWindowXY[1] + yOff, viewOnScreenXY[1]);
+        mPopupWindow.getContentView().getLocationInWindow(popupContentViewInWindowXY);
+        mPopupWindow.getContentView().getLocationOnScreen(popupContentViewOnScreenXY);
+        assertTrue(popupContentViewInWindowXY[0] >= 0);
+        assertTrue(popupContentViewInWindowXY[1] >= 0);
+        assertEquals(popupContentViewInWindowXY[0] + xOff, popupContentViewOnScreenXY[0]);
+        assertEquals(popupContentViewInWindowXY[1] + yOff, popupContentViewOnScreenXY[1]);
 
         dismissPopup();
     }
@@ -453,6 +456,9 @@
         mInstrumentation.runOnMainSync(new Runnable() {
             public void run() {
                 mPopupWindow = createPopupWindow(createPopupContent());
+                // Do not attach within the decor; we will be measuring location
+                // with regard to screen coordinates.
+                mPopupWindow.setAttachedInDecor(false);
             }
         });
 
diff --git a/tests/tests/widget/src/android/widget/cts/TableLayoutTest.java b/tests/tests/widget/src/android/widget/cts/TableLayoutTest.java
index c8211f6..5259736 100644
--- a/tests/tests/widget/src/android/widget/cts/TableLayoutTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TableLayoutTest.java
@@ -404,8 +404,8 @@
         // exceptional
         try {
             tableLayout.addView(null);
-            fail("Should throw NullPointerException");
-        } catch (NullPointerException e) {
+            fail("Should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
         }
     }
 
@@ -445,8 +445,8 @@
 
         try {
             tableLayout.addView(null, -1);
-            fail("Should throw NullPointerException");
-        } catch (NullPointerException e) {
+            fail("Should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
         }
     }
 
@@ -477,8 +477,8 @@
 
         try {
             tableLayout.addView(null, new TableLayout.LayoutParams(200, 300));
-            fail("Should throw NullPointerException");
-        } catch (NullPointerException e) {
+            fail("Should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
         }
 
         try {
@@ -540,8 +540,8 @@
 
         try {
             tableLayout.addView(null, -1, new TableLayout.LayoutParams(200, 300));
-            fail("Should throw NullPointerException");
-        } catch (NullPointerException e) {
+            fail("Should throw IllegalArgumentException");
+        } catch (IllegalArgumentException e) {
         }
 
         try {
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 72193e7..480e1a6 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -2046,8 +2046,14 @@
 
         assertEquals(SingleLineTransformationMethod.getInstance(),
                 textView.getTransformationMethod());
-        int singleLineWidth = textView.getLayout().getWidth();
-        int singleLineHeight = textView.getLayout().getHeight();
+
+        int singleLineWidth = 0;
+        int singleLineHeight = 0;
+
+        if (textView.getLayout() != null) {
+            singleLineWidth = textView.getLayout().getWidth();
+            singleLineHeight = textView.getLayout().getHeight();
+        }
 
         mActivity.runOnUiThread(new Runnable() {
             public void run() {
@@ -2056,8 +2062,11 @@
         });
         mInstrumentation.waitForIdleSync();
         assertEquals(null, textView.getTransformationMethod());
-        assertTrue(textView.getLayout().getHeight() > singleLineHeight);
-        assertTrue(textView.getLayout().getWidth() < singleLineWidth);
+
+        if (textView.getLayout() != null) {
+            assertTrue(textView.getLayout().getHeight() > singleLineHeight);
+            assertTrue(textView.getLayout().getWidth() < singleLineWidth);
+        }
 
         // same behaviours as setSingLine(true)
         mActivity.runOnUiThread(new Runnable() {
@@ -2068,8 +2077,11 @@
         mInstrumentation.waitForIdleSync();
         assertEquals(SingleLineTransformationMethod.getInstance(),
                 textView.getTransformationMethod());
-        assertEquals(singleLineHeight, textView.getLayout().getHeight());
-        assertEquals(singleLineWidth, textView.getLayout().getWidth());
+
+        if (textView.getLayout() != null) {
+            assertEquals(singleLineHeight, textView.getLayout().getHeight());
+            assertEquals(singleLineWidth, textView.getLayout().getWidth());
+        }
     }
 
     @UiThreadTest
@@ -2833,15 +2845,19 @@
         assertEquals(1, mTextView.getImeActionId());
     }
 
-    @UiThreadTest
     public void testSetTextLong() {
-        final int MAX_COUNT = 1 << 21;
-        char[] longText = new char[MAX_COUNT];
-        for (int n = 0; n < MAX_COUNT; n++) {
-            longText[n] = 'm';
-        }
-        mTextView = findTextView(R.id.textview_text);
-        mTextView.setText(new String(longText));
+        mActivity.runOnUiThread(new Runnable() {
+            public void run() {
+                final int MAX_COUNT = 1 << 21;
+                char[] longText = new char[MAX_COUNT];
+                for (int n = 0; n < MAX_COUNT; n++) {
+                    longText[n] = 'm';
+                }
+                mTextView = findTextView(R.id.textview_text);
+                mTextView.setText(new String(longText));
+            }
+        });
+        mInstrumentation.waitForIdleSync();
     }
 
     @UiThreadTest
diff --git a/tests/tests/widget/src/android/widget/cts/VideoViewTest.java b/tests/tests/widget/src/android/widget/cts/VideoViewTest.java
index 6e514f8..d9af514 100644
--- a/tests/tests/widget/src/android/widget/cts/VideoViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/VideoViewTest.java
@@ -21,6 +21,7 @@
 import android.app.Activity;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.cts.util.MediaUtils;
 import android.cts.util.PollingCheck;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecList;
@@ -53,8 +54,6 @@
     private static final int    TEST_VIDEO_DURATION = 11047;
     /** The full name of R.raw.testvideo. */
     private static final String VIDEO_NAME   = "testvideo.3gp";
-    /** The MIME type. */
-    private static final String MIME_TYPE = "video/3gpp";
     /** delta for duration in case user uses different decoders on different
         hardware that report a duration that's different by a few milliseconds */
     private static final int DURATION_DELTA = 100;
@@ -101,26 +100,8 @@
         }
     }
 
-    // TODO: Make a public method selectCodec() in common libraries (e.g. cts/libs/), to avoid
-    // redundant function definitions in this and other media related test files.
-    private static boolean hasCodec(String mimeType) {
-        int numCodecs = MediaCodecList.getCodecCount();
-
-        for (int i = 0; i < numCodecs; i++) {
-            MediaCodecInfo codecInfo = MediaCodecList.getCodecInfoAt(i);
-
-            if (!codecInfo.isEncoder()) {
-                continue;
-            }
-
-            String[] types = codecInfo.getSupportedTypes();
-            for (int j = 0; j < types.length; j++) {
-                if (types[j].equalsIgnoreCase(mimeType)) {
-                    return true;
-                }
-            }
-        }
-        return false;
+    private boolean hasCodec() {
+        return MediaUtils.hasCodecsForResource(mActivity, R.raw.testvideo);
     }
 
     /**
@@ -204,8 +185,8 @@
     public void testPlayVideo1() throws Throwable {
         makeVideoView();
         // Don't run the test if the codec isn't supported.
-        if (!hasCodec(MIME_TYPE)) {
-            Log.w(TAG, "Codec " + MIME_TYPE + " not supported. Return from testPlayVideo1.");
+        if (!hasCodec()) {
+            Log.i(TAG, "SKIPPING testPlayVideo1(): codec is not supported");
             return;
         }
 
@@ -266,8 +247,8 @@
     public void testGetBufferPercentage() throws Throwable {
         makeVideoView();
         // Don't run the test if the codec isn't supported.
-        if (!hasCodec(MIME_TYPE)) {
-            Log.w(TAG, MIME_TYPE + " not supported. Return from testGetBufferPercentage.");
+        if (!hasCodec()) {
+            Log.i(TAG, "SKIPPING testGetBufferPercentage(): codec is not supported");
             return;
         }
 
@@ -309,8 +290,8 @@
 
     public void testGetDuration() throws Throwable {
         // Don't run the test if the codec isn't supported.
-        if (!hasCodec(MIME_TYPE)) {
-            Log.w(TAG, "Codec " + MIME_TYPE + " not supported. Return from testGetDuration.");
+        if (!hasCodec()) {
+            Log.i(TAG, "SKIPPING testGetDuration(): codec is not supported");
             return;
         }
 
diff --git a/tools/cts-java-scanner/src/com/android/cts/javascanner/CtsJavaScanner.java b/tools/cts-java-scanner/src/com/android/cts/javascanner/CtsJavaScanner.java
index a843fc6..fc774e9 100644
--- a/tools/cts-java-scanner/src/com/android/cts/javascanner/CtsJavaScanner.java
+++ b/tools/cts-java-scanner/src/com/android/cts/javascanner/CtsJavaScanner.java
@@ -16,7 +16,9 @@
 package com.android.cts.javascanner;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 /**
  * Class that searches a source directory for native gTests and outputs a
@@ -31,12 +33,12 @@
     }
 
     public static void main(String[] args) throws Exception {
-        File sourceDir = null;
+        List<File> sourceDirs = new ArrayList<File>();
         File docletPath = null;
 
         for (int i = 0; i < args.length; i++) {
             if ("-s".equals(args[i])) {
-                sourceDir = new File(getArg(args, ++i, "Missing value for source directory"));
+                sourceDirs.add(new File(getArg(args, ++i, "Missing value for source directory")));
             } else if ("-d".equals(args[i])) {
                 docletPath = new File(getArg(args, ++i, "Missing value for docletPath"));
             } else {
@@ -45,7 +47,7 @@
             }
         }
 
-        if (sourceDir == null) {
+        if (sourceDirs.isEmpty()) {
             System.err.println("Source directory is required");
             usage(args);
         }
@@ -55,7 +57,7 @@
             usage(args);
         }
 
-        DocletRunner runner = new DocletRunner(sourceDir, docletPath);
+        DocletRunner runner = new DocletRunner(sourceDirs, docletPath);
         System.exit(runner.runJavaDoc());
     }
 
diff --git a/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java b/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
index 06951b9..94761fb 100644
--- a/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
+++ b/tools/cts-java-scanner/src/com/android/cts/javascanner/DocletRunner.java
@@ -25,11 +25,11 @@
 
 class DocletRunner {
 
-    private final File mSourceDir;
+    private final List<File> mSourceDirs;
     private final File mDocletPath;
 
-    DocletRunner(File sourceDir, File docletPath) {
-        mSourceDir = sourceDir;
+    DocletRunner(List<File> sourceDirs, File docletPath) {
+        mSourceDirs = sourceDirs;
         mDocletPath = docletPath;
     }
 
@@ -41,10 +41,12 @@
         args.add("-docletpath");
         args.add(mDocletPath.toString());
         args.add("-sourcepath");
-        args.add(getSourcePath(mSourceDir));
+        args.add(getSourcePath(mSourceDirs));
         args.add("-classpath");
         args.add(getClassPath());
-        args.addAll(getSourceFiles(mSourceDir));
+        for (File sourceDir : mSourceDirs) {
+            args.addAll(getSourceFiles(sourceDir));
+        }
 
 
         // NOTE: We redirect the error stream to make sure the child process
@@ -67,7 +69,7 @@
         return process.waitFor();
     }
 
-    private String getSourcePath(File sourceDir) {
+    private String getSourcePath(List<File> sourceDirs) {
         List<String> sourcePath = new ArrayList<String>();
         sourcePath.add("./frameworks/base/core/java");
         sourcePath.add("./frameworks/base/test-runner/src");
@@ -77,7 +79,9 @@
         sourcePath.add("./cts/tests/src");
         sourcePath.add("./cts/libs/commonutil/src");
         sourcePath.add("./cts/libs/deviceutil/src");
-        sourcePath.add(sourceDir.toString());
+        for (File sourceDir : sourceDirs) {
+            sourcePath.add(sourceDir.toString());
+        }
         return join(sourcePath, ":");
     }
 
diff --git a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java
index 1eb4acb..62c268d 100644
--- a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java
+++ b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoConstants.java
@@ -68,4 +68,5 @@
     public static final String SERIAL_NUMBER = "deviceID";
     public static final String STORAGE_DEVICES = "storage_devices";
     public static final String MULTI_USER = "multi_user";
+    public static final String ENCRYPTED = "encrypted";
 }
diff --git a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
index 19349e5..52ddfe9 100644
--- a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
+++ b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/DeviceInfoInstrument.java
@@ -149,6 +149,9 @@
         // Multi-user support
         addResult(MULTI_USER, getMultiUserInfo());
 
+        // Encrypted
+        addResult(ENCRYPTED, getEncrypted());
+
         finish(Activity.RESULT_OK, mResults);
     }
 
@@ -394,4 +397,29 @@
 
         return "unknown";
     }
+
+    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;
+    }
+
+    private int getEncrypted() {
+        try {
+            return "encrypted".equals(getProperty("ro.crypto.state")) ? 1 : 0;
+        } catch (IOException e) {
+        }
+
+        return 0;
+    }
 }
diff --git a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java
index d8018a1..01ca21b 100644
--- a/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java
+++ b/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java
@@ -29,12 +29,16 @@
     /** Processes that are allowed to run as root. */
     private static final Pattern ROOT_PROCESS_WHITELIST_PATTERN = getRootProcessWhitelistPattern(
             "debuggerd",
+            "debuggerd64",
+            "healthd",
             "init",
             "installd",
+            "lmkd",
             "netd",
             "servicemanager",
             "ueventd",
             "vold",
+            "watchdogd",
             "zygote"
     );
 
diff --git a/tools/selinux/SELinuxNeverallowTestFrame.py b/tools/selinux/SELinuxNeverallowTestFrame.py
new file mode 100644
index 0000000..932014a
--- /dev/null
+++ b/tools/selinux/SELinuxNeverallowTestFrame.py
@@ -0,0 +1,106 @@
+#!/usr/bin/python
+
+src_header = """/*
+ * 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.cts.security;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.FileOutputStream;
+import java.lang.String;
+import java.net.URL;
+import java.util.Scanner;
+
+/**
+ * Neverallow Rules SELinux tests.
+ */
+public class SELinuxNeverallowRulesTest extends DeviceTestCase {
+    private File sepolicyAnalyze;
+    private File devicePolicyFile;
+
+    /**
+     * A reference to the device under test.
+     */
+    private ITestDevice mDevice;
+
+    private File copyResourceToTempFile(String resName) throws IOException {
+        InputStream is = this.getClass().getResourceAsStream(resName);
+        File tempFile = File.createTempFile("SELinuxHostTest", ".tmp");
+        FileOutputStream os = new FileOutputStream(tempFile);
+        int rByte = 0;
+        while ((rByte = is.read()) != -1) {
+            os.write(rByte);
+        }
+        os.flush();
+        os.close();
+        tempFile.deleteOnExit();
+        return tempFile;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mDevice = getDevice();
+
+        /* retrieve the sepolicy-analyze executable from jar */
+        sepolicyAnalyze = copyResourceToTempFile("/sepolicy-analyze");
+        sepolicyAnalyze.setExecutable(true);
+
+        /* obtain sepolicy file from running device */
+        devicePolicyFile = File.createTempFile("sepolicy", ".tmp");
+        devicePolicyFile.deleteOnExit();
+        mDevice.executeAdbCommand("pull", "/sys/fs/selinux/policy",
+                devicePolicyFile.getAbsolutePath());
+    }
+"""
+src_body = ""
+src_footer = """}
+"""
+
+src_method = """
+    public void testNeverallowRules() throws Exception {
+        String neverallowRule = "$NEVERALLOW_RULE_HERE$";
+
+        /* run sepolicy-analyze neverallow check on policy file using given neverallow rules */
+        ProcessBuilder pb = new ProcessBuilder(sepolicyAnalyze.getAbsolutePath(),
+                devicePolicyFile.getAbsolutePath(), "neverallow", "-n",
+                neverallowRule);
+        pb.redirectOutput(ProcessBuilder.Redirect.PIPE);
+        pb.redirectErrorStream(true);
+        Process p = pb.start();
+        p.waitFor();
+        BufferedReader result = new BufferedReader(new InputStreamReader(p.getInputStream()));
+        String line;
+        StringBuilder errorString = new StringBuilder();
+        while ((line = result.readLine()) != null) {
+            errorString.append(line);
+            errorString.append("\\n");
+        }
+        assertTrue("The following errors were encountered when validating the SELinux"
+                   + "neverallow rule:\\n" + neverallowRule + "\\n" + errorString,
+                   errorString.length() == 0);
+    }
+"""
diff --git a/tools/selinux/SELinuxNeverallowTestGen.py b/tools/selinux/SELinuxNeverallowTestGen.py
new file mode 100755
index 0000000..9cb1e24
--- /dev/null
+++ b/tools/selinux/SELinuxNeverallowTestGen.py
@@ -0,0 +1,53 @@
+#!/usr/bin/python
+
+import re
+import sys
+import SELinuxNeverallowTestFrame
+
+usage = "Usage: ./gen_SELinux_CTS_neverallows.py <input policy file> <output cts java source>"
+
+# extract_neverallow_rules - takes an intermediate policy file and pulls out the
+# neverallow rules by taking all of the non-commented text between the 'neverallow'
+# keyword and a terminating ';'
+# returns: a list of strings representing these rules
+def extract_neverallow_rules(policy_file):
+    with open(policy_file, 'r') as in_file:
+        policy_str = in_file.read()
+        # remove comments
+        no_comments = re.sub(r'#.+?$', r'', policy_str, flags = re.M)
+        # match neverallow rules
+        return re.findall(r'(^neverallow\s.+?;)', no_comments, flags = re.M |re.S);
+
+# neverallow_rule_to_test - takes a neverallow statement and transforms it into
+# the output necessary to form a cts unit test in a java source file.
+# returns: a string representing a generic test method based on this rule.
+def neverallow_rule_to_test(neverallow_rule, test_num):
+    squashed_neverallow = neverallow_rule.replace("\n", " ")
+    method  = SELinuxNeverallowTestFrame.src_method
+    method = method.replace("testNeverallowRules()",
+        "testNeverallowRules" + str(test_num) + "()")
+    return method.replace("$NEVERALLOW_RULE_HERE$", squashed_neverallow)
+
+if __name__ == "__main__":
+    # check usage
+    if len(sys.argv) != 3:
+        print usage
+        exit()
+    input_file = sys.argv[1]
+    output_file = sys.argv[2]
+
+    src_header = SELinuxNeverallowTestFrame.src_header
+    src_body = SELinuxNeverallowTestFrame.src_body
+    src_footer = SELinuxNeverallowTestFrame.src_footer
+
+    # grab the neverallow rules from the policy file and transform into tests
+    neverallow_rules = extract_neverallow_rules(input_file)
+    i = 0
+    for rule in neverallow_rules:
+        src_body += neverallow_rule_to_test(rule, i)
+        i += 1
+
+    with open(output_file, 'w') as out_file:
+        out_file.write(src_header)
+        out_file.write(src_body)
+        out_file.write(src_footer)
diff --git a/tools/selinux/src/SELinux_CTS.py b/tools/selinux/src/SELinux_CTS.py
deleted file mode 100644
index ec12be0..0000000
--- a/tools/selinux/src/SELinux_CTS.py
+++ /dev/null
@@ -1,542 +0,0 @@
-import pdb
-import re
-from xml.etree.ElementTree import Element, SubElement, tostring
-
-#define equivalents
-TYPE = 0
-ATTRIBUTE = 1
-TYPEATTRIBUTE = 2
-CLASS = 3
-COMMON = 4
-ALLOW_RULE = 5
-NEVERALLOW_RULE = 6
-OTHER = 7
-
-#define helper methods
-# advance_past_whitespace(): helper function to skip whitespace at current
-# position in file.
-# returns: the non-whitespace character at the file's new position
-#TODO: should I deal with comments here as well?
-def advance_past_whitespace(file_obj):
-    c = file_obj.read(1)
-    while c.isspace():
-        c = file_obj.read(1)
-    file_obj.seek(-1, 1)
-    return c
-
-# advance_until_whitespace(): helper function to grab the string represented
-# by the current position in file until next whitespace.
-# returns: string until next whitespace.  overlooks comments.
-def advance_until_whitespace(file_obj):
-    ret_string = ""
-    c = file_obj.read(1)
-    #TODO: make a better way to deal with ':' and ';'
-    while not (c.isspace() or c == ':' or c == '' or c == ';'):
-        #don't count comments
-        if c == '#':
-            file_obj.readline()
-            return ret_string
-        else:
-            ret_string+=c
-            c = file_obj.read(1)
-    if not c == ':':
-        file_obj.seek(-1, 1)
-    return ret_string
-
-# expand_avc_rule - takes a processed avc rule and converts it into a list of
-# 4-tuples for use in an access check of form:
-    # (source_type, target_type, class, permission)
-def expand_avc_rule(policy, avc_rule):
-    ret_list = [ ]
-
-    #expand source_types
-    source_types = avc_rule['source_types']['set']
-    source_types = policy.expand_types(source_types)
-    if(avc_rule['source_types']['flags']['complement']):
-        #TODO: deal with negated 'self', not present in current policy.conf, though (I think)
-        source_types = policy.types - source_types #complement these types
-    if len(source_types) == 0:
-        print "ERROR: source_types empty after expansion"
-        print "Before: "
-        print avc_rule['source_types']['set']
-        return
-
-    #expand target_types
-    target_types = avc_rule['target_types']['set']
-    target_types = policy.expand_types(target_types)
-    if(avc_rule['target_types']['flags']['complement']):
-        #TODO: deal with negated 'self', not present in current policy.conf, though (I think)
-        target_types = policy.types - target_types #complement these types
-    if len(target_types) == 0:
-        print "ERROR: target_types empty after expansion"
-        print "Before: "
-        print avc_rule['target_types']['set']
-        return
-
-    # get classes
-    rule_classes = avc_rule['classes']['set']
-    if '' in rule_classes:
-        print "FOUND EMPTY STRING IN CLASSES"
-        print "Total sets:"
-        print avc_rule['source_types']['set']
-        print avc_rule['target_types']['set']
-        print rule_classes
-        print avc_rule['permissions']['set']
-
-    if len(rule_classes) == 0:
-        print "ERROR: empy set of object classes in avc rule"
-        return
-
-    # get permissions
-    permissions = avc_rule['permissions']['set']
-    if len(permissions) == 0:
-        print "ERROR: empy set of permissions in avc rule\n"
-        return
-
-    #create the list with collosal nesting, n^4 baby!
-    for s in source_types:
-        for t in target_types:
-            for c in rule_classes:
-                if c == '':
-                   continue
-                #expand permissions on a per-class basis
-                exp_permissions = policy.expand_permissions(c, permissions)
-                if(avc_rule['permissions']['flags']['complement']):
-                    exp_permissions = policy.classes[c] - exp_permissions
-                if len(exp_permissions) == 0:
-                    print "ERROR: permissions empty after expansion\n"
-                    print "Before: "
-                    print avc_rule['permissions']['set']
-                    return
-                for p in exp_permissions:
-                    source = s
-                    if t == 'self':
-                        target = s
-                    else:
-                        target = t
-                    obj_class = c
-                    permission = p
-                    ret_list.append((source, target, obj_class, permission))
-    return ret_list
-
-# expand_avc_rule - takes a processed avc rule and converts it into an xml
-# representation with the information needed in a checkSELinuxAccess() call.
-# (source_type, target_type, class, permission)
-def expand_avc_rule_to_xml(policy, avc_rule, rule_name, rule_type):
-    rule_xml = Element('avc_rule')
-    rule_xml.set('name', rule_name)
-    rule_xml.set('type', rule_type)
-
-    #expand source_types
-    source_types = avc_rule['source_types']['set']
-    source_types = policy.expand_types(source_types)
-    if(avc_rule['source_types']['flags']['complement']):
-        #TODO: deal with negated 'self', not present in current policy.conf, though (I think)
-        source_types = policy.types - source_types #complement these types
-    if len(source_types) == 0:
-        print "ERROR: source_types empty after expansion"
-        print "Before: "
-        print avc_rule['source_types']['set']
-        return
-    for s in source_types:
-        elem = SubElement(rule_xml, 'type')
-        elem.set('type', 'source')
-        elem.text = s
-
-    #expand target_types
-    target_types = avc_rule['target_types']['set']
-    target_types = policy.expand_types(target_types)
-    if(avc_rule['target_types']['flags']['complement']):
-        #TODO: deal with negated 'self', not present in current policy.conf, though (I think)
-        target_types = policy.types - target_types #complement these types
-    if len(target_types) == 0:
-        print "ERROR: target_types empty after expansion"
-        print "Before: "
-        print avc_rule['target_types']['set']
-        return
-    for t in target_types:
-        elem = SubElement(rule_xml, 'type')
-        elem.set('type', 'target')
-        elem.text = t
-
-    # get classes
-    rule_classes = avc_rule['classes']['set']
-
-    if len(rule_classes) == 0:
-        print "ERROR: empy set of object classes in avc rule"
-        return
-
-    # get permissions
-    permissions = avc_rule['permissions']['set']
-    if len(permissions) == 0:
-        print "ERROR: empy set of permissions in avc rule\n"
-        return
-
-    # permissions are class-dependent, so bundled together
-    for c in rule_classes:
-        if c == '':
-            print "AH!!! empty class found!\n"
-            continue
-        c_elem = SubElement(rule_xml, 'obj_class')
-        c_elem.set('name', c)
-        #expand permissions on a per-class basis
-        exp_permissions = policy.expand_permissions(c, permissions)
-        if(avc_rule['permissions']['flags']['complement']):
-            exp_permissions = policy.classes[c] - exp_permissions
-        if len(exp_permissions) == 0:
-            print "ERROR: permissions empty after expansion\n"
-            print "Before: "
-            print avc_rule['permissions']['set']
-            return
-
-        for p in exp_permissions:
-            p_elem = SubElement(c_elem, 'permission')
-            p_elem.text = p
-
-    return rule_xml
-
-# expand_brackets - helper function which reads a file into a string until '{ }'s
-# are balanced.  Brackets are removed from the string.  This function is based
-# on the understanding that nested brackets in our policy.conf file occur only due
-# to macro expansion, and we just need to know how much is included in a given
-# policy sub-component.
-def expand_brackets(file_obj):
-    ret_string = ""
-    c = file_obj.read(1)
-    if not c == '{':
-        print "Invalid bracket expression: " + c + "\n"
-        file_obj.seek(-1, 1)
-        return ""
-    else:
-        bracket_count = 1
-    while bracket_count > 0:
-        c = file_obj.read(1)
-        if c == '{':
-            bracket_count+=1
-        elif c == '}':
-            bracket_count-=1
-        elif c == '#':
-            #get rid of comment and replace with whitespace
-            file_obj.readline()
-            ret_string+=' '
-        else:
-            ret_string+=c
-    return ret_string
-
-# get_avc_rule_component - grabs the next component from an avc rule.  Basically,
-# just reads the next word or bracketed set of words.
-# returns - a set of the word, or words with metadata
-def get_avc_rule_component(file_obj):
-    ret_dict = { 'flags': {}, 'set': set() }
-    c = advance_past_whitespace(file_obj)
-    if c == '~':
-        ret_dict['flags']['complement'] = True
-        file_obj.read(1) #move to next char
-        c = advance_past_whitespace(file_obj)
-    else:
-        ret_dict['flags']['complement'] = False
-    if not c == '{':
-        #TODO: change operations on file to operations on string?
-        single_type =  advance_until_whitespace(file_obj)
-        ret_dict['set'].add(single_type)
-    else:
-        mult_types = expand_brackets(file_obj)
-        mult_types = mult_types.split()
-        for t in mult_types:
-            ret_dict['set'].add(t)
-    return ret_dict
-
-def get_line_type(line):
-    if re.search(r'^type\s', line):
-        return TYPE
-    if re.search(r'^attribute\s', line):
-        return ATTRIBUTE
-    if re.search(r'^typeattribute\s', line):
-        return TYPEATTRIBUTE
-    if re.search(r'^class\s', line):
-        return CLASS
-    if re.search(r'^common\s', line):
-        return COMMON
-    if re.search(r'^allow\s', line):
-        return ALLOW_RULE
-    if re.search(r'^neverallow\s', line):
-        return NEVERALLOW_RULE
-    else:
-        return OTHER
-
-def is_multi_line(line_type):
-    if line_type == CLASS:
-        return True
-    elif line_type == COMMON:
-        return True
-    elif line_type == ALLOW_RULE:
-        return True
-    elif line_type == NEVERALLOW_RULE:
-        return True
-    else:
-        return False
-
-
-#should only be called with file pointing to the 'i' in 'inherits' segment
-def process_inherits_segment(file_obj):
-    inherit_keyword = file_obj.read(8)
-    if not inherit_keyword == 'inherits':
-        #TODO: handle error, invalid class statement
-        print "ERROR: invalid inherits statement"
-        return
-    else:
-        advance_past_whitespace(file_obj)
-        ret_inherited_common = advance_until_whitespace(file_obj)
-        return ret_inherited_common
-
-class SELinuxPolicy:
-
-    def __init__(self):
-        self.types = set()
-        self.attributes = { }
-        self.classes = { }
-        self.common_classes = { }
-        self.allow_rules = [ ]
-        self.neverallow_rules = [ ]
-
-    # create policy directly from policy file
-    #@classmethod
-    def from_file_name(self, policy_file_name):
-        self.types = set()
-        self.attributes = { }
-        self.classes = { }
-        self.common_classes = { }
-        self.allow_rules = [ ]
-        self.neverallow_rules = [ ]
-        with open(policy_file_name, 'r') as policy_file:
-            line = policy_file.readline()
-            while line:
-                line_type = get_line_type(line)
-                if is_multi_line(line_type):
-                    self.parse_multi_line(line, line_type, policy_file)
-                else:
-                    self.parse_single_line(line, line_type)
-                line = policy_file.readline()
-
-    # expand_permissions - generates the actual permission set based on the listed
-    # permissions with wildcards and the given class on which they're based.
-    def expand_permissions(self, obj_class, permission_set):
-        ret_set = set()
-        neg_set = set()
-        for p in permission_set:
-            if p[0] == '-':
-                real_p = p[1:]
-                if real_p in self.classes[obj_class]:
-                    neg_set.add(real_p)
-                else:
-                    print "ERROR: invalid permission in avc rule " + real_t + "\n"
-                    return
-            else:
-                if p in self.classes[obj_class]:
-                    ret_set.add(p)
-                elif p == '*':  #pretty sure this can't be negated? eg -*
-                    ret_set |= self.classes[obj_class]  #All of the permissions
-                else:
-                    print "ERROR: invalid permission in avc rule " + p + "\n"
-                    return
-        return ret_set - neg_set
-
-    # expand_types - generates the actual type set based on the listed types,
-    # attributes, wildcards and negation.  self is left as-is, and is processed
-    # specially when generating checkAccess() 4-tuples
-    def expand_types(self, type_set):
-        ret_set = set()
-        neg_set = set()
-        for t in type_set:
-            if t[0] == '-':
-                real_t = t[1:]
-                if real_t in self.attributes:
-                    neg_set |= self.attributes[real_t]
-                elif real_t in self.types:
-                    neg_set.add(real_t)
-                elif real_t == 'self':
-                    ret_set |= real_t
-                else:
-                    print "ERROR: invalid type in avc rule " + real_t + "\nTYPE SET:"
-                    print type_set
-                    return
-            else:
-                if t in self.attributes:
-                     ret_set |= self.attributes[t]
-                elif t in self.types:
-                    ret_set.add(t)
-                elif t == 'self':
-                    ret_set.add(t)
-                elif t == '*':  #pretty sure this can't be negated?
-                     ret_set |= self.types  #All of the types
-                else:
-                    print "ERROR: invalid type in avc rule " + t + "\nTYPE SET"
-                    print type_set
-                    return
-        return ret_set - neg_set
-
-    def parse_multi_line(self, line, line_type, file_obj):
-        if line_type == CLASS:
-            self.process_class_line(line, file_obj)
-        elif line_type == COMMON:
-            self.process_common_line(line, file_obj)
-        elif line_type == ALLOW_RULE:
-            self.process_avc_rule_line(line, file_obj)
-        elif line_type == NEVERALLOW_RULE:
-            self.process_avc_rule_line(line, file_obj)
-        else:
-            print "Error: This is not a multi-line input"
-
-    def parse_single_line(self, line, line_type):
-        if line_type == TYPE:
-            self.process_type_line(line)
-        elif line_type == ATTRIBUTE:
-            self.process_attribute_line(line)
-        elif line_type == TYPEATTRIBUTE:
-            self.process_typeattribute_line(line)
-        return
-
-    def process_attribute_line(self, line):
-        match = re.search(r'^attribute\s+(.+);', line)
-        if match:
-            declared_attribute = match.group(1)
-            self.attributes[declared_attribute] = set()
-        else:
-            #TODO: handle error? (no state changed)
-            return
-
-    def process_class_line(self, line, file_obj):
-        match = re.search(r'^class\s([^\s]+)\s(.*$)', line)
-        if match:
-            declared_class = match.group(1)
-            #first class declaration has no perms
-            if not declared_class in self.classes:
-                self.classes[declared_class] = set()
-                return
-            else:
-                #need to parse file from after class name until end of '{ }'s
-                file_obj.seek(-(len(match.group(2)) + 1), 1)
-                c = advance_past_whitespace(file_obj)
-                if not (c == 'i' or c == '{'):
-                    print "ERROR: invalid class statement"
-                    return
-                elif c == 'i':
-                    #add inherited permissions
-                    inherited = process_inherits_segment(file_obj)
-                    self.classes[declared_class] |= self.common_classes[inherited]
-                    c = advance_past_whitespace(file_obj)
-                if c == '{':
-                    permissions = expand_brackets(file_obj)
-                    permissions = re.sub(r'#[^\n]*\n','\n' , permissions) #get rid of all comments
-                    permissions = permissions.split()
-                    for p in permissions:
-                        self.classes[declared_class].add(p)
-
-    def process_common_line(self, line, file_obj):
-        match = re.search(r'^common\s([^\s]+)(.*$)', line)
-        if match:
-            declared_common_class = match.group(1)
-            #TODO: common classes should only be declared once...
-            if not declared_common_class in self.common_classes:
-                self.common_classes[declared_common_class] = set()
-            #need to parse file from after common_class name until end of '{ }'s
-            file_obj.seek(-(len(match.group(2)) + 1), 1)
-            c = advance_past_whitespace(file_obj)
-            if not c == '{':
-                print "ERROR: invalid common statement"
-                return
-            permissions = expand_brackets(file_obj)
-            permissions = permissions.split()
-            for p in permissions:
-                self.common_classes[declared_common_class].add(p)
-        return
-
-    def process_avc_rule_line(self, line, file_obj):
-        match = re.search(r'^(never)?allow\s(.*$)', line)
-        if match:
-            if(match.group(1)):
-                rule_type = 'neverallow'
-            else:
-                rule_type = 'allow'
-            #need to parse file from after class name until end of '{ }'s
-            file_obj.seek(-(len(match.group(2)) + 1), 1)
-
-            #grab source type(s)
-            source_types = get_avc_rule_component(file_obj)
-            if len(source_types['set']) == 0:
-                print "ERROR: no source types for avc rule at line: " + line
-                return
-
-            #grab target type(s)
-            target_types = get_avc_rule_component(file_obj)
-            if len(target_types['set']) == 0:
-                print "ERROR: no target types for avc rule at line: " + line
-                return
-
-            #skip ':' potentially already handled by advance_until_whitespace
-            c = advance_past_whitespace(file_obj)
-            if c == ':':
-                file_obj.read(1)
-
-            #grab class(es)
-            classes = get_avc_rule_component(file_obj)
-            if len(classes['set']) == 0:
-                print "ERROR: no classes for avc rule at line: " + line
-                return
-
-            #grab permission(s)
-            permissions = get_avc_rule_component(file_obj)
-            if len(permissions['set']) == 0:
-                print "ERROR: no permissions for avc rule at line: " + line
-                return
-            rule_dict = {
-                'source_types': source_types,
-                'target_types': target_types,
-                'classes': classes,
-                'permissions': permissions }
-
-            if rule_type == 'allow':
-                self.allow_rules.append(rule_dict)
-            elif rule_type == 'neverallow':
-                self.neverallow_rules.append(rule_dict)
-
-    def process_type_line(self, line):
-        #TODO: add support for aliases (not yet in current policy.conf)
-        match = re.search(r'^type\s([^,]+),?(.*);', line)
-        if match:
-            declared_type = match.group(1)
-            self.types.add(declared_type)
-            if match.group(2):
-                declared_attributes = match.group(2)
-                declared_attributes = declared_attributes.replace(" ", "") #remove whitespace
-                declared_attributes = declared_attributes.split(',') #separate based on delimiter
-                for a in declared_attributes:
-                    if not a in self.attributes:
-                        #TODO: hanlde error? attribute should already exist
-                        self.attributes[a] = set()
-                    self.attributes[a].add(declared_type)
-        else:
-            #TODO: handle error? (no state changed)
-            return
-
-    def process_typeattribute_line(self, line):
-        match = re.search(r'^typeattribute\s([^\s]+)\s(.*);', line)
-        if match:
-            declared_type = match.group(1)
-            if not declared_type in self.types:
-                #TODO: handle error? type should already exist
-                self.types.add(declared_type)
-            if match.group(2):
-                declared_attributes = match.group(2)
-                declared_attributes = declared_attributes.replace(" ", "") #remove whitespace
-                declared_attributes = declared_attributes.split(',') #separate based on delimiter
-                for a in declared_attributes:
-                    if not a in self.attributes:
-                        #TODO: hanlde error? attribute should already exist
-                        self.attributes[a] = set()
-                    self.attributes[a].add(declared_type)
-            else:
-                return
-        else:
-            #TODO: handle error? (no state changed)
-            return
diff --git a/tools/selinux/src/example_input_policy.conf b/tools/selinux/src/example_input_policy.conf
deleted file mode 100644
index aeef5f8..0000000
--- a/tools/selinux/src/example_input_policy.conf
+++ /dev/null
@@ -1,9850 +0,0 @@
-#line 1 "external/sepolicy/security_classes"
-# FLASK
-
-#
-# Define the security object classes
-#
-
-# Classes marked as userspace are classes
-# for userspace object managers
-
-class security
-class process
-class system
-class capability
-
-# file-related classes
-class filesystem
-class file
-class dir
-class fd
-class lnk_file
-class chr_file
-class blk_file
-class sock_file
-class fifo_file
-
-# network-related classes
-class socket
-class tcp_socket
-class udp_socket
-class rawip_socket
-class node
-class netif
-class netlink_socket
-class packet_socket
-class key_socket
-class unix_stream_socket
-class unix_dgram_socket
-
-# sysv-ipc-related classes
-class sem
-class msg
-class msgq
-class shm
-class ipc
-
-#
-# userspace object manager classes
-#
-
-# passwd/chfn/chsh
-class passwd			# userspace
-
-# SE-X Windows stuff (more classes below)
-class x_drawable		# userspace
-class x_screen			# userspace
-class x_gc			# userspace
-class x_font			# userspace
-class x_colormap		# userspace
-class x_property		# userspace
-class x_selection		# userspace
-class x_cursor			# userspace
-class x_client			# userspace
-class x_device			# userspace
-class x_server			# userspace
-class x_extension		# userspace
-
-# extended netlink sockets
-class netlink_route_socket
-class netlink_firewall_socket
-class netlink_tcpdiag_socket
-class netlink_nflog_socket
-class netlink_xfrm_socket
-class netlink_selinux_socket
-class netlink_audit_socket
-class netlink_ip6fw_socket
-class netlink_dnrt_socket
-
-class dbus			# userspace
-class nscd			# userspace
-
-# IPSec association
-class association
-
-# Updated Netlink class for KOBJECT_UEVENT family.
-class netlink_kobject_uevent_socket
-
-class appletalk_socket
-
-class packet
-
-# Kernel access key retention
-class key
-
-class context			# userspace
-
-class dccp_socket
-
-class memprotect
-
-class db_database		# userspace
-class db_table			# userspace
-class db_procedure		# userspace
-class db_column			# userspace
-class db_tuple			# userspace
-class db_blob			# userspace
-
-# network peer labels
-class peer
-
-# Capabilities >= 32
-class capability2
-
-# More SE-X Windows stuff
-class x_resource		# userspace
-class x_event			# userspace
-class x_synthetic_event		# userspace
-class x_application_data	# userspace
-
-# kernel services that need to override task security, e.g. cachefiles
-class kernel_service
-
-class tun_socket
-
-# Still More SE-X Windows stuff
-class x_pointer			# userspace
-class x_keyboard		# userspace
-
-# More Database stuff
-class db_schema			# userspace
-class db_view			# userspace
-class db_sequence		# userspace
-class db_language		# userspace
-
-class binder
-class zygote
-
-# Property service
-class property_service          # userspace
-
-# FLASK
-#line 1 "external/sepolicy/initial_sids"
-# FLASK
-
-#
-# Define initial security identifiers
-#
-
-sid kernel
-sid security
-sid unlabeled
-sid fs
-sid file
-sid file_labels
-sid init
-sid any_socket
-sid port
-sid netif
-sid netmsg
-sid node
-sid igmp_packet
-sid icmp_socket
-sid tcp_socket
-sid sysctl_modprobe
-sid sysctl
-sid sysctl_fs
-sid sysctl_kernel
-sid sysctl_net
-sid sysctl_net_unix
-sid sysctl_vm
-sid sysctl_dev
-sid kmod
-sid policy
-sid scmp_packet
-sid devnull
-
-# FLASK
-#line 1 "external/sepolicy/access_vectors"
-#
-# Define common prefixes for access vectors
-#
-# common common_name { permission_name ... }
-
-
-#
-# Define a common prefix for file access vectors.
-#
-
-common file
-{
-	ioctl
-	read
-	write
-	create
-	getattr
-	setattr
-	lock
-	relabelfrom
-	relabelto
-	append
-	unlink
-	link
-	rename
-	execute
-	swapon
-	quotaon
-	mounton
-}
-
-
-#
-# Define a common prefix for socket access vectors.
-#
-
-common socket
-{
-# inherited from file
-	ioctl
-	read
-	write
-	create
-	getattr
-	setattr
-	lock
-	relabelfrom
-	relabelto
-	append
-# socket-specific
-	bind
-	connect
-	listen
-	accept
-	getopt
-	setopt
-	shutdown
-	recvfrom
-	sendto
-	recv_msg
-	send_msg
-	name_bind
-}
-
-#
-# Define a common prefix for ipc access vectors.
-#
-
-common ipc
-{
-	create
-	destroy
-	getattr
-	setattr
-	read
-	write
-	associate
-	unix_read
-	unix_write
-}
-
-#
-#  Define a common prefix for userspace database object access vectors.
-#
-
-common database
-{
-	create
-	drop
-	getattr
-	setattr
-	relabelfrom
-	relabelto
-}
-
-#
-# Define a common prefix for pointer and keyboard access vectors.
-#
-
-common x_device
-{
-	getattr
-	setattr
-	use
-	read
-	write
-	getfocus
-	setfocus
-	bell
-	force_cursor
-	freeze
-	grab
-	manage
-	list_property
-	get_property
-	set_property
-	add
-	remove
-	create
-	destroy
-}
-
-#
-# Define the access vectors.
-#
-# class class_name [ inherits common_name ] { permission_name ... }
-
-
-#
-# Define the access vector interpretation for file-related objects.
-#
-
-class filesystem
-{
-	mount
-	remount
-	unmount
-	getattr
-	relabelfrom
-	relabelto
-	transition
-	associate
-	quotamod
-	quotaget
-}
-
-class dir
-inherits file
-{
-	add_name
-	remove_name
-	reparent
-	search
-	rmdir
-	open
-	audit_access
-	execmod
-}
-
-class file
-inherits file
-{
-	execute_no_trans
-	entrypoint
-	execmod
-	open
-	audit_access
-}
-
-class lnk_file
-inherits file
-{
-	open
-	audit_access
-	execmod
-}
-
-class chr_file
-inherits file
-{
-	execute_no_trans
-	entrypoint
-	execmod
-	open
-	audit_access
-}
-
-class blk_file
-inherits file
-{
-	open
-	audit_access
-	execmod
-}
-
-class sock_file
-inherits file
-{
-	open
-	audit_access
-	execmod
-}
-
-class fifo_file
-inherits file
-{
-	open
-	audit_access
-	execmod
-}
-
-class fd
-{
-	use
-}
-
-
-#
-# Define the access vector interpretation for network-related objects.
-#
-
-class socket
-inherits socket
-
-class tcp_socket
-inherits socket
-{
-	connectto
-	newconn
-	acceptfrom
-	node_bind
-	name_connect
-}
-
-class udp_socket
-inherits socket
-{
-	node_bind
-}
-
-class rawip_socket
-inherits socket
-{
-	node_bind
-}
-
-class node
-{
-	tcp_recv
-	tcp_send
-	udp_recv
-	udp_send
-	rawip_recv
-	rawip_send
-	enforce_dest
-	dccp_recv
-	dccp_send
-	recvfrom
-	sendto
-}
-
-class netif
-{
-	tcp_recv
-	tcp_send
-	udp_recv
-	udp_send
-	rawip_recv
-	rawip_send
-	dccp_recv
-	dccp_send
-	ingress
-	egress
-}
-
-class netlink_socket
-inherits socket
-
-class packet_socket
-inherits socket
-
-class key_socket
-inherits socket
-
-class unix_stream_socket
-inherits socket
-{
-	connectto
-	newconn
-	acceptfrom
-}
-
-class unix_dgram_socket
-inherits socket
-
-#
-# Define the access vector interpretation for process-related objects
-#
-
-class process
-{
-	fork
-	transition
-	sigchld # commonly granted from child to parent
-	sigkill # cannot be caught or ignored
-	sigstop # cannot be caught or ignored
-	signull # for kill(pid, 0)
-	signal  # all other signals
-	ptrace
-	getsched
-	setsched
-	getsession
-	getpgid
-	setpgid
-	getcap
-	setcap
-	share
-	getattr
-	setexec
-	setfscreate
-	noatsecure
-	siginh
-	setrlimit
-	rlimitinh
-	dyntransition
-	setcurrent
-	execmem
-	execstack
-	execheap
-	setkeycreate
-	setsockcreate
-}
-
-
-#
-# Define the access vector interpretation for ipc-related objects
-#
-
-class ipc
-inherits ipc
-
-class sem
-inherits ipc
-
-class msgq
-inherits ipc
-{
-	enqueue
-}
-
-class msg
-{
-	send
-	receive
-}
-
-class shm
-inherits ipc
-{
-	lock
-}
-
-
-#
-# Define the access vector interpretation for the security server.
-#
-
-class security
-{
-	compute_av
-	compute_create
-	compute_member
-	check_context
-	load_policy
-	compute_relabel
-	compute_user
-	setenforce     # was avc_toggle in system class
-	setbool
-	setsecparam
-	setcheckreqprot
-	read_policy
-}
-
-
-#
-# Define the access vector interpretation for system operations.
-#
-
-class system
-{
-	ipc_info
-	syslog_read
-	syslog_mod
-	syslog_console
-	module_request
-}
-
-#
-# Define the access vector interpretation for controling capabilies
-#
-
-class capability
-{
-	# The capabilities are defined in include/linux/capability.h
-	# Capabilities >= 32 are defined in the capability2 class.
-	# Care should be taken to ensure that these are consistent with
-	# those definitions. (Order matters)
-
-	chown
-	dac_override
-	dac_read_search
-	fowner
-	fsetid
-	kill
-	setgid
-	setuid
-	setpcap
-	linux_immutable
-	net_bind_service
-	net_broadcast
-	net_admin
-	net_raw
-	ipc_lock
-	ipc_owner
-	sys_module
-	sys_rawio
-	sys_chroot
-	sys_ptrace
-	sys_pacct
-	sys_admin
-	sys_boot
-	sys_nice
-	sys_resource
-	sys_time
-	sys_tty_config
-	mknod
-	lease
-	audit_write
-	audit_control
-	setfcap
-}
-
-class capability2
-{
-	mac_override	# unused by SELinux
-	mac_admin	# unused by SELinux
-	syslog
-	wake_alarm
-	block_suspend
-}
-
-#
-# Define the access vector interpretation for controlling
-# changes to passwd information.
-#
-class passwd
-{
-	passwd	# change another user passwd
-	chfn	# change another user finger info
-	chsh	# change another user shell
-	rootok  # pam_rootok check (skip auth)
-	crontab # crontab on another user
-}
-
-#
-# SE-X Windows stuff
-#
-class x_drawable
-{
-	create
-	destroy
-	read
-	write
-	blend
-	getattr
-	setattr
-	list_child
-	add_child
-	remove_child
-	list_property
-	get_property
-	set_property
-	manage
-	override
-	show
-	hide
-	send
-	receive
-}
-
-class x_screen
-{
-	getattr
-	setattr
-	hide_cursor
-	show_cursor
-	saver_getattr
-	saver_setattr
-	saver_hide
-	saver_show
-}
-
-class x_gc
-{
-	create
-	destroy
-	getattr
-	setattr
-	use
-}
-
-class x_font
-{
-	create
-	destroy
-	getattr
-	add_glyph
-	remove_glyph
-	use
-}
-
-class x_colormap
-{
-	create
-	destroy
-	read
-	write
-	getattr
-	add_color
-	remove_color
-	install
-	uninstall
-	use
-}
-
-class x_property
-{
-	create
-	destroy
-	read
-	write
-	append
-	getattr
-	setattr
-}
-
-class x_selection
-{
-	read
-	write
-	getattr
-	setattr
-}
-
-class x_cursor
-{
-	create
-	destroy
-	read
-	write
-	getattr
-	setattr
-	use
-}
-
-class x_client
-{
-	destroy
-	getattr
-	setattr
-	manage
-}
-
-class x_device
-inherits x_device
-
-class x_server
-{
-	getattr
-	setattr
-	record
-	debug
-	grab
-	manage
-}
-
-class x_extension
-{
-	query
-	use
-}
-
-class x_resource
-{
-	read
-	write
-}
-
-class x_event
-{
-	send
-	receive
-}
-
-class x_synthetic_event
-{
-	send
-	receive
-}
-
-#
-# Extended Netlink classes
-#
-class netlink_route_socket
-inherits socket
-{
-	nlmsg_read
-	nlmsg_write
-}
-
-class netlink_firewall_socket
-inherits socket
-{
-	nlmsg_read
-	nlmsg_write
-}
-
-class netlink_tcpdiag_socket
-inherits socket
-{
-	nlmsg_read
-	nlmsg_write
-}
-
-class netlink_nflog_socket
-inherits socket
-
-class netlink_xfrm_socket
-inherits socket
-{
-	nlmsg_read
-	nlmsg_write
-}
-
-class netlink_selinux_socket
-inherits socket
-
-class netlink_audit_socket
-inherits socket
-{
-	nlmsg_read
-	nlmsg_write
-	nlmsg_relay
-	nlmsg_readpriv
-	nlmsg_tty_audit
-}
-
-class netlink_ip6fw_socket
-inherits socket
-{
-	nlmsg_read
-	nlmsg_write
-}
-
-class netlink_dnrt_socket
-inherits socket
-
-# Define the access vector interpretation for controlling
-# access and communication through the D-BUS messaging
-# system.
-#
-class dbus
-{
-	acquire_svc
-	send_msg
-}
-
-# Define the access vector interpretation for controlling
-# access through the name service cache daemon (nscd).
-#
-class nscd
-{
-	getpwd
-	getgrp
-	gethost
-	getstat
-	admin
-	shmempwd
-	shmemgrp
-	shmemhost
-	getserv
-	shmemserv
-}
-
-# Define the access vector interpretation for controlling
-# access to IPSec network data by association
-#
-class association
-{
-	sendto
-	recvfrom
-	setcontext
-	polmatch
-}
-
-# Updated Netlink class for KOBJECT_UEVENT family.
-class netlink_kobject_uevent_socket
-inherits socket
-
-class appletalk_socket
-inherits socket
-
-class packet
-{
-	send
-	recv
-	relabelto
-	flow_in		# deprecated
-	flow_out	# deprecated
-	forward_in
-	forward_out
-}
-
-class key
-{
-	view
-	read
-	write
-	search
-	link
-	setattr
-	create
-}
-
-class context
-{
-	translate
-	contains
-}
-
-class dccp_socket
-inherits socket
-{
-	node_bind
-	name_connect
-}
-
-class memprotect
-{
-	mmap_zero
-}
-
-class db_database
-inherits database
-{
-	access
-	install_module
-	load_module
-	get_param	# deprecated
-	set_param	# deprecated
-}
-
-class db_table
-inherits database
-{
-	use		# deprecated
-	select
-	update
-	insert
-	delete
-	lock
-}
-
-class db_procedure
-inherits database
-{
-	execute
-	entrypoint
-	install
-}
-
-class db_column
-inherits database
-{
-	use		# deprecated
-	select
-	update
-	insert
-}
-
-class db_tuple
-{
-	relabelfrom
-	relabelto
-	use		# deprecated
-	select
-	update
-	insert
-	delete
-}
-
-class db_blob
-inherits database
-{
-	read
-	write
-	import
-	export
-}
-
-# network peer labels
-class peer
-{
-	recv
-}
-
-class x_application_data
-{
-	paste
-	paste_after_confirm
-	copy
-}
-
-class kernel_service
-{
-	use_as_override
-	create_files_as
-}
-
-class tun_socket
-inherits socket
-
-class x_pointer
-inherits x_device
-
-class x_keyboard
-inherits x_device
-
-class db_schema
-inherits database
-{
-	search
-	add_name
-	remove_name
-}
-
-class db_view
-inherits database
-{
-	expand
-}
-
-class db_sequence
-inherits database
-{
-	get_value
-	next_value
-	set_value
-}
-
-class db_language
-inherits database
-{
-	implement
-	execute
-}
-
-class binder
-{
-	impersonate
-	call
-	set_context_mgr
-	transfer
-}
-
-class zygote
-{
-	specifyids
-	specifyrlimits
-	specifycapabilities
-	specifyinvokewith
-	specifyseinfo
-}
-
-class property_service
-{
-	set
-}
-#line 1 "external/sepolicy/global_macros"
-#####################################
-# Common groupings of object classes.
-#
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-#####################################
-# Common groupings of permissions.
-#
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-#####################################
-# Common socket permission sets.
-
-
-#line 1 "external/sepolicy/mls_macros"
-########################################
-#
-# gen_cats(N)
-#
-# declares categores c0 to c(N-1)
-#
-#line 10
-
-
-
-
-########################################
-#
-# gen_sens(N)
-#
-# declares sensitivites s0 to s(N-1) with dominance
-# in increasing numeric order with s0 lowest, s(N-1) highest
-#
-#line 24
-
-
-
-
-#line 34
-
-
-########################################
-#
-# gen_levels(N,M)
-#
-# levels from s0 to (N-1) with categories c0 to (M-1)
-#
-#line 45
-
-
-
-
-########################################
-#
-# Basic level names for system low and high
-#
-
-
-#line 1 "external/sepolicy/mls"
-#########################################
-# MLS declarations
-#
-
-# Generate the desired number of sensitivities and categories.
-
-#line 6
-# Each sensitivity has a name and zero or more aliases.
-#line 6
-sensitivity s0;
-#line 6
-
-#line 6
-
-#line 6
-# Define the ordering of the sensitivity levels (least to greatest)
-#line 6
-dominance { s0  }
-#line 6
-
-category c0;
-#line 7
-category c1;
-#line 7
-category c2;
-#line 7
-category c3;
-#line 7
-category c4;
-#line 7
-category c5;
-#line 7
-category c6;
-#line 7
-category c7;
-#line 7
-category c8;
-#line 7
-category c9;
-#line 7
-category c10;
-#line 7
-category c11;
-#line 7
-category c12;
-#line 7
-category c13;
-#line 7
-category c14;
-#line 7
-category c15;
-#line 7
-category c16;
-#line 7
-category c17;
-#line 7
-category c18;
-#line 7
-category c19;
-#line 7
-category c20;
-#line 7
-category c21;
-#line 7
-category c22;
-#line 7
-category c23;
-#line 7
-category c24;
-#line 7
-category c25;
-#line 7
-category c26;
-#line 7
-category c27;
-#line 7
-category c28;
-#line 7
-category c29;
-#line 7
-category c30;
-#line 7
-category c31;
-#line 7
-category c32;
-#line 7
-category c33;
-#line 7
-category c34;
-#line 7
-category c35;
-#line 7
-category c36;
-#line 7
-category c37;
-#line 7
-category c38;
-#line 7
-category c39;
-#line 7
-category c40;
-#line 7
-category c41;
-#line 7
-category c42;
-#line 7
-category c43;
-#line 7
-category c44;
-#line 7
-category c45;
-#line 7
-category c46;
-#line 7
-category c47;
-#line 7
-category c48;
-#line 7
-category c49;
-#line 7
-category c50;
-#line 7
-category c51;
-#line 7
-category c52;
-#line 7
-category c53;
-#line 7
-category c54;
-#line 7
-category c55;
-#line 7
-category c56;
-#line 7
-category c57;
-#line 7
-category c58;
-#line 7
-category c59;
-#line 7
-category c60;
-#line 7
-category c61;
-#line 7
-category c62;
-#line 7
-category c63;
-#line 7
-category c64;
-#line 7
-category c65;
-#line 7
-category c66;
-#line 7
-category c67;
-#line 7
-category c68;
-#line 7
-category c69;
-#line 7
-category c70;
-#line 7
-category c71;
-#line 7
-category c72;
-#line 7
-category c73;
-#line 7
-category c74;
-#line 7
-category c75;
-#line 7
-category c76;
-#line 7
-category c77;
-#line 7
-category c78;
-#line 7
-category c79;
-#line 7
-category c80;
-#line 7
-category c81;
-#line 7
-category c82;
-#line 7
-category c83;
-#line 7
-category c84;
-#line 7
-category c85;
-#line 7
-category c86;
-#line 7
-category c87;
-#line 7
-category c88;
-#line 7
-category c89;
-#line 7
-category c90;
-#line 7
-category c91;
-#line 7
-category c92;
-#line 7
-category c93;
-#line 7
-category c94;
-#line 7
-category c95;
-#line 7
-category c96;
-#line 7
-category c97;
-#line 7
-category c98;
-#line 7
-category c99;
-#line 7
-category c100;
-#line 7
-category c101;
-#line 7
-category c102;
-#line 7
-category c103;
-#line 7
-category c104;
-#line 7
-category c105;
-#line 7
-category c106;
-#line 7
-category c107;
-#line 7
-category c108;
-#line 7
-category c109;
-#line 7
-category c110;
-#line 7
-category c111;
-#line 7
-category c112;
-#line 7
-category c113;
-#line 7
-category c114;
-#line 7
-category c115;
-#line 7
-category c116;
-#line 7
-category c117;
-#line 7
-category c118;
-#line 7
-category c119;
-#line 7
-category c120;
-#line 7
-category c121;
-#line 7
-category c122;
-#line 7
-category c123;
-#line 7
-category c124;
-#line 7
-category c125;
-#line 7
-category c126;
-#line 7
-category c127;
-#line 7
-category c128;
-#line 7
-category c129;
-#line 7
-category c130;
-#line 7
-category c131;
-#line 7
-category c132;
-#line 7
-category c133;
-#line 7
-category c134;
-#line 7
-category c135;
-#line 7
-category c136;
-#line 7
-category c137;
-#line 7
-category c138;
-#line 7
-category c139;
-#line 7
-category c140;
-#line 7
-category c141;
-#line 7
-category c142;
-#line 7
-category c143;
-#line 7
-category c144;
-#line 7
-category c145;
-#line 7
-category c146;
-#line 7
-category c147;
-#line 7
-category c148;
-#line 7
-category c149;
-#line 7
-category c150;
-#line 7
-category c151;
-#line 7
-category c152;
-#line 7
-category c153;
-#line 7
-category c154;
-#line 7
-category c155;
-#line 7
-category c156;
-#line 7
-category c157;
-#line 7
-category c158;
-#line 7
-category c159;
-#line 7
-category c160;
-#line 7
-category c161;
-#line 7
-category c162;
-#line 7
-category c163;
-#line 7
-category c164;
-#line 7
-category c165;
-#line 7
-category c166;
-#line 7
-category c167;
-#line 7
-category c168;
-#line 7
-category c169;
-#line 7
-category c170;
-#line 7
-category c171;
-#line 7
-category c172;
-#line 7
-category c173;
-#line 7
-category c174;
-#line 7
-category c175;
-#line 7
-category c176;
-#line 7
-category c177;
-#line 7
-category c178;
-#line 7
-category c179;
-#line 7
-category c180;
-#line 7
-category c181;
-#line 7
-category c182;
-#line 7
-category c183;
-#line 7
-category c184;
-#line 7
-category c185;
-#line 7
-category c186;
-#line 7
-category c187;
-#line 7
-category c188;
-#line 7
-category c189;
-#line 7
-category c190;
-#line 7
-category c191;
-#line 7
-category c192;
-#line 7
-category c193;
-#line 7
-category c194;
-#line 7
-category c195;
-#line 7
-category c196;
-#line 7
-category c197;
-#line 7
-category c198;
-#line 7
-category c199;
-#line 7
-category c200;
-#line 7
-category c201;
-#line 7
-category c202;
-#line 7
-category c203;
-#line 7
-category c204;
-#line 7
-category c205;
-#line 7
-category c206;
-#line 7
-category c207;
-#line 7
-category c208;
-#line 7
-category c209;
-#line 7
-category c210;
-#line 7
-category c211;
-#line 7
-category c212;
-#line 7
-category c213;
-#line 7
-category c214;
-#line 7
-category c215;
-#line 7
-category c216;
-#line 7
-category c217;
-#line 7
-category c218;
-#line 7
-category c219;
-#line 7
-category c220;
-#line 7
-category c221;
-#line 7
-category c222;
-#line 7
-category c223;
-#line 7
-category c224;
-#line 7
-category c225;
-#line 7
-category c226;
-#line 7
-category c227;
-#line 7
-category c228;
-#line 7
-category c229;
-#line 7
-category c230;
-#line 7
-category c231;
-#line 7
-category c232;
-#line 7
-category c233;
-#line 7
-category c234;
-#line 7
-category c235;
-#line 7
-category c236;
-#line 7
-category c237;
-#line 7
-category c238;
-#line 7
-category c239;
-#line 7
-category c240;
-#line 7
-category c241;
-#line 7
-category c242;
-#line 7
-category c243;
-#line 7
-category c244;
-#line 7
-category c245;
-#line 7
-category c246;
-#line 7
-category c247;
-#line 7
-category c248;
-#line 7
-category c249;
-#line 7
-category c250;
-#line 7
-category c251;
-#line 7
-category c252;
-#line 7
-category c253;
-#line 7
-category c254;
-#line 7
-category c255;
-#line 7
-category c256;
-#line 7
-category c257;
-#line 7
-category c258;
-#line 7
-category c259;
-#line 7
-category c260;
-#line 7
-category c261;
-#line 7
-category c262;
-#line 7
-category c263;
-#line 7
-category c264;
-#line 7
-category c265;
-#line 7
-category c266;
-#line 7
-category c267;
-#line 7
-category c268;
-#line 7
-category c269;
-#line 7
-category c270;
-#line 7
-category c271;
-#line 7
-category c272;
-#line 7
-category c273;
-#line 7
-category c274;
-#line 7
-category c275;
-#line 7
-category c276;
-#line 7
-category c277;
-#line 7
-category c278;
-#line 7
-category c279;
-#line 7
-category c280;
-#line 7
-category c281;
-#line 7
-category c282;
-#line 7
-category c283;
-#line 7
-category c284;
-#line 7
-category c285;
-#line 7
-category c286;
-#line 7
-category c287;
-#line 7
-category c288;
-#line 7
-category c289;
-#line 7
-category c290;
-#line 7
-category c291;
-#line 7
-category c292;
-#line 7
-category c293;
-#line 7
-category c294;
-#line 7
-category c295;
-#line 7
-category c296;
-#line 7
-category c297;
-#line 7
-category c298;
-#line 7
-category c299;
-#line 7
-category c300;
-#line 7
-category c301;
-#line 7
-category c302;
-#line 7
-category c303;
-#line 7
-category c304;
-#line 7
-category c305;
-#line 7
-category c306;
-#line 7
-category c307;
-#line 7
-category c308;
-#line 7
-category c309;
-#line 7
-category c310;
-#line 7
-category c311;
-#line 7
-category c312;
-#line 7
-category c313;
-#line 7
-category c314;
-#line 7
-category c315;
-#line 7
-category c316;
-#line 7
-category c317;
-#line 7
-category c318;
-#line 7
-category c319;
-#line 7
-category c320;
-#line 7
-category c321;
-#line 7
-category c322;
-#line 7
-category c323;
-#line 7
-category c324;
-#line 7
-category c325;
-#line 7
-category c326;
-#line 7
-category c327;
-#line 7
-category c328;
-#line 7
-category c329;
-#line 7
-category c330;
-#line 7
-category c331;
-#line 7
-category c332;
-#line 7
-category c333;
-#line 7
-category c334;
-#line 7
-category c335;
-#line 7
-category c336;
-#line 7
-category c337;
-#line 7
-category c338;
-#line 7
-category c339;
-#line 7
-category c340;
-#line 7
-category c341;
-#line 7
-category c342;
-#line 7
-category c343;
-#line 7
-category c344;
-#line 7
-category c345;
-#line 7
-category c346;
-#line 7
-category c347;
-#line 7
-category c348;
-#line 7
-category c349;
-#line 7
-category c350;
-#line 7
-category c351;
-#line 7
-category c352;
-#line 7
-category c353;
-#line 7
-category c354;
-#line 7
-category c355;
-#line 7
-category c356;
-#line 7
-category c357;
-#line 7
-category c358;
-#line 7
-category c359;
-#line 7
-category c360;
-#line 7
-category c361;
-#line 7
-category c362;
-#line 7
-category c363;
-#line 7
-category c364;
-#line 7
-category c365;
-#line 7
-category c366;
-#line 7
-category c367;
-#line 7
-category c368;
-#line 7
-category c369;
-#line 7
-category c370;
-#line 7
-category c371;
-#line 7
-category c372;
-#line 7
-category c373;
-#line 7
-category c374;
-#line 7
-category c375;
-#line 7
-category c376;
-#line 7
-category c377;
-#line 7
-category c378;
-#line 7
-category c379;
-#line 7
-category c380;
-#line 7
-category c381;
-#line 7
-category c382;
-#line 7
-category c383;
-#line 7
-category c384;
-#line 7
-category c385;
-#line 7
-category c386;
-#line 7
-category c387;
-#line 7
-category c388;
-#line 7
-category c389;
-#line 7
-category c390;
-#line 7
-category c391;
-#line 7
-category c392;
-#line 7
-category c393;
-#line 7
-category c394;
-#line 7
-category c395;
-#line 7
-category c396;
-#line 7
-category c397;
-#line 7
-category c398;
-#line 7
-category c399;
-#line 7
-category c400;
-#line 7
-category c401;
-#line 7
-category c402;
-#line 7
-category c403;
-#line 7
-category c404;
-#line 7
-category c405;
-#line 7
-category c406;
-#line 7
-category c407;
-#line 7
-category c408;
-#line 7
-category c409;
-#line 7
-category c410;
-#line 7
-category c411;
-#line 7
-category c412;
-#line 7
-category c413;
-#line 7
-category c414;
-#line 7
-category c415;
-#line 7
-category c416;
-#line 7
-category c417;
-#line 7
-category c418;
-#line 7
-category c419;
-#line 7
-category c420;
-#line 7
-category c421;
-#line 7
-category c422;
-#line 7
-category c423;
-#line 7
-category c424;
-#line 7
-category c425;
-#line 7
-category c426;
-#line 7
-category c427;
-#line 7
-category c428;
-#line 7
-category c429;
-#line 7
-category c430;
-#line 7
-category c431;
-#line 7
-category c432;
-#line 7
-category c433;
-#line 7
-category c434;
-#line 7
-category c435;
-#line 7
-category c436;
-#line 7
-category c437;
-#line 7
-category c438;
-#line 7
-category c439;
-#line 7
-category c440;
-#line 7
-category c441;
-#line 7
-category c442;
-#line 7
-category c443;
-#line 7
-category c444;
-#line 7
-category c445;
-#line 7
-category c446;
-#line 7
-category c447;
-#line 7
-category c448;
-#line 7
-category c449;
-#line 7
-category c450;
-#line 7
-category c451;
-#line 7
-category c452;
-#line 7
-category c453;
-#line 7
-category c454;
-#line 7
-category c455;
-#line 7
-category c456;
-#line 7
-category c457;
-#line 7
-category c458;
-#line 7
-category c459;
-#line 7
-category c460;
-#line 7
-category c461;
-#line 7
-category c462;
-#line 7
-category c463;
-#line 7
-category c464;
-#line 7
-category c465;
-#line 7
-category c466;
-#line 7
-category c467;
-#line 7
-category c468;
-#line 7
-category c469;
-#line 7
-category c470;
-#line 7
-category c471;
-#line 7
-category c472;
-#line 7
-category c473;
-#line 7
-category c474;
-#line 7
-category c475;
-#line 7
-category c476;
-#line 7
-category c477;
-#line 7
-category c478;
-#line 7
-category c479;
-#line 7
-category c480;
-#line 7
-category c481;
-#line 7
-category c482;
-#line 7
-category c483;
-#line 7
-category c484;
-#line 7
-category c485;
-#line 7
-category c486;
-#line 7
-category c487;
-#line 7
-category c488;
-#line 7
-category c489;
-#line 7
-category c490;
-#line 7
-category c491;
-#line 7
-category c492;
-#line 7
-category c493;
-#line 7
-category c494;
-#line 7
-category c495;
-#line 7
-category c496;
-#line 7
-category c497;
-#line 7
-category c498;
-#line 7
-category c499;
-#line 7
-category c500;
-#line 7
-category c501;
-#line 7
-category c502;
-#line 7
-category c503;
-#line 7
-category c504;
-#line 7
-category c505;
-#line 7
-category c506;
-#line 7
-category c507;
-#line 7
-category c508;
-#line 7
-category c509;
-#line 7
-category c510;
-#line 7
-category c511;
-#line 7
-category c512;
-#line 7
-category c513;
-#line 7
-category c514;
-#line 7
-category c515;
-#line 7
-category c516;
-#line 7
-category c517;
-#line 7
-category c518;
-#line 7
-category c519;
-#line 7
-category c520;
-#line 7
-category c521;
-#line 7
-category c522;
-#line 7
-category c523;
-#line 7
-category c524;
-#line 7
-category c525;
-#line 7
-category c526;
-#line 7
-category c527;
-#line 7
-category c528;
-#line 7
-category c529;
-#line 7
-category c530;
-#line 7
-category c531;
-#line 7
-category c532;
-#line 7
-category c533;
-#line 7
-category c534;
-#line 7
-category c535;
-#line 7
-category c536;
-#line 7
-category c537;
-#line 7
-category c538;
-#line 7
-category c539;
-#line 7
-category c540;
-#line 7
-category c541;
-#line 7
-category c542;
-#line 7
-category c543;
-#line 7
-category c544;
-#line 7
-category c545;
-#line 7
-category c546;
-#line 7
-category c547;
-#line 7
-category c548;
-#line 7
-category c549;
-#line 7
-category c550;
-#line 7
-category c551;
-#line 7
-category c552;
-#line 7
-category c553;
-#line 7
-category c554;
-#line 7
-category c555;
-#line 7
-category c556;
-#line 7
-category c557;
-#line 7
-category c558;
-#line 7
-category c559;
-#line 7
-category c560;
-#line 7
-category c561;
-#line 7
-category c562;
-#line 7
-category c563;
-#line 7
-category c564;
-#line 7
-category c565;
-#line 7
-category c566;
-#line 7
-category c567;
-#line 7
-category c568;
-#line 7
-category c569;
-#line 7
-category c570;
-#line 7
-category c571;
-#line 7
-category c572;
-#line 7
-category c573;
-#line 7
-category c574;
-#line 7
-category c575;
-#line 7
-category c576;
-#line 7
-category c577;
-#line 7
-category c578;
-#line 7
-category c579;
-#line 7
-category c580;
-#line 7
-category c581;
-#line 7
-category c582;
-#line 7
-category c583;
-#line 7
-category c584;
-#line 7
-category c585;
-#line 7
-category c586;
-#line 7
-category c587;
-#line 7
-category c588;
-#line 7
-category c589;
-#line 7
-category c590;
-#line 7
-category c591;
-#line 7
-category c592;
-#line 7
-category c593;
-#line 7
-category c594;
-#line 7
-category c595;
-#line 7
-category c596;
-#line 7
-category c597;
-#line 7
-category c598;
-#line 7
-category c599;
-#line 7
-category c600;
-#line 7
-category c601;
-#line 7
-category c602;
-#line 7
-category c603;
-#line 7
-category c604;
-#line 7
-category c605;
-#line 7
-category c606;
-#line 7
-category c607;
-#line 7
-category c608;
-#line 7
-category c609;
-#line 7
-category c610;
-#line 7
-category c611;
-#line 7
-category c612;
-#line 7
-category c613;
-#line 7
-category c614;
-#line 7
-category c615;
-#line 7
-category c616;
-#line 7
-category c617;
-#line 7
-category c618;
-#line 7
-category c619;
-#line 7
-category c620;
-#line 7
-category c621;
-#line 7
-category c622;
-#line 7
-category c623;
-#line 7
-category c624;
-#line 7
-category c625;
-#line 7
-category c626;
-#line 7
-category c627;
-#line 7
-category c628;
-#line 7
-category c629;
-#line 7
-category c630;
-#line 7
-category c631;
-#line 7
-category c632;
-#line 7
-category c633;
-#line 7
-category c634;
-#line 7
-category c635;
-#line 7
-category c636;
-#line 7
-category c637;
-#line 7
-category c638;
-#line 7
-category c639;
-#line 7
-category c640;
-#line 7
-category c641;
-#line 7
-category c642;
-#line 7
-category c643;
-#line 7
-category c644;
-#line 7
-category c645;
-#line 7
-category c646;
-#line 7
-category c647;
-#line 7
-category c648;
-#line 7
-category c649;
-#line 7
-category c650;
-#line 7
-category c651;
-#line 7
-category c652;
-#line 7
-category c653;
-#line 7
-category c654;
-#line 7
-category c655;
-#line 7
-category c656;
-#line 7
-category c657;
-#line 7
-category c658;
-#line 7
-category c659;
-#line 7
-category c660;
-#line 7
-category c661;
-#line 7
-category c662;
-#line 7
-category c663;
-#line 7
-category c664;
-#line 7
-category c665;
-#line 7
-category c666;
-#line 7
-category c667;
-#line 7
-category c668;
-#line 7
-category c669;
-#line 7
-category c670;
-#line 7
-category c671;
-#line 7
-category c672;
-#line 7
-category c673;
-#line 7
-category c674;
-#line 7
-category c675;
-#line 7
-category c676;
-#line 7
-category c677;
-#line 7
-category c678;
-#line 7
-category c679;
-#line 7
-category c680;
-#line 7
-category c681;
-#line 7
-category c682;
-#line 7
-category c683;
-#line 7
-category c684;
-#line 7
-category c685;
-#line 7
-category c686;
-#line 7
-category c687;
-#line 7
-category c688;
-#line 7
-category c689;
-#line 7
-category c690;
-#line 7
-category c691;
-#line 7
-category c692;
-#line 7
-category c693;
-#line 7
-category c694;
-#line 7
-category c695;
-#line 7
-category c696;
-#line 7
-category c697;
-#line 7
-category c698;
-#line 7
-category c699;
-#line 7
-category c700;
-#line 7
-category c701;
-#line 7
-category c702;
-#line 7
-category c703;
-#line 7
-category c704;
-#line 7
-category c705;
-#line 7
-category c706;
-#line 7
-category c707;
-#line 7
-category c708;
-#line 7
-category c709;
-#line 7
-category c710;
-#line 7
-category c711;
-#line 7
-category c712;
-#line 7
-category c713;
-#line 7
-category c714;
-#line 7
-category c715;
-#line 7
-category c716;
-#line 7
-category c717;
-#line 7
-category c718;
-#line 7
-category c719;
-#line 7
-category c720;
-#line 7
-category c721;
-#line 7
-category c722;
-#line 7
-category c723;
-#line 7
-category c724;
-#line 7
-category c725;
-#line 7
-category c726;
-#line 7
-category c727;
-#line 7
-category c728;
-#line 7
-category c729;
-#line 7
-category c730;
-#line 7
-category c731;
-#line 7
-category c732;
-#line 7
-category c733;
-#line 7
-category c734;
-#line 7
-category c735;
-#line 7
-category c736;
-#line 7
-category c737;
-#line 7
-category c738;
-#line 7
-category c739;
-#line 7
-category c740;
-#line 7
-category c741;
-#line 7
-category c742;
-#line 7
-category c743;
-#line 7
-category c744;
-#line 7
-category c745;
-#line 7
-category c746;
-#line 7
-category c747;
-#line 7
-category c748;
-#line 7
-category c749;
-#line 7
-category c750;
-#line 7
-category c751;
-#line 7
-category c752;
-#line 7
-category c753;
-#line 7
-category c754;
-#line 7
-category c755;
-#line 7
-category c756;
-#line 7
-category c757;
-#line 7
-category c758;
-#line 7
-category c759;
-#line 7
-category c760;
-#line 7
-category c761;
-#line 7
-category c762;
-#line 7
-category c763;
-#line 7
-category c764;
-#line 7
-category c765;
-#line 7
-category c766;
-#line 7
-category c767;
-#line 7
-category c768;
-#line 7
-category c769;
-#line 7
-category c770;
-#line 7
-category c771;
-#line 7
-category c772;
-#line 7
-category c773;
-#line 7
-category c774;
-#line 7
-category c775;
-#line 7
-category c776;
-#line 7
-category c777;
-#line 7
-category c778;
-#line 7
-category c779;
-#line 7
-category c780;
-#line 7
-category c781;
-#line 7
-category c782;
-#line 7
-category c783;
-#line 7
-category c784;
-#line 7
-category c785;
-#line 7
-category c786;
-#line 7
-category c787;
-#line 7
-category c788;
-#line 7
-category c789;
-#line 7
-category c790;
-#line 7
-category c791;
-#line 7
-category c792;
-#line 7
-category c793;
-#line 7
-category c794;
-#line 7
-category c795;
-#line 7
-category c796;
-#line 7
-category c797;
-#line 7
-category c798;
-#line 7
-category c799;
-#line 7
-category c800;
-#line 7
-category c801;
-#line 7
-category c802;
-#line 7
-category c803;
-#line 7
-category c804;
-#line 7
-category c805;
-#line 7
-category c806;
-#line 7
-category c807;
-#line 7
-category c808;
-#line 7
-category c809;
-#line 7
-category c810;
-#line 7
-category c811;
-#line 7
-category c812;
-#line 7
-category c813;
-#line 7
-category c814;
-#line 7
-category c815;
-#line 7
-category c816;
-#line 7
-category c817;
-#line 7
-category c818;
-#line 7
-category c819;
-#line 7
-category c820;
-#line 7
-category c821;
-#line 7
-category c822;
-#line 7
-category c823;
-#line 7
-category c824;
-#line 7
-category c825;
-#line 7
-category c826;
-#line 7
-category c827;
-#line 7
-category c828;
-#line 7
-category c829;
-#line 7
-category c830;
-#line 7
-category c831;
-#line 7
-category c832;
-#line 7
-category c833;
-#line 7
-category c834;
-#line 7
-category c835;
-#line 7
-category c836;
-#line 7
-category c837;
-#line 7
-category c838;
-#line 7
-category c839;
-#line 7
-category c840;
-#line 7
-category c841;
-#line 7
-category c842;
-#line 7
-category c843;
-#line 7
-category c844;
-#line 7
-category c845;
-#line 7
-category c846;
-#line 7
-category c847;
-#line 7
-category c848;
-#line 7
-category c849;
-#line 7
-category c850;
-#line 7
-category c851;
-#line 7
-category c852;
-#line 7
-category c853;
-#line 7
-category c854;
-#line 7
-category c855;
-#line 7
-category c856;
-#line 7
-category c857;
-#line 7
-category c858;
-#line 7
-category c859;
-#line 7
-category c860;
-#line 7
-category c861;
-#line 7
-category c862;
-#line 7
-category c863;
-#line 7
-category c864;
-#line 7
-category c865;
-#line 7
-category c866;
-#line 7
-category c867;
-#line 7
-category c868;
-#line 7
-category c869;
-#line 7
-category c870;
-#line 7
-category c871;
-#line 7
-category c872;
-#line 7
-category c873;
-#line 7
-category c874;
-#line 7
-category c875;
-#line 7
-category c876;
-#line 7
-category c877;
-#line 7
-category c878;
-#line 7
-category c879;
-#line 7
-category c880;
-#line 7
-category c881;
-#line 7
-category c882;
-#line 7
-category c883;
-#line 7
-category c884;
-#line 7
-category c885;
-#line 7
-category c886;
-#line 7
-category c887;
-#line 7
-category c888;
-#line 7
-category c889;
-#line 7
-category c890;
-#line 7
-category c891;
-#line 7
-category c892;
-#line 7
-category c893;
-#line 7
-category c894;
-#line 7
-category c895;
-#line 7
-category c896;
-#line 7
-category c897;
-#line 7
-category c898;
-#line 7
-category c899;
-#line 7
-category c900;
-#line 7
-category c901;
-#line 7
-category c902;
-#line 7
-category c903;
-#line 7
-category c904;
-#line 7
-category c905;
-#line 7
-category c906;
-#line 7
-category c907;
-#line 7
-category c908;
-#line 7
-category c909;
-#line 7
-category c910;
-#line 7
-category c911;
-#line 7
-category c912;
-#line 7
-category c913;
-#line 7
-category c914;
-#line 7
-category c915;
-#line 7
-category c916;
-#line 7
-category c917;
-#line 7
-category c918;
-#line 7
-category c919;
-#line 7
-category c920;
-#line 7
-category c921;
-#line 7
-category c922;
-#line 7
-category c923;
-#line 7
-category c924;
-#line 7
-category c925;
-#line 7
-category c926;
-#line 7
-category c927;
-#line 7
-category c928;
-#line 7
-category c929;
-#line 7
-category c930;
-#line 7
-category c931;
-#line 7
-category c932;
-#line 7
-category c933;
-#line 7
-category c934;
-#line 7
-category c935;
-#line 7
-category c936;
-#line 7
-category c937;
-#line 7
-category c938;
-#line 7
-category c939;
-#line 7
-category c940;
-#line 7
-category c941;
-#line 7
-category c942;
-#line 7
-category c943;
-#line 7
-category c944;
-#line 7
-category c945;
-#line 7
-category c946;
-#line 7
-category c947;
-#line 7
-category c948;
-#line 7
-category c949;
-#line 7
-category c950;
-#line 7
-category c951;
-#line 7
-category c952;
-#line 7
-category c953;
-#line 7
-category c954;
-#line 7
-category c955;
-#line 7
-category c956;
-#line 7
-category c957;
-#line 7
-category c958;
-#line 7
-category c959;
-#line 7
-category c960;
-#line 7
-category c961;
-#line 7
-category c962;
-#line 7
-category c963;
-#line 7
-category c964;
-#line 7
-category c965;
-#line 7
-category c966;
-#line 7
-category c967;
-#line 7
-category c968;
-#line 7
-category c969;
-#line 7
-category c970;
-#line 7
-category c971;
-#line 7
-category c972;
-#line 7
-category c973;
-#line 7
-category c974;
-#line 7
-category c975;
-#line 7
-category c976;
-#line 7
-category c977;
-#line 7
-category c978;
-#line 7
-category c979;
-#line 7
-category c980;
-#line 7
-category c981;
-#line 7
-category c982;
-#line 7
-category c983;
-#line 7
-category c984;
-#line 7
-category c985;
-#line 7
-category c986;
-#line 7
-category c987;
-#line 7
-category c988;
-#line 7
-category c989;
-#line 7
-category c990;
-#line 7
-category c991;
-#line 7
-category c992;
-#line 7
-category c993;
-#line 7
-category c994;
-#line 7
-category c995;
-#line 7
-category c996;
-#line 7
-category c997;
-#line 7
-category c998;
-#line 7
-category c999;
-#line 7
-category c1000;
-#line 7
-category c1001;
-#line 7
-category c1002;
-#line 7
-category c1003;
-#line 7
-category c1004;
-#line 7
-category c1005;
-#line 7
-category c1006;
-#line 7
-category c1007;
-#line 7
-category c1008;
-#line 7
-category c1009;
-#line 7
-category c1010;
-#line 7
-category c1011;
-#line 7
-category c1012;
-#line 7
-category c1013;
-#line 7
-category c1014;
-#line 7
-category c1015;
-#line 7
-category c1016;
-#line 7
-category c1017;
-#line 7
-category c1018;
-#line 7
-category c1019;
-#line 7
-category c1020;
-#line 7
-category c1021;
-#line 7
-category c1022;
-#line 7
-category c1023;
-#line 7
-
-
-# Generate level definitions for each sensitivity and category.
-level s0:c0.c1023;
-#line 10
-
-
-
-#################################################
-# MLS policy constraints
-#
-
-#
-# Process constraints
-#
-
-# Process transition:  Require equivalence unless the subject is trusted.
-mlsconstrain process { transition dyntransition }
-	     ((h1 eq h2 and l1 eq l2) or t1 == mlstrustedsubject);
-
-# Process read operations: No read up unless trusted.
-mlsconstrain process { getsched getsession getpgid getcap getattr ptrace share }
-	     (l1 dom l2 or t1 == mlstrustedsubject);
-
-# Process write operations:  No write down unless trusted.
-mlsconstrain process { sigkill sigstop signal setsched setpgid setcap setrlimit ptrace share }
-	     (l1 domby l2 or t1 == mlstrustedsubject);
-
-#
-# Socket constraints
-#
-
-# Create/relabel operations:  Subject must be equivalent to object unless
-# the subject is trusted.  Sockets inherit the range of their creator.
-mlsconstrain { socket tcp_socket udp_socket rawip_socket netlink_socket packet_socket key_socket unix_stream_socket unix_dgram_socket appletalk_socket netlink_route_socket netlink_firewall_socket netlink_tcpdiag_socket netlink_nflog_socket netlink_xfrm_socket netlink_selinux_socket netlink_audit_socket netlink_ip6fw_socket netlink_dnrt_socket netlink_kobject_uevent_socket tun_socket } { create relabelfrom relabelto }
-	     ((h1 eq h2 and l1 eq l2) or t1 == mlstrustedsubject);
-
-# Datagram send: Sender must be dominated by receiver unless one of them is
-# trusted.
-mlsconstrain unix_dgram_socket { sendto }
-	     (l1 domby l2 or t1 == mlstrustedsubject or t2 == mlstrustedsubject);
-
-# Stream connect:  Client must be equivalent to server unless one of them
-# is trusted.
-mlsconstrain unix_stream_socket { connectto }
-	     (l1 eq l2 or t1 == mlstrustedsubject or t2 == mlstrustedsubject);
-
-#
-# Directory/file constraints
-#
-
-# Create/relabel operations:  Subject must be equivalent to object unless
-# the subject is trusted. Also, files should always be single-level.
-# Do NOT exempt mlstrustedobject types from this constraint.
-mlsconstrain { dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } } { create relabelfrom relabelto }
-	     (l2 eq h2 and (l1 eq l2 or t1 == mlstrustedsubject));
-
-#
-# Constraints for app data files only.
-#
-
-# Only constrain open, not read/write.
-# Also constrain other forms of manipulation, e.g. chmod/chown, unlink, rename, etc.
-# Subject must be equivalent to object unless the subject is trusted.
-mlsconstrain dir { open search setattr rename add_name remove_name reparent rmdir }
-	     (t2 != app_data_file or l1 eq l2 or t1 == mlstrustedsubject);
-mlsconstrain { file lnk_file sock_file } { open setattr unlink link rename }
-	     (t2 != app_data_file or l1 eq l2 or t1 == mlstrustedsubject);
-
-#
-# Constraints for file types other than app data files.
-#
-
-# Read operations: Subject must dominate object unless the subject
-# or the object is trusted.
-mlsconstrain dir { read getattr search }
-	     (t2 == app_data_file or l1 dom l2 or t1 == mlstrustedsubject or t2 == mlstrustedobject);
-
-mlsconstrain { file lnk_file sock_file chr_file blk_file } { read getattr execute }
-	     (t2 == app_data_file or l1 dom l2 or t1 == mlstrustedsubject or t2 == mlstrustedobject);
-
-# Write operations: Subject must be dominated by the object unless the
-# subject or the object is trusted.
-mlsconstrain dir { write setattr rename add_name remove_name reparent rmdir }
-	     (t2 == app_data_file or l1 domby l2 or t1 == mlstrustedsubject or t2 == mlstrustedobject);
-
-mlsconstrain { file lnk_file sock_file chr_file blk_file } { write setattr append unlink link rename }
-	     (t2 == app_data_file or l1 domby l2 or t1 == mlstrustedsubject or t2 == mlstrustedobject);
-
-# Special case for FIFOs.
-# These can be unnamed pipes, in which case they will be labeled with the
-# creating process' label. Thus we also have an exemption when the "object"
-# is a MLS trusted subject and can receive data at any level.
-mlsconstrain fifo_file { read getattr }
-	     (l1 dom l2 or t1 == mlstrustedsubject or t2 == mlstrustedobject or t2 == mlstrustedsubject);
-
-mlsconstrain fifo_file { write setattr append unlink link rename }
-	     (l1 domby l2 or t1 == mlstrustedsubject or t2 == mlstrustedobject or t2 == mlstrustedsubject);
-
-#
-# IPC constraints
-#
-
-# Create/destroy: equivalence or trusted.
-mlsconstrain { sem msgq shm ipc } { create destroy }
-	     (l2 eq h2 and (l1 eq l2 or t1 == mlstrustedsubject));
-
-# Read ops: No read up unless trusted.
-mlsconstrain { sem msgq shm ipc } { getattr read associate unix_read }
-	     (l1 dom l2 or t1 == mlstrustedsubject);
-
-# Write ops: No write down unless trusted.
-mlsconstrain { sem msgq shm ipc } { write unix_write }
-	     (l1 domby l2 or t1 == mlstrustedsubject);
-
-#
-# Binder IPC constraints
-#
-# Presently commented out, as apps are expected to call one another.
-# This would only make sense if apps were assigned categories
-# based on allowable communications rather than per-app categories.
-#mlsconstrain binder call
-#	(l1 eq l2 or t1 == mlstrustedsubject or t2 == mlstrustedsubject);
-#line 1 "external/sepolicy/policy_capabilities"
-# Enable new networking controls.
-policycap network_peer_controls;
-
-# Enable open permission check.
-policycap open_perms;
-#line 1 "external/sepolicy/te_macros"
-#####################################
-# domain_trans(olddomain, type, newdomain)
-# Allow a transition from olddomain to newdomain
-# upon executing a file labeled with type.
-# This only allows the transition; it does not
-# cause it to occur automatically - use domain_auto_trans
-# if that is what you want.
-#
-#line 21
-
-
-#####################################
-# domain_auto_trans(olddomain, type, newdomain)
-# Automatically transition from olddomain to newdomain
-# upon executing a file labeled with type.
-#
-#line 33
-
-
-#####################################
-# file_type_trans(domain, dir_type, file_type)
-# Allow domain to create a file labeled file_type in a
-# directory labeled dir_type.
-# This only allows the transition; it does not
-# cause it to occur automatically - use file_type_auto_trans
-# if that is what you want.
-#
-#line 49
-
-
-#####################################
-# file_type_auto_trans(domain, dir_type, file_type)
-# Automatically label new files with file_type when
-# they are created by domain in directories labeled dir_type.
-#
-#line 62
-
-
-#####################################
-# r_dir_file(domain, type)
-# Allow the specified domain to read directories, files
-# and symbolic links of the specified type.
-#line 71
-
-
-#####################################
-# unconfined_domain(domain)
-# Allow the specified domain to perform more privileged operations
-# than would be typically allowed. Please see the comments at the
-# top of unconfined.te.
-#
-#line 82
-
-
-#####################################
-# tmpfs_domain(domain)
-# Define and allow access to a unique type for
-# this domain when creating tmpfs / shmem / ashmem files.
-#line 92
-
-
-#####################################
-# init_daemon_domain(domain)
-# Set up a transition from init to the daemon domain
-# upon executing its binary.
-#line 101
-
-
-#####################################
-# app_domain(domain)
-# Allow a base set of permissions required for all apps.
-#line 112
-
-
-#####################################
-# relabelto_domain(domain)
-# Allows this domain to use the relabelto permission
-#line 119
-
-
-#####################################
-# platform_app_domain(domain)
-# Allow permissions specific to platform apps.
-#line 127
-
-
-#####################################
-# net_domain(domain)
-# Allow a base set of permissions required for network access.
-#line 134
-
-
-#####################################
-# bluetooth_domain(domain)
-# Allow a base set of permissions required for bluetooth access.
-#line 141
-
-
-#####################################
-# unix_socket_connect(clientdomain, socket, serverdomain)
-# Allow a local socket connection from clientdomain via
-# socket to serverdomain.
-#line 150
-
-
-#####################################
-# unix_socket_send(clientdomain, socket, serverdomain)
-# Allow a local socket send from clientdomain via
-# socket to serverdomain.
-#line 159
-
-
-#####################################
-# binder_use(domain)
-# Allow domain to use Binder IPC.
-#line 169
-
-
-#####################################
-# binder_call(clientdomain, serverdomain)
-# Allow clientdomain to perform binder IPC to serverdomain.
-#line 181
-
-
-#####################################
-# binder_service(domain)
-# Mark a domain as being a Binder service domain.
-# Used to allow binder IPC to the various system services.
-#line 189
-
-
-#####################################
-# selinux_check_access(domain)
-# Allow domain to check SELinux permissions via selinuxfs.
-#line 199
-
-
-#####################################
-# selinux_check_context(domain)
-# Allow domain to check SELinux contexts via selinuxfs.
-#line 208
-
-
-#####################################
-# selinux_getenforce(domain)
-# Allow domain to check whether SELinux is enforcing.
-#line 216
-
-
-#####################################
-# selinux_setenforce(domain)
-# Allow domain to set SELinux to enforcing.
-#line 225
-
-
-#####################################
-# selinux_setbool(domain)
-# Allow domain to set SELinux booleans.
-#line 234
-
-
-#####################################
-# security_access_policy(domain)
-# Read only access to all policy files and
-# selinuxfs
-#line 248
-
-
-#####################################
-# selinux_manage_policy(domain)
-# Ability to manage policy files and
-# trigger runtime reload.
-#line 261
-
-
-#####################################
-# mmac_manage_policy(domain)
-# Ability to manage mmac policy files,
-# trigger runtime reload, change
-# mmac enforcing mode and access logcat.
-#line 274
-
-
-#####################################
-# access_kmsg(domain)
-# Ability to read from kernel logs
-# and execute the klogctl syscall
-# in a non destructive manner. See
-# man 2 klogctl
-#line 284
-
-
-#####################################
-# write_klog(domain)
-# Ability to write to kernel log via
-# klog_write()
-# See system/core/libcutil/klog.c
-#line 295
-
-
-#####################################
-# create_pty(domain)
-# Allow domain to create and use a pty, isolated from any other domain ptys.
-#line 309
-
-
-#####################################
-# Non system_app application set
-#
-
-
-#####################################
-# Userdebug or eng builds
-# SELinux rules which apply only to userdebug or eng builds
-#
-
-
-#####################################
-# permissive_or_unconfined
-# Returns "permissive $1" if FORCE_PERMISSIVE_TO_UNCONFINED is false,
-# and "unconfined($1)" otherwise.
-#
-# This is used for experimental domains, where we want to ensure
-# the domain is unconfined+enforcing once new SELinux policy development
-# has ceased.
-#
-
-
-#####################################
-# write_logd(domain)
-# Ability to write to android log
-# daemon via sockets
-#line 345
-
-
-#####################################
-# read_logd(domain)
-# Ability to read from android
-# log daemon via sockets
-#line 353
-
-
-#####################################
-# control_logd(domain)
-# Ability to control
-# android log daemon via sockets
-#line 363
-
-#line 1 "external/sepolicy/attributes"
-######################################
-# Attribute declarations
-#
-
-# All types used for devices.
-attribute dev_type;
-
-# All types used for processes.
-attribute domain;
-
-# All types used for filesystems.
-attribute fs_type;
-
-# All types used for files that can exist on a labeled fs.
-# Do not use for pseudo file types.
-attribute file_type;
-
-# All types used for domain entry points.
-attribute exec_type;
-
-# All types used for /data files.
-attribute data_file_type;
-
-# All types use for sysfs files.
-attribute sysfs_type;
-
-# Attribute used for all sdcards
-attribute sdcard_type;
-
-# All types used for nodes/hosts.
-attribute node_type;
-
-# All types used for network interfaces.
-attribute netif_type;
-
-# All types used for network ports.
-attribute port_type;
-
-# All types used for property service
-attribute property_type;
-
-# All domains that can override MLS restrictions.
-# i.e. processes that can read up and write down.
-attribute mlstrustedsubject;
-
-# All types that can override MLS restrictions.
-# i.e. files that can be read by lower and written by higher
-attribute mlstrustedobject;
-
-# Domains that are allowed all permissions ("unconfined").
-attribute unconfineddomain;
-
-# All domains used for shells.
-attribute shelldomain;
-
-# All domains used for apps.
-attribute appdomain;
-
-# All domains used for apps with network access.
-attribute netdomain;
-
-# All domains used for apps with bluetooth access.
-attribute bluetoothdomain;
-
-# All domains used for binder service domains.
-attribute binderservicedomain;
-
-# Allow domains used for platform (signed by build key) apps.
-attribute platformappdomain;
-
-# All domains which are allowed the "relabelto" permission
-attribute relabeltodomain;
-#line 1 "external/sepolicy/adbd.te"
-# adbd seclabel is specified in init.rc since
-# it lives in the rootfs and has no unique file type.
-type adbd, domain;
-
-#line 7
-
-
-
-#line 9
-# Allow the necessary permissions.
-#line 9
-
-#line 9
-# Old domain may exec the file and transition to the new domain.
-#line 9
-allow adbd shell_exec:file { getattr open read execute };
-#line 9
-allow adbd shell:process transition;
-#line 9
-# New domain is entered by executing the file.
-#line 9
-allow shell shell_exec:file { entrypoint read execute };
-#line 9
-# New domain can send SIGCHLD to its caller.
-#line 9
-allow shell adbd:process sigchld;
-#line 9
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 9
-dontaudit adbd shell:process noatsecure;
-#line 9
-# XXX dontaudit candidate but requires further study.
-#line 9
-allow adbd shell:process { siginh rlimitinh };
-#line 9
-
-#line 9
-# Make the transition occur by default.
-#line 9
-type_transition adbd shell_exec:process shell;
-#line 9
-
-# this is an entrypoint
-allow adbd rootfs:file entrypoint;
-
-# Do not sanitize the environment or open fds of the shell.
-allow adbd shell:process noatsecure;
-
-# Set UID and GID to shell.  Set supplementary groups.
-allow adbd self:capability { setuid setgid };
-
-# Drop capabilities from bounding set on user builds.
-allow adbd self:capability setpcap;
-
-# Create and use network sockets.
-
-#line 23
-typeattribute adbd netdomain;
-#line 23
-
-
-# Access /dev/android_adb.
-allow adbd adb_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# On emulator, access /dev/qemu*.
-allow adbd qemu_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Use a pseudo tty.
-allow adbd devpts:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# adb push/pull /data/local/tmp.
-allow adbd shell_data_file:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-allow adbd shell_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# adb push/pull sdcard.
-allow adbd sdcard_type:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow adbd sdcard_type:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# Set service.adb.*, sys.powerctl properties.
-
-#line 43
-allow adbd property_socket:sock_file write;
-#line 43
-allow adbd init:unix_stream_socket connectto;
-#line 43
-
-allow adbd shell_prop:property_service set;
-allow adbd powerctl_prop:property_service set;
-
-# XXX Run /system/bin/vdc to connect to vold.  Run in a separate domain?
-# Also covers running /system/bin/bu.
-allow adbd system_file:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-
-#line 50
-allow adbd vold_socket:sock_file write;
-#line 50
-allow adbd vold:unix_stream_socket connectto;
-#line 50
-
-
-# Perform binder IPC to surfaceflinger (screencap)
-# XXX Run screencap in a separate domain?
-
-#line 54
-# Call the servicemanager and transfer references to it.
-#line 54
-allow adbd servicemanager:binder { call transfer };
-#line 54
-# rw access to /dev/binder and /dev/ashmem is presently granted to
-#line 54
-# all domains in domain.te.
-#line 54
-
-
-#line 55
-# Call the server domain and optionally transfer references to it.
-#line 55
-allow adbd surfaceflinger:binder { call transfer };
-#line 55
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 55
-allow surfaceflinger adbd:binder transfer;
-#line 55
-# Receive and use open files from the server.
-#line 55
-allow adbd surfaceflinger:fd use;
-#line 55
-
-
-# Read /data/misc/adb/adb_keys.
-allow adbd adb_keys_file:dir search;
-allow adbd adb_keys_file:file { getattr open read ioctl lock };
-
-# Allow access in case /data/misc/adb still has the old type.
-allow adbd system_data_file:dir search;
-allow adbd system_data_file:file { getattr open read ioctl lock };
-
-# ndk-gdb invokes adb forward to forward the gdbserver socket.
-allow adbd app_data_file:dir search;
-allow adbd app_data_file:sock_file write;
-allow adbd appdomain:unix_stream_socket connectto;
-
-# ndk-gdb invokes adb pull of app_process, linker, and libc.so.
-allow adbd zygote_exec:file { getattr open read ioctl lock };
-allow adbd system_file:file { getattr open read ioctl lock };
-#line 1 "external/sepolicy/app.te"
-###
-### Domain for all zygote spawned apps
-###
-### This file is the base policy for all zygote spawned apps.
-### Other policy files, such as isolated_app.te, untrusted_app.te, etc
-### extend from this policy. Only policies which should apply to ALL
-### zygote spawned apps should be added here.
-###
-
-# Dalvik Compiler JIT Mapping.
-allow appdomain self:process execmem;
-allow appdomain ashmem_device:chr_file execute;
-
-# Allow apps to connect to the keystore
-
-#line 15
-allow appdomain keystore_socket:sock_file write;
-#line 15
-allow appdomain keystore:unix_stream_socket connectto;
-#line 15
-
-
-# Receive and use open file descriptors inherited from zygote.
-allow appdomain zygote:fd use;
-
-# gdbserver for ndk-gdb reads the zygote.
-allow appdomain zygote_exec:file { getattr open read ioctl lock };
-
-# gdbserver for ndk-gdb ptrace attaches to app process.
-allow appdomain self:process ptrace;
-
-# Read system properties managed by zygote.
-allow appdomain zygote_tmpfs:file read;
-
-# Notify zygote of death;
-allow appdomain zygote:process sigchld;
-
-# Notify shell and adbd of death when spawned via runas for ndk-gdb.
-allow appdomain shell:process sigchld;
-allow appdomain adbd:process sigchld;
-
-# child shell or gdbserver pty access for runas.
-allow appdomain devpts:chr_file { getattr read write ioctl };
-
-# Communicate with system_server.
-allow appdomain system_server:fifo_file { { getattr open read ioctl lock } { open append write } };
-allow appdomain system_server:unix_stream_socket { read write setopt };
-
-#line 42
-# Call the server domain and optionally transfer references to it.
-#line 42
-allow appdomain system_server:binder { call transfer };
-#line 42
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 42
-allow system_server appdomain:binder transfer;
-#line 42
-# Receive and use open files from the server.
-#line 42
-allow appdomain system_server:fd use;
-#line 42
-
-
-# Communication with other apps via fifos
-allow appdomain appdomain:fifo_file { { getattr open read ioctl lock } { open append write } };
-
-# Communicate with surfaceflinger.
-allow appdomain surfaceflinger:unix_stream_socket { read write setopt };
-
-#line 49
-# Call the server domain and optionally transfer references to it.
-#line 49
-allow appdomain surfaceflinger:binder { call transfer };
-#line 49
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 49
-allow surfaceflinger appdomain:binder transfer;
-#line 49
-# Receive and use open files from the server.
-#line 49
-allow appdomain surfaceflinger:fd use;
-#line 49
-
-
-# App sandbox file accesses.
-allow appdomain app_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow appdomain app_data_file:{ file lnk_file sock_file fifo_file } { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# Read/write data files created by the platform apps if they
-# were passed to the app via binder or local IPC.  Do not allow open.
-allow appdomain platform_app_data_file:file { getattr read write };
-
-# lib subdirectory of /data/data dir is system-owned.
-allow appdomain system_data_file:dir { open getattr read search ioctl };
-allow appdomain system_data_file:file { execute execute_no_trans open };
-
-# Execute the shell or other system executables.
-allow appdomain shell_exec:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-allow appdomain system_file:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-
-# Read/write wallpaper file (opened by system).
-allow appdomain wallpaper_file:file { getattr read write };
-
-# Write to /data/anr/traces.txt.
-allow appdomain anr_data_file:dir search;
-allow appdomain anr_data_file:file { open append };
-
-# Allow apps to send dump information to dumpstate
-allow appdomain dumpstate:fd use;
-allow appdomain dumpstate:unix_stream_socket { read write getopt getattr };
-allow appdomain shell_data_file:file { write getattr };
-
-# Write to /proc/net/xt_qtaguid/ctrl file.
-allow appdomain qtaguid_proc:file { { getattr open read ioctl lock } { open append write } };
-# Everybody can read the xt_qtaguid resource tracking misc dev.
-# So allow all apps to read from /dev/xt_qtaguid.
-allow appdomain qtaguid_device:chr_file { getattr open read ioctl lock };
-
-# Grant GPU access to all processes started by Zygote.
-# They need that to render the standard UI.
-allow appdomain gpu_device:chr_file { { { getattr open read ioctl lock } { open append write } } execute };
-
-# Use the Binder.
-
-#line 90
-# Call the servicemanager and transfer references to it.
-#line 90
-allow appdomain servicemanager:binder { call transfer };
-#line 90
-# rw access to /dev/binder and /dev/ashmem is presently granted to
-#line 90
-# all domains in domain.te.
-#line 90
-
-# Perform binder IPC to binder services.
-
-#line 92
-# Call the server domain and optionally transfer references to it.
-#line 92
-allow appdomain binderservicedomain:binder { call transfer };
-#line 92
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 92
-allow binderservicedomain appdomain:binder transfer;
-#line 92
-# Receive and use open files from the server.
-#line 92
-allow appdomain binderservicedomain:fd use;
-#line 92
-
-# Perform binder IPC to other apps.
-
-#line 94
-# Call the server domain and optionally transfer references to it.
-#line 94
-allow appdomain appdomain:binder { call transfer };
-#line 94
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 94
-allow appdomain appdomain:binder transfer;
-#line 94
-# Receive and use open files from the server.
-#line 94
-allow appdomain appdomain:fd use;
-#line 94
-
-
-# Appdomain interaction with isolated apps
-
-#line 97
-allow appdomain isolated_app:dir { open getattr read search ioctl };
-#line 97
-allow appdomain isolated_app:{ file lnk_file } { getattr open read ioctl lock };
-#line 97
-
-
-# Already connected, unnamed sockets being passed over some other IPC
-# hence no sock_file or connectto permission. This appears to be how
-# Chrome works, may need to be updated as more apps using isolated services
-# are examined.
-allow appdomain isolated_app:unix_stream_socket { read write };
-
-# Backup ability for every app. BMS opens and passes the fd
-# to any app that has backup ability. Hence, no open permissions here.
-allow appdomain backup_data_file:file { read write getattr };
-allow appdomain cache_backup_file:file { read write getattr };
-# Backup ability using 'adb backup'
-allow appdomain system_data_file:lnk_file getattr;
-
-# Allow all applications to read downloaded files
-allow appdomain download_file:dir search;
-allow appdomain download_file:file { getattr open read ioctl lock };
-
-# Allow applications to communicate with netd via /dev/socket/dnsproxyd
-# to do DNS resolution
-
-#line 118
-allow appdomain dnsproxyd_socket:sock_file write;
-#line 118
-allow appdomain netd:unix_stream_socket connectto;
-#line 118
-
-
-# Allow applications to communicate with drmserver over binder
-
-#line 121
-# Call the server domain and optionally transfer references to it.
-#line 121
-allow appdomain drmserver:binder { call transfer };
-#line 121
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 121
-allow drmserver appdomain:binder transfer;
-#line 121
-# Receive and use open files from the server.
-#line 121
-allow appdomain drmserver:fd use;
-#line 121
-
-
-# Allow applications to communicate with mediaserver over binder
-
-#line 124
-# Call the server domain and optionally transfer references to it.
-#line 124
-allow appdomain mediaserver:binder { call transfer };
-#line 124
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 124
-allow mediaserver appdomain:binder transfer;
-#line 124
-# Receive and use open files from the server.
-#line 124
-allow appdomain mediaserver:fd use;
-#line 124
-
-
-# Allow applications to make outbound tcp connections to any port
-allow appdomain port_type:tcp_socket name_connect;
-
-# Allow apps to see changes to the routing table.
-allow appdomain self:netlink_route_socket {
-    read
-    bind
-    create
-    nlmsg_read
-    ioctl
-    getattr
-    setattr
-    getopt
-    setopt
-    shutdown
-};
-
-# Allow apps to use rawip sockets. This is needed for apps which execute
-# /system/bin/ping, for example.
-allow appdomain self:rawip_socket { create { ioctl read getattr write setattr append bind connect getopt setopt shutdown } };
-
-# Allow apps to use the USB Accessory interface.
-# http://developer.android.com/guide/topics/connectivity/usb/accessory.html
-#
-# USB devices are first opened by the system server (USBDeviceManagerService)
-# and the file descriptor is passed to the right Activity via binder.
-allow appdomain usb_device:chr_file { read write getattr ioctl };
-allow appdomain usbaccessory_device:chr_file { read write getattr };
-
-# For art.
-allow appdomain dalvikcache_data_file:file execute;
-
-# For legacy unlabeled userdata on existing devices.
-# See discussion of Unlabeled files in domain.te for more information.
-allow appdomain unlabeled:file { getattr execute execute_no_trans };
-
-###
-### CTS-specific rules
-###
-
-# For cts/tools/device-setup/TestDeviceSetup/src/android/tests/getinfo/RootProcessScanner.java.
-# Reads /proc/pid/status and statm entries to check that
-# no unexpected root processes are running.
-# Also for cts/tests/tests/security/src/android/security/cts/VoldExploitTest.java
-# Reads /proc/pid/cmdline of vold.
-allow appdomain domain:dir { open read search getattr };
-allow appdomain domain:{ file lnk_file } { open read getattr };
-
-# For cts/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java.
-# testRunAsHasCorrectCapabilities
-allow appdomain runas_exec:file getattr;
-# Others are either allowed elsewhere or not desired.
-
-# For cts/tests/tests/security/src/android/security/cts/SELinuxTest.java
-# Check SELinux policy and contexts.
-
-#line 181
-allow appdomain selinuxfs:dir { open getattr read search ioctl };
-#line 181
-allow appdomain selinuxfs:file { { getattr open read ioctl lock } { open append write } };
-#line 181
-allow appdomain kernel:security compute_av;
-#line 181
-allow appdomain self:netlink_selinux_socket *;
-#line 181
-
-
-#line 182
-allow appdomain selinuxfs:dir { open getattr read search ioctl };
-#line 182
-allow appdomain selinuxfs:file { { getattr open read ioctl lock } { open append write } };
-#line 182
-allow appdomain kernel:security check_context;
-#line 182
-
-# Validate that each process is running in the correct security context.
-allow appdomain domain:process getattr;
-
-# logd access
-
-#line 187
-
-#line 187
-allow appdomain logdr_socket:sock_file write;
-#line 187
-allow appdomain logd:unix_stream_socket connectto;
-#line 187
-
-#line 187
-
-# application inherit logd write socket (urge is to deprecate this long term)
-allow appdomain zygote:unix_dgram_socket write;
-
-###
-### Neverallow rules
-###
-### These are things that Android apps should NEVER be able to do
-###
-
-# Superuser capabilities.
-# bluetooth requires net_admin.
-neverallow { appdomain -unconfineddomain -bluetooth } self:capability *;
-neverallow { appdomain -unconfineddomain } self:capability2 *;
-
-# Block device access.
-neverallow { appdomain -unconfineddomain } dev_type:blk_file { read write };
-
-# Access to any of the following character devices.
-neverallow { appdomain -unconfineddomain } {
-    audio_device
-    camera_device
-    dm_device
-    radio_device
-    gps_device
-    rpmsg_device
-}:chr_file { read write };
-
-# Note: Try expanding list of app domains in the future.
-neverallow { untrusted_app isolated_app shell -unconfineddomain }
-    graphics_device:chr_file { read write };
-
-neverallow { appdomain -nfc -unconfineddomain } nfc_device:chr_file
-    { read write };
-neverallow { appdomain -bluetooth -unconfineddomain } hci_attach_dev:chr_file
-    { read write };
-neverallow { appdomain -unconfineddomain } tee_device:chr_file { read write };
-
-# Set SELinux enforcing mode, booleans or any other SELinux settings.
-neverallow { appdomain -unconfineddomain } kernel:security
-    { setenforce setbool setsecparam setcheckreqprot };
-
-# Load security policy.
-neverallow appdomain kernel:security load_policy;
-
-# Privileged netlink socket interfaces.
-neverallow { appdomain -unconfineddomain }
-    self:{
-        netlink_socket
-        netlink_firewall_socket
-        netlink_tcpdiag_socket
-        netlink_nflog_socket
-        netlink_xfrm_socket
-        netlink_audit_socket
-        netlink_ip6fw_socket
-        netlink_dnrt_socket
-        netlink_kobject_uevent_socket
-    } *;
-
-# Sockets under /dev/socket that are not specifically typed.
-neverallow { appdomain -unconfineddomain } socket_device:sock_file write;
-
-# Unix domain sockets.
-neverallow { appdomain -unconfineddomain } adbd_socket:sock_file write;
-neverallow { appdomain -unconfineddomain } installd_socket:sock_file write;
-neverallow { appdomain -bluetooth -radio -shell -system_app -unconfineddomain }
-    property_socket:sock_file write;
-neverallow { appdomain -radio -unconfineddomain } rild_socket:sock_file write;
-neverallow { appdomain -unconfineddomain } vold_socket:sock_file write;
-neverallow { appdomain -unconfineddomain } zygote_socket:sock_file write;
-
-# ptrace access to non-app domains.
-neverallow { appdomain -unconfineddomain } { domain -appdomain }:process ptrace;
-
-# Write access to /proc/pid entries for any non-app domain.
-neverallow { appdomain -unconfineddomain } { domain -appdomain }:file write;
-
-# signal access to non-app domains.
-# sigchld allowed for parent death notification.
-# signull allowed for kill(pid, 0) existence test.
-# All others prohibited.
-neverallow { appdomain -unconfineddomain } { domain -appdomain }:process
-    { sigkill sigstop signal };
-
-# Transition to a non-app domain.
-# Exception for the shell domain, can transition to runas, etc.
-neverallow { appdomain -shell -unconfineddomain } ~appdomain:process
-    { transition dyntransition };
-
-# Map low memory.
-# Note: Take to domain.te and apply to all domains in the future.
-neverallow { appdomain -unconfineddomain } self:memprotect mmap_zero;
-
-# Write to rootfs.
-neverallow { appdomain -unconfineddomain } rootfs:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-
-# Write to /system.
-neverallow { appdomain -unconfineddomain } system_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-
-# Write to entrypoint executables.
-neverallow { appdomain -unconfineddomain } exec_type:file
-    { create write setattr relabelfrom relabelto append unlink link rename };
-
-# Write to system-owned parts of /data.
-# This is the default type for anything under /data not otherwise
-# specified in file_contexts.  Define a different type for portions
-# that should be writable by apps.
-# Exception for system_app for Settings.
-neverallow { appdomain -unconfineddomain -system_app }
-    system_data_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-
-# Write to various other parts of /data.
-neverallow { appdomain -system_app -unconfineddomain }
-    security_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-neverallow { appdomain -unconfineddomain } drm_data_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-neverallow { appdomain -unconfineddomain } gps_data_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-neverallow { appdomain -platform_app -unconfineddomain }
-    apk_data_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-neverallow { appdomain -platform_app -unconfineddomain }
-    apk_tmp_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-neverallow { appdomain -platform_app -unconfineddomain }
-    apk_private_data_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-neverallow { appdomain -platform_app -unconfineddomain }
-    apk_private_tmp_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-neverallow { appdomain -shell -unconfineddomain }
-    shell_data_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create setattr relabelfrom relabelto append unlink link rename };
-neverallow { appdomain -bluetooth -unconfineddomain }
-    bluetooth_data_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-neverallow { appdomain -unconfineddomain }
-    keystore_data_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-neverallow { appdomain -unconfineddomain }
-    systemkeys_data_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-neverallow { appdomain -unconfineddomain }
-    wifi_data_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-neverallow { appdomain -unconfineddomain }
-    dhcp_data_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } }
-    { create write setattr relabelfrom relabelto append unlink link rename };
-
-# Access to factory files.
-neverallow { appdomain -unconfineddomain }
-    efs_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } } { read write };
-
-# Write to various pseudo file systems.
-neverallow { appdomain -bluetooth -nfc -unconfineddomain }
-    sysfs:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } } write;
-neverallow { appdomain -unconfineddomain }
-    proc:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } } write;
-
-# Access to syslog(2) or /proc/kmsg.
-neverallow { appdomain -system_app -unconfineddomain }
-    kernel:system { syslog_read syslog_mod syslog_console };
-
-# Ability to perform any filesystem operation other than statfs(2).
-# i.e. no mount(2), unmount(2), etc.
-neverallow { appdomain -unconfineddomain } fs_type:filesystem ~getattr;
-
-# Ability to set system properties.
-neverallow { appdomain -system_app -radio -shell -bluetooth -unconfineddomain }
-    property_type:property_service set;
-#line 1 "external/sepolicy/binderservicedomain.te"
-# Rules common to all binder service domains
-
-# Allow dumpstate to collect information from binder services
-allow binderservicedomain dumpstate:fd use;
-allow binderservicedomain dumpstate:unix_stream_socket { read write getopt getattr };
-allow binderservicedomain shell_data_file:file { getattr write };
-
-# Allow dumpsys to work from adb shell
-allow binderservicedomain devpts:chr_file { { getattr open read ioctl lock } { open append write } };
-#line 1 "external/sepolicy/bluetooth.te"
-# bluetooth subsystem
-type bluetooth, domain;
-
-#line 3
-typeattribute bluetooth appdomain;
-#line 3
-# Label ashmem objects with our own unique type.
-#line 3
-
-#line 3
-type bluetooth_tmpfs, file_type;
-#line 3
-type_transition bluetooth tmpfs:file bluetooth_tmpfs;
-#line 3
-allow bluetooth bluetooth_tmpfs:file { read write };
-#line 3
-
-#line 3
-# Map with PROT_EXEC.
-#line 3
-allow bluetooth bluetooth_tmpfs:file execute;
-#line 3
-
-
-# Data file accesses.
-allow bluetooth bluetooth_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow bluetooth bluetooth_data_file:{ file lnk_file sock_file fifo_file } { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# Socket creation under /data/misc/bluedroid.
-type_transition bluetooth bluetooth_data_file:sock_file bluetooth_socket;
-allow bluetooth bluetooth_socket:sock_file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# bluetooth factory file accesses.
-
-#line 14
-allow bluetooth bluetooth_efs_file:dir { open getattr read search ioctl };
-#line 14
-allow bluetooth bluetooth_efs_file:{ file lnk_file } { getattr open read ioctl lock };
-#line 14
-
-
-# Device accesses.
-allow bluetooth { tun_device uhid_device hci_attach_dev }:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Other domains that can create and use bluetooth sockets.
-# SELinux does not presently define a specific socket class for
-# bluetooth sockets, nor does it distinguish among the bluetooth protocols.
-allow bluetoothdomain self:socket *;
-
-# sysfs access.
-allow bluetooth sysfs_bluetooth_writable:file { { getattr open read ioctl lock } { open append write } };
-allow bluetooth self:capability net_admin;
-
-# Allow clients to use a socket provided by the bluetooth app.
-allow bluetoothdomain bluetooth:unix_stream_socket { read write shutdown };
-
-# tethering
-allow bluetooth self:{ tun_socket udp_socket } { ioctl create };
-allow bluetooth efs_file:dir search;
-
-# Talk to init over the property socket.
-
-#line 36
-allow bluetooth property_socket:sock_file write;
-#line 36
-allow bluetooth init:unix_stream_socket connectto;
-#line 36
-
-
-# proc access.
-allow bluetooth proc_bluetooth_writable:file { { getattr open read ioctl lock } { open append write } };
-
-# bluetooth file transfers
-allow bluetooth sdcard_internal:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow bluetooth sdcard_internal:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# Allow reading of media_rw_data_file file descriptors
-# passed to bluetooth
-allow bluetooth media_rw_data_file:file { read getattr };
-
-# Allow write access to bluetooth specific properties
-allow bluetooth bluetooth_prop:property_service set;
-
-###
-### Neverallow rules
-###
-### These are things that the bluetooth app should NEVER be able to do
-###
-
-# Superuser capabilities.
-# bluetooth requires net_admin.
-neverallow { bluetooth -unconfineddomain } self:capability ~net_admin;
-#line 1 "external/sepolicy/bootanim.te"
-# bootanimation oneshot service
-type bootanim, domain;
-type bootanim_exec, exec_type, file_type;
-
-
-#line 5
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow init bootanim_exec:file { getattr open read execute };
-#line 5
-allow init bootanim:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow bootanim bootanim_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow bootanim init:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit init bootanim:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow init bootanim:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition init bootanim_exec:process bootanim;
-#line 5
-
-#line 5
-
-#line 5
-type bootanim_tmpfs, file_type;
-#line 5
-type_transition bootanim tmpfs:file bootanim_tmpfs;
-#line 5
-allow bootanim bootanim_tmpfs:file { read write };
-#line 5
-
-#line 5
-
-
-
-#line 7
-# Call the servicemanager and transfer references to it.
-#line 7
-allow bootanim servicemanager:binder { call transfer };
-#line 7
-# rw access to /dev/binder and /dev/ashmem is presently granted to
-#line 7
-# all domains in domain.te.
-#line 7
-
-
-#line 8
-# Call the server domain and optionally transfer references to it.
-#line 8
-allow bootanim surfaceflinger:binder { call transfer };
-#line 8
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 8
-allow surfaceflinger bootanim:binder transfer;
-#line 8
-# Receive and use open files from the server.
-#line 8
-allow bootanim surfaceflinger:fd use;
-#line 8
-
-
-allow bootanim gpu_device:chr_file { { getattr open read ioctl lock } { open append write } };
-#line 1 "external/sepolicy/clatd.te"
-# 464xlat daemon
-type clatd, domain;
-
-#line 3
-typeattribute clatd mlstrustedsubject;
-#line 3
-typeattribute clatd unconfineddomain;
-#line 3
-
-type clatd_exec, exec_type, file_type;
-
-
-#line 6
-
-#line 6
-# Allow the necessary permissions.
-#line 6
-
-#line 6
-# Old domain may exec the file and transition to the new domain.
-#line 6
-allow init clatd_exec:file { getattr open read execute };
-#line 6
-allow init clatd:process transition;
-#line 6
-# New domain is entered by executing the file.
-#line 6
-allow clatd clatd_exec:file { entrypoint read execute };
-#line 6
-# New domain can send SIGCHLD to its caller.
-#line 6
-allow clatd init:process sigchld;
-#line 6
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 6
-dontaudit init clatd:process noatsecure;
-#line 6
-# XXX dontaudit candidate but requires further study.
-#line 6
-allow init clatd:process { siginh rlimitinh };
-#line 6
-
-#line 6
-# Make the transition occur by default.
-#line 6
-type_transition init clatd_exec:process clatd;
-#line 6
-
-#line 6
-
-#line 6
-type clatd_tmpfs, file_type;
-#line 6
-type_transition clatd tmpfs:file clatd_tmpfs;
-#line 6
-allow clatd clatd_tmpfs:file { read write };
-#line 6
-
-#line 6
-
-
-#line 7
-typeattribute clatd netdomain;
-#line 7
-
-#line 1 "external/sepolicy/debuggerd.te"
-# debugger interface
-type debuggerd, domain;
-type debuggerd_exec, exec_type, file_type;
-
-
-#line 5
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow init debuggerd_exec:file { getattr open read execute };
-#line 5
-allow init debuggerd:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow debuggerd debuggerd_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow debuggerd init:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit init debuggerd:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow init debuggerd:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition init debuggerd_exec:process debuggerd;
-#line 5
-
-#line 5
-
-#line 5
-type debuggerd_tmpfs, file_type;
-#line 5
-type_transition debuggerd tmpfs:file debuggerd_tmpfs;
-#line 5
-allow debuggerd debuggerd_tmpfs:file { read write };
-#line 5
-
-#line 5
-
-typeattribute debuggerd mlstrustedsubject;
-allow debuggerd self:capability { dac_override sys_ptrace chown kill fowner };
-allow debuggerd self:capability2 { syslog };
-allow debuggerd domain:dir { open getattr read search ioctl };
-allow debuggerd domain:file { getattr open read ioctl lock };
-allow debuggerd { domain -init -ueventd -watchdogd -healthd -adbd }:process ptrace;
-
-#line 12
-allow debuggerd security_file:dir { open getattr read search ioctl };
-#line 12
-allow debuggerd security_file:file { getattr open read ioctl lock };
-#line 12
-allow debuggerd security_file:lnk_file { getattr open read ioctl lock };
-#line 12
-allow debuggerd selinuxfs:dir { open getattr read search ioctl };
-#line 12
-allow debuggerd selinuxfs:file { getattr open read ioctl lock };
-#line 12
-allow debuggerd rootfs:dir { open getattr read search ioctl };
-#line 12
-allow debuggerd rootfs:file { getattr open read ioctl lock };
-#line 12
-
-allow debuggerd system_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow debuggerd system_data_file:dir relabelfrom;
-
-#line 15
-typeattribute debuggerd relabeltodomain;
-#line 15
-
-allow debuggerd tombstone_data_file:dir relabelto;
-allow debuggerd tombstone_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow debuggerd tombstone_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow debuggerd domain:process { sigstop signal };
-allow debuggerd exec_type:file { getattr open read ioctl lock };
-# Access app library
-allow debuggerd system_data_file:file open;
-
-# Connect to system_server via /data/system/ndebugsocket.
-
-#line 25
-allow debuggerd system_ndebug_socket:sock_file write;
-#line 25
-allow debuggerd system_server:unix_stream_socket connectto;
-#line 25
-
-
-#line 30
-
-
-# logd access
-
-#line 33
-
-#line 33
-allow debuggerd logdr_socket:sock_file write;
-#line 33
-allow debuggerd logd:unix_stream_socket connectto;
-#line 33
-
-#line 33
-
-#line 1 "external/sepolicy/device.te"
-# Device types
-type device, dev_type, fs_type;
-type alarm_device, dev_type, mlstrustedobject;
-type adb_device, dev_type;
-type ashmem_device, dev_type, mlstrustedobject;
-type audio_device, dev_type;
-type binder_device, dev_type, mlstrustedobject;
-type block_device, dev_type;
-type camera_device, dev_type;
-type dm_device, dev_type;
-type loop_device, dev_type;
-type radio_device, dev_type;
-type ram_device, dev_type;
-type console_device, dev_type;
-type cpuctl_device, dev_type;
-type fscklogs, dev_type;
-type full_device, dev_type;
-# GPU (used by most UI apps)
-type gpu_device, dev_type, mlstrustedobject;
-type graphics_device, dev_type;
-type hw_random_device, dev_type;
-type input_device, dev_type;
-type kmem_device, dev_type;
-type log_device, dev_type, mlstrustedobject;
-type mtd_device, dev_type;
-type mtp_device, dev_type, mlstrustedobject;
-type nfc_device, dev_type;
-type ptmx_device, dev_type, mlstrustedobject;
-type qemu_device, dev_type;
-type kmsg_device, dev_type;
-type null_device, dev_type, mlstrustedobject;
-type random_device, dev_type;
-type sensors_device, dev_type;
-type serial_device, dev_type;
-type socket_device, dev_type;
-type owntty_device, dev_type, mlstrustedobject;
-type tty_device, dev_type;
-type urandom_device, dev_type;
-type video_device, dev_type;
-type vcs_device, dev_type;
-type zero_device, dev_type;
-type fuse_device, dev_type;
-type iio_device, dev_type;
-type ion_device, dev_type, mlstrustedobject;
-type gps_device, dev_type;
-type qtaguid_device, dev_type;
-type watchdog_device, dev_type;
-type uhid_device, dev_type;
-type tun_device, dev_type, mlstrustedobject;
-type usbaccessory_device, dev_type;
-type usb_device, dev_type;
-type klog_device, dev_type;
-type properties_device, dev_type;
-
-# All devices have a uart for the hci
-# attach service. The uart dev node
-# varies per device. This type
-# is used in per device policy
-type hci_attach_dev, dev_type;
-
-# All devices have a rpmsg device for
-# achieving remoteproc and rpmsg modules
-type rpmsg_device, dev_type;
-
-# Partition layout block device
-type root_block_device, dev_type;
-#line 1 "external/sepolicy/dhcp.te"
-type dhcp, domain;
-
-#line 2
-typeattribute dhcp mlstrustedsubject;
-#line 2
-typeattribute dhcp unconfineddomain;
-#line 2
-
-type dhcp_exec, exec_type, file_type;
-type dhcp_data_file, file_type, data_file_type;
-
-
-#line 6
-
-#line 6
-# Allow the necessary permissions.
-#line 6
-
-#line 6
-# Old domain may exec the file and transition to the new domain.
-#line 6
-allow init dhcp_exec:file { getattr open read execute };
-#line 6
-allow init dhcp:process transition;
-#line 6
-# New domain is entered by executing the file.
-#line 6
-allow dhcp dhcp_exec:file { entrypoint read execute };
-#line 6
-# New domain can send SIGCHLD to its caller.
-#line 6
-allow dhcp init:process sigchld;
-#line 6
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 6
-dontaudit init dhcp:process noatsecure;
-#line 6
-# XXX dontaudit candidate but requires further study.
-#line 6
-allow init dhcp:process { siginh rlimitinh };
-#line 6
-
-#line 6
-# Make the transition occur by default.
-#line 6
-type_transition init dhcp_exec:process dhcp;
-#line 6
-
-#line 6
-
-#line 6
-type dhcp_tmpfs, file_type;
-#line 6
-type_transition dhcp tmpfs:file dhcp_tmpfs;
-#line 6
-allow dhcp dhcp_tmpfs:file { read write };
-#line 6
-
-#line 6
-
-
-#line 7
-typeattribute dhcp netdomain;
-#line 7
-
-
-allow dhcp cgroup:dir { create write add_name };
-allow dhcp self:capability { setgid setuid net_admin net_raw net_bind_service };
-allow dhcp self:packet_socket { create { ioctl read getattr write setattr append bind connect getopt setopt shutdown } };
-allow dhcp self:netlink_route_socket { { create { ioctl read getattr write setattr append bind connect getopt setopt shutdown } } nlmsg_write };
-allow dhcp self:rawip_socket { create { ioctl read getattr write setattr append bind connect getopt setopt shutdown } };
-allow dhcp shell_exec:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-allow dhcp system_file:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-# For /proc/sys/net/ipv4/conf/*/promote_secondaries
-allow dhcp proc_net:file write;
-allow dhcp system_prop:property_service set ;
-
-#line 19
-allow dhcp property_socket:sock_file write;
-#line 19
-allow dhcp init:unix_stream_socket connectto;
-#line 19
-
-allow dhcp owntty_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-type_transition dhcp system_data_file:{ dir file } dhcp_data_file;
-allow dhcp dhcp_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow dhcp dhcp_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# PAN connections
-allow dhcp netd:fd use;
-allow dhcp netd:fifo_file { { getattr open read ioctl lock } { open append write } };
-allow dhcp netd:{ { udp_socket unix_dgram_socket } unix_stream_socket } { read write };
-allow dhcp netd:{ netlink_kobject_uevent_socket netlink_route_socket netlink_nflog_socket } { read write };
-#line 1 "external/sepolicy/dnsmasq.te"
-# DNS, DHCP services
-type dnsmasq, domain;
-
-#line 3
-typeattribute dnsmasq mlstrustedsubject;
-#line 3
-typeattribute dnsmasq unconfineddomain;
-#line 3
-
-type dnsmasq_exec, exec_type, file_type;
-
-allow dnsmasq self:capability { net_bind_service setgid setuid };
-allow dnsmasq self:tcp_socket { create { ioctl read getattr write setattr append bind connect getopt setopt shutdown } };
-
-allow dnsmasq dhcp_data_file:dir { open search write add_name remove_name };
-allow dnsmasq dhcp_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow dnsmasq port:tcp_socket name_bind;
-allow dnsmasq node:tcp_socket node_bind;
-#line 1 "external/sepolicy/domain.te"
-# Rules for all domains.
-
-# Allow reaping by init.
-allow domain init:process sigchld;
-
-# Read access to properties mapping.
-allow domain kernel:fd use;
-allow domain tmpfs:file { read getattr };
-
-# Search /storage/emulated tmpfs mount.
-allow domain tmpfs:dir { open getattr read search ioctl };
-
-# Intra-domain accesses.
-allow domain self:process ~{ execmem execstack execheap ptrace };
-allow domain self:fd use;
-allow domain self:dir { open getattr read search ioctl };
-allow domain self:lnk_file { getattr open read ioctl lock };
-allow domain self:{ fifo_file file } { { getattr open read ioctl lock } { open append write } };
-allow domain self:{ unix_dgram_socket unix_stream_socket } *;
-
-# Inherit or receive open files from others.
-allow domain init:fd use;
-allow domain system_server:fd use;
-
-# Connect to adbd and use a socket transferred from it.
-# This is used for e.g. adb backup/restore.
-allow domain adbd:unix_stream_socket connectto;
-allow domain adbd:fd use;
-allow domain adbd:unix_stream_socket { getattr getopt read write shutdown };
-
-#line 43
-
-
-###
-### Talk to debuggerd.
-###
-allow domain debuggerd:process sigchld;
-allow domain debuggerd:unix_stream_socket connectto;
-
-# Root fs.
-allow domain rootfs:dir { open getattr read search ioctl };
-allow domain rootfs:file { getattr open read ioctl lock };
-allow domain rootfs:lnk_file { getattr open read ioctl lock };
-
-# Device accesses.
-allow domain device:dir search;
-allow domain dev_type:lnk_file { getattr open read ioctl lock };
-allow domain devpts:dir search;
-allow domain device:file read;
-allow domain socket_device:dir search;
-allow domain owntty_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow domain null_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow domain zero_device:chr_file { getattr open read ioctl lock };
-allow domain ashmem_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow domain binder_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow domain ptmx_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow domain log_device:dir search;
-allow domain log_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow domain alarm_device:chr_file { getattr open read ioctl lock };
-allow domain urandom_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow domain random_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow domain properties_device:file { getattr open read ioctl lock };
-
-# logd access
-
-#line 76
-
-#line 76
-
-#line 76
-allow domain logdw_socket:sock_file write;
-#line 76
-allow domain logd:unix_dgram_socket sendto;
-#line 76
-
-#line 76
-
-
-# Filesystem accesses.
-allow domain fs_type:filesystem getattr;
-allow domain fs_type:dir getattr;
-
-# System file accesses.
-allow domain system_file:dir { open getattr read search ioctl };
-allow domain system_file:file { getattr open read ioctl lock };
-allow domain system_file:file execute;
-allow domain system_file:lnk_file { getattr open read ioctl lock };
-
-# Read files already opened under /data.
-allow domain system_data_file:dir { search getattr };
-allow domain system_data_file:file { getattr read };
-allow domain system_data_file:lnk_file { getattr open read ioctl lock };
-
-# Read apk files under /data/app.
-allow domain apk_data_file:dir { getattr search };
-allow domain apk_data_file:file { getattr open read ioctl lock };
-
-# Read /data/dalvik-cache.
-allow domain dalvikcache_data_file:dir { search getattr };
-allow domain dalvikcache_data_file:file { getattr open read ioctl lock };
-
-# Read already opened /cache files.
-allow domain cache_file:dir { open getattr read search ioctl };
-allow domain cache_file:file { getattr read };
-allow domain cache_file:lnk_file { getattr open read ioctl lock };
-
-# Read timezone related information
-
-#line 107
-allow domain zoneinfo_data_file:dir { open getattr read search ioctl };
-#line 107
-allow domain zoneinfo_data_file:{ file lnk_file } { getattr open read ioctl lock };
-#line 107
-
-
-# For /acct/uid/*/tasks.
-allow domain cgroup:dir { search write };
-allow domain cgroup:file { open append write };
-
-#Allow access to ion memory allocation device
-allow domain ion_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Read access to pseudo filesystems.
-
-#line 117
-allow domain proc:dir { open getattr read search ioctl };
-#line 117
-allow domain proc:{ file lnk_file } { getattr open read ioctl lock };
-#line 117
-
-
-#line 118
-allow domain sysfs:dir { open getattr read search ioctl };
-#line 118
-allow domain sysfs:{ file lnk_file } { getattr open read ioctl lock };
-#line 118
-
-
-#line 119
-allow domain sysfs_devices_system_cpu:dir { open getattr read search ioctl };
-#line 119
-allow domain sysfs_devices_system_cpu:{ file lnk_file } { getattr open read ioctl lock };
-#line 119
-
-
-#line 120
-allow domain inotify:dir { open getattr read search ioctl };
-#line 120
-allow domain inotify:{ file lnk_file } { getattr open read ioctl lock };
-#line 120
-
-
-#line 121
-allow domain cgroup:dir { open getattr read search ioctl };
-#line 121
-allow domain cgroup:{ file lnk_file } { getattr open read ioctl lock };
-#line 121
-
-
-#line 122
-allow domain proc_net:dir { open getattr read search ioctl };
-#line 122
-allow domain proc_net:{ file lnk_file } { getattr open read ioctl lock };
-#line 122
-
-
-# debugfs access
-allow domain debugfs:dir { open getattr read search ioctl };
-allow domain debugfs:file { open append write };
-
-# Get SELinux enforcing status.
-
-#line 129
-allow domain selinuxfs:dir { open getattr read search ioctl };
-#line 129
-allow domain selinuxfs:file { getattr open read ioctl lock };
-#line 129
-
-
-# security files
-allow domain security_file:dir { search getattr };
-allow domain security_file:file getattr;
-
-# World readable asec image contents
-allow domain asec_public_file:file { getattr open read ioctl lock };
-allow domain { asec_public_file asec_apk_file }:dir { open getattr read search ioctl };
-
-######## Backwards compatibility - Unlabeled files ############
-
-# Revert to DAC rules when looking at unlabeled files. Over time, the number
-# of unlabeled files should decrease.
-# TODO: delete these rules in the future.
-#
-# Note on relabelfrom: We allow any app relabelfrom, but without the relabelto
-# capability, it's essentially useless. This is needed to allow an app with
-# relabelto to relabel unlabeled files.
-#
-allow domain unlabeled:{ file lnk_file sock_file fifo_file } { { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } } relabelfrom };
-allow domain unlabeled:dir { { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } } relabelfrom };
-neverallow { domain -relabeltodomain } *:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } } relabelto;
-
-###
-### neverallow rules
-###
-
-# Limit ability to ptrace or read sensitive /proc/pid files of processes
-# with other UIDs to these whitelisted domains.
-neverallow { domain -debuggerd -vold -dumpstate -system_server } self:capability sys_ptrace;
-
-# Limit device node creation and raw I/O to these whitelisted domains.
-neverallow { domain -kernel -init -recovery -ueventd -watchdogd -healthd -vold -uncrypt } self:capability { sys_rawio mknod };
-
-# No domain needs mac_override as it is unused by SELinux.
-neverallow domain self:capability2 mac_override;
-
-# Only recovery needs mac_admin to set contexts not defined in current policy.
-neverallow { domain -recovery } self:capability2 mac_admin;
-
-# Only init should be able to load SELinux policies.
-# The first load technically occurs while still in the kernel domain,
-# but this does not trigger a denial since there is no policy yet.
-# Policy reload requires allowing this to the init domain.
-neverallow { domain -init } kernel:security load_policy;
-
-# Only init prior to switching context should be able to set enforcing mode.
-# init starts in kernel domain and switches to init domain via setcon in
-# the init.rc, so the setenforce occurs while still in kernel. After
-# switching domains, there is never any need to setenforce again by init.
-neverallow { domain -kernel } kernel:security { setenforce setcheckreqprot };
-
-# Only init, ueventd and system_server should be able to access HW RNG
-neverallow { domain -init -system_server -ueventd -unconfineddomain } hw_random_device:chr_file *;
-
-# Ensure that all entrypoint executables are in exec_type.
-neverallow domain { file_type -exec_type }:file entrypoint;
-
-# Ensure that nothing in userspace can access /dev/mem or /dev/kmem
-neverallow { domain -kernel -ueventd -init } kmem_device:chr_file *;
-neverallow domain kmem_device:chr_file ~{ create relabelto unlink setattr };
-
-# Only init should be able to configure kernel usermodehelpers or
-# security-sensitive proc settings.
-neverallow { domain -init } usermodehelper:file { append write };
-neverallow { domain -init } proc_security:file { append write };
-
-# No domain should be allowed to ptrace init.
-neverallow domain init:process ptrace;
-
-# Init can't receive binder calls. If this neverallow rule is being
-# triggered, it's probably due to a service with no SELinux domain.
-neverallow domain init:binder call;
-
-# Don't allow raw read/write/open access to block_device
-# Rather force a relabel to a more specific type
-neverallow { domain -kernel -init -recovery -vold -uncrypt } block_device:blk_file { open read write };
-
-# Don't allow raw read/write/open access to generic devices.
-# Rather force a relabel to a more specific type.
-# ueventd is exempt from this, as its managing these devices.
-neverallow { domain -unconfineddomain -ueventd } device:chr_file { open read write };
-
-# Limit what domains can mount filesystems or change their mount flags.
-# sdcard_type / vfat is exempt as a larger set of domains need
-# this capability, including device-specific domains.
-neverallow { domain -kernel -init -recovery -vold -zygote } { fs_type -sdcard_type }:filesystem { mount remount relabelfrom relabelto };
-#line 1 "external/sepolicy/drmserver.te"
-# drmserver - DRM service
-type drmserver, domain;
-type drmserver_exec, exec_type, file_type;
-
-
-#line 5
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow init drmserver_exec:file { getattr open read execute };
-#line 5
-allow init drmserver:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow drmserver drmserver_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow drmserver init:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit init drmserver:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow init drmserver:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition init drmserver_exec:process drmserver;
-#line 5
-
-#line 5
-
-#line 5
-type drmserver_tmpfs, file_type;
-#line 5
-type_transition drmserver tmpfs:file drmserver_tmpfs;
-#line 5
-allow drmserver drmserver_tmpfs:file { read write };
-#line 5
-
-#line 5
-
-typeattribute drmserver mlstrustedsubject;
-
-# Perform Binder IPC to system server.
-
-#line 9
-# Call the servicemanager and transfer references to it.
-#line 9
-allow drmserver servicemanager:binder { call transfer };
-#line 9
-# rw access to /dev/binder and /dev/ashmem is presently granted to
-#line 9
-# all domains in domain.te.
-#line 9
-
-
-#line 10
-# Call the server domain and optionally transfer references to it.
-#line 10
-allow drmserver system_server:binder { call transfer };
-#line 10
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 10
-allow system_server drmserver:binder transfer;
-#line 10
-# Receive and use open files from the server.
-#line 10
-allow drmserver system_server:fd use;
-#line 10
-
-
-#line 11
-# Call the server domain and optionally transfer references to it.
-#line 11
-allow drmserver appdomain:binder { call transfer };
-#line 11
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 11
-allow appdomain drmserver:binder transfer;
-#line 11
-# Receive and use open files from the server.
-#line 11
-allow drmserver appdomain:fd use;
-#line 11
-
-
-#line 12
-typeattribute drmserver binderservicedomain;
-#line 12
-
-
-# Perform Binder IPC to mediaserver
-
-#line 15
-# Call the server domain and optionally transfer references to it.
-#line 15
-allow drmserver mediaserver:binder { call transfer };
-#line 15
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 15
-allow mediaserver drmserver:binder transfer;
-#line 15
-# Receive and use open files from the server.
-#line 15
-allow drmserver mediaserver:fd use;
-#line 15
-
-
-allow drmserver sdcard_type:dir search;
-allow drmserver drm_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow drmserver drm_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow drmserver self:{ tcp_socket udp_socket } *;
-allow drmserver port:tcp_socket name_connect;
-allow drmserver tee_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow drmserver platform_app_data_file:file { read write getattr };
-allow drmserver app_data_file:file { read write getattr };
-allow drmserver sdcard_type:file { read write getattr };
-
-#line 26
-allow drmserver efs_file:dir { open getattr read search ioctl };
-#line 26
-allow drmserver efs_file:{ file lnk_file } { getattr open read ioctl lock };
-#line 26
-
-
-type drmserver_socket, file_type;
-
-# /data/app/tlcd_sock socket file.
-# Clearly, /data/app is the most logical place to create a socket.  Not.
-allow drmserver apk_data_file:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-type_transition drmserver apk_data_file:sock_file drmserver_socket;
-allow drmserver drmserver_socket:sock_file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow drmserver tee:unix_stream_socket connectto;
-# Delete old socket file if present.
-allow drmserver apk_data_file:sock_file unlink;
-
-# After taking a video, drmserver looks at the video file.
-
-#line 40
-allow drmserver media_rw_data_file:dir { open getattr read search ioctl };
-#line 40
-allow drmserver media_rw_data_file:{ file lnk_file } { getattr open read ioctl lock };
-#line 40
-
-#line 1 "external/sepolicy/dumpstate.te"
-# dumpstate
-type dumpstate, domain;
-
-#line 3
-typeattribute dumpstate mlstrustedsubject;
-#line 3
-typeattribute dumpstate unconfineddomain;
-#line 3
-
-type dumpstate_exec, exec_type, file_type;
-
-
-#line 6
-
-#line 6
-# Allow the necessary permissions.
-#line 6
-
-#line 6
-# Old domain may exec the file and transition to the new domain.
-#line 6
-allow init dumpstate_exec:file { getattr open read execute };
-#line 6
-allow init dumpstate:process transition;
-#line 6
-# New domain is entered by executing the file.
-#line 6
-allow dumpstate dumpstate_exec:file { entrypoint read execute };
-#line 6
-# New domain can send SIGCHLD to its caller.
-#line 6
-allow dumpstate init:process sigchld;
-#line 6
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 6
-dontaudit init dumpstate:process noatsecure;
-#line 6
-# XXX dontaudit candidate but requires further study.
-#line 6
-allow init dumpstate:process { siginh rlimitinh };
-#line 6
-
-#line 6
-# Make the transition occur by default.
-#line 6
-type_transition init dumpstate_exec:process dumpstate;
-#line 6
-
-#line 6
-
-#line 6
-type dumpstate_tmpfs, file_type;
-#line 6
-type_transition dumpstate tmpfs:file dumpstate_tmpfs;
-#line 6
-allow dumpstate dumpstate_tmpfs:file { read write };
-#line 6
-
-#line 6
-
-
-#line 7
-typeattribute dumpstate netdomain;
-#line 7
-
-
-#line 8
-typeattribute dumpstate relabeltodomain;
-#line 8
-
-
-#line 9
-# Call the servicemanager and transfer references to it.
-#line 9
-allow dumpstate servicemanager:binder { call transfer };
-#line 9
-# rw access to /dev/binder and /dev/ashmem is presently granted to
-#line 9
-# all domains in domain.te.
-#line 9
-
-
-# Drop privileges by switching UID / GID
-allow dumpstate self:capability { setuid setgid };
-
-# Allow dumpstate to scan through /proc/pid for all processes
-
-#line 15
-allow dumpstate domain:dir { open getattr read search ioctl };
-#line 15
-allow dumpstate domain:{ file lnk_file } { getattr open read ioctl lock };
-#line 15
-
-
-# Send signals to processes
-allow dumpstate self:capability kill;
-
-# Allow executing files on system, such as:
-#   /system/bin/toolbox
-#   /system/bin/logcat
-#   /system/bin/dumpsys
-allow dumpstate system_file:file execute_no_trans;
-
-# Create and write into /data/anr/
-allow dumpstate self:capability { dac_override chown fowner fsetid };
-allow dumpstate anr_data_file:dir { { { open getattr read search ioctl } { open search write add_name remove_name } } relabelto };
-allow dumpstate anr_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow dumpstate system_data_file:dir { { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } } relabelfrom };
-
-# Allow reading /data/system/uiderrors.txt
-# TODO: scope this down.
-allow dumpstate system_data_file:file { getattr open read ioctl lock };
-
-# Read dmesg
-allow dumpstate self:capability2 syslog;
-allow dumpstate kernel:system syslog_read;
-
-# Get process attributes
-allow dumpstate domain:process getattr;
-
-# Signal java processes to dump their stack
-allow dumpstate { appdomain system_server }:process signal;
-
-# Signal native processes to dump their stack.
-# This list comes from native_processes_to_dump in dumpstate/utils.c
-allow dumpstate { drmserver mediaserver sdcardd surfaceflinger }:process signal;
-
-# The /system/bin/ip command needs this for routing table information.
-allow dumpstate self:netlink_route_socket { write getattr setopt };
-
-# The vdc command needs to talk to the vold socket.
-
-#line 54
-allow dumpstate vold_socket:sock_file write;
-#line 54
-allow dumpstate vold:unix_stream_socket connectto;
-#line 54
-
-
-# Vibrate the device after we're done collecting the bugreport
-# /sys/class/timed_output/vibrator/enable
-# TODO: create a new file class, instead of allowing write access to all of /sys
-allow dumpstate sysfs:file { open append write };
-
-# Other random bits of data we want to collect
-allow dumpstate qtaguid_proc:file { getattr open read ioctl lock };
-allow dumpstate debugfs:file { getattr open read ioctl lock };
-
-# Allow dumpstate to make binder calls to any binder service
-
-#line 66
-# Call the server domain and optionally transfer references to it.
-#line 66
-allow dumpstate binderservicedomain:binder { call transfer };
-#line 66
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 66
-allow binderservicedomain dumpstate:binder transfer;
-#line 66
-# Receive and use open files from the server.
-#line 66
-allow dumpstate binderservicedomain:fd use;
-#line 66
-
-
-#line 67
-# Call the server domain and optionally transfer references to it.
-#line 67
-allow dumpstate appdomain:binder { call transfer };
-#line 67
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 67
-allow appdomain dumpstate:binder transfer;
-#line 67
-# Receive and use open files from the server.
-#line 67
-allow dumpstate appdomain:fd use;
-#line 67
-
-
-# Reading /proc/PID/maps of other processes
-allow dumpstate self:capability sys_ptrace;
-
-# Allow the bugreport service to create a file in
-# /data/data/com.android.shell/files/bugreports/bugreport
-allow dumpstate shell_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow dumpstate shell_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# Run a shell.
-allow dumpstate shell_exec:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-
-# For running am and similar framework commands.
-# Run /system/bin/app_process.
-allow dumpstate zygote_exec:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-# Dalvik Compiler JIT.
-allow dumpstate ashmem_device:chr_file execute;
-allow dumpstate dumpstate_tmpfs:file execute;
-allow dumpstate self:process execmem;
-# For art.
-allow dumpstate dalvikcache_data_file:file execute;
-
-# logd access
-
-#line 91
-
-#line 91
-allow dumpstate logdr_socket:sock_file write;
-#line 91
-allow dumpstate logd:unix_stream_socket connectto;
-#line 91
-
-#line 91
-
-
-#line 92
-# Group AID_LOG checked by filesystem & logd
-#line 92
-# to permit control commands
-#line 92
-
-#line 92
-allow dumpstate logd_socket:sock_file write;
-#line 92
-allow dumpstate logd:unix_stream_socket connectto;
-#line 92
-
-#line 92
-
-#line 1 "external/sepolicy/file.te"
-# Filesystem types
-type labeledfs, fs_type;
-type pipefs, fs_type;
-type sockfs, fs_type;
-type rootfs, fs_type;
-type proc, fs_type;
-# Security-sensitive proc nodes that should not be writable to most.
-type proc_security, fs_type;
-# proc, sysfs, or other nodes that permit configuration of kernel usermodehelpers.
-type usermodehelper, fs_type, sysfs_type;
-type qtaguid_proc, fs_type, mlstrustedobject;
-type proc_bluetooth_writable, fs_type;
-type proc_net, fs_type;
-type selinuxfs, fs_type;
-type cgroup, fs_type, mlstrustedobject;
-type sysfs, fs_type, mlstrustedobject;
-type sysfs_writable, fs_type, sysfs_type, mlstrustedobject;
-type sysfs_bluetooth_writable, fs_type, sysfs_type, mlstrustedobject;
-type sysfs_nfc_power_writable, fs_type, sysfs_type, mlstrustedobject;
-type sysfs_wake_lock, fs_type, sysfs_type;
-# /sys/devices/system/cpu
-type sysfs_devices_system_cpu, fs_type, sysfs_type;
-# /sys/module/lowmemorykiller
-type sysfs_lowmemorykiller, fs_type, sysfs_type;
-type inotify, fs_type, mlstrustedobject;
-type devpts, fs_type, mlstrustedobject;
-type tmpfs, fs_type;
-type shm, fs_type;
-type mqueue, fs_type;
-type sdcard_internal, sdcard_type, fs_type, mlstrustedobject;
-type sdcard_external, sdcard_type, fs_type, mlstrustedobject;
-type debugfs, fs_type, mlstrustedobject;
-
-# File types
-type unlabeled, file_type;
-# Default type for anything under /system.
-type system_file, file_type;
-# Default type for anything under /data.
-type system_data_file, file_type, data_file_type;
-# /data/drm - DRM plugin data
-type drm_data_file, file_type, data_file_type;
-# /data/anr - ANR traces
-type anr_data_file, file_type, data_file_type, mlstrustedobject;
-# /data/tombstones - core dumps
-type tombstone_data_file, file_type, data_file_type;
-# /data/app - user-installed apps
-type apk_data_file, file_type, data_file_type;
-type apk_tmp_file, file_type, data_file_type, mlstrustedobject;
-# /data/app-private - forward-locked apps
-type apk_private_data_file, file_type, data_file_type;
-type apk_private_tmp_file, file_type, data_file_type, mlstrustedobject;
-# /data/dalvik-cache
-type dalvikcache_data_file, file_type, data_file_type;
-# /data/local - writable by shell
-type shell_data_file, file_type, data_file_type;
-# /data/gps
-type gps_data_file, file_type, data_file_type;
-
-# /data/misc subdirectories
-type adb_keys_file, file_type, data_file_type;
-type audio_data_file, file_type, data_file_type;
-type bluetooth_data_file, file_type, data_file_type;
-type camera_data_file, file_type, data_file_type;
-type keystore_data_file, file_type, data_file_type;
-type media_data_file, file_type, data_file_type;
-type media_rw_data_file, file_type, data_file_type;
-type nfc_data_file, file_type, data_file_type;
-type radio_data_file, file_type, data_file_type;
-type systemkeys_data_file, file_type, data_file_type;
-type vpn_data_file, file_type, data_file_type;
-type wifi_data_file, file_type, data_file_type;
-type zoneinfo_data_file, file_type, data_file_type;
-
-# Compatibility with type names used in vanilla Android 4.3 and 4.4.
-typealias audio_data_file alias audio_firmware_file;
-# /data/data subdirectories - app sandboxes
-type app_data_file, file_type, data_file_type;
-type platform_app_data_file, file_type, data_file_type, mlstrustedobject;
-# Default type for anything under /cache
-type cache_file, file_type, mlstrustedobject;
-# Type for /cache/.*\.{data|restore} and default
-# type for anything under /cache/backup
-type cache_backup_file, file_type, mlstrustedobject;
-# Default type for anything under /efs
-type efs_file, file_type;
-# Type for wallpaper file.
-type wallpaper_file, file_type, mlstrustedobject;
-# /mnt/asec
-type asec_apk_file, file_type, data_file_type;
-# Elements of asec files (/mnt/asec) that are world readable
-type asec_public_file, file_type, data_file_type;
-# /data/app-asec
-type asec_image_file, file_type, data_file_type;
-# /data/backup and /data/secure/backup
-type backup_data_file, file_type, data_file_type, mlstrustedobject;
-# For /data/security
-type security_file, file_type;
-# All devices have bluetooth efs files. But they
-# vary per device, so this type is used in per
-# device policy
-type bluetooth_efs_file, file_type;
-# Downloaded files
-type download_file, file_type;
-
-# Socket types
-type adbd_socket, file_type;
-type bluetooth_socket, file_type;
-type dnsproxyd_socket, file_type, mlstrustedobject;
-type dumpstate_socket, file_type;
-type gps_socket, file_type;
-type installd_socket, file_type;
-type keystore_socket, file_type;
-type lmkd_socket, file_type;
-type logd_debug, file_type;
-type logd_socket, file_type;
-type logdr_socket, file_type;
-type logdw_socket, file_type;
-type mdns_socket, file_type;
-type netd_socket, file_type;
-type property_socket, file_type;
-type qemud_socket, file_type;
-type racoon_socket, file_type;
-type rild_socket, file_type;
-type rild_debug_socket, file_type;
-type system_wpa_socket, file_type;
-type system_ndebug_socket, file_type;
-type vold_socket, file_type;
-type wpa_socket, file_type;
-type zygote_socket, file_type;
-
-# UART (for GPS) control proc file
-type gps_control, file_type;
-
-# Allow files to be created in their appropriate filesystems.
-allow fs_type self:filesystem associate;
-allow sysfs_type sysfs:filesystem associate;
-allow file_type labeledfs:filesystem associate;
-allow file_type tmpfs:filesystem associate;
-allow file_type rootfs:filesystem associate;
-allow dev_type tmpfs:filesystem associate;
-#line 1 "external/sepolicy/gpsd.te"
-# gpsd - GPS daemon
-type gpsd, domain;
-
-#line 3
-typeattribute gpsd mlstrustedsubject;
-#line 3
-typeattribute gpsd unconfineddomain;
-#line 3
-
-type gpsd_exec, exec_type, file_type;
-
-
-#line 6
-
-#line 6
-# Allow the necessary permissions.
-#line 6
-
-#line 6
-# Old domain may exec the file and transition to the new domain.
-#line 6
-allow init gpsd_exec:file { getattr open read execute };
-#line 6
-allow init gpsd:process transition;
-#line 6
-# New domain is entered by executing the file.
-#line 6
-allow gpsd gpsd_exec:file { entrypoint read execute };
-#line 6
-# New domain can send SIGCHLD to its caller.
-#line 6
-allow gpsd init:process sigchld;
-#line 6
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 6
-dontaudit init gpsd:process noatsecure;
-#line 6
-# XXX dontaudit candidate but requires further study.
-#line 6
-allow init gpsd:process { siginh rlimitinh };
-#line 6
-
-#line 6
-# Make the transition occur by default.
-#line 6
-type_transition init gpsd_exec:process gpsd;
-#line 6
-
-#line 6
-
-#line 6
-type gpsd_tmpfs, file_type;
-#line 6
-type_transition gpsd tmpfs:file gpsd_tmpfs;
-#line 6
-allow gpsd gpsd_tmpfs:file { read write };
-#line 6
-
-#line 6
-
-
-#line 7
-typeattribute gpsd netdomain;
-#line 7
-
-allow gpsd gps_data_file:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-allow gpsd gps_data_file:{ file lnk_file sock_file fifo_file } { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-# Socket is created by the daemon, not by init, and under /data/gps,
-# not under /dev/socket.
-type_transition gpsd gps_data_file:sock_file gps_socket;
-allow gpsd gps_socket:sock_file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-# XXX Label sysfs files with a specific type?
-allow gpsd sysfs:file { { getattr open read ioctl lock } { open append write } };
-
-allow gpsd gps_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Execute the shell or system commands.
-allow gpsd shell_exec:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-allow gpsd system_file:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-#line 1 "external/sepolicy/hci_attach.te"
-type hci_attach, domain;
-type hci_attach_exec, exec_type, file_type;
-
-
-#line 4
-
-#line 4
-# Allow the necessary permissions.
-#line 4
-
-#line 4
-# Old domain may exec the file and transition to the new domain.
-#line 4
-allow init hci_attach_exec:file { getattr open read execute };
-#line 4
-allow init hci_attach:process transition;
-#line 4
-# New domain is entered by executing the file.
-#line 4
-allow hci_attach hci_attach_exec:file { entrypoint read execute };
-#line 4
-# New domain can send SIGCHLD to its caller.
-#line 4
-allow hci_attach init:process sigchld;
-#line 4
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 4
-dontaudit init hci_attach:process noatsecure;
-#line 4
-# XXX dontaudit candidate but requires further study.
-#line 4
-allow init hci_attach:process { siginh rlimitinh };
-#line 4
-
-#line 4
-# Make the transition occur by default.
-#line 4
-type_transition init hci_attach_exec:process hci_attach;
-#line 4
-
-#line 4
-
-#line 4
-type hci_attach_tmpfs, file_type;
-#line 4
-type_transition hci_attach tmpfs:file hci_attach_tmpfs;
-#line 4
-allow hci_attach hci_attach_tmpfs:file { read write };
-#line 4
-
-#line 4
-
-
-allow hci_attach kernel:system module_request;
-allow hci_attach hci_attach_dev:chr_file { { getattr open read ioctl lock } { open append write } };
-allow hci_attach bluetooth_efs_file:dir { open getattr read search ioctl };
-allow hci_attach bluetooth_efs_file:file { getattr open read ioctl lock };
-#line 1 "external/sepolicy/healthd.te"
-# healthd seclabel is specified in init.rc since
-# it lives in the rootfs and has no unique file type.
-type healthd, domain;
-
-allow healthd rootfs:file { read entrypoint };
-
-#line 6
-type_transition healthd device:chr_file klog_device "__kmsg__";
-#line 6
-allow healthd klog_device:chr_file { create open write unlink };
-#line 6
-allow healthd device:dir { write add_name remove_name };
-#line 6
-
-# /dev/__null__ created by init prior to policy load,
-# open fd inherited by healthd.
-allow healthd tmpfs:chr_file { read write };
-
-allow healthd self:capability { net_admin mknod };
-allow healthd self:capability2 block_suspend;
-allow healthd self:netlink_kobject_uevent_socket { create { ioctl read getattr write setattr append bind connect getopt setopt shutdown } };
-
-#line 14
-# Call the servicemanager and transfer references to it.
-#line 14
-allow healthd servicemanager:binder { call transfer };
-#line 14
-# rw access to /dev/binder and /dev/ashmem is presently granted to
-#line 14
-# all domains in domain.te.
-#line 14
-
-
-#line 15
-typeattribute healthd binderservicedomain;
-#line 15
-
-
-#line 16
-# Call the server domain and optionally transfer references to it.
-#line 16
-allow healthd system_server:binder { call transfer };
-#line 16
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 16
-allow system_server healthd:binder transfer;
-#line 16
-# Receive and use open files from the server.
-#line 16
-allow healthd system_server:fd use;
-#line 16
-
-
-###
-### healthd: charger mode
-###
-
-allow healthd graphics_device:dir { open getattr read search ioctl };
-allow healthd graphics_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow healthd input_device:dir { open getattr read search ioctl };
-allow healthd input_device:chr_file { getattr open read ioctl lock };
-allow healthd ashmem_device:chr_file execute;
-allow healthd self:process execmem;
-#line 1 "external/sepolicy/hostapd.te"
-# userspace wifi access points
-type hostapd, domain;
-
-#line 3
-typeattribute hostapd mlstrustedsubject;
-#line 3
-typeattribute hostapd unconfineddomain;
-#line 3
-
-type hostapd_exec, exec_type, file_type;
-
-allow hostapd self:capability { net_admin net_raw setuid setgid };
-allow hostapd self:netlink_socket { create { ioctl read getattr write setattr append bind connect getopt setopt shutdown } };
-allow hostapd self:packet_socket { create write read };
-allow hostapd self:netlink_route_socket { bind create write nlmsg_write read };
-allow hostapd self:udp_socket { create ioctl };
-
-allow hostapd wifi_data_file:file { { getattr open read ioctl lock } { open append write } };
-allow hostapd wifi_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow hostapd wpa_socket:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow hostapd wpa_socket:sock_file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow hostapd netd:fd use;
-allow hostapd netd:udp_socket { read write };
-allow hostapd netd:netlink_kobject_uevent_socket { read write };
-allow hostapd netd:netlink_nflog_socket { read write };
-allow hostapd netd:netlink_route_socket { read write };
-allow hostapd netd:unix_stream_socket { read write };
-allow hostapd netd:fifo_file { read write };
-#line 1 "external/sepolicy/init_shell.te"
-# Restricted domain for shell processes spawned by init
-type init_shell, domain, shelldomain;
-
-#line 3
-# Allow the necessary permissions.
-#line 3
-
-#line 3
-# Old domain may exec the file and transition to the new domain.
-#line 3
-allow init shell_exec:file { getattr open read execute };
-#line 3
-allow init init_shell:process transition;
-#line 3
-# New domain is entered by executing the file.
-#line 3
-allow init_shell shell_exec:file { entrypoint read execute };
-#line 3
-# New domain can send SIGCHLD to its caller.
-#line 3
-allow init_shell init:process sigchld;
-#line 3
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 3
-dontaudit init init_shell:process noatsecure;
-#line 3
-# XXX dontaudit candidate but requires further study.
-#line 3
-allow init init_shell:process { siginh rlimitinh };
-#line 3
-
-#line 3
-# Make the transition occur by default.
-#line 3
-type_transition init shell_exec:process init_shell;
-#line 3
-
-
-#line 4
-typeattribute init_shell mlstrustedsubject;
-#line 4
-typeattribute init_shell unconfineddomain;
-#line 4
-
-
-# inherits from shelldomain.te
-#line 1 "external/sepolicy/init.te"
-# init switches to init domain (via init.rc).
-type init, domain;
-# init is unconfined.
-
-#line 4
-typeattribute init mlstrustedsubject;
-#line 4
-typeattribute init unconfineddomain;
-#line 4
-
-
-#line 5
-type init_tmpfs, file_type;
-#line 5
-type_transition init tmpfs:file init_tmpfs;
-#line 5
-allow init init_tmpfs:file { read write };
-#line 5
-
-
-#line 6
-typeattribute init relabeltodomain;
-#line 6
-
-# add a rule to handle unlabelled mounts
-allow init unlabeled:filesystem mount;
-
-allow init self:capability { sys_rawio mknod };
-
-allow init dev_type:blk_file { { getattr open read ioctl lock } { open append write } };
-allow init fs_type:filesystem *;
-allow init {fs_type dev_type file_type}:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } } relabelto;
-allow init kernel:security load_policy;
-allow init usermodehelper:file { { getattr open read ioctl lock } { open append write } };
-allow init proc_security:file { { getattr open read ioctl lock } { open append write } };
-
-# Transitions to seclabel processes in init.rc
-allow init adbd:process transition;
-allow init healthd:process transition;
-allow init recovery:process transition;
-allow init shell:process transition;
-allow init ueventd:process transition;
-allow init watchdogd:process transition;
-#line 1 "external/sepolicy/inputflinger.te"
-# inputflinger
-type inputflinger, domain;
-
-#line 3
-typeattribute inputflinger mlstrustedsubject;
-#line 3
-typeattribute inputflinger unconfineddomain;
-#line 3
-
-type inputflinger_exec, exec_type, file_type;
-
-
-#line 6
-
-#line 6
-# Allow the necessary permissions.
-#line 6
-
-#line 6
-# Old domain may exec the file and transition to the new domain.
-#line 6
-allow init inputflinger_exec:file { getattr open read execute };
-#line 6
-allow init inputflinger:process transition;
-#line 6
-# New domain is entered by executing the file.
-#line 6
-allow inputflinger inputflinger_exec:file { entrypoint read execute };
-#line 6
-# New domain can send SIGCHLD to its caller.
-#line 6
-allow inputflinger init:process sigchld;
-#line 6
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 6
-dontaudit init inputflinger:process noatsecure;
-#line 6
-# XXX dontaudit candidate but requires further study.
-#line 6
-allow init inputflinger:process { siginh rlimitinh };
-#line 6
-
-#line 6
-# Make the transition occur by default.
-#line 6
-type_transition init inputflinger_exec:process inputflinger;
-#line 6
-
-#line 6
-
-#line 6
-type inputflinger_tmpfs, file_type;
-#line 6
-type_transition inputflinger tmpfs:file inputflinger_tmpfs;
-#line 6
-allow inputflinger inputflinger_tmpfs:file { read write };
-#line 6
-
-#line 6
-
-
-#line 7
-# Call the servicemanager and transfer references to it.
-#line 7
-allow inputflinger servicemanager:binder { call transfer };
-#line 7
-# rw access to /dev/binder and /dev/ashmem is presently granted to
-#line 7
-# all domains in domain.te.
-#line 7
-
-
-#line 8
-typeattribute inputflinger binderservicedomain;
-#line 8
-
-#line 1 "external/sepolicy/installd.te"
-# installer daemon
-type installd, domain;
-type installd_exec, exec_type, file_type;
-
-
-#line 5
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow init installd_exec:file { getattr open read execute };
-#line 5
-allow init installd:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow installd installd_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow installd init:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit init installd:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow init installd:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition init installd_exec:process installd;
-#line 5
-
-#line 5
-
-#line 5
-type installd_tmpfs, file_type;
-#line 5
-type_transition installd tmpfs:file installd_tmpfs;
-#line 5
-allow installd installd_tmpfs:file { read write };
-#line 5
-
-#line 5
-
-
-#line 6
-typeattribute installd relabeltodomain;
-#line 6
-
-typeattribute installd mlstrustedsubject;
-allow installd self:capability { chown dac_override fowner fsetid setgid setuid };
-allow installd system_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow installd system_data_file:lnk_file create;
-allow installd dalvikcache_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow installd data_file_type:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow installd data_file_type:dir { relabelfrom relabelto };
-allow installd data_file_type:{ { { chr_file blk_file } { file lnk_file sock_file fifo_file } } } { getattr unlink };
-allow installd apk_data_file:file { getattr open read ioctl lock };
-allow installd apk_tmp_file:file { getattr open read ioctl lock };
-allow installd system_file:file { getattr execute execute_no_trans };
-allow installd cgroup:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow installd download_file:dir { { open getattr read search ioctl } write remove_name };
-allow installd download_file:file { { getattr open read ioctl lock } unlink };
-dontaudit installd self:capability sys_admin;
-# Check validity of SELinux context before use.
-
-#line 23
-allow installd selinuxfs:dir { open getattr read search ioctl };
-#line 23
-allow installd selinuxfs:file { { getattr open read ioctl lock } { open append write } };
-#line 23
-allow installd kernel:security check_context;
-#line 23
-
-# Read /seapp_contexts and /data/security/seapp_contexts
-
-#line 25
-allow installd security_file:dir { open getattr read search ioctl };
-#line 25
-allow installd security_file:file { getattr open read ioctl lock };
-#line 25
-allow installd security_file:lnk_file { getattr open read ioctl lock };
-#line 25
-allow installd selinuxfs:dir { open getattr read search ioctl };
-#line 25
-allow installd selinuxfs:file { getattr open read ioctl lock };
-#line 25
-allow installd rootfs:dir { open getattr read search ioctl };
-#line 25
-allow installd rootfs:file { getattr open read ioctl lock };
-#line 25
-
-# ASEC
-allow installd platform_app_data_file:lnk_file { create setattr };
-allow installd app_data_file:lnk_file { create setattr };
-allow installd asec_apk_file:file { getattr open read ioctl lock };
-allow installd bluetooth_data_file:lnk_file { create setattr };
-allow installd nfc_data_file:lnk_file { create setattr };
-allow installd radio_data_file:lnk_file { create setattr };
-allow installd shell_data_file:lnk_file { create setattr };
-#line 1 "external/sepolicy/isolated_app.te"
-###
-### Services with isolatedProcess=true in their manifest.
-###
-### This file defines the rules for isolated apps. An "isolated
-### app" is an APP with UID between AID_ISOLATED_START (99000)
-### and AID_ISOLATED_END (99999).
-###
-### isolated_app includes all the appdomain rules, plus the
-### additional following rules:
-###
-
-type isolated_app, domain;
-
-#line 13
-typeattribute isolated_app appdomain;
-#line 13
-# Label ashmem objects with our own unique type.
-#line 13
-
-#line 13
-type isolated_app_tmpfs, file_type;
-#line 13
-type_transition isolated_app tmpfs:file isolated_app_tmpfs;
-#line 13
-allow isolated_app isolated_app_tmpfs:file { read write };
-#line 13
-
-#line 13
-# Map with PROT_EXEC.
-#line 13
-allow isolated_app isolated_app_tmpfs:file execute;
-#line 13
-
-
-# Already connected, unnamed sockets being passed over some other IPC
-# hence no sock_file or connectto permission. This appears to be how
-# Chrome works, may need to be updated as more apps using isolated services
-# are examined.
-allow isolated_app appdomain:unix_stream_socket { read write };
-
-allow isolated_app dalvikcache_data_file:file execute;
-allow isolated_app apk_data_file:dir getattr;
-#line 1 "external/sepolicy/kernel.te"
-# Life begins with the kernel.
-type kernel, domain;
-
-allow kernel init:process dyntransition;
-
-# The kernel is unconfined.
-
-#line 7
-typeattribute kernel mlstrustedsubject;
-#line 7
-typeattribute kernel unconfineddomain;
-#line 7
-
-
-#line 8
-typeattribute kernel relabeltodomain;
-#line 8
-
-
-allow kernel {fs_type dev_type file_type}:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } } relabelto;
-allow kernel unlabeled:filesystem mount;
-allow kernel fs_type:filesystem *;
-
-# Initial setenforce by init prior to switching to init domain.
-allow kernel self:security setenforce;
-
-# Set checkreqprot by init.rc prior to switching to init domain.
-allow kernel self:security setcheckreqprot;
-
-# For operations performed by kernel or init prior to switching to init domain.
-## TODO: Investigate whether it is safe to remove these
-allow kernel self:capability { sys_rawio mknod };
-auditallow kernel self:capability { sys_rawio mknod };
-allow kernel dev_type:blk_file { { getattr open read ioctl lock } { open append write } };
-auditallow kernel dev_type:blk_file { { getattr open read ioctl lock } { open append write } };
-#line 1 "external/sepolicy/keystore.te"
-type keystore, domain;
-type keystore_exec, exec_type, file_type;
-
-# keystore daemon
-
-#line 5
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow init keystore_exec:file { getattr open read execute };
-#line 5
-allow init keystore:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow keystore keystore_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow keystore init:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit init keystore:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow init keystore:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition init keystore_exec:process keystore;
-#line 5
-
-#line 5
-
-#line 5
-type keystore_tmpfs, file_type;
-#line 5
-type_transition keystore tmpfs:file keystore_tmpfs;
-#line 5
-allow keystore keystore_tmpfs:file { read write };
-#line 5
-
-#line 5
-
-typeattribute keystore mlstrustedsubject;
-
-#line 7
-# Call the servicemanager and transfer references to it.
-#line 7
-allow keystore servicemanager:binder { call transfer };
-#line 7
-# rw access to /dev/binder and /dev/ashmem is presently granted to
-#line 7
-# all domains in domain.te.
-#line 7
-
-
-#line 8
-typeattribute keystore binderservicedomain;
-#line 8
-
-allow keystore keystore_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow keystore keystore_data_file:{ file lnk_file sock_file fifo_file } { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow keystore keystore_exec:file { getattr };
-allow keystore tee_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow keystore tee:unix_stream_socket connectto;
-#line 1 "external/sepolicy/lmkd.te"
-# lmkd low memory killer daemon
-type lmkd, domain;
-type lmkd_exec, exec_type, file_type;
-
-
-#line 5
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow init lmkd_exec:file { getattr open read execute };
-#line 5
-allow init lmkd:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow lmkd lmkd_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow lmkd init:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit init lmkd:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow init lmkd:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition init lmkd_exec:process lmkd;
-#line 5
-
-#line 5
-
-#line 5
-type lmkd_tmpfs, file_type;
-#line 5
-type_transition lmkd tmpfs:file lmkd_tmpfs;
-#line 5
-allow lmkd lmkd_tmpfs:file { read write };
-#line 5
-
-#line 5
-
-
-allow lmkd self:capability { dac_override sys_resource };
-
-## Open and write to /proc/PID/oom_score_adj
-## TODO: maybe scope this down?
-
-#line 11
-allow lmkd appdomain:dir { open getattr read search ioctl };
-#line 11
-allow lmkd appdomain:{ file lnk_file } { getattr open read ioctl lock };
-#line 11
-
-allow lmkd appdomain:file write;
-
-#line 13
-allow lmkd system_server:dir { open getattr read search ioctl };
-#line 13
-allow lmkd system_server:{ file lnk_file } { getattr open read ioctl lock };
-#line 13
-
-allow lmkd system_server:file write;
-
-## Writes to /sys/module/lowmemorykiller/parameters/minfree
-allow lmkd sysfs_lowmemorykiller:file { open append write };
-#line 1 "external/sepolicy/logd.te"
-# android user-space log manager
-type logd, domain;
-type logd_exec, exec_type, file_type;
-
-
-#line 5
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow init logd_exec:file { getattr open read execute };
-#line 5
-allow init logd:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow logd logd_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow logd init:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit init logd:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow init logd:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition init logd_exec:process logd;
-#line 5
-
-#line 5
-
-#line 5
-type logd_tmpfs, file_type;
-#line 5
-type_transition logd tmpfs:file logd_tmpfs;
-#line 5
-allow logd logd_tmpfs:file { read write };
-#line 5
-
-#line 5
-
-allow logd self:unix_stream_socket *;
-
-allow logd self:capability { setuid setgid sys_nice };
-
-
-#line 10
-allow logd domain:dir { open getattr read search ioctl };
-#line 10
-allow logd domain:{ file lnk_file } { getattr open read ioctl lock };
-#line 10
-
-
-#line 17
-
-
-###
-### Neverallow rules
-###
-### logd should NEVER do any of this
-
-# Block device access.
-neverallow logd dev_type:blk_file { read write };
-
-# ptrace any other app
-neverallow logd domain:process ptrace;
-
-# Write to /system.
-neverallow logd system_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } } write;
-
-# Write to files in /data/data or system files on /data
-neverallow logd { app_data_file system_data_file }:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } } write;
-#line 1 "external/sepolicy/media_app.te"
-###
-### Apps signed with the media key.
-###
-
-type media_app, domain;
-
-#line 6
-typeattribute media_app appdomain;
-#line 6
-# Label ashmem objects with our own unique type.
-#line 6
-
-#line 6
-type media_app_tmpfs, file_type;
-#line 6
-type_transition media_app tmpfs:file media_app_tmpfs;
-#line 6
-allow media_app media_app_tmpfs:file { read write };
-#line 6
-
-#line 6
-# Map with PROT_EXEC.
-#line 6
-allow media_app media_app_tmpfs:file execute;
-#line 6
-
-
-#line 7
-typeattribute media_app platformappdomain;
-#line 7
-typeattribute media_app mlstrustedsubject;
-#line 7
-
-
-#line 8
-typeattribute media_app binderservicedomain;
-#line 8
-
-# Access the network.
-
-#line 10
-typeattribute media_app netdomain;
-#line 10
-
-# Access /dev/mtp_usb.
-allow media_app mtp_device:chr_file { { getattr open read ioctl lock } { open append write } };
-# Write to /cache.
-allow media_app cache_file:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-allow media_app cache_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-# Stat /cache/lost+found
-allow media_app unlabeled:file getattr;
-allow media_app unlabeled:dir getattr;
-# Stat /cache/backup
-allow media_app cache_backup_file:file getattr;
-allow media_app cache_backup_file:dir getattr;
-# Read files in the rootdir (in particular, file_contexts for restorecon).
-allow media_app rootfs:file { getattr open read ioctl lock };
-allow media_app download_file:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-allow media_app download_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-# Allow platform apps to mark platform app data files as download files
-
-#line 27
-typeattribute media_app relabeltodomain;
-#line 27
-
-allow media_app platform_app_data_file:dir relabelfrom;
-allow media_app download_file:dir relabelto;
-#line 1 "external/sepolicy/mediaserver.te"
-# mediaserver - multimedia daemon
-type mediaserver, domain;
-
-#line 3
-typeattribute mediaserver mlstrustedsubject;
-#line 3
-typeattribute mediaserver unconfineddomain;
-#line 3
-
-type mediaserver_exec, exec_type, file_type;
-
-typeattribute mediaserver mlstrustedsubject;
-
-
-#line 8
-typeattribute mediaserver netdomain;
-#line 8
-
-
-#line 9
-
-#line 9
-# Allow the necessary permissions.
-#line 9
-
-#line 9
-# Old domain may exec the file and transition to the new domain.
-#line 9
-allow init mediaserver_exec:file { getattr open read execute };
-#line 9
-allow init mediaserver:process transition;
-#line 9
-# New domain is entered by executing the file.
-#line 9
-allow mediaserver mediaserver_exec:file { entrypoint read execute };
-#line 9
-# New domain can send SIGCHLD to its caller.
-#line 9
-allow mediaserver init:process sigchld;
-#line 9
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 9
-dontaudit init mediaserver:process noatsecure;
-#line 9
-# XXX dontaudit candidate but requires further study.
-#line 9
-allow init mediaserver:process { siginh rlimitinh };
-#line 9
-
-#line 9
-# Make the transition occur by default.
-#line 9
-type_transition init mediaserver_exec:process mediaserver;
-#line 9
-
-#line 9
-
-#line 9
-type mediaserver_tmpfs, file_type;
-#line 9
-type_transition mediaserver tmpfs:file mediaserver_tmpfs;
-#line 9
-allow mediaserver mediaserver_tmpfs:file { read write };
-#line 9
-
-#line 9
-
-
-#line 10
-allow mediaserver property_socket:sock_file write;
-#line 10
-allow mediaserver init:unix_stream_socket connectto;
-#line 10
-
-
-
-#line 12
-allow mediaserver sdcard_type:dir { open getattr read search ioctl };
-#line 12
-allow mediaserver sdcard_type:{ file lnk_file } { getattr open read ioctl lock };
-#line 12
-
-
-
-#line 14
-# Call the servicemanager and transfer references to it.
-#line 14
-allow mediaserver servicemanager:binder { call transfer };
-#line 14
-# rw access to /dev/binder and /dev/ashmem is presently granted to
-#line 14
-# all domains in domain.te.
-#line 14
-
-
-#line 15
-# Call the server domain and optionally transfer references to it.
-#line 15
-allow mediaserver binderservicedomain:binder { call transfer };
-#line 15
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 15
-allow binderservicedomain mediaserver:binder transfer;
-#line 15
-# Receive and use open files from the server.
-#line 15
-allow mediaserver binderservicedomain:fd use;
-#line 15
-
-
-#line 16
-# Call the server domain and optionally transfer references to it.
-#line 16
-allow mediaserver appdomain:binder { call transfer };
-#line 16
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 16
-allow appdomain mediaserver:binder transfer;
-#line 16
-# Receive and use open files from the server.
-#line 16
-allow mediaserver appdomain:fd use;
-#line 16
-
-
-#line 17
-typeattribute mediaserver binderservicedomain;
-#line 17
-
-
-allow mediaserver self:process execmem;
-allow mediaserver kernel:system module_request;
-allow mediaserver media_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow mediaserver media_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow mediaserver app_data_file:dir search;
-allow mediaserver app_data_file:file { { getattr open read ioctl lock } { open append write } };
-allow mediaserver platform_app_data_file:file { getattr read };
-allow mediaserver sdcard_type:file write;
-allow mediaserver { gpu_device graphics_device }:chr_file { { getattr open read ioctl lock } { open append write } };
-allow mediaserver video_device:dir { open getattr read search ioctl };
-allow mediaserver video_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow mediaserver audio_device:dir { open getattr read search ioctl };
-allow mediaserver qemu_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow mediaserver tee_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow mediaserver audio_prop:property_service set;
-
-# Access audio devices at all.
-allow mediaserver audio_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# XXX Label with a specific type?
-allow mediaserver sysfs:file { { getattr open read ioctl lock } { open append write } };
-
-# XXX Why?
-allow mediaserver apk_data_file:file { read getattr };
-
-# Access camera device.
-allow mediaserver camera_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow mediaserver rpmsg_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Inter System processes communicate over named pipe (FIFO)
-allow mediaserver system_server:fifo_file { getattr open read ioctl lock };
-
-# Camera data
-
-#line 52
-allow mediaserver camera_data_file:dir { open getattr read search ioctl };
-#line 52
-allow mediaserver camera_data_file:{ file lnk_file } { getattr open read ioctl lock };
-#line 52
-
-
-#line 53
-allow mediaserver media_rw_data_file:dir { open getattr read search ioctl };
-#line 53
-allow mediaserver media_rw_data_file:{ file lnk_file } { getattr open read ioctl lock };
-#line 53
-
-
-# Grant access to audio files to mediaserver
-allow mediaserver audio_data_file:dir { { open getattr read search ioctl } add_name write };
-allow mediaserver audio_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# Read/[write] to /proc/net/xt_qtaguid/ctrl and /dev/xt_qtaguid
-allow mediaserver qtaguid_proc:file { { getattr open read ioctl lock } { open append write } };
-allow mediaserver qtaguid_device:chr_file { getattr open read ioctl lock };
-
-# Allow abstract socket connection
-allow mediaserver rild:unix_stream_socket { connectto read write setopt };
-
-# Needed on some devices for playing DRM protected content,
-# but seems expected and appropriate for all devices.
-
-#line 68
-allow mediaserver drmserver_socket:sock_file write;
-#line 68
-allow mediaserver drmserver:unix_stream_socket connectto;
-#line 68
-
-
-# Needed on some devices for playing audio on paired BT device,
-# but seems appropriate for all devices.
-
-#line 72
-allow mediaserver bluetooth_socket:sock_file write;
-#line 72
-allow mediaserver bluetooth:unix_stream_socket connectto;
-#line 72
-
-#line 1 "external/sepolicy/mtp.te"
-# vpn tunneling protocol manager
-type mtp, domain;
-
-#line 3
-typeattribute mtp mlstrustedsubject;
-#line 3
-typeattribute mtp unconfineddomain;
-#line 3
-
-type mtp_exec, exec_type, file_type;
-
-
-#line 6
-
-#line 6
-# Allow the necessary permissions.
-#line 6
-
-#line 6
-# Old domain may exec the file and transition to the new domain.
-#line 6
-allow init mtp_exec:file { getattr open read execute };
-#line 6
-allow init mtp:process transition;
-#line 6
-# New domain is entered by executing the file.
-#line 6
-allow mtp mtp_exec:file { entrypoint read execute };
-#line 6
-# New domain can send SIGCHLD to its caller.
-#line 6
-allow mtp init:process sigchld;
-#line 6
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 6
-dontaudit init mtp:process noatsecure;
-#line 6
-# XXX dontaudit candidate but requires further study.
-#line 6
-allow init mtp:process { siginh rlimitinh };
-#line 6
-
-#line 6
-# Make the transition occur by default.
-#line 6
-type_transition init mtp_exec:process mtp;
-#line 6
-
-#line 6
-
-#line 6
-type mtp_tmpfs, file_type;
-#line 6
-type_transition mtp tmpfs:file mtp_tmpfs;
-#line 6
-allow mtp mtp_tmpfs:file { read write };
-#line 6
-
-#line 6
-
-
-#line 7
-typeattribute mtp netdomain;
-#line 7
-
-
-# pptp policy
-allow mtp self:tcp_socket { create { ioctl read getattr write setattr append bind connect getopt setopt shutdown } };
-allow mtp self:socket { create { ioctl read getattr write setattr append bind connect getopt setopt shutdown } };
-allow mtp self:rawip_socket { create { ioctl read getattr write setattr append bind connect getopt setopt shutdown } };
-allow mtp self:capability net_raw;
-allow mtp ppp:process signal;
-allow mtp port:tcp_socket name_connect;
-allow mtp vpn_data_file:dir search;
-#line 1 "external/sepolicy/netd.te"
-# network manager
-type netd, domain;
-type netd_exec, exec_type, file_type;
-
-
-#line 5
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow init netd_exec:file { getattr open read execute };
-#line 5
-allow init netd:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow netd netd_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow netd init:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit init netd:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow init netd:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition init netd_exec:process netd;
-#line 5
-
-#line 5
-
-#line 5
-type netd_tmpfs, file_type;
-#line 5
-type_transition netd tmpfs:file netd_tmpfs;
-#line 5
-allow netd netd_tmpfs:file { read write };
-#line 5
-
-#line 5
-
-
-#line 6
-typeattribute netd netdomain;
-#line 6
-
-
-allow netd self:capability { net_admin net_raw kill fsetid };
-allow netd self:netlink_kobject_uevent_socket *;
-allow netd self:netlink_route_socket *;
-allow netd self:netlink_nflog_socket *;
-allow netd self:rawip_socket *;
-allow netd self:unix_stream_socket *;
-allow netd shell_exec:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-allow netd system_file:file { getattr execute execute_no_trans };
-allow netd devpts:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# For /proc/sys/net/ipv[46]/route/flush.
-allow netd proc_net:file write;
-
-# For /sys/modules/bcmdhd/parameters/firmware_path
-# XXX Split into its own type.
-allow netd sysfs:file write;
-
-# Set dhcp lease for PAN connection
-
-#line 26
-allow netd property_socket:sock_file write;
-#line 26
-allow netd init:unix_stream_socket connectto;
-#line 26
-
-allow netd system_prop:property_service set;
-
-# Connect to PAN
-
-#line 30
-# Allow the necessary permissions.
-#line 30
-
-#line 30
-# Old domain may exec the file and transition to the new domain.
-#line 30
-allow netd dhcp_exec:file { getattr open read execute };
-#line 30
-allow netd dhcp:process transition;
-#line 30
-# New domain is entered by executing the file.
-#line 30
-allow dhcp dhcp_exec:file { entrypoint read execute };
-#line 30
-# New domain can send SIGCHLD to its caller.
-#line 30
-allow dhcp netd:process sigchld;
-#line 30
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 30
-dontaudit netd dhcp:process noatsecure;
-#line 30
-# XXX dontaudit candidate but requires further study.
-#line 30
-allow netd dhcp:process { siginh rlimitinh };
-#line 30
-
-#line 30
-# Make the transition occur by default.
-#line 30
-type_transition netd dhcp_exec:process dhcp;
-#line 30
-
-allow netd dhcp:process signal;
-
-# Needed to update /data/misc/wifi/hostapd.conf
-# TODO: See what we can do to reduce the need for
-# these capabilities
-allow netd self:capability { dac_override chown fowner };
-allow netd wifi_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow netd wifi_data_file:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-
-# Allow netd to spawn hostapd in it's own domain
-
-#line 41
-# Allow the necessary permissions.
-#line 41
-
-#line 41
-# Old domain may exec the file and transition to the new domain.
-#line 41
-allow netd hostapd_exec:file { getattr open read execute };
-#line 41
-allow netd hostapd:process transition;
-#line 41
-# New domain is entered by executing the file.
-#line 41
-allow hostapd hostapd_exec:file { entrypoint read execute };
-#line 41
-# New domain can send SIGCHLD to its caller.
-#line 41
-allow hostapd netd:process sigchld;
-#line 41
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 41
-dontaudit netd hostapd:process noatsecure;
-#line 41
-# XXX dontaudit candidate but requires further study.
-#line 41
-allow netd hostapd:process { siginh rlimitinh };
-#line 41
-
-#line 41
-# Make the transition occur by default.
-#line 41
-type_transition netd hostapd_exec:process hostapd;
-#line 41
-
-allow netd hostapd:process signal;
-
-# Allow netd to spawn dnsmasq in it's own domain
-
-#line 45
-# Allow the necessary permissions.
-#line 45
-
-#line 45
-# Old domain may exec the file and transition to the new domain.
-#line 45
-allow netd dnsmasq_exec:file { getattr open read execute };
-#line 45
-allow netd dnsmasq:process transition;
-#line 45
-# New domain is entered by executing the file.
-#line 45
-allow dnsmasq dnsmasq_exec:file { entrypoint read execute };
-#line 45
-# New domain can send SIGCHLD to its caller.
-#line 45
-allow dnsmasq netd:process sigchld;
-#line 45
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 45
-dontaudit netd dnsmasq:process noatsecure;
-#line 45
-# XXX dontaudit candidate but requires further study.
-#line 45
-allow netd dnsmasq:process { siginh rlimitinh };
-#line 45
-
-#line 45
-# Make the transition occur by default.
-#line 45
-type_transition netd dnsmasq_exec:process dnsmasq;
-#line 45
-
-allow netd dnsmasq:process signal;
-
-# Allow netd to start clatd in its own domain
-
-#line 49
-# Allow the necessary permissions.
-#line 49
-
-#line 49
-# Old domain may exec the file and transition to the new domain.
-#line 49
-allow netd clatd_exec:file { getattr open read execute };
-#line 49
-allow netd clatd:process transition;
-#line 49
-# New domain is entered by executing the file.
-#line 49
-allow clatd clatd_exec:file { entrypoint read execute };
-#line 49
-# New domain can send SIGCHLD to its caller.
-#line 49
-allow clatd netd:process sigchld;
-#line 49
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 49
-dontaudit netd clatd:process noatsecure;
-#line 49
-# XXX dontaudit candidate but requires further study.
-#line 49
-allow netd clatd:process { siginh rlimitinh };
-#line 49
-
-#line 49
-# Make the transition occur by default.
-#line 49
-type_transition netd clatd_exec:process clatd;
-#line 49
-
-allow netd clatd:process signal;
-
-# Support netd running mdnsd
-# TODO: prune this back further
-allow netd ctl_default_prop:property_service set;
-allow netd device:sock_file write;
-
-###
-### Neverallow rules
-###
-### netd should NEVER do any of this
-
-# Block device access.
-neverallow netd dev_type:blk_file { read write };
-
-# Setting SELinux enforcing status or booleans.
-neverallow netd kernel:security { setenforce setbool };
-
-# Load security policy.
-neverallow netd kernel:security load_policy;
-
-# ptrace any other app
-neverallow netd { domain }:process ptrace;
-
-# Write to /system.
-neverallow netd system_file:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } } write;
-
-# Write to files in /data/data or system files on /data
-neverallow netd { app_data_file system_data_file }:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } } write;
-#line 1 "external/sepolicy/net.te"
-# Network types
-type node, node_type;
-type netif, netif_type;
-type port, port_type;
-
-# Use network sockets.
-allow netdomain self:{ tcp_socket udp_socket } *;
-# Connect to ports.
-allow netdomain port_type:tcp_socket name_connect;
-# Bind to ports.
-allow netdomain node_type:{ tcp_socket udp_socket } node_bind;
-allow netdomain port_type:udp_socket name_bind;
-allow netdomain port_type:tcp_socket name_bind;
-# Get route information.
-allow netdomain self:netlink_route_socket { create bind read nlmsg_read };
-
-# Talks to netd via dnsproxyd socket.
-
-#line 18
-allow netdomain dnsproxyd_socket:sock_file write;
-#line 18
-allow netdomain netd:unix_stream_socket connectto;
-#line 18
-
-#line 1 "external/sepolicy/nfc.te"
-# nfc subsystem
-type nfc, domain;
-
-#line 3
-typeattribute nfc appdomain;
-#line 3
-# Label ashmem objects with our own unique type.
-#line 3
-
-#line 3
-type nfc_tmpfs, file_type;
-#line 3
-type_transition nfc tmpfs:file nfc_tmpfs;
-#line 3
-allow nfc nfc_tmpfs:file { read write };
-#line 3
-
-#line 3
-# Map with PROT_EXEC.
-#line 3
-allow nfc nfc_tmpfs:file execute;
-#line 3
-
-
-#line 4
-typeattribute nfc binderservicedomain;
-#line 4
-
-
-# NFC device access.
-allow nfc nfc_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Data file accesses.
-allow nfc nfc_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow nfc nfc_data_file:{ file lnk_file sock_file fifo_file } { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-allow nfc sysfs_nfc_power_writable:file { { getattr open read ioctl lock } { open append write } };
-allow nfc sysfs:file write;
-
-allow nfc sdcard_type:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow nfc sdcard_type:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-#line 1 "external/sepolicy/platform_app.te"
-###
-### Apps signed with the platform key.
-###
-
-type platform_app, domain;
-
-#line 6
-typeattribute platform_app mlstrustedsubject;
-#line 6
-typeattribute platform_app unconfineddomain;
-#line 6
-
-
-#line 7
-typeattribute platform_app appdomain;
-#line 7
-# Label ashmem objects with our own unique type.
-#line 7
-
-#line 7
-type platform_app_tmpfs, file_type;
-#line 7
-type_transition platform_app tmpfs:file platform_app_tmpfs;
-#line 7
-allow platform_app platform_app_tmpfs:file { read write };
-#line 7
-
-#line 7
-# Map with PROT_EXEC.
-#line 7
-allow platform_app platform_app_tmpfs:file execute;
-#line 7
-
-
-#line 8
-typeattribute platform_app platformappdomain;
-#line 8
-typeattribute platform_app mlstrustedsubject;
-#line 8
-
-# Access the network.
-
-#line 10
-typeattribute platform_app netdomain;
-#line 10
-
-# Access bluetooth.
-
-#line 12
-typeattribute platform_app bluetoothdomain;
-#line 12
-
-# Write to /cache.
-allow platform_app cache_file:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-allow platform_app cache_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-# Read from /data/local.
-allow platform_app shell_data_file:dir search;
-allow platform_app shell_data_file:file { open getattr read };
-allow platform_app shell_data_file:lnk_file read;
-# Populate /data/app/vmdl*.tmp, /data/app-private/vmdl*.tmp files
-# created by system server.
-allow platform_app { apk_tmp_file apk_private_tmp_file }:file { { getattr open read ioctl lock } { open append write } };
-allow platform_app apk_private_data_file:dir search;
-# ASEC
-allow platform_app asec_apk_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow platform_app asec_apk_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-# Access download files.
-allow platform_app download_file:file { { getattr open read ioctl lock } { open append write } };
-# Allow BackupManagerService to backup all app domains
-allow platform_app appdomain:fifo_file write;
-
-#
-# Rules for all platform app domains.
-#
-
-# App sandbox file accesses.
-allow platformappdomain platform_app_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow platformappdomain platform_app_data_file:{ file lnk_file sock_file fifo_file } { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow platformappdomain platform_app_data_file:file execute;
-# App sdcard file accesses
-allow platformappdomain sdcard_type:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow platformappdomain sdcard_type:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-# Access to /data/media.
-allow platformappdomain media_rw_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow platformappdomain media_rw_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-#line 1 "external/sepolicy/ppp.te"
-# Point to Point Protocol daemon
-type ppp, domain;
-
-#line 3
-typeattribute ppp mlstrustedsubject;
-#line 3
-typeattribute ppp unconfineddomain;
-#line 3
-
-type ppp_device, dev_type;
-type ppp_exec, exec_type, file_type;
-
-#line 6
-# Allow the necessary permissions.
-#line 6
-
-#line 6
-# Old domain may exec the file and transition to the new domain.
-#line 6
-allow mtp ppp_exec:file { getattr open read execute };
-#line 6
-allow mtp ppp:process transition;
-#line 6
-# New domain is entered by executing the file.
-#line 6
-allow ppp ppp_exec:file { entrypoint read execute };
-#line 6
-# New domain can send SIGCHLD to its caller.
-#line 6
-allow ppp mtp:process sigchld;
-#line 6
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 6
-dontaudit mtp ppp:process noatsecure;
-#line 6
-# XXX dontaudit candidate but requires further study.
-#line 6
-allow mtp ppp:process { siginh rlimitinh };
-#line 6
-
-#line 6
-# Make the transition occur by default.
-#line 6
-type_transition mtp ppp_exec:process ppp;
-#line 6
-
-
-allow ppp mtp:socket { ioctl read getattr write setattr append bind connect getopt setopt shutdown };
-allow ppp ppp_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow ppp self:capability net_admin;
-allow ppp self:udp_socket { create { ioctl read getattr write setattr append bind connect getopt setopt shutdown } };
-allow ppp system_file:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-allow ppp vpn_data_file:dir { open search write add_name remove_name };
-allow ppp vpn_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow ppp mtp:fd use;
-#line 1 "external/sepolicy/property.te"
-type default_prop, property_type;
-type shell_prop, property_type;
-type debug_prop, property_type;
-type debuggerd_prop, property_type;
-type radio_prop, property_type;
-type system_prop, property_type;
-type vold_prop, property_type;
-type rild_prop, property_type;
-type ctl_default_prop, property_type;
-type ctl_dumpstate_prop, property_type;
-type ctl_rildaemon_prop, property_type;
-type audio_prop, property_type;
-type security_prop, property_type;
-type bluetooth_prop, property_type;
-type powerctl_prop, property_type;
-#line 1 "external/sepolicy/qemud.te"
-# qemu support daemon
-type qemud, domain;
-type qemud_exec, exec_type, file_type;
-
-
-#line 5
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow init qemud_exec:file { getattr open read execute };
-#line 5
-allow init qemud:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow qemud qemud_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow qemud init:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit init qemud:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow init qemud:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition init qemud_exec:process qemud;
-#line 5
-
-#line 5
-
-#line 5
-type qemud_tmpfs, file_type;
-#line 5
-type_transition qemud tmpfs:file qemud_tmpfs;
-#line 5
-allow qemud qemud_tmpfs:file { read write };
-#line 5
-
-#line 5
-
-
-#line 6
-typeattribute qemud mlstrustedsubject;
-#line 6
-typeattribute qemud unconfineddomain;
-#line 1 "external/sepolicy/racoon.te"
-# IKE key management daemon
-type racoon, domain;
-
-#line 3
-typeattribute racoon mlstrustedsubject;
-#line 3
-typeattribute racoon unconfineddomain;
-#line 3
-
-type racoon_exec, exec_type, file_type;
-
-
-#line 6
-
-#line 6
-# Allow the necessary permissions.
-#line 6
-
-#line 6
-# Old domain may exec the file and transition to the new domain.
-#line 6
-allow init racoon_exec:file { getattr open read execute };
-#line 6
-allow init racoon:process transition;
-#line 6
-# New domain is entered by executing the file.
-#line 6
-allow racoon racoon_exec:file { entrypoint read execute };
-#line 6
-# New domain can send SIGCHLD to its caller.
-#line 6
-allow racoon init:process sigchld;
-#line 6
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 6
-dontaudit init racoon:process noatsecure;
-#line 6
-# XXX dontaudit candidate but requires further study.
-#line 6
-allow init racoon:process { siginh rlimitinh };
-#line 6
-
-#line 6
-# Make the transition occur by default.
-#line 6
-type_transition init racoon_exec:process racoon;
-#line 6
-
-#line 6
-
-#line 6
-type racoon_tmpfs, file_type;
-#line 6
-type_transition racoon tmpfs:file racoon_tmpfs;
-#line 6
-allow racoon racoon_tmpfs:file { read write };
-#line 6
-
-#line 6
-
-typeattribute racoon mlstrustedsubject;
-
-
-#line 9
-# Call the server domain and optionally transfer references to it.
-#line 9
-allow racoon servicemanager:binder { call transfer };
-#line 9
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 9
-allow servicemanager racoon:binder transfer;
-#line 9
-# Receive and use open files from the server.
-#line 9
-allow racoon servicemanager:fd use;
-#line 9
-
-
-#line 10
-# Call the server domain and optionally transfer references to it.
-#line 10
-allow racoon keystore:binder { call transfer };
-#line 10
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 10
-allow keystore racoon:binder transfer;
-#line 10
-# Receive and use open files from the server.
-#line 10
-allow racoon keystore:fd use;
-#line 10
-
-
-allow racoon tun_device:chr_file { getattr open read ioctl lock };
-allow racoon cgroup:dir { add_name create };
-allow racoon kernel:system module_request;
-allow racoon port:udp_socket name_bind;
-allow racoon node:udp_socket node_bind;
-
-allow racoon self:{ key_socket udp_socket } { create { ioctl read getattr write setattr append bind connect getopt setopt shutdown } };
-allow racoon self:tun_socket create;
-allow racoon self:capability { net_admin net_bind_service net_raw setuid };
-
-# XXX: should we give ip-up-vpn its own label (currently racoon domain)
-allow racoon system_file:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-allow racoon vpn_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow racoon vpn_data_file:dir { open search write add_name remove_name };
-#line 1 "external/sepolicy/radio.te"
-# phone subsystem
-type radio, domain;
-
-#line 3
-typeattribute radio appdomain;
-#line 3
-# Label ashmem objects with our own unique type.
-#line 3
-
-#line 3
-type radio_tmpfs, file_type;
-#line 3
-type_transition radio tmpfs:file radio_tmpfs;
-#line 3
-allow radio radio_tmpfs:file { read write };
-#line 3
-
-#line 3
-# Map with PROT_EXEC.
-#line 3
-allow radio radio_tmpfs:file execute;
-#line 3
-
-
-#line 4
-typeattribute radio netdomain;
-#line 4
-
-
-#line 5
-typeattribute radio bluetoothdomain;
-#line 5
-
-
-#line 6
-typeattribute radio binderservicedomain;
-#line 6
-
-
-# Talks to init via the property socket.
-
-#line 9
-allow radio property_socket:sock_file write;
-#line 9
-allow radio init:unix_stream_socket connectto;
-#line 9
-
-
-# Talks to rild via the rild socket.
-
-#line 12
-allow radio rild_socket:sock_file write;
-#line 12
-allow radio rild:unix_stream_socket connectto;
-#line 12
-
-
-# Data file accesses.
-allow radio radio_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow radio radio_data_file:{ file lnk_file sock_file fifo_file } { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-allow radio alarm_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Property service
-allow radio radio_prop:property_service set;
-
-# ctl interface
-allow radio ctl_rildaemon_prop:property_service set;
-#line 1 "external/sepolicy/recovery.te"
-# recovery console (used in recovery init.rc for /sbin/recovery)
-type recovery, domain;
-allow recovery rootfs:file entrypoint;
-
-#line 4
-typeattribute recovery mlstrustedsubject;
-#line 4
-typeattribute recovery unconfineddomain;
-#line 4
-
-
-#line 5
-typeattribute recovery relabeltodomain;
-#line 5
-
-
-allow recovery self:capability2 mac_admin;
-
-allow recovery {fs_type dev_type -kmem_device file_type}:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } } relabelto;
-allow recovery unlabeled:filesystem mount;
-allow recovery fs_type:filesystem *;
-
-# Required to e.g. wipe userdata/cache.
-allow recovery dev_type:blk_file { { getattr open read ioctl lock } { open append write } };
-
-allow recovery self:process execmem;
-allow recovery ashmem_device:chr_file execute;
-allow recovery tmpfs:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-
-## TODO: Investigate whether it is safe to remove these
-allow recovery self:capability { sys_rawio mknod };
-auditallow recovery self:capability { sys_rawio mknod };
-#line 1 "external/sepolicy/release_app.te"
-###
-### Apps signed with the release key (testkey in AOSP).
-###
-
-type release_app, domain;
-
-#line 6
-typeattribute release_app mlstrustedsubject;
-#line 6
-typeattribute release_app unconfineddomain;
-#line 6
-
-
-#line 7
-typeattribute release_app appdomain;
-#line 7
-# Label ashmem objects with our own unique type.
-#line 7
-
-#line 7
-type release_app_tmpfs, file_type;
-#line 7
-type_transition release_app tmpfs:file release_app_tmpfs;
-#line 7
-allow release_app release_app_tmpfs:file { read write };
-#line 7
-
-#line 7
-# Map with PROT_EXEC.
-#line 7
-allow release_app release_app_tmpfs:file execute;
-#line 7
-
-
-#line 8
-typeattribute release_app platformappdomain;
-#line 8
-typeattribute release_app mlstrustedsubject;
-#line 8
-
-# Access the network.
-
-#line 10
-typeattribute release_app netdomain;
-#line 10
-
-# Access bluetooth.
-
-#line 12
-typeattribute release_app bluetoothdomain;
-#line 12
-
-
-# Write to /cache.
-allow release_app cache_file:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-allow release_app cache_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-#line 1 "external/sepolicy/rild.te"
-# rild - radio interface layer daemon
-type rild, domain;
-
-#line 3
-typeattribute rild mlstrustedsubject;
-#line 3
-typeattribute rild unconfineddomain;
-#line 3
-
-type rild_exec, exec_type, file_type;
-
-
-#line 6
-
-#line 6
-# Allow the necessary permissions.
-#line 6
-
-#line 6
-# Old domain may exec the file and transition to the new domain.
-#line 6
-allow init rild_exec:file { getattr open read execute };
-#line 6
-allow init rild:process transition;
-#line 6
-# New domain is entered by executing the file.
-#line 6
-allow rild rild_exec:file { entrypoint read execute };
-#line 6
-# New domain can send SIGCHLD to its caller.
-#line 6
-allow rild init:process sigchld;
-#line 6
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 6
-dontaudit init rild:process noatsecure;
-#line 6
-# XXX dontaudit candidate but requires further study.
-#line 6
-allow init rild:process { siginh rlimitinh };
-#line 6
-
-#line 6
-# Make the transition occur by default.
-#line 6
-type_transition init rild_exec:process rild;
-#line 6
-
-#line 6
-
-#line 6
-type rild_tmpfs, file_type;
-#line 6
-type_transition rild tmpfs:file rild_tmpfs;
-#line 6
-allow rild rild_tmpfs:file { read write };
-#line 6
-
-#line 6
-
-
-#line 7
-typeattribute rild netdomain;
-#line 7
-
-allow rild self:netlink_route_socket { setopt write };
-allow rild kernel:system module_request;
-
-#line 10
-allow rild property_socket:sock_file write;
-#line 10
-allow rild init:unix_stream_socket connectto;
-#line 10
-
-
-#line 11
-allow rild qemud_socket:sock_file write;
-#line 11
-allow rild qemud:unix_stream_socket connectto;
-#line 11
-
-allow rild self:capability { setuid net_admin net_raw };
-allow rild alarm_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow rild cgroup:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow rild radio_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow rild radio_device:blk_file { getattr open read ioctl lock };
-allow rild qemu_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow rild mtd_device:dir search;
-allow rild efs_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow rild efs_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow rild shell_exec:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-allow rild bluetooth_efs_file:file { getattr open read ioctl lock };
-allow rild bluetooth_efs_file:dir { open getattr read search ioctl };
-allow rild radio_data_file:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-allow rild radio_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow rild sdcard_type:dir { open getattr read search ioctl };
-allow rild system_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow rild system_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow rild system_file:file { getattr execute execute_no_trans };
-dontaudit rild self:capability sys_admin;
-
-# property service
-allow rild rild_prop:property_service set;
-allow rild radio_prop:property_service set;
-
-# Read/Write to uart driver (for GPS)
-allow rild gps_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-allow rild tty_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Allow rild to create, bind, read, write to itself through a netlink socket
-allow rild self:netlink_socket { create bind read write };
-
-allow rild self:netlink_kobject_uevent_socket { bind create getopt read setopt };
-
-# Access to wake locks
-allow rild sysfs_wake_lock:file { { getattr open read ioctl lock } { open append write } };
-
-allow rild self:socket { create { ioctl read getattr write setattr append bind connect getopt setopt shutdown } };
-#line 1 "external/sepolicy/runas.te"
-type runas, domain, mlstrustedsubject;
-type runas_exec, exec_type, file_type;
-
-# ndk-gdb invokes adb shell run-as.
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow shell runas_exec:file { getattr open read execute };
-#line 5
-allow shell runas:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow runas runas_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow runas shell:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit shell runas:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow shell runas:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition shell runas_exec:process runas;
-#line 5
-
-allow runas adbd:process sigchld;
-allow runas shell:fd  use;
-allow runas devpts:chr_file { read write ioctl };
-
-# run-as reads package information.
-allow runas system_data_file:file { getattr open read ioctl lock };
-
-# run-as checks and changes to the app data dir.
-dontaudit runas self:capability dac_override;
-allow runas app_data_file:dir { getattr search };
-
-# run-as switches to the app UID/GID.
-allow runas self:capability { setuid setgid };
-
-# run-as switches to the app security context.
-# read /seapp_contexts and /data/security/seapp_contexts
-
-#line 22
-allow runas security_file:dir { open getattr read search ioctl };
-#line 22
-allow runas security_file:file { getattr open read ioctl lock };
-#line 22
-allow runas security_file:lnk_file { getattr open read ioctl lock };
-#line 22
-allow runas selinuxfs:dir { open getattr read search ioctl };
-#line 22
-allow runas selinuxfs:file { getattr open read ioctl lock };
-#line 22
-allow runas rootfs:dir { open getattr read search ioctl };
-#line 22
-allow runas rootfs:file { getattr open read ioctl lock };
-#line 22
-
-
-#line 23
-allow runas selinuxfs:dir { open getattr read search ioctl };
-#line 23
-allow runas selinuxfs:file { { getattr open read ioctl lock } { open append write } };
-#line 23
-allow runas kernel:security check_context;
-#line 23
- # validate context
-allow runas { appdomain -system_app }:process dyntransition; # setcon
-#line 1 "external/sepolicy/sdcardd.te"
-type sdcardd, domain;
-type sdcardd_exec, exec_type, file_type;
-
-
-#line 4
-
-#line 4
-# Allow the necessary permissions.
-#line 4
-
-#line 4
-# Old domain may exec the file and transition to the new domain.
-#line 4
-allow init sdcardd_exec:file { getattr open read execute };
-#line 4
-allow init sdcardd:process transition;
-#line 4
-# New domain is entered by executing the file.
-#line 4
-allow sdcardd sdcardd_exec:file { entrypoint read execute };
-#line 4
-# New domain can send SIGCHLD to its caller.
-#line 4
-allow sdcardd init:process sigchld;
-#line 4
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 4
-dontaudit init sdcardd:process noatsecure;
-#line 4
-# XXX dontaudit candidate but requires further study.
-#line 4
-allow init sdcardd:process { siginh rlimitinh };
-#line 4
-
-#line 4
-# Make the transition occur by default.
-#line 4
-type_transition init sdcardd_exec:process sdcardd;
-#line 4
-
-#line 4
-
-#line 4
-type sdcardd_tmpfs, file_type;
-#line 4
-type_transition sdcardd tmpfs:file sdcardd_tmpfs;
-#line 4
-allow sdcardd sdcardd_tmpfs:file { read write };
-#line 4
-
-#line 4
-
-
-allow sdcardd cgroup:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow sdcardd fuse_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow sdcardd rootfs:dir mounton;
-allow sdcardd sdcard_type:filesystem mount;
-allow sdcardd self:capability { setuid setgid dac_override sys_admin sys_resource };
-
-allow sdcardd sdcard_type:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow sdcardd sdcard_type:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-type_transition sdcardd system_data_file:{ dir file } media_rw_data_file;
-allow sdcardd media_rw_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow sdcardd media_rw_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# Read /data/system/packages.list.
-allow sdcardd system_data_file:file { getattr open read ioctl lock };
-
-# Compatibility for existing devices with /data/media in system_data_file.
-# TODO: Remove these lines after we have guaranteed that /data/media has been relabeled to media_rw_data_file.
-allow sdcardd system_data_file:dir  { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow sdcardd system_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-#line 1 "external/sepolicy/servicemanager.te"
-# servicemanager - the Binder context manager
-type servicemanager, domain;
-type servicemanager_exec, exec_type, file_type;
-
-
-#line 5
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow init servicemanager_exec:file { getattr open read execute };
-#line 5
-allow init servicemanager:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow servicemanager servicemanager_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow servicemanager init:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit init servicemanager:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow init servicemanager:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition init servicemanager_exec:process servicemanager;
-#line 5
-
-#line 5
-
-#line 5
-type servicemanager_tmpfs, file_type;
-#line 5
-type_transition servicemanager tmpfs:file servicemanager_tmpfs;
-#line 5
-allow servicemanager servicemanager_tmpfs:file { read write };
-#line 5
-
-#line 5
-
-
-# Note that we do not use the binder_* macros here.
-# servicemanager is unique in that it only provides
-# name service (aka context manager) for Binder.
-# As such, it only ever receives and transfers other references
-# created by other domains.  It never passes its own references
-# or initiates a Binder IPC.
-allow servicemanager self:binder set_context_mgr;
-allow servicemanager domain:binder transfer;
-#line 1 "external/sepolicy/shared_app.te"
-###
-### Apps signed with the shared key.
-###
-
-type shared_app, domain;
-
-#line 6
-typeattribute shared_app mlstrustedsubject;
-#line 6
-typeattribute shared_app unconfineddomain;
-#line 6
-
-
-#line 7
-typeattribute shared_app appdomain;
-#line 7
-# Label ashmem objects with our own unique type.
-#line 7
-
-#line 7
-type shared_app_tmpfs, file_type;
-#line 7
-type_transition shared_app tmpfs:file shared_app_tmpfs;
-#line 7
-allow shared_app shared_app_tmpfs:file { read write };
-#line 7
-
-#line 7
-# Map with PROT_EXEC.
-#line 7
-allow shared_app shared_app_tmpfs:file execute;
-#line 7
-
-
-#line 8
-typeattribute shared_app platformappdomain;
-#line 8
-typeattribute shared_app mlstrustedsubject;
-#line 8
-
-# Access the network.
-
-#line 10
-typeattribute shared_app netdomain;
-#line 10
-
-# Access bluetooth.
-
-#line 12
-typeattribute shared_app bluetoothdomain;
-#line 12
-
-#line 1 "external/sepolicy/shelldomain.te"
-# Rules for all shell domains (e.g. console service and adb shell).
-
-# Access /data/local/tmp.
-allow shelldomain shell_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow shelldomain shell_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow shelldomain shell_data_file:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-
-# Access sdcard.
-allow shelldomain sdcard_type:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow shelldomain sdcard_type:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# adb bugreport
-
-#line 13
-allow shelldomain dumpstate_socket:sock_file write;
-#line 13
-allow shelldomain dumpstate:unix_stream_socket connectto;
-#line 13
-
-
-allow shelldomain rootfs:dir { open getattr read search ioctl };
-allow shelldomain devpts:chr_file { { getattr open read ioctl lock } { open append write } };
-allow shelldomain tty_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow shelldomain console_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow shelldomain input_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow shelldomain system_file:file { getattr execute execute_no_trans };
-allow shelldomain shell_exec:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-allow shelldomain zygote_exec:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-
-
-#line 24
-allow shelldomain apk_data_file:dir { open getattr read search ioctl };
-#line 24
-allow shelldomain apk_data_file:{ file lnk_file } { getattr open read ioctl lock };
-#line 24
-
-
-# Set properties.
-
-#line 27
-allow shelldomain property_socket:sock_file write;
-#line 27
-allow shelldomain init:unix_stream_socket connectto;
-#line 27
-
-allow shelldomain shell_prop:property_service set;
-allow shelldomain ctl_dumpstate_prop:property_service set;
-allow shelldomain debug_prop:property_service set;
-allow shelldomain powerctl_prop:property_service set;
-
-# ndk-gdb invokes adb shell ps to find the app PID.
-
-#line 34
-allow shelldomain { appdomain -system_app }:dir { open getattr read search ioctl };
-#line 34
-allow shelldomain { appdomain -system_app }:{ file lnk_file } { getattr open read ioctl lock };
-#line 34
-
-
-# ndk-gdb invokes adb shell ls to check the app data dir.
-allow shelldomain app_data_file:dir search;
-
-# ps and ps -Z output for app processes.
-
-#line 40
-allow shelldomain appdomain:dir { open getattr read search ioctl };
-#line 40
-allow shelldomain appdomain:{ file lnk_file } { getattr open read ioctl lock };
-#line 40
-
-allow shelldomain appdomain:process getattr;
-#line 1 "external/sepolicy/shell.te"
-# Domain for shell processes spawned by ADB
-type shell, domain, shelldomain, mlstrustedsubject;
-type shell_exec, exec_type, file_type;
-
-# Create and use network sockets.
-
-#line 6
-typeattribute shell netdomain;
-#line 6
-
-
-# Run app_process.
-# XXX Transition into its own domain?
-
-#line 10
-typeattribute shell appdomain;
-#line 10
-# Label ashmem objects with our own unique type.
-#line 10
-
-#line 10
-type shell_tmpfs, file_type;
-#line 10
-type_transition shell tmpfs:file shell_tmpfs;
-#line 10
-allow shell shell_tmpfs:file { read write };
-#line 10
-
-#line 10
-# Map with PROT_EXEC.
-#line 10
-allow shell shell_tmpfs:file execute;
-#line 10
-
-
-# inherits from shelldomain.te
-#line 1 "external/sepolicy/surfaceflinger.te"
-# surfaceflinger - display compositor service
-type surfaceflinger, domain;
-
-#line 3
-typeattribute surfaceflinger mlstrustedsubject;
-#line 3
-typeattribute surfaceflinger unconfineddomain;
-#line 3
-
-type surfaceflinger_exec, exec_type, file_type;
-
-
-#line 6
-
-#line 6
-# Allow the necessary permissions.
-#line 6
-
-#line 6
-# Old domain may exec the file and transition to the new domain.
-#line 6
-allow init surfaceflinger_exec:file { getattr open read execute };
-#line 6
-allow init surfaceflinger:process transition;
-#line 6
-# New domain is entered by executing the file.
-#line 6
-allow surfaceflinger surfaceflinger_exec:file { entrypoint read execute };
-#line 6
-# New domain can send SIGCHLD to its caller.
-#line 6
-allow surfaceflinger init:process sigchld;
-#line 6
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 6
-dontaudit init surfaceflinger:process noatsecure;
-#line 6
-# XXX dontaudit candidate but requires further study.
-#line 6
-allow init surfaceflinger:process { siginh rlimitinh };
-#line 6
-
-#line 6
-# Make the transition occur by default.
-#line 6
-type_transition init surfaceflinger_exec:process surfaceflinger;
-#line 6
-
-#line 6
-
-#line 6
-type surfaceflinger_tmpfs, file_type;
-#line 6
-type_transition surfaceflinger tmpfs:file surfaceflinger_tmpfs;
-#line 6
-allow surfaceflinger surfaceflinger_tmpfs:file { read write };
-#line 6
-
-#line 6
-
-typeattribute surfaceflinger mlstrustedsubject;
-
-# Talk to init over the property socket.
-
-#line 10
-allow surfaceflinger property_socket:sock_file write;
-#line 10
-allow surfaceflinger init:unix_stream_socket connectto;
-#line 10
-
-
-# Perform Binder IPC.
-
-#line 13
-# Call the servicemanager and transfer references to it.
-#line 13
-allow surfaceflinger servicemanager:binder { call transfer };
-#line 13
-# rw access to /dev/binder and /dev/ashmem is presently granted to
-#line 13
-# all domains in domain.te.
-#line 13
-
-
-#line 14
-# Call the server domain and optionally transfer references to it.
-#line 14
-allow surfaceflinger system_server:binder { call transfer };
-#line 14
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 14
-allow system_server surfaceflinger:binder transfer;
-#line 14
-# Receive and use open files from the server.
-#line 14
-allow surfaceflinger system_server:fd use;
-#line 14
-
-
-#line 15
-# Call the server domain and optionally transfer references to it.
-#line 15
-allow surfaceflinger nfc:binder { call transfer };
-#line 15
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 15
-allow nfc surfaceflinger:binder transfer;
-#line 15
-# Receive and use open files from the server.
-#line 15
-allow surfaceflinger nfc:fd use;
-#line 15
-
-
-#line 16
-# Call the server domain and optionally transfer references to it.
-#line 16
-allow surfaceflinger mediaserver:binder { call transfer };
-#line 16
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 16
-allow mediaserver surfaceflinger:binder transfer;
-#line 16
-# Receive and use open files from the server.
-#line 16
-allow surfaceflinger mediaserver:fd use;
-#line 16
-
-
-#line 17
-typeattribute surfaceflinger binderservicedomain;
-#line 17
-
-
-# Access the GPU.
-allow surfaceflinger gpu_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Access /dev/graphics/fb0.
-allow surfaceflinger graphics_device:dir search;
-allow surfaceflinger graphics_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Access /dev/video1.
-allow surfaceflinger video_device:dir { open getattr read search ioctl };
-allow surfaceflinger video_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Create and use netlink kobject uevent sockets.
-allow surfaceflinger self:netlink_kobject_uevent_socket *;
-
-# Set properties.
-allow surfaceflinger system_prop:property_service set;
-allow surfaceflinger ctl_default_prop:property_service set;
-
-# Use open files supplied by an app.
-allow surfaceflinger appdomain:fd use;
-allow surfaceflinger platform_app_data_file:file { read write };
-allow surfaceflinger app_data_file:file { read write };
-
-# Use open file provided by bootanim.
-allow surfaceflinger bootanim:fd use;
-
-# Allow a dumpstate triggered screenshot
-
-#line 46
-# Call the server domain and optionally transfer references to it.
-#line 46
-allow surfaceflinger dumpstate:binder { call transfer };
-#line 46
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 46
-allow dumpstate surfaceflinger:binder transfer;
-#line 46
-# Receive and use open files from the server.
-#line 46
-allow surfaceflinger dumpstate:fd use;
-#line 46
-
-
-#line 47
-# Call the server domain and optionally transfer references to it.
-#line 47
-allow surfaceflinger shell:binder { call transfer };
-#line 47
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 47
-allow shell surfaceflinger:binder transfer;
-#line 47
-# Receive and use open files from the server.
-#line 47
-allow surfaceflinger shell:fd use;
-#line 47
-
-
-# Needed on some devices for playing DRM protected content,
-# but seems expected and appropriate for all devices.
-allow surfaceflinger tee:unix_stream_socket connectto;
-allow surfaceflinger tee_device:chr_file { { getattr open read ioctl lock } { open append write } };
-#line 1 "external/sepolicy/su.te"
-# File types must be defined for file_contexts.
-type su_exec, exec_type, file_type;
-
-#line 23
-
-#line 1 "external/sepolicy/system_app.te"
-#
-# Apps that run with the system UID, e.g. com.android.system.ui,
-# com.android.settings.  These are not as privileged as the system
-# server.
-#
-type system_app, domain;
-
-#line 7
-typeattribute system_app mlstrustedsubject;
-#line 7
-typeattribute system_app unconfineddomain;
-#line 7
-
-
-#line 8
-typeattribute system_app appdomain;
-#line 8
-# Label ashmem objects with our own unique type.
-#line 8
-
-#line 8
-type system_app_tmpfs, file_type;
-#line 8
-type_transition system_app tmpfs:file system_app_tmpfs;
-#line 8
-allow system_app system_app_tmpfs:file { read write };
-#line 8
-
-#line 8
-# Map with PROT_EXEC.
-#line 8
-allow system_app system_app_tmpfs:file execute;
-#line 8
-
-
-#line 9
-typeattribute system_app binderservicedomain;
-#line 9
-
-
-# Perform binder IPC to any app domain.
-
-#line 12
-# Call the server domain and optionally transfer references to it.
-#line 12
-allow system_app appdomain:binder { call transfer };
-#line 12
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 12
-allow appdomain system_app:binder transfer;
-#line 12
-# Receive and use open files from the server.
-#line 12
-allow system_app appdomain:fd use;
-#line 12
-
-
-# Read and write system data files.
-# May want to split into separate types.
-allow system_app system_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow system_app system_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# Read wallpaper file.
-allow system_app wallpaper_file:file { getattr open read ioctl lock };
-
-# Write to dalvikcache.
-allow system_app dalvikcache_data_file:file { write setattr };
-
-# Talk to keystore.
-
-#line 26
-allow system_app keystore_socket:sock_file write;
-#line 26
-allow system_app keystore:unix_stream_socket connectto;
-#line 26
-
-
-# Read SELinux enforcing status.
-
-#line 29
-allow system_app selinuxfs:dir { open getattr read search ioctl };
-#line 29
-allow system_app selinuxfs:file { getattr open read ioctl lock };
-#line 29
-
-
-# Settings app reads sdcard for storage stats
-allow system_app sdcard_type:dir { open getattr read search ioctl };
-
-# Write to properties
-
-#line 35
-allow system_app property_socket:sock_file write;
-#line 35
-allow system_app init:unix_stream_socket connectto;
-#line 35
-
-allow system_app debug_prop:property_service set;
-allow system_app radio_prop:property_service set;
-allow system_app system_prop:property_service set;
-#line 1 "external/sepolicy/system_server.te"
-#
-# System Server aka system_server spawned by zygote.
-# Most of the framework services run in this process.
-#
-type system_server, domain, mlstrustedsubject;
-
-#line 6
-typeattribute system_server mlstrustedsubject;
-#line 6
-typeattribute system_server unconfineddomain;
-#line 6
-
-
-# Define a type for tmpfs-backed ashmem regions.
-
-#line 9
-type system_server_tmpfs, file_type;
-#line 9
-type_transition system_server tmpfs:file system_server_tmpfs;
-#line 9
-allow system_server system_server_tmpfs:file { read write };
-#line 9
-
-
-# Dalvik Compiler JIT Mapping.
-allow system_server self:process execmem;
-allow system_server ashmem_device:chr_file execute;
-allow system_server system_server_tmpfs:file execute;
-
-# For art.
-allow system_server dalvikcache_data_file:file execute;
-
-# Child of the zygote.
-allow system_server zygote:fd use;
-allow system_server zygote:process sigchld;
-allow system_server zygote_tmpfs:file read;
-
-# Needed to close the zygote socket, which involves getopt / getattr
-# This should be deleted after b/12061011 is fixed
-allow system_server zygote:unix_stream_socket { getopt getattr };
-
-# system server gets network and bluetooth permissions.
-
-#line 29
-typeattribute system_server netdomain;
-#line 29
-
-
-#line 30
-typeattribute system_server bluetoothdomain;
-#line 30
-
-
-# These are the capabilities assigned by the zygote to the
-# system server.
-allow system_server self:capability {
-    kill
-    net_admin
-    net_bind_service
-    net_broadcast
-    net_raw
-    sys_boot
-    sys_module
-    sys_nice
-    sys_resource
-    sys_time
-    sys_tty_config
-};
-
-allow system_server self:capability2 block_suspend;
-
-# Triggered by /proc/pid accesses, not allowed.
-dontaudit system_server self:capability sys_ptrace;
-
-# Trigger module auto-load.
-allow system_server kernel:system module_request;
-
-# Use netlink uevent sockets.
-allow system_server self:netlink_kobject_uevent_socket *;
-
-# Kill apps.
-allow system_server appdomain:process { sigkill signal };
-
-# Set scheduling info for apps.
-allow system_server appdomain:process { getsched setsched };
-allow system_server mediaserver:process { getsched setsched };
-
-# Read /proc data for apps.
-allow system_server appdomain:dir { open getattr read search ioctl };
-allow system_server appdomain:{ file lnk_file } { { getattr open read ioctl lock } { open append write } };
-
-# Read/Write to /proc/net/xt_qtaguid/ctrl and and /dev/xt_qtaguid.
-allow system_server qtaguid_proc:file { { getattr open read ioctl lock } { open append write } };
-allow system_server qtaguid_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Read /sys/kernel/debug/wakeup_sources.
-allow system_server debugfs:file { getattr open read ioctl lock };
-
-# WifiWatchdog uses a packet_socket
-allow system_server self:packet_socket *;
-
-# 3rd party VPN clients require a tun_socket to be created
-allow system_server self:tun_socket create;
-
-# Notify init of death.
-allow system_server init:process sigchld;
-
-# Talk to init and various daemons via sockets.
-
-#line 87
-allow system_server property_socket:sock_file write;
-#line 87
-allow system_server init:unix_stream_socket connectto;
-#line 87
-
-
-#line 88
-allow system_server qemud_socket:sock_file write;
-#line 88
-allow system_server qemud:unix_stream_socket connectto;
-#line 88
-
-
-#line 89
-allow system_server installd_socket:sock_file write;
-#line 89
-allow system_server installd:unix_stream_socket connectto;
-#line 89
-
-
-#line 90
-allow system_server lmkd_socket:sock_file write;
-#line 90
-allow system_server lmkd:unix_stream_socket connectto;
-#line 90
-
-
-#line 91
-allow system_server netd_socket:sock_file write;
-#line 91
-allow system_server netd:unix_stream_socket connectto;
-#line 91
-
-
-#line 92
-allow system_server vold_socket:sock_file write;
-#line 92
-allow system_server vold:unix_stream_socket connectto;
-#line 92
-
-
-#line 93
-allow system_server zygote_socket:sock_file write;
-#line 93
-allow system_server zygote:unix_stream_socket connectto;
-#line 93
-
-
-#line 94
-allow system_server keystore_socket:sock_file write;
-#line 94
-allow system_server keystore:unix_stream_socket connectto;
-#line 94
-
-
-#line 95
-allow system_server gps_socket:sock_file write;
-#line 95
-allow system_server gpsd:unix_stream_socket connectto;
-#line 95
-
-
-#line 96
-allow system_server racoon_socket:sock_file write;
-#line 96
-allow system_server racoon:unix_stream_socket connectto;
-#line 96
-
-
-#line 97
-allow system_server wpa_socket:sock_file write;
-#line 97
-allow system_server wpa:unix_dgram_socket sendto;
-#line 97
-
-
-# Communicate over a socket created by surfaceflinger.
-allow system_server surfaceflinger:unix_stream_socket { read write setopt };
-
-# Perform Binder IPC.
-
-#line 103
-# Call the servicemanager and transfer references to it.
-#line 103
-allow system_server servicemanager:binder { call transfer };
-#line 103
-# rw access to /dev/binder and /dev/ashmem is presently granted to
-#line 103
-# all domains in domain.te.
-#line 103
-
-
-#line 104
-# Call the server domain and optionally transfer references to it.
-#line 104
-allow system_server binderservicedomain:binder { call transfer };
-#line 104
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 104
-allow binderservicedomain system_server:binder transfer;
-#line 104
-# Receive and use open files from the server.
-#line 104
-allow system_server binderservicedomain:fd use;
-#line 104
-
-
-#line 105
-# Call the server domain and optionally transfer references to it.
-#line 105
-allow system_server appdomain:binder { call transfer };
-#line 105
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 105
-allow appdomain system_server:binder transfer;
-#line 105
-# Receive and use open files from the server.
-#line 105
-allow system_server appdomain:fd use;
-#line 105
-
-
-#line 106
-# Call the server domain and optionally transfer references to it.
-#line 106
-allow system_server healthd:binder { call transfer };
-#line 106
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 106
-allow healthd system_server:binder transfer;
-#line 106
-# Receive and use open files from the server.
-#line 106
-allow system_server healthd:fd use;
-#line 106
-
-
-#line 107
-# Call the server domain and optionally transfer references to it.
-#line 107
-allow system_server dumpstate:binder { call transfer };
-#line 107
-# Allow the serverdomain to transfer references to the client on the reply.
-#line 107
-allow dumpstate system_server:binder transfer;
-#line 107
-# Receive and use open files from the server.
-#line 107
-allow system_server dumpstate:fd use;
-#line 107
-
-
-#line 108
-typeattribute system_server binderservicedomain;
-#line 108
-
-
-# Read /proc/pid files for Binder clients.
-
-#line 111
-allow system_server appdomain:dir { open getattr read search ioctl };
-#line 111
-allow system_server appdomain:{ file lnk_file } { getattr open read ioctl lock };
-#line 111
-
-
-#line 112
-allow system_server mediaserver:dir { open getattr read search ioctl };
-#line 112
-allow system_server mediaserver:{ file lnk_file } { getattr open read ioctl lock };
-#line 112
-
-allow system_server appdomain:process getattr;
-allow system_server mediaserver:process getattr;
-
-# Check SELinux permissions.
-
-#line 117
-allow system_server selinuxfs:dir { open getattr read search ioctl };
-#line 117
-allow system_server selinuxfs:file { { getattr open read ioctl lock } { open append write } };
-#line 117
-allow system_server kernel:security compute_av;
-#line 117
-allow system_server self:netlink_selinux_socket *;
-#line 117
-
-
-# XXX Label sysfs files with a specific type?
-allow system_server sysfs:file { { getattr open read ioctl lock } { open append write } };
-allow system_server sysfs_nfc_power_writable:file { { getattr open read ioctl lock } { open append write } };
-
-# Access devices.
-allow system_server device:dir { open getattr read search ioctl };
-allow system_server mdns_socket:sock_file { { getattr open read ioctl lock } { open append write } };
-allow system_server alarm_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow system_server gpu_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow system_server graphics_device:dir search;
-allow system_server graphics_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow system_server iio_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow system_server input_device:dir { open getattr read search ioctl };
-allow system_server input_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow system_server tty_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow system_server urandom_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow system_server usbaccessory_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow system_server video_device:dir { open getattr read search ioctl };
-allow system_server video_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow system_server qemu_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow system_server adbd_socket:sock_file { { getattr open read ioctl lock } { open append write } };
-
-# tun device used for 3rd party vpn apps
-allow system_server tun_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Manage data files.
-allow system_server data_file_type:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow system_server data_file_type:{ file lnk_file sock_file fifo_file } { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# Read /file_contexts and /data/security/file_contexts
-
-#line 149
-allow system_server security_file:dir { open getattr read search ioctl };
-#line 149
-allow system_server security_file:file { getattr open read ioctl lock };
-#line 149
-allow system_server security_file:lnk_file { getattr open read ioctl lock };
-#line 149
-allow system_server selinuxfs:dir { open getattr read search ioctl };
-#line 149
-allow system_server selinuxfs:file { getattr open read ioctl lock };
-#line 149
-allow system_server rootfs:dir { open getattr read search ioctl };
-#line 149
-allow system_server rootfs:file { getattr open read ioctl lock };
-#line 149
-
-
-# Relabel apk files.
-
-#line 152
-typeattribute system_server relabeltodomain;
-#line 152
-
-allow system_server { apk_tmp_file apk_private_tmp_file }:file { relabelfrom relabelto };
-allow system_server { apk_data_file apk_private_data_file }:file { relabelfrom relabelto };
-
-# Relabel wallpaper.
-allow system_server system_data_file:file relabelfrom;
-allow system_server wallpaper_file:file relabelto;
-allow system_server wallpaper_file:file { { getattr open read ioctl lock } { open append write } };
-
-# Relabel /data/anr.
-allow system_server system_data_file:dir relabelfrom;
-allow system_server anr_data_file:dir relabelto;
-
-# Property Service write
-allow system_server system_prop:property_service set;
-allow system_server radio_prop:property_service set;
-allow system_server debug_prop:property_service set;
-allow system_server powerctl_prop:property_service set;
-
-# ctl interface
-allow system_server ctl_default_prop:property_service set;
-
-# Create a socket for receiving info from wpa.
-type_transition system_server wifi_data_file:sock_file system_wpa_socket;
-type_transition system_server wpa_socket:sock_file system_wpa_socket;
-allow system_server wpa_socket:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-allow system_server system_wpa_socket:sock_file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# Remove sockets created by wpa_supplicant
-allow system_server wpa_socket:sock_file unlink;
-
-# Create a socket for connections from debuggerd.
-type_transition system_server system_data_file:sock_file system_ndebug_socket "ndebugsocket";
-allow system_server system_ndebug_socket:sock_file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# Specify any arguments to zygote.
-allow system_server self:zygote { specifyids specifyrlimits specifyseinfo };
-
-# Manage cache files.
-allow system_server cache_file:dir { relabelfrom { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } } };
-allow system_server cache_file:file { relabelfrom { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } } };
-
-# Run system programs, e.g. dexopt.
-allow system_server system_file:file { getattr execute execute_no_trans };
-
-# Allow reading of /proc/pid data for other domains.
-# XXX dontaudit candidate
-allow system_server domain:dir { open getattr read search ioctl };
-allow system_server domain:file { getattr open read ioctl lock };
-
-# LocationManager(e.g, GPS) needs to read and write
-# to uart driver and ctrl proc entry
-allow system_server gps_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow system_server gps_control:file { { getattr open read ioctl lock } { open append write } };
-
-# Allow system_server to use app-created sockets.
-allow system_server appdomain:{ tcp_socket udp_socket } { setopt read write };
-
-# Allow abstract socket connection
-allow system_server rild:unix_stream_socket connectto;
-
-# connect to vpn tunnel
-allow system_server mtp:unix_stream_socket { connectto };
-
-# BackupManagerService lets PMS create a data backup file
-allow system_server cache_backup_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-# Relabel /data/backup
-allow system_server backup_data_file:dir { relabelto relabelfrom };
-# Relabel /cache/.*\.{data|restore}
-allow system_server cache_backup_file:file { relabelto relabelfrom };
-# LocalTransport creates and relabels /cache/backup
-allow system_server cache_backup_file:dir { relabelto relabelfrom { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } } };
-
-# Allow system to talk to usb device
-allow system_server usb_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow system_server usb_device:dir { open getattr read search ioctl };
-
-# Allow system to talk to sensors
-allow system_server sensors_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Read from HW RNG (needed by EntropyMixer).
-allow system_server hw_random_device:chr_file { getattr open read ioctl lock };
-
-# Access to wake locks
-allow system_server sysfs_wake_lock:file { { getattr open read ioctl lock } { open append write } };
-
-# Read and delete files under /dev/fscklogs.
-
-#line 239
-allow system_server fscklogs:dir { open getattr read search ioctl };
-#line 239
-allow system_server fscklogs:{ file lnk_file } { getattr open read ioctl lock };
-#line 239
-
-allow system_server fscklogs:dir { write remove_name };
-allow system_server fscklogs:file unlink;
-
-# For SELinuxPolicyInstallReceiver
-
-#line 244
-
-#line 244
-allow system_server security_file:dir { open getattr read search ioctl };
-#line 244
-allow system_server security_file:file { getattr open read ioctl lock };
-#line 244
-allow system_server security_file:lnk_file { getattr open read ioctl lock };
-#line 244
-allow system_server selinuxfs:dir { open getattr read search ioctl };
-#line 244
-allow system_server selinuxfs:file { getattr open read ioctl lock };
-#line 244
-allow system_server rootfs:dir { open getattr read search ioctl };
-#line 244
-allow system_server rootfs:file { getattr open read ioctl lock };
-#line 244
-
-#line 244
-
-#line 244
-allow system_server property_socket:sock_file write;
-#line 244
-allow system_server init:unix_stream_socket connectto;
-#line 244
-
-#line 244
-allow system_server security_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-#line 244
-allow system_server security_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-#line 244
-allow system_server security_file:lnk_file { create rename unlink };
-#line 244
-allow system_server security_prop:property_service set;
-#line 244
-
-
-# For legacy unlabeled userdata on existing devices.
-# See discussion of Unlabeled files in domain.te for more information.
-# This rule is for dalvikcache mmap/mprotect PROT_EXEC.
-allow system_server unlabeled:file execute;
-
-# logd access, system_server inherit logd write socket
-# (urge is to deprecate this long term)
-allow system_server zygote:unix_dgram_socket write;
-
-# Be consistent with DAC permissions. Allow system_server to write to
-# /sys/module/lowmemorykiller/parameters/adj
-# /sys/module/lowmemorykiller/parameters/minfree
-allow system_server sysfs_lowmemorykiller:file { open append write };
-#line 1 "external/sepolicy/tee.te"
-##
-# trusted execution environment (tee) daemon
-#
-type tee, domain;
-type tee_exec, exec_type, file_type;
-type tee_device, dev_type;
-type tee_data_file, file_type, data_file_type;
-
-
-#line 9
-
-#line 9
-# Allow the necessary permissions.
-#line 9
-
-#line 9
-# Old domain may exec the file and transition to the new domain.
-#line 9
-allow init tee_exec:file { getattr open read execute };
-#line 9
-allow init tee:process transition;
-#line 9
-# New domain is entered by executing the file.
-#line 9
-allow tee tee_exec:file { entrypoint read execute };
-#line 9
-# New domain can send SIGCHLD to its caller.
-#line 9
-allow tee init:process sigchld;
-#line 9
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 9
-dontaudit init tee:process noatsecure;
-#line 9
-# XXX dontaudit candidate but requires further study.
-#line 9
-allow init tee:process { siginh rlimitinh };
-#line 9
-
-#line 9
-# Make the transition occur by default.
-#line 9
-type_transition init tee_exec:process tee;
-#line 9
-
-#line 9
-
-#line 9
-type tee_tmpfs, file_type;
-#line 9
-type_transition tee tmpfs:file tee_tmpfs;
-#line 9
-allow tee tee_tmpfs:file { read write };
-#line 9
-
-#line 9
-
-allow tee self:capability { dac_override };
-allow tee tee_device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow tee tee_data_file:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-allow tee tee_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow tee self:netlink_socket { create bind read };
-#line 1 "external/sepolicy/ueventd.te"
-# ueventd seclabel is specified in init.rc since
-# it lives in the rootfs and has no unique file type.
-type ueventd, domain;
-
-#line 4
-type ueventd_tmpfs, file_type;
-#line 4
-type_transition ueventd tmpfs:file ueventd_tmpfs;
-#line 4
-allow ueventd ueventd_tmpfs:file { read write };
-#line 4
-
-
-#line 5
-type_transition ueventd device:chr_file klog_device "__kmsg__";
-#line 5
-allow ueventd klog_device:chr_file { create open write unlink };
-#line 5
-allow ueventd device:dir { write add_name remove_name };
-#line 5
-
-
-#line 6
-allow ueventd security_file:dir { open getattr read search ioctl };
-#line 6
-allow ueventd security_file:file { getattr open read ioctl lock };
-#line 6
-allow ueventd security_file:lnk_file { getattr open read ioctl lock };
-#line 6
-allow ueventd selinuxfs:dir { open getattr read search ioctl };
-#line 6
-allow ueventd selinuxfs:file { getattr open read ioctl lock };
-#line 6
-allow ueventd rootfs:dir { open getattr read search ioctl };
-#line 6
-allow ueventd rootfs:file { getattr open read ioctl lock };
-#line 6
-
-
-#line 7
-typeattribute ueventd relabeltodomain;
-#line 7
-
-allow ueventd rootfs:file entrypoint;
-allow ueventd init:process sigchld;
-allow ueventd self:capability { chown mknod net_admin setgid fsetid sys_rawio dac_override fowner };
-allow ueventd device:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow ueventd device:chr_file { { getattr open read ioctl lock } { open append write } };
-allow ueventd sysfs:file { { getattr open read ioctl lock } { open append write } };
-allow ueventd sysfs:file setattr;
-allow ueventd sysfs_type:file { relabelfrom relabelto };
-allow ueventd sysfs_devices_system_cpu:file { { getattr open read ioctl lock } { open append write } };
-allow ueventd tmpfs:chr_file { { getattr open read ioctl lock } { open append write } };
-allow ueventd dev_type:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow ueventd dev_type:lnk_file { create unlink };
-allow ueventd dev_type:chr_file { create setattr unlink };
-allow ueventd dev_type:blk_file { create setattr unlink };
-allow ueventd self:netlink_kobject_uevent_socket *;
-allow ueventd efs_file:dir search;
-allow ueventd efs_file:file { getattr open read ioctl lock };
-#line 1 "external/sepolicy/unconfined.te"
-#######################################################
-#
-# This is the unconfined template. This template is the base policy
-# which is used by daemons and other privileged components of
-# Android.
-#
-# Historically, this template was called "unconfined" because it
-# allowed the domain to do anything it wanted. Over time,
-# this has changed, and will continue to change in the future.
-# The rules in this file will be removed when no remaining
-# unconfined domains require it, or when the rules contradict
-# Android security best practices. Domains which need rules not
-# provided by the unconfined template should add them directly to
-# the relevant policy.
-#
-# The use of this template is discouraged.
-######################################################
-
-allow unconfineddomain self:capability ~{ sys_ptrace sys_rawio mknod sys_module };
-allow unconfineddomain self:capability2 ~{ mac_override mac_admin };
-allow unconfineddomain kernel:security ~{ load_policy setenforce setcheckreqprot };
-allow unconfineddomain kernel:system *;
-allow unconfineddomain domain:process ~{ execmem execstack execheap ptrace transition dyntransition };
-allow unconfineddomain domain:fd *;
-allow unconfineddomain domain:dir { open getattr read search ioctl };
-allow unconfineddomain domain:lnk_file { getattr open read ioctl lock };
-allow unconfineddomain domain:{ fifo_file file } { { getattr open read ioctl lock } { open append write } };
-allow unconfineddomain domain:{ socket tcp_socket udp_socket rawip_socket netlink_socket packet_socket key_socket unix_stream_socket unix_dgram_socket appletalk_socket netlink_route_socket netlink_firewall_socket netlink_tcpdiag_socket netlink_nflog_socket netlink_xfrm_socket netlink_selinux_socket netlink_audit_socket netlink_ip6fw_socket netlink_dnrt_socket netlink_kobject_uevent_socket tun_socket } *;
-allow unconfineddomain domain:{ sem msgq shm ipc } *;
-allow unconfineddomain domain:key *;
-allow unconfineddomain {fs_type dev_type file_type}:{ dir lnk_file sock_file fifo_file } ~relabelto;
-allow unconfineddomain {fs_type -usermodehelper -proc_security}:{ chr_file file } ~{entrypoint execmod execute relabelto};
-allow unconfineddomain {dev_type -kmem_device}:{ chr_file file } ~{entrypoint execmod execute relabelto};
-allow unconfineddomain file_type:{ chr_file file } ~{entrypoint execmod execute relabelto};
-allow unconfineddomain { rootfs system_file exec_type }:file execute;
-allow unconfineddomain node_type:node *;
-allow unconfineddomain node_type:{ tcp_socket udp_socket rawip_socket } node_bind;
-allow unconfineddomain netif_type:netif *;
-allow unconfineddomain port_type:{ socket tcp_socket udp_socket rawip_socket netlink_socket packet_socket key_socket unix_stream_socket unix_dgram_socket appletalk_socket netlink_route_socket netlink_firewall_socket netlink_tcpdiag_socket netlink_nflog_socket netlink_xfrm_socket netlink_selinux_socket netlink_audit_socket netlink_ip6fw_socket netlink_dnrt_socket netlink_kobject_uevent_socket tun_socket } name_bind;
-allow unconfineddomain port_type:{ tcp_socket dccp_socket } name_connect;
-allow unconfineddomain domain:peer recv;
-allow unconfineddomain { domain -init }:binder { call transfer set_context_mgr };
-allow unconfineddomain property_type:property_service set;
-#line 1 "external/sepolicy/uncrypt.te"
-# uncrypt
-type uncrypt, domain;
-type uncrypt_exec, exec_type, file_type;
-
-
-#line 5
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow init uncrypt_exec:file { getattr open read execute };
-#line 5
-allow init uncrypt:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow uncrypt uncrypt_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow uncrypt init:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit init uncrypt:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow init uncrypt:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition init uncrypt_exec:process uncrypt;
-#line 5
-
-#line 5
-
-#line 5
-type uncrypt_tmpfs, file_type;
-#line 5
-type_transition uncrypt tmpfs:file uncrypt_tmpfs;
-#line 5
-allow uncrypt uncrypt_tmpfs:file { read write };
-#line 5
-
-#line 5
-
-
-#line 6
-typeattribute uncrypt mlstrustedsubject;
-#line 6
-typeattribute uncrypt unconfineddomain;
-#line 6
-
-
-allow uncrypt self:capability dac_override;
-
-# Read OTA zip file from /data/data/com.google.android.gsf/app_download
-
-#line 11
-allow uncrypt app_data_file:dir { open getattr read search ioctl };
-#line 11
-allow uncrypt app_data_file:{ file lnk_file } { getattr open read ioctl lock };
-#line 11
-
-
-#line 16
-
-
-# Create tmp file /cache/recovery/command.tmp
-# Read /cache/recovery/command
-# Rename /cache/recovery/command.tmp to /cache/recovery/command
-allow uncrypt cache_file:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-allow uncrypt cache_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# Set a property to reboot the device.
-
-#line 25
-allow uncrypt property_socket:sock_file write;
-#line 25
-allow uncrypt init:unix_stream_socket connectto;
-#line 25
-
-allow uncrypt powerctl_prop:property_service set;
-
-# Raw writes to block device
-allow uncrypt self:capability sys_rawio;
-allow uncrypt block_device:blk_file { open append write };
-#line 1 "external/sepolicy/untrusted_app.te"
-###
-### Untrusted apps.
-###
-### This file defines the rules for untrusted apps. An "untrusted
-### app" is an APP with UID between APP_AID (10000)
-### and AID_ISOLATED_START (99000).
-###
-### untrusted_app includes all the appdomain rules, plus the
-### additional following rules:
-###
-
-type untrusted_app, domain;
-
-#line 13
-typeattribute untrusted_app mlstrustedsubject;
-#line 13
-typeattribute untrusted_app unconfineddomain;
-#line 13
-
-
-#line 14
-typeattribute untrusted_app appdomain;
-#line 14
-# Label ashmem objects with our own unique type.
-#line 14
-
-#line 14
-type untrusted_app_tmpfs, file_type;
-#line 14
-type_transition untrusted_app tmpfs:file untrusted_app_tmpfs;
-#line 14
-allow untrusted_app untrusted_app_tmpfs:file { read write };
-#line 14
-
-#line 14
-# Map with PROT_EXEC.
-#line 14
-allow untrusted_app untrusted_app_tmpfs:file execute;
-#line 14
-
-
-#line 15
-typeattribute untrusted_app netdomain;
-#line 15
-
-
-#line 16
-typeattribute untrusted_app bluetoothdomain;
-#line 16
-
-
-# Some apps ship with shared libraries and binaries that they write out
-# to their sandbox directory and then execute.
-allow untrusted_app app_data_file:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-
-allow untrusted_app tun_device:chr_file { { getattr open read ioctl lock } { open append write } };
-
-# Internal SDCard rw access.
-allow untrusted_app sdcard_internal:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow untrusted_app sdcard_internal:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# External SDCard rw access.
-allow untrusted_app sdcard_external:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow untrusted_app sdcard_external:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# ASEC
-allow untrusted_app asec_apk_file:dir { getattr };
-allow untrusted_app asec_apk_file:file { getattr open read ioctl lock };
-# Execute libs in asec containers.
-allow untrusted_app asec_public_file:file execute;
-
-# Create tcp/udp sockets
-allow untrusted_app node_type:{ tcp_socket udp_socket } node_bind;
-allow untrusted_app self:{ tcp_socket udp_socket } { { create { ioctl read getattr write setattr append bind connect getopt setopt shutdown } } accept listen };
-# Bind to a particular hostname/address/interface (e.g., localhost) instead of
-# ANY. Normally, apps should not be listening on all interfaces.
-allow untrusted_app port:{ tcp_socket udp_socket } name_bind;
-
-# Allow the allocation and use of ptys
-# Used by: https://play.google.com/store/apps/details?id=jackpal.androidterm
-
-#line 47
-# Each domain gets a unique devpts type.
-#line 47
-type untrusted_app_devpts, fs_type;
-#line 47
-# Label the pty with the unique type when created.
-#line 47
-type_transition untrusted_app devpts:chr_file untrusted_app_devpts;
-#line 47
-# Allow use of the pty after creation.
-#line 47
-allow untrusted_app untrusted_app_devpts:chr_file { open getattr read write ioctl };
-#line 47
-# Note: devpts:dir search and ptmx_device:chr_file rw_file_perms
-#line 47
-# allowed to everyone via domain.te.
-#line 47
-
-
-# Used by Finsky / Android "Verify Apps" functionality when
-# running "adb install foo.apk".
-# TODO: Long term, we don't want apps probing into shell data files.
-# Figure out a way to remove these rules.
-allow untrusted_app shell_data_file:file { getattr open read ioctl lock };
-allow untrusted_app shell_data_file:dir { open getattr read search ioctl };
-#line 1 "external/sepolicy/vold.te"
-# volume manager
-type vold, domain;
-type vold_exec, exec_type, file_type;
-
-
-#line 5
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow init vold_exec:file { getattr open read execute };
-#line 5
-allow init vold:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow vold vold_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow vold init:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit init vold:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow init vold:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition init vold_exec:process vold;
-#line 5
-
-#line 5
-
-#line 5
-type vold_tmpfs, file_type;
-#line 5
-type_transition vold tmpfs:file vold_tmpfs;
-#line 5
-allow vold vold_tmpfs:file { read write };
-#line 5
-
-#line 5
-
-
-typeattribute vold mlstrustedsubject;
-allow vold system_file:file { getattr execute execute_no_trans };
-allow vold block_device:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow vold block_device:blk_file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow vold device:dir write;
-allow vold devpts:chr_file { { getattr open read ioctl lock } { open append write } };
-allow vold rootfs:dir mounton;
-allow vold sdcard_type:dir mounton;
-allow vold sdcard_type:filesystem { mount remount unmount };
-allow vold sdcard_type:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow vold sdcard_type:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow vold tmpfs:filesystem { mount unmount };
-allow vold tmpfs:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow vold tmpfs:dir mounton;
-allow vold self:capability { net_admin dac_override mknod sys_admin chown fowner fsetid };
-allow vold self:netlink_kobject_uevent_socket *;
-allow vold app_data_file:dir search;
-allow vold app_data_file:file { { getattr open read ioctl lock } { open append write } };
-allow vold loop_device:blk_file { { getattr open read ioctl lock } { open append write } };
-allow vold dm_device:chr_file { { getattr open read ioctl lock } { open append write } };
-# For vold Process::killProcessesWithOpenFiles function.
-allow vold domain:dir { open getattr read search ioctl };
-allow vold domain:{ file lnk_file } { getattr open read ioctl lock };
-allow vold domain:process { signal sigkill };
-allow vold self:capability { sys_ptrace kill };
-
-# For blkid
-allow vold shell_exec:file { { getattr open read ioctl lock } { getattr execute execute_no_trans } };
-
-# XXX Label sysfs files with a specific type?
-allow vold sysfs:file { { getattr open read ioctl lock } { open append write } };
-
-
-#line 39
-type_transition vold device:chr_file klog_device "__kmsg__";
-#line 39
-allow vold klog_device:chr_file { create open write unlink };
-#line 39
-allow vold device:dir { write add_name remove_name };
-#line 39
-
-
-# Log fsck results
-allow vold fscklogs:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-allow vold fscklogs:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-#
-# Rules to support encrypted fs support.
-#
-
-# Set property.
-
-#line 50
-allow vold property_socket:sock_file write;
-#line 50
-allow vold init:unix_stream_socket connectto;
-#line 50
-
-
-# Unmount and mount the fs.
-allow vold labeledfs:filesystem { mount unmount remount };
-
-# Access /efs/userdata_footer.
-# XXX Split into a separate type?
-allow vold efs_file:file { { getattr open read ioctl lock } { open append write } };
-
-# Create and mount on /data/tmp_mnt.
-allow vold system_data_file:dir { create { { open getattr read search ioctl } { open search write add_name remove_name } } mounton };
-
-# Set scheduling policy of kernel processes
-allow vold kernel:process setsched;
-
-# Property Service
-allow vold vold_prop:property_service set;
-allow vold powerctl_prop:property_service set;
-allow vold ctl_default_prop:property_service set;
-
-# ASEC
-allow vold asec_image_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow vold asec_image_file:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-
-#line 73
-allow vold security_file:dir { open getattr read search ioctl };
-#line 73
-allow vold security_file:file { getattr open read ioctl lock };
-#line 73
-allow vold security_file:lnk_file { getattr open read ioctl lock };
-#line 73
-allow vold selinuxfs:dir { open getattr read search ioctl };
-#line 73
-allow vold selinuxfs:file { getattr open read ioctl lock };
-#line 73
-allow vold rootfs:dir { open getattr read search ioctl };
-#line 73
-allow vold rootfs:file { getattr open read ioctl lock };
-#line 73
-
-
-#line 74
-typeattribute vold relabeltodomain;
-#line 74
-
-allow vold asec_apk_file:dir { { { open getattr read search ioctl } { open search write add_name remove_name } } setattr relabelfrom };
-allow vold asec_public_file:dir { relabelto setattr };
-allow vold asec_apk_file:file { { getattr open read ioctl lock } setattr relabelfrom };
-allow vold asec_public_file:file { relabelto setattr };
-
-# Handle wake locks (used for device encryption)
-allow vold sysfs_wake_lock:file { { getattr open read ioctl lock } { open append write } };
-allow vold self:capability2 block_suspend;
-#line 1 "external/sepolicy/watchdogd.te"
-# watchdogd seclabel is specified in init.<board>.rc
-type watchdogd, domain;
-allow watchdogd rootfs:file { entrypoint { getattr open read ioctl lock } };
-allow watchdogd self:capability mknod;
-allow watchdogd device:dir { add_name write remove_name };
-allow watchdogd watchdog_device:chr_file { { getattr open read ioctl lock } { open append write } };
-# because of /dev/__kmsg__ and /dev/__null__
-
-#line 8
-type_transition watchdogd device:chr_file klog_device "__kmsg__";
-#line 8
-allow watchdogd klog_device:chr_file { create open write unlink };
-#line 8
-allow watchdogd device:dir { write add_name remove_name };
-#line 8
-
-type_transition watchdogd device:chr_file null_device "__null__";
-allow watchdogd null_device:chr_file { create unlink };
-#line 1 "external/sepolicy/wpa_supplicant.te"
-# wpa - wpa supplicant or equivalent
-type wpa, domain;
-type wpa_exec, exec_type, file_type;
-
-
-#line 5
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow init wpa_exec:file { getattr open read execute };
-#line 5
-allow init wpa:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow wpa wpa_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow wpa init:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit init wpa:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow init wpa:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition init wpa_exec:process wpa;
-#line 5
-
-#line 5
-
-#line 5
-type wpa_tmpfs, file_type;
-#line 5
-type_transition wpa tmpfs:file wpa_tmpfs;
-#line 5
-allow wpa wpa_tmpfs:file { read write };
-#line 5
-
-#line 5
-
-allow wpa kernel:system module_request;
-allow wpa self:capability { setuid net_admin setgid net_raw };
-allow wpa cgroup:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow wpa self:netlink_route_socket *;
-allow wpa self:netlink_socket *;
-allow wpa self:packet_socket *;
-allow wpa self:udp_socket *;
-allow wpa wifi_data_file:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow wpa wifi_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-#line 15
-allow wpa system_wpa_socket:sock_file write;
-#line 15
-allow wpa system_server:unix_dgram_socket sendto;
-#line 15
-
-allow wpa random_device:chr_file { getattr open read ioctl lock };
-
-# Create a socket for receiving info from wpa
-type_transition wpa wifi_data_file:sock_file wpa_socket;
-allow wpa wpa_socket:dir { { { open getattr read search ioctl } { open search write add_name remove_name } } setattr };
-allow wpa wpa_socket:sock_file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-
-# Allow wpa_cli to work. wpa_cli creates a socket in
-# /data/misc/wifi/sockets which wpa supplicant communicates with.
-#line 27
-
-#line 1 "external/sepolicy/zygote.te"
-# zygote
-type zygote, domain;
-type zygote_exec, exec_type, file_type;
-
-
-#line 5
-
-#line 5
-# Allow the necessary permissions.
-#line 5
-
-#line 5
-# Old domain may exec the file and transition to the new domain.
-#line 5
-allow init zygote_exec:file { getattr open read execute };
-#line 5
-allow init zygote:process transition;
-#line 5
-# New domain is entered by executing the file.
-#line 5
-allow zygote zygote_exec:file { entrypoint read execute };
-#line 5
-# New domain can send SIGCHLD to its caller.
-#line 5
-allow zygote init:process sigchld;
-#line 5
-# Enable AT_SECURE, i.e. libc secure mode.
-#line 5
-dontaudit init zygote:process noatsecure;
-#line 5
-# XXX dontaudit candidate but requires further study.
-#line 5
-allow init zygote:process { siginh rlimitinh };
-#line 5
-
-#line 5
-# Make the transition occur by default.
-#line 5
-type_transition init zygote_exec:process zygote;
-#line 5
-
-#line 5
-
-#line 5
-type zygote_tmpfs, file_type;
-#line 5
-type_transition zygote tmpfs:file zygote_tmpfs;
-#line 5
-allow zygote zygote_tmpfs:file { read write };
-#line 5
-
-#line 5
-
-typeattribute zygote mlstrustedsubject;
-# Override DAC on files and switch uid/gid.
-allow zygote self:capability { dac_override setgid setuid fowner };
-# Drop capabilities from bounding set.
-allow zygote self:capability setpcap;
-# Switch SELinux context to app domains.
-allow zygote system_server:process dyntransition;
-allow zygote appdomain:process dyntransition;
-# Allow zygote to read app /proc/pid dirs (b/10455872)
-allow zygote appdomain:dir { getattr search };
-allow zygote appdomain:file { { getattr open read ioctl lock } };
-# Move children into the peer process group.
-allow zygote system_server:process { getpgid setpgid };
-allow zygote appdomain:process { getpgid setpgid };
-# Write to system data.
-allow zygote system_data_file:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-allow zygote system_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-allow zygote dalvikcache_data_file:dir { { open getattr read search ioctl } { open search write add_name remove_name } };
-allow zygote dalvikcache_data_file:file { create setattr { { getattr open read ioctl lock } { open append write } } { getattr link unlink rename } };
-# For art.
-allow zygote dalvikcache_data_file:file execute;
-# Execute dexopt.
-allow zygote system_file:file { getattr execute execute_no_trans };
-# Control cgroups.
-allow zygote cgroup:dir { create reparent rmdir setattr { { open getattr read search ioctl } { open search write add_name remove_name } } { getattr link unlink rename } };
-allow zygote self:capability sys_admin;
-# Check validity of SELinux context before use.
-
-#line 33
-allow zygote selinuxfs:dir { open getattr read search ioctl };
-#line 33
-allow zygote selinuxfs:file { { getattr open read ioctl lock } { open append write } };
-#line 33
-allow zygote kernel:security check_context;
-#line 33
-
-# Check SELinux permissions.
-
-#line 35
-allow zygote selinuxfs:dir { open getattr read search ioctl };
-#line 35
-allow zygote selinuxfs:file { { getattr open read ioctl lock } { open append write } };
-#line 35
-allow zygote kernel:security compute_av;
-#line 35
-allow zygote self:netlink_selinux_socket *;
-#line 35
-
-# Read /seapp_contexts and /data/security/seapp_contexts
-
-#line 37
-allow zygote security_file:dir { open getattr read search ioctl };
-#line 37
-allow zygote security_file:file { getattr open read ioctl lock };
-#line 37
-allow zygote security_file:lnk_file { getattr open read ioctl lock };
-#line 37
-allow zygote selinuxfs:dir { open getattr read search ioctl };
-#line 37
-allow zygote selinuxfs:file { getattr open read ioctl lock };
-#line 37
-allow zygote rootfs:dir { open getattr read search ioctl };
-#line 37
-allow zygote rootfs:file { getattr open read ioctl lock };
-#line 37
-
-
-# Setting up /storage/emulated.
-allow zygote rootfs:dir mounton;
-allow zygote sdcard_type:dir { write search setattr create add_name mounton };
-dontaudit zygote self:capability fsetid;
-allow zygote tmpfs:dir { write create add_name setattr mounton search };
-allow zygote tmpfs:filesystem mount;
-allow zygote labeledfs:filesystem remount;
-
-# Handle --invoke-with command when launching Zygote with a wrapper command.
-allow zygote zygote_exec:file { execute_no_trans open };
-
-# handle bugreports b/10498304
-allow zygote ashmem_device:chr_file execute;
-allow zygote shell_data_file:file { write getattr };
-allow zygote system_server:binder { transfer call };
-allow zygote servicemanager:binder { call };
-
-# For legacy unlabeled userdata on existing devices.
-# See discussion of Unlabeled files in domain.te for more information.
-# This rule is for dalvikcache mmap/mprotect PROT_EXEC.
-allow zygote unlabeled:file execute;
-#line 1 "build/target/board/generic/sepolicy/bootanim.te"
-allow bootanim self:process execmem;
-allow bootanim ashmem_device:chr_file execute;
-#line 1 "build/target/board/generic/sepolicy/domain.te"
-# For /sys/qemu_trace files in the emulator.
-allow domain sysfs_writable:file { { getattr open read ioctl lock } { open append write } };
-#line 1 "build/target/board/generic/sepolicy/surfaceflinger.te"
-allow surfaceflinger self:process execmem;
-allow surfaceflinger ashmem_device:chr_file execute;
-#line 1 "external/sepolicy/roles"
-role r;
-role r types domain;
-#line 1 "external/sepolicy/users"
-user u roles { r } level s0 range s0 - s0:c0.c1023;
-#line 1 "external/sepolicy/initial_sid_contexts"
-sid kernel u:r:kernel:s0
-sid security u:object_r:kernel:s0
-sid unlabeled u:object_r:unlabeled:s0
-sid fs u:object_r:labeledfs:s0
-sid file u:object_r:unlabeled:s0
-sid file_labels u:object_r:unlabeled:s0
-sid init u:object_r:unlabeled:s0
-sid any_socket u:object_r:unlabeled:s0
-sid port u:object_r:port:s0
-sid netif u:object_r:netif:s0
-sid netmsg u:object_r:unlabeled:s0
-sid node u:object_r:node:s0
-sid igmp_packet u:object_r:unlabeled:s0
-sid icmp_socket u:object_r:unlabeled:s0
-sid tcp_socket u:object_r:unlabeled:s0
-sid sysctl_modprobe u:object_r:unlabeled:s0
-sid sysctl u:object_r:proc:s0
-sid sysctl_fs u:object_r:unlabeled:s0
-sid sysctl_kernel u:object_r:unlabeled:s0
-sid sysctl_net u:object_r:unlabeled:s0
-sid sysctl_net_unix u:object_r:unlabeled:s0
-sid sysctl_vm u:object_r:unlabeled:s0
-sid sysctl_dev u:object_r:unlabeled:s0
-sid kmod u:object_r:unlabeled:s0
-sid policy u:object_r:unlabeled:s0
-sid scmp_packet u:object_r:unlabeled:s0
-sid devnull u:object_r:null_device:s0
-#line 1 "external/sepolicy/fs_use"
-# Label inodes via getxattr.
-fs_use_xattr yaffs2 u:object_r:labeledfs:s0;
-fs_use_xattr jffs2 u:object_r:labeledfs:s0;
-fs_use_xattr ext2 u:object_r:labeledfs:s0;
-fs_use_xattr ext3 u:object_r:labeledfs:s0;
-fs_use_xattr ext4 u:object_r:labeledfs:s0;
-fs_use_xattr xfs u:object_r:labeledfs:s0;
-fs_use_xattr btrfs u:object_r:labeledfs:s0;
-
-# Label inodes from task label.
-fs_use_task pipefs u:object_r:pipefs:s0;
-fs_use_task sockfs u:object_r:sockfs:s0;
-
-# Label inodes from combination of task label and fs label.
-# Define type_transition rules if you want per-domain types.
-fs_use_trans devpts u:object_r:devpts:s0;
-fs_use_trans tmpfs u:object_r:tmpfs:s0;
-fs_use_trans devtmpfs u:object_r:device:s0;
-fs_use_trans shm u:object_r:shm:s0;
-fs_use_trans mqueue u:object_r:mqueue:s0;
-
-#line 1 "external/sepolicy/genfs_contexts"
-# Label inodes with the fs label.
-genfscon rootfs / u:object_r:rootfs:s0
-# proc labeling can be further refined (longest matching prefix).
-genfscon proc / u:object_r:proc:s0
-genfscon proc /net u:object_r:proc_net:s0
-genfscon proc /net/xt_qtaguid/ctrl u:object_r:qtaguid_proc:s0
-genfscon proc /sys/fs/protected_hardlinks u:object_r:proc_security:s0
-genfscon proc /sys/fs/protected_symlinks u:object_r:proc_security:s0
-genfscon proc /sys/fs/suid_dumpable u:object_r:proc_security:s0
-genfscon proc /sys/kernel/core_pattern u:object_r:usermodehelper:s0
-genfscon proc /sys/kernel/dmesg_restrict u:object_r:proc_security:s0
-genfscon proc /sys/kernel/hotplug u:object_r:usermodehelper:s0
-genfscon proc /sys/kernel/kptr_restrict u:object_r:proc_security:s0
-genfscon proc /sys/kernel/modprobe u:object_r:usermodehelper:s0
-genfscon proc /sys/kernel/modules_disabled u:object_r:proc_security:s0
-genfscon proc /sys/kernel/poweroff_cmd u:object_r:usermodehelper:s0
-genfscon proc /sys/kernel/randomize_va_space u:object_r:proc_security:s0
-genfscon proc /sys/kernel/usermodehelper u:object_r:usermodehelper:s0
-genfscon proc /sys/net u:object_r:proc_net:s0
-genfscon proc /sys/vm/mmap_min_addr u:object_r:proc_security:s0
-# selinuxfs booleans can be individually labeled.
-genfscon selinuxfs / u:object_r:selinuxfs:s0
-genfscon cgroup / u:object_r:cgroup:s0
-# sysfs labels can be set by userspace.
-genfscon sysfs / u:object_r:sysfs:s0
-genfscon inotifyfs / u:object_r:inotify:s0
-genfscon vfat / u:object_r:sdcard_external:s0
-genfscon debugfs / u:object_r:debugfs:s0
-genfscon fuse / u:object_r:sdcard_internal:s0
-#line 1 "external/sepolicy/port_contexts"
-# portcon statements go here, e.g.
-# portcon tcp 80 u:object_r:http_port:s0
-
diff --git a/tools/selinux/src/gen_SELinux_CTS.py b/tools/selinux/src/gen_SELinux_CTS.py
deleted file mode 100755
index 85d49a8..0000000
--- a/tools/selinux/src/gen_SELinux_CTS.py
+++ /dev/null
@@ -1,58 +0,0 @@
-#!/usr/bin/python
-# genCheckAccessCTS.py - takes an input SELinux policy.conf file and generates
-# an XML file based on the allow and neverallow rules.  The file contains rules,
-# which are created by expanding the SELinux rule notation into the individual
-# components which a checkAccess() check, that a policy manager would have to
-# perform, needs.
-#
-# This test does not work with all valid SELinux policy.conf files.  It is meant
-# to simply use a given AOSP generated policy.conf file to create sets
-# representing the policy's types, attributes, classes and permissions, which
-# are used to expand the allow and neverallow rules found.  For a full parser
-# and compiler of SELinux, see external/checkpolicy.
-# @dcashman
-
-import pdb
-import re
-import sys
-from xml.etree.ElementTree import Element, SubElement, tostring
-from xml.dom import minidom
-
-import SELinux_CTS
-from SELinux_CTS import SELinuxPolicy
-
-usage = "Usage: ./gen_SELinux_CTS.py input_policy_file output_xml_avc_rules_file neverallow_only=[t/f]"
-
-if __name__ == "__main__":
-    # check usage
-    if len(sys.argv) != 4:
-        print usage
-        exit()
-    input_file = sys.argv[1]
-    output_file = sys.argv[2]
-    neverallow_only = (sys.argv[3] == "neverallow_only=t")
-    policy = SELinuxPolicy()
-    policy.from_file_name(input_file) #load data from file
-
-    # expand rules into 4-tuples for SELinux.h checkAccess() check
-    xml_root = Element('SELinux_AVC_Rules')
-    if not neverallow_only:
-        count = 1
-        for a in policy.allow_rules:
-            expanded_xml = SELinux_CTS.expand_avc_rule_to_xml(policy, a, str(count), 'allow')
-            if len(expanded_xml):
-                xml_root.append(expanded_xml)
-                count += 1
-    count = 1
-    for n in policy.neverallow_rules:
-        expanded_xml = SELinux_CTS.expand_avc_rule_to_xml(policy, n, str(count), 'neverallow')
-        if len(expanded_xml):
-            xml_root.append(expanded_xml)
-            count += 1
-
-    #print out the xml file
-    s = tostring(xml_root)
-    s_parsed = minidom.parseString(s)
-    output = s_parsed.toprettyxml(indent="    ")
-    with open(output_file, 'w') as out_file:
-        out_file.write(output)
diff --git a/tools/selinux/test/policy_clean_test.conf b/tools/selinux/test/policy_clean_test.conf
deleted file mode 100644
index 074a63b..0000000
--- a/tools/selinux/test/policy_clean_test.conf
+++ /dev/null
@@ -1,2230 +0,0 @@
-#line 1 "external/sepolicy/security_classes"
-# FLASK
-
-#
-# Define the security object classes
-#
-
-# Classes marked as userspace are classes
-# for userspace object managers
-
-class capability
-
-# file-related classes
-class file
-
-#
-# Define a common prefix for file access vectors.
-#
-
-common file
-{
-	ioctl
-	read
-	write
-	create
-	getattr
-	setattr
-	lock
-	relabelfrom
-	relabelto
-	append
-	unlink
-	link
-	rename
-	execute
-	swapon
-	quotaon
-	mounton
-}
-
-class file
-inherits file
-{
-	execute_no_trans
-	entrypoint
-	execmod
-	open
-	audit_access
-}
-
-class capability
-{
-	# The capabilities are defined in include/linux/capability.h
-	# Capabilities >= 32 are defined in the capability2 class.
-	# Care should be taken to ensure that these are consistent with
-	# those definitions. (Order matters)
-
-	chown
-	dac_override
-	dac_read_search
-	fowner
-	fsetid
-	kill
-	setgid
-	setuid
-	setpcap
-	linux_immutable
-	net_bind_service
-	net_broadcast
-	net_admin
-	net_raw
-	ipc_lock
-	ipc_owner
-	sys_module
-	sys_rawio
-	sys_chroot
-	sys_ptrace
-	sys_pacct
-	sys_admin
-	sys_boot
-	sys_nice
-	sys_resource
-	sys_time
-	sys_tty_config
-	mknod
-	lease
-	audit_write
-	audit_control
-	setfcap
-}
-
-########################################
-#
-# Basic level names for system low and high
-#
-
-
-#line 1 "external/sepolicy/mls"
-#########################################
-# MLS declarations
-#
-
-# Generate the desired number of sensitivities and categories.
-
-#line 6
-# Each sensitivity has a name and zero or more aliases.
-#line 6
-sensitivity s0;
-#line 6
-
-#line 6
-
-#line 6
-# Define the ordering of the sensitivity levels (least to greatest)
-#line 6
-dominance { s0  }
-#line 6
-
-category c0;
-#line 7
-category c1;
-#line 7
-category c2;
-#line 7
-category c3;
-#line 7
-category c4;
-#line 7
-category c5;
-#line 7
-category c6;
-#line 7
-category c7;
-#line 7
-category c8;
-#line 7
-category c9;
-#line 7
-category c10;
-#line 7
-category c11;
-#line 7
-category c12;
-#line 7
-category c13;
-#line 7
-category c14;
-#line 7
-category c15;
-#line 7
-category c16;
-#line 7
-category c17;
-#line 7
-category c18;
-#line 7
-category c19;
-#line 7
-category c20;
-#line 7
-category c21;
-#line 7
-category c22;
-#line 7
-category c23;
-#line 7
-category c24;
-#line 7
-category c25;
-#line 7
-category c26;
-#line 7
-category c27;
-#line 7
-category c28;
-#line 7
-category c29;
-#line 7
-category c30;
-#line 7
-category c31;
-#line 7
-category c32;
-#line 7
-category c33;
-#line 7
-category c34;
-#line 7
-category c35;
-#line 7
-category c36;
-#line 7
-category c37;
-#line 7
-category c38;
-#line 7
-category c39;
-#line 7
-category c40;
-#line 7
-category c41;
-#line 7
-category c42;
-#line 7
-category c43;
-#line 7
-category c44;
-#line 7
-category c45;
-#line 7
-category c46;
-#line 7
-category c47;
-#line 7
-category c48;
-#line 7
-category c49;
-#line 7
-category c50;
-#line 7
-category c51;
-#line 7
-category c52;
-#line 7
-category c53;
-#line 7
-category c54;
-#line 7
-category c55;
-#line 7
-category c56;
-#line 7
-category c57;
-#line 7
-category c58;
-#line 7
-category c59;
-#line 7
-category c60;
-#line 7
-category c61;
-#line 7
-category c62;
-#line 7
-category c63;
-#line 7
-category c64;
-#line 7
-category c65;
-#line 7
-category c66;
-#line 7
-category c67;
-#line 7
-category c68;
-#line 7
-category c69;
-#line 7
-category c70;
-#line 7
-category c71;
-#line 7
-category c72;
-#line 7
-category c73;
-#line 7
-category c74;
-#line 7
-category c75;
-#line 7
-category c76;
-#line 7
-category c77;
-#line 7
-category c78;
-#line 7
-category c79;
-#line 7
-category c80;
-#line 7
-category c81;
-#line 7
-category c82;
-#line 7
-category c83;
-#line 7
-category c84;
-#line 7
-category c85;
-#line 7
-category c86;
-#line 7
-category c87;
-#line 7
-category c88;
-#line 7
-category c89;
-#line 7
-category c90;
-#line 7
-category c91;
-#line 7
-category c92;
-#line 7
-category c93;
-#line 7
-category c94;
-#line 7
-category c95;
-#line 7
-category c96;
-#line 7
-category c97;
-#line 7
-category c98;
-#line 7
-category c99;
-#line 7
-category c100;
-#line 7
-category c101;
-#line 7
-category c102;
-#line 7
-category c103;
-#line 7
-category c104;
-#line 7
-category c105;
-#line 7
-category c106;
-#line 7
-category c107;
-#line 7
-category c108;
-#line 7
-category c109;
-#line 7
-category c110;
-#line 7
-category c111;
-#line 7
-category c112;
-#line 7
-category c113;
-#line 7
-category c114;
-#line 7
-category c115;
-#line 7
-category c116;
-#line 7
-category c117;
-#line 7
-category c118;
-#line 7
-category c119;
-#line 7
-category c120;
-#line 7
-category c121;
-#line 7
-category c122;
-#line 7
-category c123;
-#line 7
-category c124;
-#line 7
-category c125;
-#line 7
-category c126;
-#line 7
-category c127;
-#line 7
-category c128;
-#line 7
-category c129;
-#line 7
-category c130;
-#line 7
-category c131;
-#line 7
-category c132;
-#line 7
-category c133;
-#line 7
-category c134;
-#line 7
-category c135;
-#line 7
-category c136;
-#line 7
-category c137;
-#line 7
-category c138;
-#line 7
-category c139;
-#line 7
-category c140;
-#line 7
-category c141;
-#line 7
-category c142;
-#line 7
-category c143;
-#line 7
-category c144;
-#line 7
-category c145;
-#line 7
-category c146;
-#line 7
-category c147;
-#line 7
-category c148;
-#line 7
-category c149;
-#line 7
-category c150;
-#line 7
-category c151;
-#line 7
-category c152;
-#line 7
-category c153;
-#line 7
-category c154;
-#line 7
-category c155;
-#line 7
-category c156;
-#line 7
-category c157;
-#line 7
-category c158;
-#line 7
-category c159;
-#line 7
-category c160;
-#line 7
-category c161;
-#line 7
-category c162;
-#line 7
-category c163;
-#line 7
-category c164;
-#line 7
-category c165;
-#line 7
-category c166;
-#line 7
-category c167;
-#line 7
-category c168;
-#line 7
-category c169;
-#line 7
-category c170;
-#line 7
-category c171;
-#line 7
-category c172;
-#line 7
-category c173;
-#line 7
-category c174;
-#line 7
-category c175;
-#line 7
-category c176;
-#line 7
-category c177;
-#line 7
-category c178;
-#line 7
-category c179;
-#line 7
-category c180;
-#line 7
-category c181;
-#line 7
-category c182;
-#line 7
-category c183;
-#line 7
-category c184;
-#line 7
-category c185;
-#line 7
-category c186;
-#line 7
-category c187;
-#line 7
-category c188;
-#line 7
-category c189;
-#line 7
-category c190;
-#line 7
-category c191;
-#line 7
-category c192;
-#line 7
-category c193;
-#line 7
-category c194;
-#line 7
-category c195;
-#line 7
-category c196;
-#line 7
-category c197;
-#line 7
-category c198;
-#line 7
-category c199;
-#line 7
-category c200;
-#line 7
-category c201;
-#line 7
-category c202;
-#line 7
-category c203;
-#line 7
-category c204;
-#line 7
-category c205;
-#line 7
-category c206;
-#line 7
-category c207;
-#line 7
-category c208;
-#line 7
-category c209;
-#line 7
-category c210;
-#line 7
-category c211;
-#line 7
-category c212;
-#line 7
-category c213;
-#line 7
-category c214;
-#line 7
-category c215;
-#line 7
-category c216;
-#line 7
-category c217;
-#line 7
-category c218;
-#line 7
-category c219;
-#line 7
-category c220;
-#line 7
-category c221;
-#line 7
-category c222;
-#line 7
-category c223;
-#line 7
-category c224;
-#line 7
-category c225;
-#line 7
-category c226;
-#line 7
-category c227;
-#line 7
-category c228;
-#line 7
-category c229;
-#line 7
-category c230;
-#line 7
-category c231;
-#line 7
-category c232;
-#line 7
-category c233;
-#line 7
-category c234;
-#line 7
-category c235;
-#line 7
-category c236;
-#line 7
-category c237;
-#line 7
-category c238;
-#line 7
-category c239;
-#line 7
-category c240;
-#line 7
-category c241;
-#line 7
-category c242;
-#line 7
-category c243;
-#line 7
-category c244;
-#line 7
-category c245;
-#line 7
-category c246;
-#line 7
-category c247;
-#line 7
-category c248;
-#line 7
-category c249;
-#line 7
-category c250;
-#line 7
-category c251;
-#line 7
-category c252;
-#line 7
-category c253;
-#line 7
-category c254;
-#line 7
-category c255;
-#line 7
-category c256;
-#line 7
-category c257;
-#line 7
-category c258;
-#line 7
-category c259;
-#line 7
-category c260;
-#line 7
-category c261;
-#line 7
-category c262;
-#line 7
-category c263;
-#line 7
-category c264;
-#line 7
-category c265;
-#line 7
-category c266;
-#line 7
-category c267;
-#line 7
-category c268;
-#line 7
-category c269;
-#line 7
-category c270;
-#line 7
-category c271;
-#line 7
-category c272;
-#line 7
-category c273;
-#line 7
-category c274;
-#line 7
-category c275;
-#line 7
-category c276;
-#line 7
-category c277;
-#line 7
-category c278;
-#line 7
-category c279;
-#line 7
-category c280;
-#line 7
-category c281;
-#line 7
-category c282;
-#line 7
-category c283;
-#line 7
-category c284;
-#line 7
-category c285;
-#line 7
-category c286;
-#line 7
-category c287;
-#line 7
-category c288;
-#line 7
-category c289;
-#line 7
-category c290;
-#line 7
-category c291;
-#line 7
-category c292;
-#line 7
-category c293;
-#line 7
-category c294;
-#line 7
-category c295;
-#line 7
-category c296;
-#line 7
-category c297;
-#line 7
-category c298;
-#line 7
-category c299;
-#line 7
-category c300;
-#line 7
-category c301;
-#line 7
-category c302;
-#line 7
-category c303;
-#line 7
-category c304;
-#line 7
-category c305;
-#line 7
-category c306;
-#line 7
-category c307;
-#line 7
-category c308;
-#line 7
-category c309;
-#line 7
-category c310;
-#line 7
-category c311;
-#line 7
-category c312;
-#line 7
-category c313;
-#line 7
-category c314;
-#line 7
-category c315;
-#line 7
-category c316;
-#line 7
-category c317;
-#line 7
-category c318;
-#line 7
-category c319;
-#line 7
-category c320;
-#line 7
-category c321;
-#line 7
-category c322;
-#line 7
-category c323;
-#line 7
-category c324;
-#line 7
-category c325;
-#line 7
-category c326;
-#line 7
-category c327;
-#line 7
-category c328;
-#line 7
-category c329;
-#line 7
-category c330;
-#line 7
-category c331;
-#line 7
-category c332;
-#line 7
-category c333;
-#line 7
-category c334;
-#line 7
-category c335;
-#line 7
-category c336;
-#line 7
-category c337;
-#line 7
-category c338;
-#line 7
-category c339;
-#line 7
-category c340;
-#line 7
-category c341;
-#line 7
-category c342;
-#line 7
-category c343;
-#line 7
-category c344;
-#line 7
-category c345;
-#line 7
-category c346;
-#line 7
-category c347;
-#line 7
-category c348;
-#line 7
-category c349;
-#line 7
-category c350;
-#line 7
-category c351;
-#line 7
-category c352;
-#line 7
-category c353;
-#line 7
-category c354;
-#line 7
-category c355;
-#line 7
-category c356;
-#line 7
-category c357;
-#line 7
-category c358;
-#line 7
-category c359;
-#line 7
-category c360;
-#line 7
-category c361;
-#line 7
-category c362;
-#line 7
-category c363;
-#line 7
-category c364;
-#line 7
-category c365;
-#line 7
-category c366;
-#line 7
-category c367;
-#line 7
-category c368;
-#line 7
-category c369;
-#line 7
-category c370;
-#line 7
-category c371;
-#line 7
-category c372;
-#line 7
-category c373;
-#line 7
-category c374;
-#line 7
-category c375;
-#line 7
-category c376;
-#line 7
-category c377;
-#line 7
-category c378;
-#line 7
-category c379;
-#line 7
-category c380;
-#line 7
-category c381;
-#line 7
-category c382;
-#line 7
-category c383;
-#line 7
-category c384;
-#line 7
-category c385;
-#line 7
-category c386;
-#line 7
-category c387;
-#line 7
-category c388;
-#line 7
-category c389;
-#line 7
-category c390;
-#line 7
-category c391;
-#line 7
-category c392;
-#line 7
-category c393;
-#line 7
-category c394;
-#line 7
-category c395;
-#line 7
-category c396;
-#line 7
-category c397;
-#line 7
-category c398;
-#line 7
-category c399;
-#line 7
-category c400;
-#line 7
-category c401;
-#line 7
-category c402;
-#line 7
-category c403;
-#line 7
-category c404;
-#line 7
-category c405;
-#line 7
-category c406;
-#line 7
-category c407;
-#line 7
-category c408;
-#line 7
-category c409;
-#line 7
-category c410;
-#line 7
-category c411;
-#line 7
-category c412;
-#line 7
-category c413;
-#line 7
-category c414;
-#line 7
-category c415;
-#line 7
-category c416;
-#line 7
-category c417;
-#line 7
-category c418;
-#line 7
-category c419;
-#line 7
-category c420;
-#line 7
-category c421;
-#line 7
-category c422;
-#line 7
-category c423;
-#line 7
-category c424;
-#line 7
-category c425;
-#line 7
-category c426;
-#line 7
-category c427;
-#line 7
-category c428;
-#line 7
-category c429;
-#line 7
-category c430;
-#line 7
-category c431;
-#line 7
-category c432;
-#line 7
-category c433;
-#line 7
-category c434;
-#line 7
-category c435;
-#line 7
-category c436;
-#line 7
-category c437;
-#line 7
-category c438;
-#line 7
-category c439;
-#line 7
-category c440;
-#line 7
-category c441;
-#line 7
-category c442;
-#line 7
-category c443;
-#line 7
-category c444;
-#line 7
-category c445;
-#line 7
-category c446;
-#line 7
-category c447;
-#line 7
-category c448;
-#line 7
-category c449;
-#line 7
-category c450;
-#line 7
-category c451;
-#line 7
-category c452;
-#line 7
-category c453;
-#line 7
-category c454;
-#line 7
-category c455;
-#line 7
-category c456;
-#line 7
-category c457;
-#line 7
-category c458;
-#line 7
-category c459;
-#line 7
-category c460;
-#line 7
-category c461;
-#line 7
-category c462;
-#line 7
-category c463;
-#line 7
-category c464;
-#line 7
-category c465;
-#line 7
-category c466;
-#line 7
-category c467;
-#line 7
-category c468;
-#line 7
-category c469;
-#line 7
-category c470;
-#line 7
-category c471;
-#line 7
-category c472;
-#line 7
-category c473;
-#line 7
-category c474;
-#line 7
-category c475;
-#line 7
-category c476;
-#line 7
-category c477;
-#line 7
-category c478;
-#line 7
-category c479;
-#line 7
-category c480;
-#line 7
-category c481;
-#line 7
-category c482;
-#line 7
-category c483;
-#line 7
-category c484;
-#line 7
-category c485;
-#line 7
-category c486;
-#line 7
-category c487;
-#line 7
-category c488;
-#line 7
-category c489;
-#line 7
-category c490;
-#line 7
-category c491;
-#line 7
-category c492;
-#line 7
-category c493;
-#line 7
-category c494;
-#line 7
-category c495;
-#line 7
-category c496;
-#line 7
-category c497;
-#line 7
-category c498;
-#line 7
-category c499;
-#line 7
-category c500;
-#line 7
-category c501;
-#line 7
-category c502;
-#line 7
-category c503;
-#line 7
-category c504;
-#line 7
-category c505;
-#line 7
-category c506;
-#line 7
-category c507;
-#line 7
-category c508;
-#line 7
-category c509;
-#line 7
-category c510;
-#line 7
-category c511;
-#line 7
-category c512;
-#line 7
-category c513;
-#line 7
-category c514;
-#line 7
-category c515;
-#line 7
-category c516;
-#line 7
-category c517;
-#line 7
-category c518;
-#line 7
-category c519;
-#line 7
-category c520;
-#line 7
-category c521;
-#line 7
-category c522;
-#line 7
-category c523;
-#line 7
-category c524;
-#line 7
-category c525;
-#line 7
-category c526;
-#line 7
-category c527;
-#line 7
-category c528;
-#line 7
-category c529;
-#line 7
-category c530;
-#line 7
-category c531;
-#line 7
-category c532;
-#line 7
-category c533;
-#line 7
-category c534;
-#line 7
-category c535;
-#line 7
-category c536;
-#line 7
-category c537;
-#line 7
-category c538;
-#line 7
-category c539;
-#line 7
-category c540;
-#line 7
-category c541;
-#line 7
-category c542;
-#line 7
-category c543;
-#line 7
-category c544;
-#line 7
-category c545;
-#line 7
-category c546;
-#line 7
-category c547;
-#line 7
-category c548;
-#line 7
-category c549;
-#line 7
-category c550;
-#line 7
-category c551;
-#line 7
-category c552;
-#line 7
-category c553;
-#line 7
-category c554;
-#line 7
-category c555;
-#line 7
-category c556;
-#line 7
-category c557;
-#line 7
-category c558;
-#line 7
-category c559;
-#line 7
-category c560;
-#line 7
-category c561;
-#line 7
-category c562;
-#line 7
-category c563;
-#line 7
-category c564;
-#line 7
-category c565;
-#line 7
-category c566;
-#line 7
-category c567;
-#line 7
-category c568;
-#line 7
-category c569;
-#line 7
-category c570;
-#line 7
-category c571;
-#line 7
-category c572;
-#line 7
-category c573;
-#line 7
-category c574;
-#line 7
-category c575;
-#line 7
-category c576;
-#line 7
-category c577;
-#line 7
-category c578;
-#line 7
-category c579;
-#line 7
-category c580;
-#line 7
-category c581;
-#line 7
-category c582;
-#line 7
-category c583;
-#line 7
-category c584;
-#line 7
-category c585;
-#line 7
-category c586;
-#line 7
-category c587;
-#line 7
-category c588;
-#line 7
-category c589;
-#line 7
-category c590;
-#line 7
-category c591;
-#line 7
-category c592;
-#line 7
-category c593;
-#line 7
-category c594;
-#line 7
-category c595;
-#line 7
-category c596;
-#line 7
-category c597;
-#line 7
-category c598;
-#line 7
-category c599;
-#line 7
-category c600;
-#line 7
-category c601;
-#line 7
-category c602;
-#line 7
-category c603;
-#line 7
-category c604;
-#line 7
-category c605;
-#line 7
-category c606;
-#line 7
-category c607;
-#line 7
-category c608;
-#line 7
-category c609;
-#line 7
-category c610;
-#line 7
-category c611;
-#line 7
-category c612;
-#line 7
-category c613;
-#line 7
-category c614;
-#line 7
-category c615;
-#line 7
-category c616;
-#line 7
-category c617;
-#line 7
-category c618;
-#line 7
-category c619;
-#line 7
-category c620;
-#line 7
-category c621;
-#line 7
-category c622;
-#line 7
-category c623;
-#line 7
-category c624;
-#line 7
-category c625;
-#line 7
-category c626;
-#line 7
-category c627;
-#line 7
-category c628;
-#line 7
-category c629;
-#line 7
-category c630;
-#line 7
-category c631;
-#line 7
-category c632;
-#line 7
-category c633;
-#line 7
-category c634;
-#line 7
-category c635;
-#line 7
-category c636;
-#line 7
-category c637;
-#line 7
-category c638;
-#line 7
-category c639;
-#line 7
-category c640;
-#line 7
-category c641;
-#line 7
-category c642;
-#line 7
-category c643;
-#line 7
-category c644;
-#line 7
-category c645;
-#line 7
-category c646;
-#line 7
-category c647;
-#line 7
-category c648;
-#line 7
-category c649;
-#line 7
-category c650;
-#line 7
-category c651;
-#line 7
-category c652;
-#line 7
-category c653;
-#line 7
-category c654;
-#line 7
-category c655;
-#line 7
-category c656;
-#line 7
-category c657;
-#line 7
-category c658;
-#line 7
-category c659;
-#line 7
-category c660;
-#line 7
-category c661;
-#line 7
-category c662;
-#line 7
-category c663;
-#line 7
-category c664;
-#line 7
-category c665;
-#line 7
-category c666;
-#line 7
-category c667;
-#line 7
-category c668;
-#line 7
-category c669;
-#line 7
-category c670;
-#line 7
-category c671;
-#line 7
-category c672;
-#line 7
-category c673;
-#line 7
-category c674;
-#line 7
-category c675;
-#line 7
-category c676;
-#line 7
-category c677;
-#line 7
-category c678;
-#line 7
-category c679;
-#line 7
-category c680;
-#line 7
-category c681;
-#line 7
-category c682;
-#line 7
-category c683;
-#line 7
-category c684;
-#line 7
-category c685;
-#line 7
-category c686;
-#line 7
-category c687;
-#line 7
-category c688;
-#line 7
-category c689;
-#line 7
-category c690;
-#line 7
-category c691;
-#line 7
-category c692;
-#line 7
-category c693;
-#line 7
-category c694;
-#line 7
-category c695;
-#line 7
-category c696;
-#line 7
-category c697;
-#line 7
-category c698;
-#line 7
-category c699;
-#line 7
-category c700;
-#line 7
-category c701;
-#line 7
-category c702;
-#line 7
-category c703;
-#line 7
-category c704;
-#line 7
-category c705;
-#line 7
-category c706;
-#line 7
-category c707;
-#line 7
-category c708;
-#line 7
-category c709;
-#line 7
-category c710;
-#line 7
-category c711;
-#line 7
-category c712;
-#line 7
-category c713;
-#line 7
-category c714;
-#line 7
-category c715;
-#line 7
-category c716;
-#line 7
-category c717;
-#line 7
-category c718;
-#line 7
-category c719;
-#line 7
-category c720;
-#line 7
-category c721;
-#line 7
-category c722;
-#line 7
-category c723;
-#line 7
-category c724;
-#line 7
-category c725;
-#line 7
-category c726;
-#line 7
-category c727;
-#line 7
-category c728;
-#line 7
-category c729;
-#line 7
-category c730;
-#line 7
-category c731;
-#line 7
-category c732;
-#line 7
-category c733;
-#line 7
-category c734;
-#line 7
-category c735;
-#line 7
-category c736;
-#line 7
-category c737;
-#line 7
-category c738;
-#line 7
-category c739;
-#line 7
-category c740;
-#line 7
-category c741;
-#line 7
-category c742;
-#line 7
-category c743;
-#line 7
-category c744;
-#line 7
-category c745;
-#line 7
-category c746;
-#line 7
-category c747;
-#line 7
-category c748;
-#line 7
-category c749;
-#line 7
-category c750;
-#line 7
-category c751;
-#line 7
-category c752;
-#line 7
-category c753;
-#line 7
-category c754;
-#line 7
-category c755;
-#line 7
-category c756;
-#line 7
-category c757;
-#line 7
-category c758;
-#line 7
-category c759;
-#line 7
-category c760;
-#line 7
-category c761;
-#line 7
-category c762;
-#line 7
-category c763;
-#line 7
-category c764;
-#line 7
-category c765;
-#line 7
-category c766;
-#line 7
-category c767;
-#line 7
-category c768;
-#line 7
-category c769;
-#line 7
-category c770;
-#line 7
-category c771;
-#line 7
-category c772;
-#line 7
-category c773;
-#line 7
-category c774;
-#line 7
-category c775;
-#line 7
-category c776;
-#line 7
-category c777;
-#line 7
-category c778;
-#line 7
-category c779;
-#line 7
-category c780;
-#line 7
-category c781;
-#line 7
-category c782;
-#line 7
-category c783;
-#line 7
-category c784;
-#line 7
-category c785;
-#line 7
-category c786;
-#line 7
-category c787;
-#line 7
-category c788;
-#line 7
-category c789;
-#line 7
-category c790;
-#line 7
-category c791;
-#line 7
-category c792;
-#line 7
-category c793;
-#line 7
-category c794;
-#line 7
-category c795;
-#line 7
-category c796;
-#line 7
-category c797;
-#line 7
-category c798;
-#line 7
-category c799;
-#line 7
-category c800;
-#line 7
-category c801;
-#line 7
-category c802;
-#line 7
-category c803;
-#line 7
-category c804;
-#line 7
-category c805;
-#line 7
-category c806;
-#line 7
-category c807;
-#line 7
-category c808;
-#line 7
-category c809;
-#line 7
-category c810;
-#line 7
-category c811;
-#line 7
-category c812;
-#line 7
-category c813;
-#line 7
-category c814;
-#line 7
-category c815;
-#line 7
-category c816;
-#line 7
-category c817;
-#line 7
-category c818;
-#line 7
-category c819;
-#line 7
-category c820;
-#line 7
-category c821;
-#line 7
-category c822;
-#line 7
-category c823;
-#line 7
-category c824;
-#line 7
-category c825;
-#line 7
-category c826;
-#line 7
-category c827;
-#line 7
-category c828;
-#line 7
-category c829;
-#line 7
-category c830;
-#line 7
-category c831;
-#line 7
-category c832;
-#line 7
-category c833;
-#line 7
-category c834;
-#line 7
-category c835;
-#line 7
-category c836;
-#line 7
-category c837;
-#line 7
-category c838;
-#line 7
-category c839;
-#line 7
-category c840;
-#line 7
-category c841;
-#line 7
-category c842;
-#line 7
-category c843;
-#line 7
-category c844;
-#line 7
-category c845;
-#line 7
-category c846;
-#line 7
-category c847;
-#line 7
-category c848;
-#line 7
-category c849;
-#line 7
-category c850;
-#line 7
-category c851;
-#line 7
-category c852;
-#line 7
-category c853;
-#line 7
-category c854;
-#line 7
-category c855;
-#line 7
-category c856;
-#line 7
-category c857;
-#line 7
-category c858;
-#line 7
-category c859;
-#line 7
-category c860;
-#line 7
-category c861;
-#line 7
-category c862;
-#line 7
-category c863;
-#line 7
-category c864;
-#line 7
-category c865;
-#line 7
-category c866;
-#line 7
-category c867;
-#line 7
-category c868;
-#line 7
-category c869;
-#line 7
-category c870;
-#line 7
-category c871;
-#line 7
-category c872;
-#line 7
-category c873;
-#line 7
-category c874;
-#line 7
-category c875;
-#line 7
-category c876;
-#line 7
-category c877;
-#line 7
-category c878;
-#line 7
-category c879;
-#line 7
-category c880;
-#line 7
-category c881;
-#line 7
-category c882;
-#line 7
-category c883;
-#line 7
-category c884;
-#line 7
-category c885;
-#line 7
-category c886;
-#line 7
-category c887;
-#line 7
-category c888;
-#line 7
-category c889;
-#line 7
-category c890;
-#line 7
-category c891;
-#line 7
-category c892;
-#line 7
-category c893;
-#line 7
-category c894;
-#line 7
-category c895;
-#line 7
-category c896;
-#line 7
-category c897;
-#line 7
-category c898;
-#line 7
-category c899;
-#line 7
-category c900;
-#line 7
-category c901;
-#line 7
-category c902;
-#line 7
-category c903;
-#line 7
-category c904;
-#line 7
-category c905;
-#line 7
-category c906;
-#line 7
-category c907;
-#line 7
-category c908;
-#line 7
-category c909;
-#line 7
-category c910;
-#line 7
-category c911;
-#line 7
-category c912;
-#line 7
-category c913;
-#line 7
-category c914;
-#line 7
-category c915;
-#line 7
-category c916;
-#line 7
-category c917;
-#line 7
-category c918;
-#line 7
-category c919;
-#line 7
-category c920;
-#line 7
-category c921;
-#line 7
-category c922;
-#line 7
-category c923;
-#line 7
-category c924;
-#line 7
-category c925;
-#line 7
-category c926;
-#line 7
-category c927;
-#line 7
-category c928;
-#line 7
-category c929;
-#line 7
-category c930;
-#line 7
-category c931;
-#line 7
-category c932;
-#line 7
-category c933;
-#line 7
-category c934;
-#line 7
-category c935;
-#line 7
-category c936;
-#line 7
-category c937;
-#line 7
-category c938;
-#line 7
-category c939;
-#line 7
-category c940;
-#line 7
-category c941;
-#line 7
-category c942;
-#line 7
-category c943;
-#line 7
-category c944;
-#line 7
-category c945;
-#line 7
-category c946;
-#line 7
-category c947;
-#line 7
-category c948;
-#line 7
-category c949;
-#line 7
-category c950;
-#line 7
-category c951;
-#line 7
-category c952;
-#line 7
-category c953;
-#line 7
-category c954;
-#line 7
-category c955;
-#line 7
-category c956;
-#line 7
-category c957;
-#line 7
-category c958;
-#line 7
-category c959;
-#line 7
-category c960;
-#line 7
-category c961;
-#line 7
-category c962;
-#line 7
-category c963;
-#line 7
-category c964;
-#line 7
-category c965;
-#line 7
-category c966;
-#line 7
-category c967;
-#line 7
-category c968;
-#line 7
-category c969;
-#line 7
-category c970;
-#line 7
-category c971;
-#line 7
-category c972;
-#line 7
-category c973;
-#line 7
-category c974;
-#line 7
-category c975;
-#line 7
-category c976;
-#line 7
-category c977;
-#line 7
-category c978;
-#line 7
-category c979;
-#line 7
-category c980;
-#line 7
-category c981;
-#line 7
-category c982;
-#line 7
-category c983;
-#line 7
-category c984;
-#line 7
-category c985;
-#line 7
-category c986;
-#line 7
-category c987;
-#line 7
-category c988;
-#line 7
-category c989;
-#line 7
-category c990;
-#line 7
-category c991;
-#line 7
-category c992;
-#line 7
-category c993;
-#line 7
-category c994;
-#line 7
-category c995;
-#line 7
-category c996;
-#line 7
-category c997;
-#line 7
-category c998;
-#line 7
-category c999;
-#line 7
-category c1000;
-#line 7
-category c1001;
-#line 7
-category c1002;
-#line 7
-category c1003;
-#line 7
-category c1004;
-#line 7
-category c1005;
-#line 7
-category c1006;
-#line 7
-category c1007;
-#line 7
-category c1008;
-#line 7
-category c1009;
-#line 7
-category c1010;
-#line 7
-category c1011;
-#line 7
-category c1012;
-#line 7
-category c1013;
-#line 7
-category c1014;
-#line 7
-category c1015;
-#line 7
-category c1016;
-#line 7
-category c1017;
-#line 7
-category c1018;
-#line 7
-category c1019;
-#line 7
-category c1020;
-#line 7
-category c1021;
-#line 7
-category c1022;
-#line 7
-category c1023;
-#line 7
-
-
-# Generate level definitions for each sensitivity and category.
-level s0:c0.c1023;
-#line 10
-
-######################################
-# Attribute declarations
-#
-
-# All types used for processes.
-attribute domain;
-
-# Domains that are allowed all permissions ("unconfined").
-attribute unconfineddomain;
-
-# All domains used for apps.
-attribute appdomain;
-
-# All types used for files that can exist on a labeled fs.
-# Do not use for pseudo file types.
-attribute file_type;
-
-# All types used for domain entry points.
-attribute exec_type;
-
-#line 1 "external/sepolicy/bluetooth.te"
-# bluetooth subsystem
-type bluetooth, domain;
-permissive bluetooth;
-
-#line 4
-typeattribute bluetooth appdomain;
-
-#line 5
-typeattribute bluetooth unconfineddomain;
-#line 5
-
-#line 1 "external/sepolicy/healthd.te"
-# healthd seclabel is specified in init.rc since
-# it lives in the rootfs and has no unique file type.
-type healthd, domain;
-permissive healthd;
-type healthd_exec, exec_type, file_type;
-
-# New domain is entered by executing the file.
-#line 7
-allow healthd healthd_exec:file { entrypoint read execute };
-
-###
-### Neverallow rules
-###
-### These are things that Android apps should NEVER be able to do
-###
-
-# Superuser capabilities.
-# bluetooth requires net_admin.
-neverallow { appdomain -unconfineddomain -bluetooth } self:capability *;
-
-# Added to make the neverallow rule make sense in a limited environment.
-# Added at the bottom to not throw off file seek numbers in test suite.  
-# This is not a problem, because allow rules are processed after all types
-# are gathered.
-type testTYPE, appdomain, domain;
diff --git a/tools/selinux/test/policy_test.conf b/tools/selinux/test/policy_test.conf
deleted file mode 100644
index d0962cd..0000000
--- a/tools/selinux/test/policy_test.conf
+++ /dev/null
@@ -1,2244 +0,0 @@
-#line 1 "external/sepolicy/security_classes"
-# FLASK
-
-#
-# Define the security object classes
-#
-
-# Classes marked as userspace are classes
-# for userspace object managers
-
-class capability
-
-# file-related classes
-class file
-
-#
-# Define a common prefix for file access vectors.
-#
-
-common file
-{
-	ioctl
-	read
-	write
-	create
-	getattr
-	setattr
-	lock
-	relabelfrom
-	relabelto
-	append
-	unlink
-	link
-	rename
-	execute
-	swapon
-	quotaon
-	mounton
-}
-
-class file
-inherits file
-{
-	execute_no_trans
-	entrypoint
-	execmod
-	open
-	audit_access
-}
-
-class capability
-{
-	# The capabilities are defined in include/linux/capability.h
-	# Capabilities >= 32 are defined in the capability2 class.
-	# Care should be taken to ensure that these are consistent with
-	# those definitions. (Order matters)
-
-	chown
-	dac_override
-	dac_read_search
-	fowner
-	fsetid
-	kill
-	setgid
-	setuid
-	setpcap
-	linux_immutable
-	net_bind_service
-	net_broadcast
-	net_admin
-	net_raw
-	ipc_lock
-	ipc_owner
-	sys_module
-	sys_rawio
-	sys_chroot
-	sys_ptrace
-	sys_pacct
-	sys_admin
-	sys_boot
-	sys_nice
-	sys_resource
-	sys_time
-	sys_tty_config
-	mknod
-	lease
-	audit_write
-	audit_control
-	setfcap
-}
-
-########################################
-#
-# Basic level names for system low and high
-#
-
-
-#line 1 "external/sepolicy/mls"
-#########################################
-# MLS declarations
-#
-
-# Generate the desired number of sensitivities and categories.
-
-#line 6
-# Each sensitivity has a name and zero or more aliases.
-#line 6
-sensitivity s0;
-#line 6
-
-#line 6
-
-#line 6
-# Define the ordering of the sensitivity levels (least to greatest)
-#line 6
-dominance { s0  }
-#line 6
-
-category c0;
-#line 7
-category c1;
-#line 7
-category c2;
-#line 7
-category c3;
-#line 7
-category c4;
-#line 7
-category c5;
-#line 7
-category c6;
-#line 7
-category c7;
-#line 7
-category c8;
-#line 7
-category c9;
-#line 7
-category c10;
-#line 7
-category c11;
-#line 7
-category c12;
-#line 7
-category c13;
-#line 7
-category c14;
-#line 7
-category c15;
-#line 7
-category c16;
-#line 7
-category c17;
-#line 7
-category c18;
-#line 7
-category c19;
-#line 7
-category c20;
-#line 7
-category c21;
-#line 7
-category c22;
-#line 7
-category c23;
-#line 7
-category c24;
-#line 7
-category c25;
-#line 7
-category c26;
-#line 7
-category c27;
-#line 7
-category c28;
-#line 7
-category c29;
-#line 7
-category c30;
-#line 7
-category c31;
-#line 7
-category c32;
-#line 7
-category c33;
-#line 7
-category c34;
-#line 7
-category c35;
-#line 7
-category c36;
-#line 7
-category c37;
-#line 7
-category c38;
-#line 7
-category c39;
-#line 7
-category c40;
-#line 7
-category c41;
-#line 7
-category c42;
-#line 7
-category c43;
-#line 7
-category c44;
-#line 7
-category c45;
-#line 7
-category c46;
-#line 7
-category c47;
-#line 7
-category c48;
-#line 7
-category c49;
-#line 7
-category c50;
-#line 7
-category c51;
-#line 7
-category c52;
-#line 7
-category c53;
-#line 7
-category c54;
-#line 7
-category c55;
-#line 7
-category c56;
-#line 7
-category c57;
-#line 7
-category c58;
-#line 7
-category c59;
-#line 7
-category c60;
-#line 7
-category c61;
-#line 7
-category c62;
-#line 7
-category c63;
-#line 7
-category c64;
-#line 7
-category c65;
-#line 7
-category c66;
-#line 7
-category c67;
-#line 7
-category c68;
-#line 7
-category c69;
-#line 7
-category c70;
-#line 7
-category c71;
-#line 7
-category c72;
-#line 7
-category c73;
-#line 7
-category c74;
-#line 7
-category c75;
-#line 7
-category c76;
-#line 7
-category c77;
-#line 7
-category c78;
-#line 7
-category c79;
-#line 7
-category c80;
-#line 7
-category c81;
-#line 7
-category c82;
-#line 7
-category c83;
-#line 7
-category c84;
-#line 7
-category c85;
-#line 7
-category c86;
-#line 7
-category c87;
-#line 7
-category c88;
-#line 7
-category c89;
-#line 7
-category c90;
-#line 7
-category c91;
-#line 7
-category c92;
-#line 7
-category c93;
-#line 7
-category c94;
-#line 7
-category c95;
-#line 7
-category c96;
-#line 7
-category c97;
-#line 7
-category c98;
-#line 7
-category c99;
-#line 7
-category c100;
-#line 7
-category c101;
-#line 7
-category c102;
-#line 7
-category c103;
-#line 7
-category c104;
-#line 7
-category c105;
-#line 7
-category c106;
-#line 7
-category c107;
-#line 7
-category c108;
-#line 7
-category c109;
-#line 7
-category c110;
-#line 7
-category c111;
-#line 7
-category c112;
-#line 7
-category c113;
-#line 7
-category c114;
-#line 7
-category c115;
-#line 7
-category c116;
-#line 7
-category c117;
-#line 7
-category c118;
-#line 7
-category c119;
-#line 7
-category c120;
-#line 7
-category c121;
-#line 7
-category c122;
-#line 7
-category c123;
-#line 7
-category c124;
-#line 7
-category c125;
-#line 7
-category c126;
-#line 7
-category c127;
-#line 7
-category c128;
-#line 7
-category c129;
-#line 7
-category c130;
-#line 7
-category c131;
-#line 7
-category c132;
-#line 7
-category c133;
-#line 7
-category c134;
-#line 7
-category c135;
-#line 7
-category c136;
-#line 7
-category c137;
-#line 7
-category c138;
-#line 7
-category c139;
-#line 7
-category c140;
-#line 7
-category c141;
-#line 7
-category c142;
-#line 7
-category c143;
-#line 7
-category c144;
-#line 7
-category c145;
-#line 7
-category c146;
-#line 7
-category c147;
-#line 7
-category c148;
-#line 7
-category c149;
-#line 7
-category c150;
-#line 7
-category c151;
-#line 7
-category c152;
-#line 7
-category c153;
-#line 7
-category c154;
-#line 7
-category c155;
-#line 7
-category c156;
-#line 7
-category c157;
-#line 7
-category c158;
-#line 7
-category c159;
-#line 7
-category c160;
-#line 7
-category c161;
-#line 7
-category c162;
-#line 7
-category c163;
-#line 7
-category c164;
-#line 7
-category c165;
-#line 7
-category c166;
-#line 7
-category c167;
-#line 7
-category c168;
-#line 7
-category c169;
-#line 7
-category c170;
-#line 7
-category c171;
-#line 7
-category c172;
-#line 7
-category c173;
-#line 7
-category c174;
-#line 7
-category c175;
-#line 7
-category c176;
-#line 7
-category c177;
-#line 7
-category c178;
-#line 7
-category c179;
-#line 7
-category c180;
-#line 7
-category c181;
-#line 7
-category c182;
-#line 7
-category c183;
-#line 7
-category c184;
-#line 7
-category c185;
-#line 7
-category c186;
-#line 7
-category c187;
-#line 7
-category c188;
-#line 7
-category c189;
-#line 7
-category c190;
-#line 7
-category c191;
-#line 7
-category c192;
-#line 7
-category c193;
-#line 7
-category c194;
-#line 7
-category c195;
-#line 7
-category c196;
-#line 7
-category c197;
-#line 7
-category c198;
-#line 7
-category c199;
-#line 7
-category c200;
-#line 7
-category c201;
-#line 7
-category c202;
-#line 7
-category c203;
-#line 7
-category c204;
-#line 7
-category c205;
-#line 7
-category c206;
-#line 7
-category c207;
-#line 7
-category c208;
-#line 7
-category c209;
-#line 7
-category c210;
-#line 7
-category c211;
-#line 7
-category c212;
-#line 7
-category c213;
-#line 7
-category c214;
-#line 7
-category c215;
-#line 7
-category c216;
-#line 7
-category c217;
-#line 7
-category c218;
-#line 7
-category c219;
-#line 7
-category c220;
-#line 7
-category c221;
-#line 7
-category c222;
-#line 7
-category c223;
-#line 7
-category c224;
-#line 7
-category c225;
-#line 7
-category c226;
-#line 7
-category c227;
-#line 7
-category c228;
-#line 7
-category c229;
-#line 7
-category c230;
-#line 7
-category c231;
-#line 7
-category c232;
-#line 7
-category c233;
-#line 7
-category c234;
-#line 7
-category c235;
-#line 7
-category c236;
-#line 7
-category c237;
-#line 7
-category c238;
-#line 7
-category c239;
-#line 7
-category c240;
-#line 7
-category c241;
-#line 7
-category c242;
-#line 7
-category c243;
-#line 7
-category c244;
-#line 7
-category c245;
-#line 7
-category c246;
-#line 7
-category c247;
-#line 7
-category c248;
-#line 7
-category c249;
-#line 7
-category c250;
-#line 7
-category c251;
-#line 7
-category c252;
-#line 7
-category c253;
-#line 7
-category c254;
-#line 7
-category c255;
-#line 7
-category c256;
-#line 7
-category c257;
-#line 7
-category c258;
-#line 7
-category c259;
-#line 7
-category c260;
-#line 7
-category c261;
-#line 7
-category c262;
-#line 7
-category c263;
-#line 7
-category c264;
-#line 7
-category c265;
-#line 7
-category c266;
-#line 7
-category c267;
-#line 7
-category c268;
-#line 7
-category c269;
-#line 7
-category c270;
-#line 7
-category c271;
-#line 7
-category c272;
-#line 7
-category c273;
-#line 7
-category c274;
-#line 7
-category c275;
-#line 7
-category c276;
-#line 7
-category c277;
-#line 7
-category c278;
-#line 7
-category c279;
-#line 7
-category c280;
-#line 7
-category c281;
-#line 7
-category c282;
-#line 7
-category c283;
-#line 7
-category c284;
-#line 7
-category c285;
-#line 7
-category c286;
-#line 7
-category c287;
-#line 7
-category c288;
-#line 7
-category c289;
-#line 7
-category c290;
-#line 7
-category c291;
-#line 7
-category c292;
-#line 7
-category c293;
-#line 7
-category c294;
-#line 7
-category c295;
-#line 7
-category c296;
-#line 7
-category c297;
-#line 7
-category c298;
-#line 7
-category c299;
-#line 7
-category c300;
-#line 7
-category c301;
-#line 7
-category c302;
-#line 7
-category c303;
-#line 7
-category c304;
-#line 7
-category c305;
-#line 7
-category c306;
-#line 7
-category c307;
-#line 7
-category c308;
-#line 7
-category c309;
-#line 7
-category c310;
-#line 7
-category c311;
-#line 7
-category c312;
-#line 7
-category c313;
-#line 7
-category c314;
-#line 7
-category c315;
-#line 7
-category c316;
-#line 7
-category c317;
-#line 7
-category c318;
-#line 7
-category c319;
-#line 7
-category c320;
-#line 7
-category c321;
-#line 7
-category c322;
-#line 7
-category c323;
-#line 7
-category c324;
-#line 7
-category c325;
-#line 7
-category c326;
-#line 7
-category c327;
-#line 7
-category c328;
-#line 7
-category c329;
-#line 7
-category c330;
-#line 7
-category c331;
-#line 7
-category c332;
-#line 7
-category c333;
-#line 7
-category c334;
-#line 7
-category c335;
-#line 7
-category c336;
-#line 7
-category c337;
-#line 7
-category c338;
-#line 7
-category c339;
-#line 7
-category c340;
-#line 7
-category c341;
-#line 7
-category c342;
-#line 7
-category c343;
-#line 7
-category c344;
-#line 7
-category c345;
-#line 7
-category c346;
-#line 7
-category c347;
-#line 7
-category c348;
-#line 7
-category c349;
-#line 7
-category c350;
-#line 7
-category c351;
-#line 7
-category c352;
-#line 7
-category c353;
-#line 7
-category c354;
-#line 7
-category c355;
-#line 7
-category c356;
-#line 7
-category c357;
-#line 7
-category c358;
-#line 7
-category c359;
-#line 7
-category c360;
-#line 7
-category c361;
-#line 7
-category c362;
-#line 7
-category c363;
-#line 7
-category c364;
-#line 7
-category c365;
-#line 7
-category c366;
-#line 7
-category c367;
-#line 7
-category c368;
-#line 7
-category c369;
-#line 7
-category c370;
-#line 7
-category c371;
-#line 7
-category c372;
-#line 7
-category c373;
-#line 7
-category c374;
-#line 7
-category c375;
-#line 7
-category c376;
-#line 7
-category c377;
-#line 7
-category c378;
-#line 7
-category c379;
-#line 7
-category c380;
-#line 7
-category c381;
-#line 7
-category c382;
-#line 7
-category c383;
-#line 7
-category c384;
-#line 7
-category c385;
-#line 7
-category c386;
-#line 7
-category c387;
-#line 7
-category c388;
-#line 7
-category c389;
-#line 7
-category c390;
-#line 7
-category c391;
-#line 7
-category c392;
-#line 7
-category c393;
-#line 7
-category c394;
-#line 7
-category c395;
-#line 7
-category c396;
-#line 7
-category c397;
-#line 7
-category c398;
-#line 7
-category c399;
-#line 7
-category c400;
-#line 7
-category c401;
-#line 7
-category c402;
-#line 7
-category c403;
-#line 7
-category c404;
-#line 7
-category c405;
-#line 7
-category c406;
-#line 7
-category c407;
-#line 7
-category c408;
-#line 7
-category c409;
-#line 7
-category c410;
-#line 7
-category c411;
-#line 7
-category c412;
-#line 7
-category c413;
-#line 7
-category c414;
-#line 7
-category c415;
-#line 7
-category c416;
-#line 7
-category c417;
-#line 7
-category c418;
-#line 7
-category c419;
-#line 7
-category c420;
-#line 7
-category c421;
-#line 7
-category c422;
-#line 7
-category c423;
-#line 7
-category c424;
-#line 7
-category c425;
-#line 7
-category c426;
-#line 7
-category c427;
-#line 7
-category c428;
-#line 7
-category c429;
-#line 7
-category c430;
-#line 7
-category c431;
-#line 7
-category c432;
-#line 7
-category c433;
-#line 7
-category c434;
-#line 7
-category c435;
-#line 7
-category c436;
-#line 7
-category c437;
-#line 7
-category c438;
-#line 7
-category c439;
-#line 7
-category c440;
-#line 7
-category c441;
-#line 7
-category c442;
-#line 7
-category c443;
-#line 7
-category c444;
-#line 7
-category c445;
-#line 7
-category c446;
-#line 7
-category c447;
-#line 7
-category c448;
-#line 7
-category c449;
-#line 7
-category c450;
-#line 7
-category c451;
-#line 7
-category c452;
-#line 7
-category c453;
-#line 7
-category c454;
-#line 7
-category c455;
-#line 7
-category c456;
-#line 7
-category c457;
-#line 7
-category c458;
-#line 7
-category c459;
-#line 7
-category c460;
-#line 7
-category c461;
-#line 7
-category c462;
-#line 7
-category c463;
-#line 7
-category c464;
-#line 7
-category c465;
-#line 7
-category c466;
-#line 7
-category c467;
-#line 7
-category c468;
-#line 7
-category c469;
-#line 7
-category c470;
-#line 7
-category c471;
-#line 7
-category c472;
-#line 7
-category c473;
-#line 7
-category c474;
-#line 7
-category c475;
-#line 7
-category c476;
-#line 7
-category c477;
-#line 7
-category c478;
-#line 7
-category c479;
-#line 7
-category c480;
-#line 7
-category c481;
-#line 7
-category c482;
-#line 7
-category c483;
-#line 7
-category c484;
-#line 7
-category c485;
-#line 7
-category c486;
-#line 7
-category c487;
-#line 7
-category c488;
-#line 7
-category c489;
-#line 7
-category c490;
-#line 7
-category c491;
-#line 7
-category c492;
-#line 7
-category c493;
-#line 7
-category c494;
-#line 7
-category c495;
-#line 7
-category c496;
-#line 7
-category c497;
-#line 7
-category c498;
-#line 7
-category c499;
-#line 7
-category c500;
-#line 7
-category c501;
-#line 7
-category c502;
-#line 7
-category c503;
-#line 7
-category c504;
-#line 7
-category c505;
-#line 7
-category c506;
-#line 7
-category c507;
-#line 7
-category c508;
-#line 7
-category c509;
-#line 7
-category c510;
-#line 7
-category c511;
-#line 7
-category c512;
-#line 7
-category c513;
-#line 7
-category c514;
-#line 7
-category c515;
-#line 7
-category c516;
-#line 7
-category c517;
-#line 7
-category c518;
-#line 7
-category c519;
-#line 7
-category c520;
-#line 7
-category c521;
-#line 7
-category c522;
-#line 7
-category c523;
-#line 7
-category c524;
-#line 7
-category c525;
-#line 7
-category c526;
-#line 7
-category c527;
-#line 7
-category c528;
-#line 7
-category c529;
-#line 7
-category c530;
-#line 7
-category c531;
-#line 7
-category c532;
-#line 7
-category c533;
-#line 7
-category c534;
-#line 7
-category c535;
-#line 7
-category c536;
-#line 7
-category c537;
-#line 7
-category c538;
-#line 7
-category c539;
-#line 7
-category c540;
-#line 7
-category c541;
-#line 7
-category c542;
-#line 7
-category c543;
-#line 7
-category c544;
-#line 7
-category c545;
-#line 7
-category c546;
-#line 7
-category c547;
-#line 7
-category c548;
-#line 7
-category c549;
-#line 7
-category c550;
-#line 7
-category c551;
-#line 7
-category c552;
-#line 7
-category c553;
-#line 7
-category c554;
-#line 7
-category c555;
-#line 7
-category c556;
-#line 7
-category c557;
-#line 7
-category c558;
-#line 7
-category c559;
-#line 7
-category c560;
-#line 7
-category c561;
-#line 7
-category c562;
-#line 7
-category c563;
-#line 7
-category c564;
-#line 7
-category c565;
-#line 7
-category c566;
-#line 7
-category c567;
-#line 7
-category c568;
-#line 7
-category c569;
-#line 7
-category c570;
-#line 7
-category c571;
-#line 7
-category c572;
-#line 7
-category c573;
-#line 7
-category c574;
-#line 7
-category c575;
-#line 7
-category c576;
-#line 7
-category c577;
-#line 7
-category c578;
-#line 7
-category c579;
-#line 7
-category c580;
-#line 7
-category c581;
-#line 7
-category c582;
-#line 7
-category c583;
-#line 7
-category c584;
-#line 7
-category c585;
-#line 7
-category c586;
-#line 7
-category c587;
-#line 7
-category c588;
-#line 7
-category c589;
-#line 7
-category c590;
-#line 7
-category c591;
-#line 7
-category c592;
-#line 7
-category c593;
-#line 7
-category c594;
-#line 7
-category c595;
-#line 7
-category c596;
-#line 7
-category c597;
-#line 7
-category c598;
-#line 7
-category c599;
-#line 7
-category c600;
-#line 7
-category c601;
-#line 7
-category c602;
-#line 7
-category c603;
-#line 7
-category c604;
-#line 7
-category c605;
-#line 7
-category c606;
-#line 7
-category c607;
-#line 7
-category c608;
-#line 7
-category c609;
-#line 7
-category c610;
-#line 7
-category c611;
-#line 7
-category c612;
-#line 7
-category c613;
-#line 7
-category c614;
-#line 7
-category c615;
-#line 7
-category c616;
-#line 7
-category c617;
-#line 7
-category c618;
-#line 7
-category c619;
-#line 7
-category c620;
-#line 7
-category c621;
-#line 7
-category c622;
-#line 7
-category c623;
-#line 7
-category c624;
-#line 7
-category c625;
-#line 7
-category c626;
-#line 7
-category c627;
-#line 7
-category c628;
-#line 7
-category c629;
-#line 7
-category c630;
-#line 7
-category c631;
-#line 7
-category c632;
-#line 7
-category c633;
-#line 7
-category c634;
-#line 7
-category c635;
-#line 7
-category c636;
-#line 7
-category c637;
-#line 7
-category c638;
-#line 7
-category c639;
-#line 7
-category c640;
-#line 7
-category c641;
-#line 7
-category c642;
-#line 7
-category c643;
-#line 7
-category c644;
-#line 7
-category c645;
-#line 7
-category c646;
-#line 7
-category c647;
-#line 7
-category c648;
-#line 7
-category c649;
-#line 7
-category c650;
-#line 7
-category c651;
-#line 7
-category c652;
-#line 7
-category c653;
-#line 7
-category c654;
-#line 7
-category c655;
-#line 7
-category c656;
-#line 7
-category c657;
-#line 7
-category c658;
-#line 7
-category c659;
-#line 7
-category c660;
-#line 7
-category c661;
-#line 7
-category c662;
-#line 7
-category c663;
-#line 7
-category c664;
-#line 7
-category c665;
-#line 7
-category c666;
-#line 7
-category c667;
-#line 7
-category c668;
-#line 7
-category c669;
-#line 7
-category c670;
-#line 7
-category c671;
-#line 7
-category c672;
-#line 7
-category c673;
-#line 7
-category c674;
-#line 7
-category c675;
-#line 7
-category c676;
-#line 7
-category c677;
-#line 7
-category c678;
-#line 7
-category c679;
-#line 7
-category c680;
-#line 7
-category c681;
-#line 7
-category c682;
-#line 7
-category c683;
-#line 7
-category c684;
-#line 7
-category c685;
-#line 7
-category c686;
-#line 7
-category c687;
-#line 7
-category c688;
-#line 7
-category c689;
-#line 7
-category c690;
-#line 7
-category c691;
-#line 7
-category c692;
-#line 7
-category c693;
-#line 7
-category c694;
-#line 7
-category c695;
-#line 7
-category c696;
-#line 7
-category c697;
-#line 7
-category c698;
-#line 7
-category c699;
-#line 7
-category c700;
-#line 7
-category c701;
-#line 7
-category c702;
-#line 7
-category c703;
-#line 7
-category c704;
-#line 7
-category c705;
-#line 7
-category c706;
-#line 7
-category c707;
-#line 7
-category c708;
-#line 7
-category c709;
-#line 7
-category c710;
-#line 7
-category c711;
-#line 7
-category c712;
-#line 7
-category c713;
-#line 7
-category c714;
-#line 7
-category c715;
-#line 7
-category c716;
-#line 7
-category c717;
-#line 7
-category c718;
-#line 7
-category c719;
-#line 7
-category c720;
-#line 7
-category c721;
-#line 7
-category c722;
-#line 7
-category c723;
-#line 7
-category c724;
-#line 7
-category c725;
-#line 7
-category c726;
-#line 7
-category c727;
-#line 7
-category c728;
-#line 7
-category c729;
-#line 7
-category c730;
-#line 7
-category c731;
-#line 7
-category c732;
-#line 7
-category c733;
-#line 7
-category c734;
-#line 7
-category c735;
-#line 7
-category c736;
-#line 7
-category c737;
-#line 7
-category c738;
-#line 7
-category c739;
-#line 7
-category c740;
-#line 7
-category c741;
-#line 7
-category c742;
-#line 7
-category c743;
-#line 7
-category c744;
-#line 7
-category c745;
-#line 7
-category c746;
-#line 7
-category c747;
-#line 7
-category c748;
-#line 7
-category c749;
-#line 7
-category c750;
-#line 7
-category c751;
-#line 7
-category c752;
-#line 7
-category c753;
-#line 7
-category c754;
-#line 7
-category c755;
-#line 7
-category c756;
-#line 7
-category c757;
-#line 7
-category c758;
-#line 7
-category c759;
-#line 7
-category c760;
-#line 7
-category c761;
-#line 7
-category c762;
-#line 7
-category c763;
-#line 7
-category c764;
-#line 7
-category c765;
-#line 7
-category c766;
-#line 7
-category c767;
-#line 7
-category c768;
-#line 7
-category c769;
-#line 7
-category c770;
-#line 7
-category c771;
-#line 7
-category c772;
-#line 7
-category c773;
-#line 7
-category c774;
-#line 7
-category c775;
-#line 7
-category c776;
-#line 7
-category c777;
-#line 7
-category c778;
-#line 7
-category c779;
-#line 7
-category c780;
-#line 7
-category c781;
-#line 7
-category c782;
-#line 7
-category c783;
-#line 7
-category c784;
-#line 7
-category c785;
-#line 7
-category c786;
-#line 7
-category c787;
-#line 7
-category c788;
-#line 7
-category c789;
-#line 7
-category c790;
-#line 7
-category c791;
-#line 7
-category c792;
-#line 7
-category c793;
-#line 7
-category c794;
-#line 7
-category c795;
-#line 7
-category c796;
-#line 7
-category c797;
-#line 7
-category c798;
-#line 7
-category c799;
-#line 7
-category c800;
-#line 7
-category c801;
-#line 7
-category c802;
-#line 7
-category c803;
-#line 7
-category c804;
-#line 7
-category c805;
-#line 7
-category c806;
-#line 7
-category c807;
-#line 7
-category c808;
-#line 7
-category c809;
-#line 7
-category c810;
-#line 7
-category c811;
-#line 7
-category c812;
-#line 7
-category c813;
-#line 7
-category c814;
-#line 7
-category c815;
-#line 7
-category c816;
-#line 7
-category c817;
-#line 7
-category c818;
-#line 7
-category c819;
-#line 7
-category c820;
-#line 7
-category c821;
-#line 7
-category c822;
-#line 7
-category c823;
-#line 7
-category c824;
-#line 7
-category c825;
-#line 7
-category c826;
-#line 7
-category c827;
-#line 7
-category c828;
-#line 7
-category c829;
-#line 7
-category c830;
-#line 7
-category c831;
-#line 7
-category c832;
-#line 7
-category c833;
-#line 7
-category c834;
-#line 7
-category c835;
-#line 7
-category c836;
-#line 7
-category c837;
-#line 7
-category c838;
-#line 7
-category c839;
-#line 7
-category c840;
-#line 7
-category c841;
-#line 7
-category c842;
-#line 7
-category c843;
-#line 7
-category c844;
-#line 7
-category c845;
-#line 7
-category c846;
-#line 7
-category c847;
-#line 7
-category c848;
-#line 7
-category c849;
-#line 7
-category c850;
-#line 7
-category c851;
-#line 7
-category c852;
-#line 7
-category c853;
-#line 7
-category c854;
-#line 7
-category c855;
-#line 7
-category c856;
-#line 7
-category c857;
-#line 7
-category c858;
-#line 7
-category c859;
-#line 7
-category c860;
-#line 7
-category c861;
-#line 7
-category c862;
-#line 7
-category c863;
-#line 7
-category c864;
-#line 7
-category c865;
-#line 7
-category c866;
-#line 7
-category c867;
-#line 7
-category c868;
-#line 7
-category c869;
-#line 7
-category c870;
-#line 7
-category c871;
-#line 7
-category c872;
-#line 7
-category c873;
-#line 7
-category c874;
-#line 7
-category c875;
-#line 7
-category c876;
-#line 7
-category c877;
-#line 7
-category c878;
-#line 7
-category c879;
-#line 7
-category c880;
-#line 7
-category c881;
-#line 7
-category c882;
-#line 7
-category c883;
-#line 7
-category c884;
-#line 7
-category c885;
-#line 7
-category c886;
-#line 7
-category c887;
-#line 7
-category c888;
-#line 7
-category c889;
-#line 7
-category c890;
-#line 7
-category c891;
-#line 7
-category c892;
-#line 7
-category c893;
-#line 7
-category c894;
-#line 7
-category c895;
-#line 7
-category c896;
-#line 7
-category c897;
-#line 7
-category c898;
-#line 7
-category c899;
-#line 7
-category c900;
-#line 7
-category c901;
-#line 7
-category c902;
-#line 7
-category c903;
-#line 7
-category c904;
-#line 7
-category c905;
-#line 7
-category c906;
-#line 7
-category c907;
-#line 7
-category c908;
-#line 7
-category c909;
-#line 7
-category c910;
-#line 7
-category c911;
-#line 7
-category c912;
-#line 7
-category c913;
-#line 7
-category c914;
-#line 7
-category c915;
-#line 7
-category c916;
-#line 7
-category c917;
-#line 7
-category c918;
-#line 7
-category c919;
-#line 7
-category c920;
-#line 7
-category c921;
-#line 7
-category c922;
-#line 7
-category c923;
-#line 7
-category c924;
-#line 7
-category c925;
-#line 7
-category c926;
-#line 7
-category c927;
-#line 7
-category c928;
-#line 7
-category c929;
-#line 7
-category c930;
-#line 7
-category c931;
-#line 7
-category c932;
-#line 7
-category c933;
-#line 7
-category c934;
-#line 7
-category c935;
-#line 7
-category c936;
-#line 7
-category c937;
-#line 7
-category c938;
-#line 7
-category c939;
-#line 7
-category c940;
-#line 7
-category c941;
-#line 7
-category c942;
-#line 7
-category c943;
-#line 7
-category c944;
-#line 7
-category c945;
-#line 7
-category c946;
-#line 7
-category c947;
-#line 7
-category c948;
-#line 7
-category c949;
-#line 7
-category c950;
-#line 7
-category c951;
-#line 7
-category c952;
-#line 7
-category c953;
-#line 7
-category c954;
-#line 7
-category c955;
-#line 7
-category c956;
-#line 7
-category c957;
-#line 7
-category c958;
-#line 7
-category c959;
-#line 7
-category c960;
-#line 7
-category c961;
-#line 7
-category c962;
-#line 7
-category c963;
-#line 7
-category c964;
-#line 7
-category c965;
-#line 7
-category c966;
-#line 7
-category c967;
-#line 7
-category c968;
-#line 7
-category c969;
-#line 7
-category c970;
-#line 7
-category c971;
-#line 7
-category c972;
-#line 7
-category c973;
-#line 7
-category c974;
-#line 7
-category c975;
-#line 7
-category c976;
-#line 7
-category c977;
-#line 7
-category c978;
-#line 7
-category c979;
-#line 7
-category c980;
-#line 7
-category c981;
-#line 7
-category c982;
-#line 7
-category c983;
-#line 7
-category c984;
-#line 7
-category c985;
-#line 7
-category c986;
-#line 7
-category c987;
-#line 7
-category c988;
-#line 7
-category c989;
-#line 7
-category c990;
-#line 7
-category c991;
-#line 7
-category c992;
-#line 7
-category c993;
-#line 7
-category c994;
-#line 7
-category c995;
-#line 7
-category c996;
-#line 7
-category c997;
-#line 7
-category c998;
-#line 7
-category c999;
-#line 7
-category c1000;
-#line 7
-category c1001;
-#line 7
-category c1002;
-#line 7
-category c1003;
-#line 7
-category c1004;
-#line 7
-category c1005;
-#line 7
-category c1006;
-#line 7
-category c1007;
-#line 7
-category c1008;
-#line 7
-category c1009;
-#line 7
-category c1010;
-#line 7
-category c1011;
-#line 7
-category c1012;
-#line 7
-category c1013;
-#line 7
-category c1014;
-#line 7
-category c1015;
-#line 7
-category c1016;
-#line 7
-category c1017;
-#line 7
-category c1018;
-#line 7
-category c1019;
-#line 7
-category c1020;
-#line 7
-category c1021;
-#line 7
-category c1022;
-#line 7
-category c1023;
-#line 7
-
-
-# Generate level definitions for each sensitivity and category.
-level s0:c0.c1023;
-#line 10
-
-######################################
-# Attribute declarations
-#
-
-# All types used for processes.
-attribute domain;
-
-# Domains that are allowed all permissions ("unconfined").
-attribute unconfineddomain;
-
-# All domains used for apps.
-attribute appdomain;
-
-# All types used for files that can exist on a labeled fs.
-# Do not use for pseudo file types.
-attribute file_type;
-
-# All types used for domain entry points.
-attribute exec_type;
-
-#line 1 "external/sepolicy/bluetooth.te"
-# bluetooth subsystem
-type bluetooth, domain;
-permissive bluetooth;
-
-#line 4
-typeattribute bluetooth appdomain;
-
-#line 5
-typeattribute bluetooth unconfineddomain;
-#line 5
-
-#line 1 "external/sepolicy/healthd.te"
-# healthd seclabel is specified in init.rc since
-# it lives in the rootfs and has no unique file type.
-type healthd, domain;
-permissive healthd;
-type healthd_exec, exec_type, file_type;
-
-# New domain is entered by executing the file.
-#line 7
-allow healthd healthd_exec:file { entrypoint read execute };
-
-###
-### Neverallow rules
-###
-### These are things that Android apps should NEVER be able to do
-###
-
-# Superuser capabilities.
-# bluetooth requires net_admin.
-neverallow { appdomain -unconfineddomain -bluetooth } self:capability *;
-
-# Added to make the neverallow rule make sense in a limited environment.
-# Added at the bottom to not throw off file seek numbers in test suite.  
-# This is not a problem, because allow rules are processed after all types
-# are gathered.
-type testTYPE, appdomain, domain;
-
-# added rules for further testing (display full range of needed functionality)
-allow unconfineddomain {fs_type dev_type file_type}:{ chr_file file } ~{entrypoint relabelto};
-
-allow init {fs_type dev_type file_type}:{ dir { { chr_file blk_file } { file lnk_file sock_file fifo_file } } } relabelto;
-
-neverallow { appdomain -unconfineddomain } {
-    audio_device
-    camera_device
-    dm_device
-    radio_device
-    gps_device
-    rpmsg_device
-}:chr_file { read write };
\ No newline at end of file
diff --git a/tools/selinux/test/testrunner.py b/tools/selinux/test/testrunner.py
deleted file mode 100755
index bc424e9..0000000
--- a/tools/selinux/test/testrunner.py
+++ /dev/null
@@ -1,442 +0,0 @@
-#!/usr/bin/python
-import sys
-sys.path.append('../src')
-import unittest
-import SELinux_CTS
-from SELinux_CTS import SELinuxPolicy
-
-policy_file_name = 'policy_test.conf'
-types = set([
-        'bluetooth',
-        'healthd',
-        'healthd_exec',
-        'testTYPE' ])  #testTYPE added for neverallow rule to make sense
-attributes = {
-    'domain': set(['bluetooth', 'healthd', 'testTYPE']),
-    'unconfineddomain': set(['bluetooth']),
-    'appdomain': set(['bluetooth', 'testTYPE']),
-    'file_type': set(['healthd_exec']),
-    'exec_type': set(['healthd_exec']) }
-common_classes = {
-    'file': set([
-            'ioctl',
-            'read',
-            'write',
-            'create',
-            'getattr',
-            'setattr',
-            'lock',
-            'relabelfrom',
-            'relabelto',
-            'append',
-            'unlink',
-            'link',
-            'rename',
-            'execute',
-            'swapon',
-            'quotaon',
-            'mounton' ]) }
-classes = {
-    'capability': set([
-            'chown',
-            'dac_override',
-            'dac_read_search',
-            'fowner',
-            'fsetid',
-            'kill',
-            'setgid',
-            'setuid',
-            'setpcap',
-            'linux_immutable',
-            'net_bind_service',
-            'net_broadcast',
-            'net_admin',
-            'net_raw',
-            'ipc_lock',
-            'ipc_owner',
-            'sys_module',
-            'sys_rawio',
-            'sys_chroot',
-            'sys_ptrace',
-            'sys_pacct',
-            'sys_admin',
-            'sys_boot',
-            'sys_nice',
-            'sys_resource',
-            'sys_time',
-            'sys_tty_config',
-            'mknod',
-            'lease',
-            'audit_write',
-            'audit_control',
-            'setfcap' ]),
-    'file': (set([
-                'execute_no_trans',
-                'entrypoint',
-                'execmod',
-                'open',
-                'audit_access' ]) | common_classes['file']) }
-
-# allow healthd healthd_exec:file { entrypoint read execute };
-allow_rules = [
-    { 'source_types': {
-        'set': set([
-                'healthd']),
-        'flags': { 'complement': False } },
-      'target_types': {
-        'set': set([
-                'healthd_exec']),
-        'flags': { 'complement': False } },
-      'classes': {
-        'set': set([
-                'file']),
-        'flags': { 'complement': False } },
-      'permissions': {
-        'set': set([
-                'entrypoint',
-                'read',
-                'execute' ]),
-        'flags': { 'complement': False } } } ]
-
-# neverallow { appdomain -unconfineddomain -bluetooth } self:capability *;
-neverallow_rules = [
-    { 'source_types': {
-        'set': set([
-                'appdomain',
-                '-unconfineddomain',
-                '-bluetooth' ]),
-        'flags': { 'complement': False } },
-      'target_types': {
-        'set': set([
-                'self']),
-        'flags': { 'complement': False } },
-      'classes': {
-        'set': set([
-                'capability']),
-        'flags': { 'complement': False } },
-      'permissions': {
-        'set': set([
-                '*' ]),
-        'flags': { 'complement': False } } } ]
-
-expected_final_allow_list = [
-        [ ('healthd', 'healthd_exec', 'file', 'entrypoint'),
-                ('healthd', 'healthd_exec', 'file', 'read'),
-                ('healthd', 'healthd_exec', 'file', 'execute') ] ]
-
-expected_final_neverallow_list = [
-        [ ('testTYPE', 'testTYPE', 'capability', 'chown'),
-                ('testTYPE', 'testTYPE', 'capability', 'dac_override'),
-                ('testTYPE', 'testTYPE', 'capability', 'dac_read_search'),
-                ('testTYPE', 'testTYPE', 'capability', 'fowner'),
-                ('testTYPE', 'testTYPE', 'capability', 'fsetid'),
-                ('testTYPE', 'testTYPE', 'capability', 'kill'),
-                ('testTYPE', 'testTYPE', 'capability', 'setgid'),
-                ('testTYPE', 'testTYPE', 'capability', 'setuid'),
-                ('testTYPE', 'testTYPE', 'capability', 'setpcap'),
-                ('testTYPE', 'testTYPE', 'capability', 'linux_immutable'),
-                ('testTYPE', 'testTYPE', 'capability', 'net_bind_service'),
-                ('testTYPE', 'testTYPE', 'capability', 'net_broadcast'),
-                ('testTYPE', 'testTYPE', 'capability', 'net_admin'),
-                ('testTYPE', 'testTYPE', 'capability', 'net_raw'),
-                ('testTYPE', 'testTYPE', 'capability', 'ipc_lock'),
-                ('testTYPE', 'testTYPE', 'capability', 'ipc_owner'),
-                ('testTYPE', 'testTYPE', 'capability', 'sys_module'),
-                ('testTYPE', 'testTYPE', 'capability', 'sys_rawio'),
-                ('testTYPE', 'testTYPE', 'capability', 'sys_chroot'),
-                ('testTYPE', 'testTYPE', 'capability', 'sys_ptrace'),
-                ('testTYPE', 'testTYPE', 'capability', 'sys_pacct'),
-                ('testTYPE', 'testTYPE', 'capability', 'sys_admin'),
-                ('testTYPE', 'testTYPE', 'capability', 'sys_boot'),
-                ('testTYPE', 'testTYPE', 'capability', 'sys_nice'),
-                ('testTYPE', 'testTYPE', 'capability', 'sys_resource'),
-                ('testTYPE', 'testTYPE', 'capability', 'sys_time'),
-                ('testTYPE', 'testTYPE', 'capability', 'sys_tty_config'),
-                ('testTYPE', 'testTYPE', 'capability', 'mknod'),
-                ('testTYPE', 'testTYPE', 'capability', 'lease'),
-                ('testTYPE', 'testTYPE', 'capability', 'audit_write'),
-                ('testTYPE', 'testTYPE', 'capability', 'audit_control'),
-                ('testTYPE', 'testTYPE', 'capability', 'setfcap') ] ]
-
-
-class SELinuxPolicyTests(unittest.TestCase):
-
-
-    def setUp(self):
-        self.test_policy = SELinuxPolicy()
-        self.test_file = open(policy_file_name, 'r')
-        self.test_policy.types = types
-        self.test_policy.attributes = attributes
-        self.test_policy.common_classes = common_classes
-        self.test_policy.classes = classes
-        self.test_policy.allow_rules = allow_rules
-        self.test_policy.neverallow_rules = neverallow_rules
-        return
-
-    def testExpandAvcRule(self):
-        #TODO: add more examples here to cover different cases
-        expanded_allow_list = SELinux_CTS.expand_avc_rule(self.test_policy, self.test_policy.allow_rules[0])
-        for a in expected_final_allow_list[0]:
-            self.failUnless(a in expanded_allow_list)
-        expanded_neverallow_list = SELinux_CTS.expand_avc_rule(self.test_policy, self.test_policy.neverallow_rules[0])
-        for n in expected_final_neverallow_list[0]:
-            self.failUnless(n in expanded_neverallow_list)
-
-    def testExpandBrackets(self):
-        #test position without bracket:
-        self.test_file.seek(279)
-        self.failIf(SELinux_CTS.expand_brackets(self.test_file))
-
-        #test position with bracket:
-        self.test_file.seek(26123)
-        self.failUnless(SELinux_CTS.expand_brackets(self.test_file) == " entrypoint read execute ")
-
-        #test position with nested brackets:
-        self.test_file.seek(26873)
-        self.failUnless(SELinux_CTS.expand_brackets(self.test_file)
-               == " dir   chr_file blk_file   file lnk_file sock_file fifo_file   ")
-
-    def testGetAvcRuleComponent(self):
-        #test against normal ('allow healthd healthd_exec:file ...)
-        self.test_file.seek(26096)
-        normal_src = { 'flags': { 'complement': False },
-                'set': set(['healthd']) }
-        normal_tgt = { 'flags': { 'complement': False },
-                'set': set(['healthd_exec']) }
-        normal_class = { 'flags': { 'complement': False },
-                'set': set(['file']) }
-        normal_perm = { 'flags': { 'complement': False },
-                'set': set(['entrypoint', 'read', 'execute']) }
-        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
-            == normal_src)
-        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
-            == normal_tgt)
-        c = SELinux_CTS.advance_past_whitespace(self.test_file)
-        if c == ':':
-            self.test_file.read(1)
-        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
-            == normal_class)
-        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
-            == normal_perm)
-
-        #test against 'hard' ('init {fs_type  ...' )
-        self.test_file.seek(26838)
-        hard_src = { 'flags': { 'complement': False },
-                'set': set(['init']) }
-        hard_tgt = { 'flags': { 'complement': False },
-                'set': set(['fs_type', 'dev_type', 'file_type']) }
-        hard_class = { 'flags': { 'complement': False },
-                'set': set(['dir', 'chr_file', 'blk_file', 'file', 'lnk_file', 'sock_file', 'fifo_file']) }
-        hard_perm = { 'flags': { 'complement': False },
-                'set': set(['relabelto']) }
-        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
-            == hard_src)
-        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
-            == hard_tgt)
-        #mimic ':' check:
-        c = SELinux_CTS.advance_past_whitespace(self.test_file)
-        if c == ':':
-            self.test_file.read(1)
-        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
-            == hard_class)
-        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
-            == hard_perm)
-
-        #test against 'multi-line' ('init {fs_type  ...' )
-        self.test_file.seek(26967)
-        multi_src = { 'flags': { 'complement': False },
-                'set': set(['appdomain', '-unconfineddomain']) }
-        multi_tgt = { 'flags': { 'complement': False },
-                'set': set(['audio_device', 'camera_device', 'dm_device', 'radio_device', 'gps_device', 'rpmsg_device']) }
-        multi_class = { 'flags': { 'complement': False },
-                'set': set(['chr_file']) }
-        multi_perm = { 'flags': { 'complement': False },
-                'set': set(['read', 'write']) }
-        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
-            == multi_src)
-        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
-            == multi_tgt)
-        c = SELinux_CTS.advance_past_whitespace(self.test_file)
-        if c == ':':
-            self.test_file.read(1)
-        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
-            == multi_class)
-        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
-            == multi_perm)
-
-        #test against 'complement'
-        self.test_file.seek(26806)
-        complement = { 'flags': { 'complement': True },
-                'set': set(['entrypoint', 'relabelto']) }
-        self.failUnless(SELinux_CTS.get_avc_rule_component(self.test_file)
-            == complement)
-
-    def testGetLineType(self):
-        self.failUnless(SELinux_CTS.get_line_type('type bluetooth, domain;')
-                == SELinux_CTS.TYPE)
-        self.failUnless(SELinux_CTS.get_line_type('attribute unconfineddomain;')
-                == SELinux_CTS.ATTRIBUTE)
-        self.failUnless(SELinux_CTS.get_line_type('typeattribute bluetooth appdomain;')
-                == SELinux_CTS.TYPEATTRIBUTE)
-        self.failUnless(SELinux_CTS.get_line_type('class file')
-                == SELinux_CTS.CLASS)
-        self.failUnless(SELinux_CTS.get_line_type('common file')
-                == SELinux_CTS.COMMON)
-        self.failUnless(SELinux_CTS.get_line_type('allow healthd healthd_exec:file { entrypoint read execute };')
-                == SELinux_CTS.ALLOW_RULE)
-        self.failUnless(SELinux_CTS.get_line_type('neverallow { appdomain -unconfineddomain -bluetooth } self:capability *;')
-                == SELinux_CTS.NEVERALLOW_RULE)
-        self.failUnless(SELinux_CTS.get_line_type('# FLASK')
-                == SELinux_CTS.OTHER)
-
-    def testIsMultiLine(self):
-        self.failIf(SELinux_CTS.is_multi_line(SELinux_CTS.TYPE))
-        self.failIf(SELinux_CTS.is_multi_line(SELinux_CTS.ATTRIBUTE))
-        self.failIf(SELinux_CTS.is_multi_line(SELinux_CTS.TYPEATTRIBUTE))
-        self.failUnless(SELinux_CTS.is_multi_line(SELinux_CTS.CLASS))
-        self.failUnless(SELinux_CTS.is_multi_line(SELinux_CTS.COMMON))
-        self.failUnless(SELinux_CTS.is_multi_line(SELinux_CTS.ALLOW_RULE))
-        self.failUnless(SELinux_CTS.is_multi_line(SELinux_CTS.NEVERALLOW_RULE))
-        self.failIf(SELinux_CTS.is_multi_line(SELinux_CTS.OTHER))
-
-    def testProcessInheritsSegment(self):
-        inherit_offset = 448 # needs changing if file changes
-        self.test_file.seek(inherit_offset, 0)
-        inherit_result = SELinux_CTS.process_inherits_segment(self.test_file)
-        self.failUnless(inherit_result == 'file')
-        return
-
-    def testFromFileName(self):
-        #using a special file, since the test_file has some lines which don't 'jive'
-        clean_policy_file = 'policy_clean_test.conf'
-        from_file_policy = SELinuxPolicy()
-        from_file_policy.from_file_name(clean_policy_file)
-        self.failUnless(from_file_policy.types == self.test_policy.types)
-        self.failUnless(from_file_policy.attributes == self.test_policy.attributes)
-        self.failUnless(from_file_policy.classes == self.test_policy.classes)
-        self.failUnless(from_file_policy.common_classes == self.test_policy.common_classes)
-        self.failUnless(from_file_policy.allow_rules == self.test_policy.allow_rules)
-        self.failUnless(from_file_policy.neverallow_rules == self.test_policy.neverallow_rules)
-
-    def testExpandPermissions(self):
-        #test general case
-        test_class_obj = 'file'
-        general_set = set(['read', 'write', 'execute'])
-        expanded_general_set = general_set
-        self.failUnless(self.test_policy.expand_permissions(test_class_obj, general_set)
-                == general_set)
-        star_set = set(['*'])
-        expanded_star_set = self.test_policy.classes['file'] #everything in the class
-        self.failUnless(self.test_policy.expand_permissions(test_class_obj, star_set)
-                == expanded_star_set)
-        complement_set = set(['*', '-open'])
-        expanded_complement_set = self.test_policy.classes['file'] - set(['open'])
-        self.failUnless(self.test_policy.expand_permissions(test_class_obj, complement_set)
-                == expanded_complement_set)
-
-    def testExpandTypes(self):
-
-        #test general case and '-' handling
-        test_source_set = set([
-                'domain',
-                '-bluetooth' ])
-        expanded_test_source_set = set([
-                'healthd', 'testTYPE' ])
-        self.failUnless(self.test_policy.expand_types(test_source_set) == expanded_test_source_set)
-
-        #test '*' handling
-        test_source_set = set([ '*' ])
-        expanded_test_source_set = set([
-                'bluetooth', 'healthd', 'testTYPE' ])
-        self.failUnless(self.test_policy.expand_types(test_source_set) == types)
-        #test - handling
-        test_source_set = set([
-                '*',
-                '-bluetooth'])
-        expanded_test_source_set = set([
-                'healthd', 'healthd_exec', 'testTYPE' ])
-        self.failUnless(self.test_policy.expand_types(test_source_set) == expanded_test_source_set)
-
-    def testProcessAttributeLine(self):
-        attribute_policy = SELinuxPolicy()
-        #test with 'normal input'
-        test_normal_string = 'attribute TEST_att;'
-        test_attribute = 'TEST_att'
-        attribute_policy.process_attribute_line(test_normal_string)
-        self.failUnless( test_attribute in attribute_policy.attributes)
-        #TODO: test on bogus inputs
-
-    def testProcessClassLine(self):
-        class_policy = SELinuxPolicy()
-        #offsets need changing if test file changes
-        common_offset  = 279
-        class_initial_offset  = 212
-        class_perm_offset = 437
-        self.test_file.seek(common_offset, 0)
-        line = self.test_file.readline()
-        class_policy.process_common_line(line, self.test_file)
-        self.test_file.seek(class_initial_offset, 0)
-        line = self.test_file.readline()
-        class_policy.process_class_line(line, self.test_file)
-        self.failUnless('file' in class_policy.classes)
-        self.test_file.seek(class_perm_offset, 0)
-        line = self.test_file.readline()
-        class_policy.process_class_line(line, self.test_file)
-        self.failUnless(class_policy.classes['file'] == classes['file'])
-
-    def testProcessCommonLine(self):
-        common_policy = SELinuxPolicy()
-        common_offset  = 279 # needs changing if file changes
-        self.test_file.seek(common_offset, 0)
-        line = self.test_file.readline()
-        common_policy.process_common_line(line, self.test_file)
-        self.failUnless('file' in common_policy.common_classes )
-        self.failUnless(common_policy.common_classes['file'] == common_classes['file'])
-
-    def testProcessAvcRuleLine(self):
-        avc_policy = SELinuxPolicy()
-        allow_offset  =  26091 # needs changing if file changes
-        neverallow_offset  = 26311  # needs changing if file changes
-        self.test_file.seek(allow_offset, 0)
-        line = self.test_file.readline()
-        avc_policy.process_avc_rule_line(line, self.test_file)
-        self.failUnless(avc_policy.allow_rules[0] == allow_rules[0] ) # always '0'?
-        self.test_file.seek(neverallow_offset, 0)
-        line = self.test_file.readline()
-        avc_policy.process_avc_rule_line(line, self.test_file)
-        self.failUnless(avc_policy.neverallow_rules[0] == neverallow_rules[0] ) # always '0'?
-
-    def testProcessTypeLine(self):
-        type_policy = SELinuxPolicy()
-        test_normal_string = 'type TEST_type, TEST_att1, TEST_att2;'
-        test_type = 'TEST_type'
-        test_atts = ['TEST_att1', 'TEST_att2']
-        #test with 'normal input'
-        type_policy.process_type_line(test_normal_string)
-        self.failUnless(test_type in type_policy.types)
-        for a in test_atts:
-            self.failUnless(a in type_policy.attributes)
-            self.failUnless(test_type in type_policy.attributes[a])
-        #TODO: test with domain only, no attributes
-        # and test on bogus inputs
-
-    def testProcessTypeattributeLine(self):
-        typ_att_policy = SELinuxPolicy()
-        test_normal_string = 'typeattribute TEST_type TEST_att1, TEST_att2;'
-        test_type = 'TEST_type'
-        test_atts = ['TEST_att1', 'TEST_att2']
-        #test with 'normal input' (type should already be declared)
-        typ_att_policy.process_type_line('type ' + test_type + ';')
-        typ_att_policy.process_typeattribute_line(test_normal_string)
-        self.failUnless(test_type in typ_att_policy.types)
-        for a in test_atts:
-            self.failUnless(a in typ_att_policy.attributes)
-            self.failUnless(test_type in typ_att_policy.attributes[a])
-        #TODO: test with domain only, no attributes
-        # and test on bogus inputs
-
-def main():
-    unittest.main()
-
-if __name__ == '__main__':
-    main()
diff --git a/tools/tradefed-host/etc/cts-tradefed b/tools/tradefed-host/etc/cts-tradefed
index f9f43bb..9a643de 100755
--- a/tools/tradefed-host/etc/cts-tradefed
+++ b/tools/tradefed-host/etc/cts-tradefed
@@ -35,7 +35,7 @@
 checkPath java
 
 # check java version
-JAVA_VERSION=$(java -version 2>&1 | head -n 1 | grep '[ "]1\.[67][\. "$$]')
+JAVA_VERSION=$(java -version 2>&1 | head -n 2 | grep '[ "]1\.[67][\. "$$]')
 if [ "${JAVA_VERSION}" == "" ]; then
     echo "Wrong java version. 1.6 or 1.7 is required."
     exit
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
index 25431b2..8544a93 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
@@ -31,7 +31,7 @@
     @Option(name="cts-install-path", description="the path to the cts installation to use")
     private String mCtsRootDirPath = System.getProperty("CTS_ROOT");
 
-    public static final String CTS_BUILD_VERSION = "5.0_r2";
+    public static final String CTS_BUILD_VERSION = "5.1_r0.5";
 
     /**
      * {@inheritDoc}
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
index ce48664..c2c2a10 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
@@ -707,9 +707,9 @@
             }
             mTestPackageList.addAll(shardTestPackageList);
         } catch (FileNotFoundException e) {
-            throw new IllegalArgumentException("failed to find XTS plan file", e);
+            throw new IllegalArgumentException("failed to find test plan file", e);
         } catch (ParseException e) {
-            throw new IllegalArgumentException("failed to parse XTS plan file", e);
+            throw new IllegalArgumentException("failed to parse test plan file", e);
         } catch (ConfigurationException e) {
             throw new IllegalArgumentException("failed to process arguments", e);
         }
@@ -747,7 +747,7 @@
                 testPkgDefs.add(testPackageDef);
             }
         } else if (mPackageNames.size() > 0){
-            Log.i(LOG_TAG, String.format("Executing XTS test packages %s", mPackageNames));
+            Log.i(LOG_TAG, String.format("Executing test packages %s", mPackageNames));
 
             Map<String, List<ITestPackageDef>> testPackageDefMap =
                     testRepo.getTestPackageDefsByName();
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PrintTestRemoteTestRunner.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PrintTestRemoteTestRunner.java
index 3d92eb3..72dccd4 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PrintTestRemoteTestRunner.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/PrintTestRemoteTestRunner.java
@@ -185,6 +185,11 @@
     }
 
     @Override
+    public void setTestCollection(boolean b) {
+        throw new UnsupportedOperationException("Test Collection mode is not supported");
+    }
+
+    @Override
     public void setTestSize(TestSize size) {
         addInstrumentationArg(SIZE_ARG_NAME, ""/*size.getRunnerValue()*/);
     }
diff --git a/tools/utils/buildCts.py b/tools/utils/buildCts.py
index 486a494..4d04e1a 100755
--- a/tools/utils/buildCts.py
+++ b/tools/utils/buildCts.py
@@ -252,18 +252,6 @@
 
     plan = tools.TestPlan(packages)
     plan.Exclude('.*')
-    plan.Include(r'android\.core\.tests\.libcore\.')
-    plan.Include(r'android\.jdwp')
-    for package, test_list in small_tests.iteritems():
-      plan.Exclude(package+'$')
-    for package, test_list in medium_tests.iteritems():
-      plan.Exclude(package+'$')
-    for package, tests_list in new_test_packages.iteritems():
-      plan.Exclude(package+'$')
-    self.__WritePlan(plan, 'CTS-ART')
-
-    plan = tools.TestPlan(packages)
-    plan.Exclude('.*')
     plan.Include(r'com\.drawelements\.')
     self.__WritePlan(plan, 'CTS-DEQP')