Merge "Increasing dhrystone failure threshold to 10%." into nyc-dr1-dev
am: 5b417b68d9

Change-Id: I707a6af27481fb75c73ef838035aa97ddfe55859
diff --git a/apps/CameraITS/build/envsetup.sh b/apps/CameraITS/build/envsetup.sh
index bcf294a..c33092d 100644
--- a/apps/CameraITS/build/envsetup.sh
+++ b/apps/CameraITS/build/envsetup.sh
@@ -31,12 +31,23 @@
 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
+for M in numpy PIL Image matplotlib pylab scipy.stats scipy.spatial
 do
     python -c "import $M" >/dev/null 2>&1 || \
         echo ">> Require Python $M module" >&2
 done
 
+CV2_VER=$(python -c "\
+try:
+    import cv2
+    print cv2.__version__
+except:
+    print \"N/A\"
+")
+
+echo $CV2_VER | grep -q "^2.4" || \
+    echo ">> Require python opencv 2.4. Got $CV2_VER" >&2
+
 export PYTHONPATH="$PWD/pymodules:$PYTHONPATH"
 
 for M in device objects image caps dng target error
diff --git a/apps/CameraITS/pymodules/its/device.py b/apps/CameraITS/pymodules/its/device.py
index 351b03c..692a62d 100644
--- a/apps/CameraITS/pymodules/its/device.py
+++ b/apps/CameraITS/pymodules/its/device.py
@@ -67,9 +67,17 @@
     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_VERSION = 'camera.its.extra.VERSION'
+    CURRENT_ITS_VERSION = '1.0' # version number to sync with CtsVerifier
     EXTRA_CAMERA_ID = 'camera.its.extra.CAMERA_ID'
-    EXTRA_SUCCESS = 'camera.its.extra.SUCCESS'
-    EXTRA_SUMMARY = 'camera.its.extra.SUMMARY'
+    EXTRA_RESULTS = 'camera.its.extra.RESULTS'
+
+    RESULT_PASS = 'PASS'
+    RESULT_FAIL = 'FAIL'
+    RESULT_NOT_EXECUTED = 'NOT_EXECUTED'
+    RESULT_VALUES = {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}
+    RESULT_KEY = 'result'
+    SUMMARY_KEY = 'summary'
 
     adb = "adb -d"
     device_id = ""
@@ -781,34 +789,43 @@
 
     return device_id
 
-def report_result(device_id, camera_id, success, summary_path=None):
+def report_result(device_id, camera_id, results):
     """Send a pass/fail result to the device, via an intent.
 
     Args:
         device_id: The ID string of the device to report the results to.
         camera_id: The ID string of the camera for which to report pass/fail.
-        success: Boolean, indicating if the result was pass or fail.
-        summary_path: (Optional) path to ITS summary file on host PC
-
+        results: a dictionary contains all ITS scenes as key and result/summary
+                 of current ITS run. See test_report_result unit test for
+                 an example.
     Returns:
         Nothing.
     """
     adb = "adb -s " + device_id
-    device_summary_path = "/sdcard/camera_" + camera_id + "_its_summary.txt"
-    if summary_path is not None:
-        _run("%s push %s %s" % (
-                adb, summary_path, device_summary_path))
-        _run("%s shell am broadcast -a %s --es %s %s --es %s %s --es %s %s" % (
-                adb, ItsSession.ACTION_ITS_RESULT,
-                ItsSession.EXTRA_CAMERA_ID, camera_id,
-                ItsSession.EXTRA_SUCCESS, 'True' if success else 'False',
-                ItsSession.EXTRA_SUMMARY, device_summary_path))
-    else:
-        _run("%s shell am broadcast -a %s --es %s %s --es %s %s --es %s %s" % (
-                adb, ItsSession.ACTION_ITS_RESULT,
-                ItsSession.EXTRA_CAMERA_ID, camera_id,
-                ItsSession.EXTRA_SUCCESS, 'True' if success else 'False',
-                ItsSession.EXTRA_SUMMARY, "null"))
+    # Validate/process results argument
+    for scene in results:
+        result_key = ItsSession.RESULT_KEY
+        summary_key = ItsSession.SUMMARY_KEY
+        if result_key not in results[scene]:
+            raise its.error.Error('ITS result not found for ' + scene)
+        if results[scene][result_key] not in ItsSession.RESULT_VALUES:
+            raise its.error.Error('Unknown ITS result for %s: %s' % (
+                    scene, results[result_key]))
+        if summary_key in results[scene]:
+            device_summary_path = "/sdcard/its_camera%s_%s.txt" % (
+                    camera_id, scene)
+            _run("%s push %s %s" % (
+                    adb, results[scene][summary_key], device_summary_path))
+            results[scene][summary_key] = device_summary_path
+    json_results = json.dumps(results)
+    cmd = "%s shell am broadcast -a %s --es %s %s --es %s %s --es %s \'%s\'" % (
+            adb, ItsSession.ACTION_ITS_RESULT,
+            ItsSession.EXTRA_VERSION, ItsSession.CURRENT_ITS_VERSION,
+            ItsSession.EXTRA_CAMERA_ID, camera_id,
+            ItsSession.EXTRA_RESULTS, json_results)
+    if len(cmd) > 4095:
+        print "ITS command string might be too long! len:", len(cmd)
+    _run(cmd)
 
 def _run(cmd):
     """Replacement for os.system, with hiding of stdout+stderr messages.
@@ -821,8 +838,20 @@
     """Run a suite of unit tests on this module.
     """
 
-    # TODO: Add some unit tests.
-    None
+    """
+    # TODO: this test currently needs connected device to pass
+    #       Need to remove that dependency before enabling the test
+    def test_report_result(self):
+        device_id = get_device_id()
+        camera_id = "1"
+        result_key = ItsSession.RESULT_KEY
+        results = {"scene0":{result_key:"PASS"},
+                   "scene1":{result_key:"PASS"},
+                   "scene2":{result_key:"PASS"},
+                   "scene3":{result_key:"PASS"},
+                   "sceneNotExist":{result_key:"FAIL"}}
+        report_result(device_id, camera_id, results)
+    """
 
 if __name__ == '__main__':
     unittest.main()
diff --git a/apps/CameraITS/pymodules/its/objects.py b/apps/CameraITS/pymodules/its/objects.py
index 58fe4ec..9766ab9 100644
--- a/apps/CameraITS/pymodules/its/objects.py
+++ b/apps/CameraITS/pymodules/its/objects.py
@@ -185,7 +185,8 @@
         ar = match_ar_size[0] / float(match_ar_size[1])
         out_sizes = [s for s in out_sizes if
                 abs(ar - s[0] / float(s[1])) <= AR_TOLERANCE]
-    out_sizes.sort(reverse=True)
+    out_sizes.sort(reverse=True, key=lambda s: s[0]) # 1st pass, sort by width
+    out_sizes.sort(reverse=True, key=lambda s: s[0]*s[1]) # sort by area
     return out_sizes
 
 def set_filter_off_or_fast_if_possible(props, req, available_modes, filter):
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 52780eb..678c35c 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -20,12 +20,20 @@
 import sys
 import textwrap
 import its.device
+from its.device import ItsSession
 
 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.
+
+    Command line Arguments:
+        camera: the camera(s) to be tested. Use comma to separate multiple
+                camera Ids. Ex: "camera=0,1" or "camera=1"
+        scenes: the test scene(s) to be executed. Use comma to separate multiple
+                scenes. Ex: "scenes=scene0,scene1" or "scenes=0,1,sensor_fusion"
+                (sceneX can be abbreviated by X where X is a integer)
     """
 
     SKIP_RET_CODE = 101
@@ -49,10 +57,7 @@
         "sensor_fusion":[]
     }
 
-    # Get all the scene0 and scene1 tests, which can be run using the same
-    # physical setup.
-    scenes = ["scene0", "scene1", "scene2", "scene3", "scene4", "scene5",
-              "sensor_fusion"]
+    all_scenes = ["scene0", "scene1", "scene2", "scene3", "scene4", "scene5"]
 
     scene_req = {
         "scene0" : None,
@@ -75,12 +80,48 @@
     scene_extra_args = {
         "scene5" : ["doAF=False"]
     }
-    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()
+
+    camera_ids = []
+    scenes = []
+    for s in sys.argv[1:]:
+        if s[:7] == "camera=" and len(s) > 7:
+            camera_ids = s[7:].split(',')
+        elif s[:7] == "scenes=" and len(s) > 7:
+            scenes = s[7:].split(',')
+
+    # Run through all scenes if user does not supply one
+    if not scenes:
+        scenes = all_scenes
+    else:
+        # Validate user input scene names
+        valid_scenes = True
+        temp_scenes = []
+        for s in scenes:
+            if s in all_scenes:
+                temp_scenes.append(s)
+            else:
+                try:
+                    # Try replace "X" to "sceneX"
+                    scene_num = int(s)
+                    scene_str = "scene" + s
+                    if scene_str not in all_scenes:
+                        valid_scenes = False
+                        break
+                    temp_scenes.append(scene_str)
+                except ValueError:
+                    valid_scenes = False
+                    break
+
+        if not valid_scenes:
+            print "Unknown scene specifiied:", s
+            assert(False)
+        scenes = temp_scenes
+
+    # Initialize test results
+    results = {}
+    result_key = ItsSession.RESULT_KEY
+    for s in all_scenes:
+        results[s] = {result_key: ItsSession.RESULT_NOT_EXECUTED}
 
     # Make output directories to hold the generated files.
     topdir = tempfile.mkdtemp()
@@ -90,11 +131,6 @@
     device_id_arg = "device=" + device_id
     print "Testing device " + device_id
 
-    camera_ids = []
-    for s in sys.argv[1:]:
-        if s[:7] == "camera=" and len(s) > 7:
-            camera_ids.append(s[7:])
-
     # user doesn't specify camera id, run through all cameras
     if not camera_ids:
         camera_ids_path = os.path.join(topdir, "camera_ids.txt")
@@ -108,7 +144,7 @@
             for line in f:
                 camera_ids.append(line.replace('\n', ''))
 
-    print "Running ITS on the following cameras:", camera_ids
+    print "Running ITS on camera: %s, scene %s" % (camera_ids, scenes)
 
     for camera_id in camera_ids:
         # Loop capturing images until user confirm test scene is correct
@@ -119,17 +155,18 @@
         for d in scenes:
             os.mkdir(os.path.join(topdir, camera_id, d))
 
-        print "Start running ITS on camera: ", camera_id
-        # Run each test, capturing stdout and stderr.
-        summary = "ITS test result summary for camera " + camera_id + "\n"
-        numpass = 0
-        numskip = 0
-        numnotmandatedfail = 0
-        numfail = 0
+        for scene in scenes:
+            tests = [(s[:-3],os.path.join("tests", scene, s))
+                     for s in os.listdir(os.path.join("tests",scene))
+                     if s[-3:] == ".py" and s[:4] == "test"]
+            tests.sort()
 
-        prev_scene = ""
-        for (scene,testname,testpath) in tests:
-            if scene != prev_scene and scene_req[scene] != None:
+            summary = "Cam" + camera_id + " " + scene + "\n"
+            numpass = 0
+            numskip = 0
+            num_not_mandated_fail = 0
+            numfail = 0
+            if scene_req[scene] != None:
                 out_path = os.path.join(topdir, camera_id, scene+".jpg")
                 out_arg = "out=" + out_path
                 scene_arg = "scene=" + scene_req[scene]
@@ -140,64 +177,71 @@
                         extra_args
                 retcode = subprocess.call(cmd,cwd=topdir)
                 assert(retcode == 0)
-                print "Start running tests for", scene
-            prev_scene = scene
-            cmd = ['python', os.path.join(os.getcwd(),testpath)] + \
-                  sys.argv[1:] + [camera_id_arg]
-            outdir = os.path.join(topdir,camera_id,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()
+            print "Start running ITS on camera %s, %s" % (camera_id, scene)
 
-            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
+            # Run each test, capturing stdout and stderr.
+            for (testname,testpath) in tests:
+                cmd = ['python', os.path.join(os.getcwd(),testpath)] + \
+                      sys.argv[1:] + [camera_id_arg]
+                outdir = os.path.join(topdir,camera_id,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()
+
+                test_failed = False
+                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*"
+                    num_not_mandated_fail += 1
+                else:
+                    retstr = "FAIL "
+                    numfail += 1
+                    test_failed = True
+
+                msg = "%s %s/%s [%.1fs]" % (retstr, scene, testname, t1-t0)
+                print msg
+                msg_short = "%s %s [%.1fs]" % (retstr, testname, t1-t0)
+                if test_failed:
+                    summary += msg_short + "\n"
+
+            if numskip > 0:
+                skipstr = ", %d test%s skipped" % (
+                        numskip, "s" if numskip > 1 else "")
             else:
-                retstr = "FAIL "
-                numfail += 1
+                skipstr = ""
 
-            msg = "%s %s/%s [%.1fs]" % (retstr, scene, testname, t1-t0)
-            print msg
-            summary += msg + "\n"
-            if retcode != 0 and retcode != SKIP_RET_CODE:
-                # Dump the stderr if the test fails
-                with open (errpath, "r") as error_file:
-                    errors = error_file.read()
-                    summary += errors + "\n"
+            test_result = "\n%d / %d tests passed (%.1f%%)%s" % (
+                    numpass + num_not_mandated_fail, len(tests) - numskip,
+                    100.0 * float(numpass + num_not_mandated_fail) /
+                            (len(tests) - numskip)
+                            if len(tests) != numskip else 100.0,
+                    skipstr)
+            print test_result
 
-        if numskip > 0:
-            skipstr = ", %d test%s skipped" % (numskip, "s" if numskip > 1 else "")
-        else:
-            skipstr = ""
+            if num_not_mandated_fail > 0:
+                msg = "(*) tests are not yet mandated"
+                print msg
 
-        test_result = "\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)
-        print test_result
-        summary += test_result + "\n"
+            summary_path = os.path.join(topdir, camera_id, scene, "summary.txt")
+            with open(summary_path, "w") as f:
+                f.write(summary)
 
-        if numnotmandatedfail > 0:
-            msg = "(*) tests are not yet mandated"
-            print msg
-            summary += msg + "\n"
+            passed = numfail == 0
+            results[scene][result_key] = (ItsSession.RESULT_PASS if passed
+                    else ItsSession.RESULT_FAIL)
+            results[scene][ItsSession.SUMMARY_KEY] = summary_path
 
-        result = numfail == 0
         print "Reporting ITS result to CtsVerifier"
-        summary_path = os.path.join(topdir, camera_id, "summary.txt")
-        with open(summary_path, "w") as f:
-            f.write(summary)
-        its.device.report_result(device_id, camera_id, result, summary_path)
+        its.device.report_result(device_id, camera_id, results)
 
     print "ITS tests finished. Please go back to CtsVerifier and proceed"
 
diff --git a/apps/CtsVerifier/res/layout/its_main.xml b/apps/CtsVerifier/res/layout/its_main.xml
index 2f5eade..26f15bb 100644
--- a/apps/CtsVerifier/res/layout/its_main.xml
+++ b/apps/CtsVerifier/res/layout/its_main.xml
@@ -21,4 +21,14 @@
 
     <include layout="@layout/pass_fail_buttons" />
 
+    <TextView
+        android:id="@+id/its_progress"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:gravity="bottom"
+        android:padding="2dp"
+        android:scrollbars = "vertical"
+        android:text="@string/its_test_progress"
+        android:textSize="16sp" />
+
 </LinearLayout>
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 4ef177e..2406b84 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -991,6 +991,10 @@
     </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>
+    <string name="its_version_mismatch">
+        CtsVerifier and ITS script version mismatch. Please update CtsVerifier and ITS script.
+    </string>
+    <string name="its_test_progress">ITS test progress will be shown here.</string>
 
     <!-- Strings for the Camera Flashlight test activity -->
     <string name="camera_flashlight_test">Camera Flashlight</string>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
index dc81e19..f9f5823 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/audio/HifiUltrasoundSpeakerTestActivity.java
@@ -41,6 +41,9 @@
 import com.androidplot.xy.XYSeries;
 import com.androidplot.xy.*;
 
+import com.android.compatibility.common.util.CddTest;
+
+@CddTest(requirement="7.8.3")
 public class HifiUltrasoundSpeakerTestActivity extends PassFailButtons.Activity {
 
   public enum Status {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
index 49525b7..1a3534d 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsService.java
@@ -404,6 +404,7 @@
         // (called on different threads) will need to send data back to the host script.
 
         public Socket mOpenSocket = null;
+        private Thread mThread = null;
 
         public SocketWriteRunnable(Socket openSocket) {
             mOpenSocket = openSocket;
@@ -421,6 +422,7 @@
                     ByteBuffer b = mSocketWriteQueue.take();
                     synchronized(mSocketWriteDrainLock) {
                         if (mOpenSocket == null) {
+                            Logt.e(TAG, "No open socket connection!");
                             continue;
                         }
                         if (b.hasArray()) {
@@ -442,14 +444,26 @@
                     }
                 } catch (IOException e) {
                     Logt.e(TAG, "Error writing to socket", e);
+                    mOpenSocket = null;
                     break;
                 } catch (java.lang.InterruptedException e) {
                     Logt.e(TAG, "Error writing to socket (interrupted)", e);
+                    mOpenSocket = null;
                     break;
                 }
             }
             Logt.i(TAG, "Socket writer thread terminated");
         }
+
+        public synchronized void checkAndStartThread() {
+            if (mThread == null || mThread.getState() == Thread.State.TERMINATED) {
+                mThread = new Thread(this);
+            }
+            if (mThread.getState() == Thread.State.NEW) {
+                mThread.start();
+            }
+        }
+
     }
 
     class SocketRunnable implements Runnable {
@@ -475,7 +489,6 @@
 
             // 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.
@@ -489,6 +502,7 @@
                     mSocketWriteQueue.clear();
                     mInflightImageSizes.clear();
                     mSocketWriteRunnable.setOpenSocket(mOpenSocket);
+                    mSocketWriteRunnable.checkAndStartThread();
                     Logt.i(TAG, "Socket connected");
                 } catch (IOException e) {
                     Logt.e(TAG, "Socket open error: ", e);
@@ -1161,12 +1175,13 @@
         } else {
             // No surface(s) specified at all.
             // Default: a single output surface which is full-res YUV.
-            Size sizes[] = ItsUtils.getYuvOutputSizes(mCameraCharacteristics);
+            Size maxYuvSize = ItsUtils.getMaxOutputSize(
+                    mCameraCharacteristics, ImageFormat.YUV_420_888);
             numSurfaces = backgroundRequest ? 2 : 1;
 
             outputSizes = new Size[numSurfaces];
             outputFormats = new int[numSurfaces];
-            outputSizes[0] = sizes[0];
+            outputSizes[0] = maxYuvSize;
             outputFormats[0] = ImageFormat.YUV_420_888;
             if (backgroundRequest) {
                 outputSizes[1] = new Size(640, 480);
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
index 0c39a9e..a8affcd 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsTestActivity.java
@@ -25,15 +25,21 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraManager;
 import android.os.Bundle;
+import android.text.method.ScrollingMovementMethod;
 import android.util.Log;
 import android.view.WindowManager;
+import android.widget.TextView;
 import android.widget.Toast;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
 import java.util.HashSet;
 import java.util.HashMap;
-import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
 import java.io.BufferedReader;
 import java.io.FileReader;
 import java.io.FileNotFoundException;
@@ -44,6 +50,8 @@
 import com.android.cts.verifier.PassFailButtons;
 import com.android.cts.verifier.R;
 
+import org.json.JSONArray;
+import org.json.JSONObject;
 
 /**
  * Test for Camera features that require that the camera be aimed at a specific test scene.
@@ -53,48 +61,216 @@
 public class ItsTestActivity extends PassFailButtons.Activity {
     private static final String TAG = "ItsTestActivity";
     private static final String EXTRA_CAMERA_ID = "camera.its.extra.CAMERA_ID";
-    private static final String EXTRA_SUCCESS = "camera.its.extra.SUCCESS";
-    private static final String EXTRA_SUMMARY = "camera.its.extra.SUMMARY";
+    private static final String EXTRA_RESULTS = "camera.its.extra.RESULTS";
+    private static final String EXTRA_VERSION = "camera.its.extra.VERSION";
+    private static final String CURRENT_VERSION = "1.0";
     private static final String ACTION_ITS_RESULT =
             "com.android.cts.verifier.camera.its.ACTION_ITS_RESULT";
 
-    class SuccessReceiver extends BroadcastReceiver {
+    private static final String RESULT_PASS = "PASS";
+    private static final String RESULT_FAIL = "FAIL";
+    private static final String RESULT_NOT_EXECUTED = "NOT_EXECUTED";
+    private static final Set<String> RESULT_VALUES = new HashSet<String>(
+            Arrays.asList(new String[] {RESULT_PASS, RESULT_FAIL, RESULT_NOT_EXECUTED}));
+    private static final int MAX_SUMMARY_LEN = 200;
+
+    private final ResultReceiver mResultsReceiver = new ResultReceiver();
+
+    // Initialized in onCreate
+    ArrayList<String> mNonLegacyCameraIds = null;
+
+    // TODO: cache the following in saved bundle
+    private Set<ResultKey> mAllScenes = null;
+    // (camera, scene) -> (pass, fail)
+    private final HashMap<ResultKey, Boolean> mExecutedScenes = new HashMap<>();
+    // map camera id to ITS summary report path
+    private final HashMap<ResultKey, String> mSummaryMap = new HashMap<>();
+
+    final class ResultKey {
+        public final String cameraId;
+        public final String sceneId;
+
+        public ResultKey(String cameraId, String sceneId) {
+            this.cameraId = cameraId;
+            this.sceneId = sceneId;
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (o == null) return false;
+            if (this == o) return true;
+            if (o instanceof ResultKey) {
+                final ResultKey other = (ResultKey) o;
+                return cameraId.equals(other.cameraId) && sceneId.equals(other.sceneId);
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            int h = cameraId.hashCode();
+            h = ((h << 5) - h) ^ sceneId.hashCode();
+            return h;
+        }
+    }
+
+    private final Comparator<ResultKey> mComparator = new Comparator<ResultKey>() {
+        @Override
+        public int compare(ResultKey k1, ResultKey k2) {
+            if (k1.cameraId.equals(k2.cameraId))
+                return k1.sceneId.compareTo(k2.sceneId);
+            return k1.cameraId.compareTo(k2.cameraId);
+        }
+    };
+
+    class ResultReceiver 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 version = intent.getStringExtra(EXTRA_VERSION);
+                if (version == null || !version.equals(CURRENT_VERSION)) {
+                    Log.e(TAG, "Its result version mismatch: expect " + CURRENT_VERSION +
+                            ", got " + ((version == null) ? "null" : version));
+                    ItsTestActivity.this.showToast(R.string.its_version_mismatch);
+                    return;
+                }
+
                 String cameraId = intent.getStringExtra(EXTRA_CAMERA_ID);
-                String result = intent.getStringExtra(EXTRA_SUCCESS);
-                String summaryPath = intent.getStringExtra(EXTRA_SUMMARY);
+                String results = intent.getStringExtra(EXTRA_RESULTS);
+                if (cameraId == null || results == null) {
+                    Log.e(TAG, "cameraId = " + ((cameraId == null) ? "null" : cameraId) +
+                            ", results = " + ((results == null) ? "null" : results));
+                    return;
+                }
+
                 if (!mNonLegacyCameraIds.contains(cameraId)) {
                     Log.e(TAG, "Unknown camera id " + cameraId + " reported to ITS");
                     return;
                 }
 
-                Log.i(TAG, "ITS summary path is: " + summaryPath);
-                mSummaryMap.put(cameraId, summaryPath);
-                // Create summary report
-                if (mSummaryMap.keySet().containsAll(mNonLegacyCameraIds)) {
+                try {
+                    /* Sample JSON results string
+                    {
+                       "scene0":{
+                          "result":"PASS",
+                          "summary":"/sdcard/cam0_scene0.txt"
+                       },
+                       "scene1":{
+                          "result":"NOT_EXECUTED"
+                       },
+                       "scene2":{
+                          "result":"FAIL",
+                          "summary":"/sdcard/cam0_scene2.txt"
+                       }
+                    }
+                    */
+                    JSONObject jsonResults = new JSONObject(results);
+                    Set<String> scenes = new HashSet<>();
+                    Iterator<String> keys = jsonResults.keys();
+                    while (keys.hasNext()) {
+                        scenes.add(keys.next());
+                    }
+                    boolean newScenes = false;
+                    if (mAllScenes == null) {
+                        mAllScenes = new TreeSet<>(mComparator);
+                        newScenes = true;
+                    } else { // See if scene lists changed
+                        for (String scene : scenes) {
+                            if (!mAllScenes.contains(new ResultKey(cameraId, scene))) {
+                                // Scene list changed. Cleanup previous test results
+                                newScenes = true;
+                                break;
+                            }
+                        }
+                        for (ResultKey k : mAllScenes) {
+                            if (!scenes.contains(k.sceneId)) {
+                                newScenes = true;
+                                break;
+                            }
+                        }
+                    }
+                    if (newScenes) {
+                        mExecutedScenes.clear();
+                        mAllScenes.clear();
+                        for (String scene : scenes) {
+                            for (String c : mNonLegacyCameraIds) {
+                                mAllScenes.add(new ResultKey(c, scene));
+                            }
+                        }
+                    }
+
+                    // Update test execution results
+                    for (String scene : scenes) {
+                        JSONObject sceneResult = jsonResults.getJSONObject(scene);
+                        String result = sceneResult.getString("result");
+                        if (result == null) {
+                            Log.e(TAG, "Result for " + scene + " is null");
+                            return;
+                        }
+                        Log.i(TAG, "ITS camera" + cameraId + " " + scene + ": result:" + result);
+                        if (!RESULT_VALUES.contains(result)) {
+                            Log.e(TAG, "Unknown result for " + scene + ": " + result);
+                            return;
+                        }
+                        ResultKey key = new ResultKey(cameraId, scene);
+                        if (result.equals(RESULT_PASS) || result.equals(RESULT_FAIL)) {
+                            boolean pass = result.equals(RESULT_PASS);
+                            mExecutedScenes.put(key, pass);
+                            String summary = sceneResult.optString("summary");
+                            if (!summary.equals("")) {
+                                mSummaryMap.put(key, summary);
+                            }
+                        } // do nothing for NOT_EXECUTED scenes
+                    }
+                } catch (org.json.JSONException e) {
+                    Log.e(TAG, "Error reading json result string:" + results , e);
+                    return;
+                }
+
+                // Set summary if all scenes reported
+                if (mSummaryMap.keySet().containsAll(mAllScenes)) {
                     StringBuilder summary = new StringBuilder();
-                    for (String id : mNonLegacyCameraIds) {
-                        String path = mSummaryMap.get(id);
+                    for (String path : mSummaryMap.values()) {
                         appendFileContentToSummary(summary, path);
                     }
+                    if (summary.length() > MAX_SUMMARY_LEN) {
+                        Log.w(TAG, "ITS summary report too long: len: " + summary.length());
+                    }
                     ItsTestActivity.this.getReportLog().setSummary(
                             summary.toString(), 1.0, ResultType.NEUTRAL, ResultUnit.NONE);
                 }
-                boolean pass = result.equals("True");
-                if(pass) {
-                    Log.i(TAG, "Received Camera " + cameraId + " ITS SUCCESS from host.");
-                    mITSPassedCameraIds.add(cameraId);
-                    if (mNonLegacyCameraIds != null && mNonLegacyCameraIds.size() != 0 &&
-                            mITSPassedCameraIds.containsAll(mNonLegacyCameraIds)) {
-                        ItsTestActivity.this.showToast(R.string.its_test_passed);
-                        ItsTestActivity.this.getPassButton().setEnabled(true);
+
+                // Display current progress
+                StringBuilder progress = new StringBuilder();
+                for (ResultKey k : mAllScenes) {
+                    String status = RESULT_NOT_EXECUTED;
+                    if (mExecutedScenes.containsKey(k)) {
+                        status = mExecutedScenes.get(k) ? RESULT_PASS : RESULT_FAIL;
                     }
+                    progress.append(String.format("Cam %s, %s: %s\n",
+                            k.cameraId, k.sceneId, status));
+                }
+                TextView progressView = (TextView) findViewById(R.id.its_progress);
+                progressView.setMovementMethod(new ScrollingMovementMethod());
+                progressView.setText(progress.toString());
+
+
+                // Enable pass button if all scenes pass
+                boolean allScenesPassed = true;
+                for (ResultKey k : mAllScenes) {
+                    Boolean pass = mExecutedScenes.get(k);
+                    if (pass == null || pass == false) {
+                        allScenesPassed = false;
+                        break;
+                    }
+                }
+                if (allScenesPassed) {
+                    // Enable pass button
+                    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);
+                    ItsTestActivity.this.getPassButton().setEnabled(false);
                 }
             }
         }
@@ -127,12 +303,6 @@
         }
     }
 
-    private final SuccessReceiver mSuccessReceiver = new SuccessReceiver();
-    private final HashSet<String> mITSPassedCameraIds = new HashSet<>();
-    // map camera id to ITS summary report path
-    private final HashMap<String, String> mSummaryMap = new HashMap<>();
-    ArrayList<String> mNonLegacyCameraIds = null;
-
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -168,6 +338,7 @@
                     "Received error from camera service while checking device capabilities: "
                             + e, Toast.LENGTH_SHORT).show();
         }
+
         getPassButton().setEnabled(false);
     }
 
@@ -180,7 +351,7 @@
         } else {
             Log.d(TAG, "register ITS result receiver");
             IntentFilter filter = new IntentFilter(ACTION_ITS_RESULT);
-            registerReceiver(mSuccessReceiver, filter);
+            registerReceiver(mResultsReceiver, filter);
         }
     }
 
@@ -188,7 +359,7 @@
     protected void onPause() {
         super.onPause();
         Log.d(TAG, "unregister ITS result receiver");
-        unregisterReceiver(mSuccessReceiver);
+        unregisterReceiver(mResultsReceiver);
     }
 
     @Override
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
index 8763223..b0eaf35 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/camera/its/ItsUtils.java
@@ -153,10 +153,13 @@
         }
 
         Size maxSize = sizes[0];
+        int maxArea = maxSize.getWidth() * maxSize.getHeight();
         for (int i = 1; i < sizes.length; i++) {
-            if (sizes[i].getWidth() * sizes[i].getHeight() >
-                    maxSize.getWidth() * maxSize.getHeight()) {
+            int area = sizes[i].getWidth() * sizes[i].getHeight();
+            if (area > maxArea ||
+                    (area == maxArea && sizes[i].getWidth() > maxSize.getWidth())) {
                 maxSize = sizes[i];
+                maxArea = area;
             }
         }
 
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
index 19db37a..6c8cab4 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/command/CompatibilityConsole.java
@@ -113,6 +113,13 @@
                 splitModules(shards);
             }
         }, "split", "m(?:odules)?", "(\\d+)");
+        trie.put(new Runnable() {
+            @Override
+            public void run() {
+                printLine(String.format("Android %s %s (%s)", SuiteInfo.FULLNAME,
+                        SuiteInfo.VERSION, SuiteInfo.BUILD_NUMBER));
+            }
+        }, "version"); // override tradefed 'version' command to print test suite name and version
 
         // find existing help for 'LIST_PATTERN' commands, and append these commands help
         String listHelp = commandHelp.get(LIST_PATTERN);
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
index ae3f271..3e99186 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/CompatibilityTest.java
@@ -83,12 +83,14 @@
     private static final String MODULE_ARG_OPTION = "module-arg";
     private static final String TEST_ARG_OPTION = "test-arg";
     public static final String RETRY_OPTION = "retry";
+    public static final String RETRY_TYPE_OPTION = "retry-type";
     private static final String ABI_OPTION = "abi";
     private static final String SHARD_OPTION = "shards";
     public static final String SKIP_DEVICE_INFO_OPTION = "skip-device-info";
     public static final String SKIP_PRECONDITIONS_OPTION = "skip-preconditions";
     public static final String PRIMARY_ABI_RUN = "primary-abi-only";
     public static final String DEVICE_TOKEN_OPTION = "device-token";
+    public static final String LOGCAT_ON_FAILURE_SIZE_OPTION = "logcat-on-failure-size";
     private static final String URL = "dynamic-config-url";
 
     /* API Key for compatibility test project, used for dynamic configuration */
@@ -134,12 +136,22 @@
             importance = Importance.ALWAYS)
     private List<String> mTestArgs = new ArrayList<>();
 
+    public enum RetryType {
+        FAILED, NOT_EXECUTED;
+    }
+
     @Option(name = RETRY_OPTION,
             shortName = 'r',
-            description = "retry a previous session.",
+            description = "retry a previous session's failed and not executed tests.",
             importance = Importance.IF_UNSET)
     private Integer mRetrySessionId = null;
 
+    @Option(name = RETRY_TYPE_OPTION,
+            description = "used with " + RETRY_OPTION + ", retry tests of a certain status. "
+            + "Possible values include \"failed\" and \"not_executed\".",
+            importance = Importance.IF_UNSET)
+    private RetryType mRetryType = null;
+
     @Option(name = ABI_OPTION,
             shortName = 'a',
             description = "the abi to test.",
@@ -185,6 +197,11 @@
             description = "Take a logcat snapshot on every test failure.")
     private boolean mLogcatOnFailure = false;
 
+    @Option(name = LOGCAT_ON_FAILURE_SIZE_OPTION,
+            description = "The max number of logcat data in bytes to capture when "
+            + "--logcat-on-failure is on. Should be an amount that can comfortably fit in memory.")
+    private int mMaxLogcatBytes = 500 * 1024; // 500K
+
     @Option(name = "screenshot-on-failure",
             description = "Take a screenshot on every test failure.")
     private boolean mScreenshotOnFailure = false;
@@ -315,7 +332,7 @@
             List<IModuleDef> modules = mModuleRepo.getModules(getDevice().getSerialNumber());
 
             listener = new FailureListener(listener, getDevice(), mBugReportOnFailure,
-                    mLogcatOnFailure, mScreenshotOnFailure, mRebootOnFailure);
+                    mLogcatOnFailure, mScreenshotOnFailure, mRebootOnFailure, mMaxLogcatBytes);
             int moduleCount = modules.size();
             CLog.logAndDisplay(LogLevel.INFO, "Starting %d module%s on %s", moduleCount,
                     (moduleCount > 1) ? "s" : "", mDevice.getSerialNumber());
@@ -342,13 +359,23 @@
             }
 
             // Set values and run preconditions
+            boolean isPrepared = true; // whether the device has been successfully prepared
             for (int i = 0; i < moduleCount; i++) {
                 IModuleDef module = modules.get(i);
                 module.setBuild(mBuildHelper.getBuildInfo());
                 module.setDevice(mDevice);
                 module.setPreparerWhitelist(mPreparerWhitelist);
-                module.prepare(mSkipPreconditions);
+                isPrepared &= (module.prepare(mSkipPreconditions));
             }
+            mModuleRepo.setPrepared(isPrepared);
+
+            if (!mModuleRepo.isPrepared()) {
+                CLog.logAndDisplay(LogLevel.ERROR,
+                        "Incorrect preparation detected, exiting test run from %s",
+                        mDevice.getSerialNumber());
+                return;
+            }
+
             // Run the tests
             for (int i = 0; i < moduleCount; i++) {
                 IModuleDef module = modules.get(i);
@@ -573,26 +600,18 @@
                     throw new RuntimeException(e);
                 }
             }
-            // Append each test that failed or was not executed to the filters
-            for (IModuleResult module : result.getModules()) {
-                if (module.isPassed()) {
-                    // Whole module passed, exclude entire module
-                    TestFilter filter = new TestFilter(module.getAbi(), module.getName(), null);
-                    mExcludeFilters.add(filter.toString());
-                } else {
-                    for (ICaseResult testResultList : module.getResults()) {
-                        for (ITestResult testResult : testResultList.getResults(TestStatus.PASS)) {
-                            // Test passed, exclude it for retry
-                            TestFilter filter = new TestFilter(
-                                    module.getAbi(), module.getName(), testResult.getFullName());
-                            mExcludeFilters.add(filter.toString());
-                        }
-                    }
-                }
+
+            ITestPlan retryPlan = new TestPlan();
+            retryPlan.excludePassed(result); // always exclude passed tests on retry
+            if (RetryType.FAILED.equals(mRetryType)) {
+                    retryPlan.includeFailed(result); // retry only failed tests
+            } else if (RetryType.NOT_EXECUTED.equals(mRetryType)){
+                    retryPlan.excludeFailed(result); // retry only not executed tests
             }
+            mIncludeFilters.addAll(retryPlan.getIncludeFilters());
+            mExcludeFilters.addAll(retryPlan.getExcludeFilters());
         }
         if (mModuleName != null) {
-            mIncludeFilters.clear();
             try {
                 List<String> modules = ModuleRepo.getModuleNamesMatching(
                         mBuildHelper.getTestsDir(), mModuleName);
@@ -605,19 +624,9 @@
                             mModuleName, ArrayUtil.join("\n", modules)));
                 } else {
                     String module = modules.get(0);
+                    cleanFilters(mIncludeFilters, module);
+                    cleanFilters(mExcludeFilters, module);
                     mIncludeFilters.add(new TestFilter(mAbiName, module, mTestName).toString());
-                    // We will run this module with previous exclusions,
-                    // unless they exclude the whole module.
-                    List<String> excludeFilters = new ArrayList<>();
-                    for (String excludeFilter : mExcludeFilters) {
-                        TestFilter filter = TestFilter.createFrom(excludeFilter);
-                        String name = filter.getName();
-                        // Add the filter if it applies to this module
-                        if (module.equals(name)) {
-                            excludeFilters.add(excludeFilter);
-                        }
-                    }
-                    mExcludeFilters = excludeFilters;
                 }
             } catch (FileNotFoundException e) {
                 e.printStackTrace();
@@ -633,6 +642,17 @@
         }
     }
 
+    /* Helper method designed to remove filters in a list not applicable to the given module */
+    private static void cleanFilters(List<String> filters, String module) {
+        List<String> cleanedFilters = new ArrayList<String>();
+        for (String filter : filters) {
+            if (module.equals(TestFilter.createFrom(filter).getName())) {
+                cleanedFilters.add(filter); // Module name matches, filter passes
+            }
+        }
+        filters = cleanedFilters;
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java
index 5d2cd38..cd0e54f 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/FailureListener.java
@@ -27,23 +27,38 @@
 
 public class FailureListener extends ResultForwarder {
 
-    private static final int MAX_LOGCAT_BYTES = 500 * 1024; // 500K
+    private static final int DEFAULT_MAX_LOGCAT_BYTES = 500 * 1024; // 500K
+    /* Arbitrary upper limit for mMaxLogcatBytes, per b/30720850 */
+    public static final int LOGCAT_BYTE_LIMIT = 20 * 1024 * 1024; // 20 MB
 
     private ITestDevice mDevice;
     private boolean mBugReportOnFailure;
     private boolean mLogcatOnFailure;
     private boolean mScreenshotOnFailure;
     private boolean mRebootOnFailure;
+    private int mMaxLogcatBytes;
 
     public FailureListener(ITestInvocationListener listener, ITestDevice device,
             boolean bugReportOnFailure, boolean logcatOnFailure, boolean screenshotOnFailure,
-            boolean rebootOnFailure) {
+            boolean rebootOnFailure, int maxLogcatBytes) {
         super(listener);
         mDevice = device;
         mBugReportOnFailure = bugReportOnFailure;
         mLogcatOnFailure = logcatOnFailure;
         mScreenshotOnFailure = screenshotOnFailure;
         mRebootOnFailure = rebootOnFailure;
+        if (maxLogcatBytes < 0 ) {
+            CLog.w("FailureListener could not set %s to '%d', using default value %d",
+                    CompatibilityTest.LOGCAT_ON_FAILURE_SIZE_OPTION, maxLogcatBytes,
+                    DEFAULT_MAX_LOGCAT_BYTES);
+            mMaxLogcatBytes = DEFAULT_MAX_LOGCAT_BYTES;
+        } else if (maxLogcatBytes > LOGCAT_BYTE_LIMIT) {
+            CLog.w("Value %d for %s exceeds limit %d, using limit value", maxLogcatBytes,
+                    CompatibilityTest.LOGCAT_ON_FAILURE_SIZE_OPTION, LOGCAT_BYTE_LIMIT);
+            mMaxLogcatBytes = LOGCAT_BYTE_LIMIT;
+        } else {
+            mMaxLogcatBytes = maxLogcatBytes;
+        }
     }
 
     /**
@@ -62,7 +77,7 @@
         if (mLogcatOnFailure) {
             // sleep 2s to ensure test failure stack trace makes it into logcat capture
             RunUtil.getDefault().sleep(2 * 1000);
-            InputStreamSource logSource = mDevice.getLogcat(MAX_LOGCAT_BYTES);
+            InputStreamSource logSource = mDevice.getLogcat(mMaxLogcatBytes);
             super.testLog(String.format("%s-logcat", test.toString()), LogDataType.LOGCAT,
                     logSource);
             logSource.cancel();
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
index 45ef438..14427ca 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleDef.java
@@ -66,7 +66,8 @@
 
     /**
      * Runs the module's precondition checks and setup tasks.
+     * @return whether preparation succeeded.
      */
-    void prepare(boolean skipPrep) throws DeviceNotAvailableException;
+    boolean prepare(boolean skipPrep) throws DeviceNotAvailableException;
 
 }
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java
index cf52f35..7cbf7a3 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/IModuleRepo.java
@@ -29,6 +29,16 @@
 public interface IModuleRepo {
 
     /**
+     * @return true after each shard has prepared successfully.
+     */
+    boolean isPrepared();
+
+    /**
+     * Indicates to the repo whether a shard is prepared to run.
+     */
+    void setPrepared(boolean isPrepared);
+
+    /**
      * @return true if this repository has been initialized.
      */
     boolean isInitialized();
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ITestPlan.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ITestPlan.java
new file mode 100644
index 0000000..16b36ea
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ITestPlan.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.tradefed.testtype;
+
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.tradefed.testtype.ITestFilterReceiver;
+import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.Set;
+
+
+/**
+ * Interface for generating, parsing, and retrieving test plan data.
+ */
+public interface ITestPlan extends ITestFilterReceiver {
+
+    /**
+     * Parse the test plan data from a stream of XML, populate collections of filters internally.
+     * @param xmlInputStream the {@link InputStream} containing test plan XML
+     */
+    public void parse(InputStream xmlInputStream) throws ParseException;
+
+    /**
+     * Add include filters for {@link ITestResult}s that have passed.
+     * @param result the {@link IInvocationResult} from which to read {@link TestStatus}es
+     */
+    public void includePassed(IInvocationResult result);
+
+    /**
+     * Add include filters for {@link ITestResult}s that have failed.
+     * @param result the {@link IInvocationResult} from which to read {@link TestStatus}es
+     */
+    public void includeFailed(IInvocationResult result);
+
+    /**
+     * Add exclude filters for {@link ITestResult}s that have passed.
+     * @param result the {@link IInvocationResult} from which to read {@link TestStatus}es
+     */
+    public void excludePassed(IInvocationResult result);
+
+    /**
+     * Add exclude filters for {@link ITestResult}s that have failed.
+     * @param result the {@link IInvocationResult} from which to read {@link TestStatus}es
+     */
+    public void excludeFailed(IInvocationResult result);
+
+    /**
+     * Retrieve the set of include filters previously added or parsed from XML.
+     * @return a set of include filter strings
+     */
+    public Set<String> getIncludeFilters();
+
+    /**
+     * Retrieve the set of exclude filters previously added or parsed from XML.
+     * @return a set of exclude filter strings
+     */
+    public Set<String> getExcludeFilters();
+
+    /**
+     * Serialize the existing filters into a stream of XML, and write to an output stream.
+     * @param xmlOutputStream the {@link OutputStream} to receive test plan XML
+     */
+    public void serialize(OutputStream xmlOutputStream) throws IOException;
+}
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
index 0844a6e..2c3f96b 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleDef.java
@@ -268,7 +268,7 @@
      * {@inheritDoc}
      */
     @Override
-    public void prepare(boolean skipPrep) throws DeviceNotAvailableException {
+    public boolean prepare(boolean skipPrep) throws DeviceNotAvailableException {
         for (ITargetPreparer preparer : mPreconditions) {
             CLog.d("Preparer: %s", preparer.getClass().getSimpleName());
             if (preparer instanceof IAbiReceiver) {
@@ -282,13 +282,16 @@
                 // This should only happen for flashing new build
                 CLog.e("Unexpected BuildError from precondition: %s",
                         preparer.getClass().getCanonicalName());
+                return false;
             } catch (TargetSetupError e) {
                 // log precondition class then rethrow & let caller handle
                 CLog.e("TargetSetupError in precondition: %s",
                         preparer.getClass().getCanonicalName());
-                throw new RuntimeException(e);
+                e.printStackTrace();
+                return false;
             }
         }
+        return true;
     }
 
     private void setOption(Object target, String option, String value) {
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
index de509c8..c4ff1b0 100644
--- a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/ModuleRepo.java
@@ -47,6 +47,7 @@
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -78,6 +79,10 @@
     private IConfigurationFactory mConfigFactory = ConfigurationFactory.getInstance();
 
     private volatile boolean mInitialized = false;
+    // Whether the modules in this repo are ready to run on their assigned devices.
+    // True until explicitly set false in setPrepared().
+    private volatile boolean mPrepared = true;
+    private CountDownLatch mPreparedLatch;
 
     // Holds all the small tests waiting to be run.
     private List<IModuleDef> mSmallModules = new ArrayList<>();
@@ -197,6 +202,28 @@
      * {@inheritDoc}
      */
     @Override
+    public boolean isPrepared() {
+        try {
+            mPreparedLatch.await();
+        } catch (InterruptedException e) {
+            return false;
+        }
+        return mPrepared;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setPrepared(boolean isPrepared) {
+        mPrepared &= isPrepared;
+        mPreparedLatch.countDown();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
     public boolean isInitialized() {
         return mInitialized;
     }
@@ -214,6 +241,7 @@
                 includeFilters, excludeFilters);
         mInitialized = true;
         mShards = shards;
+        mPreparedLatch = new CountDownLatch(shards);
         for (String line : deviceTokens) {
             String[] parts = line.split(":");
             if (parts.length == 2) {
diff --git a/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/TestPlan.java b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/TestPlan.java
new file mode 100644
index 0000000..46dbe8d
--- /dev/null
+++ b/common/host-side/tradefed/src/com/android/compatibility/common/tradefed/testtype/TestPlan.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.tradefed.testtype;
+
+import com.android.compatibility.common.util.ICaseResult;
+import com.android.compatibility.common.util.IInvocationResult;
+import com.android.compatibility.common.util.IModuleResult;
+import com.android.compatibility.common.util.ITestResult;
+
+import com.android.compatibility.common.util.TestFilter;
+import com.android.compatibility.common.util.TestStatus;
+import com.android.tradefed.util.xml.AbstractXmlParser;
+
+import org.kxml2.io.KXmlSerializer;
+import org.xml.sax.Attributes;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Container, parser, and generator of TestPlan info.
+ */
+public class TestPlan extends AbstractXmlParser implements ITestPlan {
+
+    private final Set<String> mIncludes;
+    private final Set<String> mExcludes;
+
+    private static final String ENCODING = "UTF-8";
+    private static final String NS = null; // namespace used for XML serializer
+    private static final String VERSION_ATTR = "version";
+    private static final String PLAN_VERSION = "2.0";
+
+
+    private static final String PLAN_TAG = "TestPlan";
+    private static final String ENTRY_TAG = "Entry";
+    private static final String EXCLUDE_ATTR = "exclude";
+    private static final String INCLUDE_ATTR = "include";
+    private static final String ABI_ATTR = "abi";
+    private static final String NAME_ATTR = "name";
+
+    public TestPlan() {
+        mIncludes = new HashSet<String>();
+        mExcludes = new HashSet<String>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void includePassed(IInvocationResult result) {
+        for (IModuleResult module : result.getModules()) {
+            if (module.isPassed()) {
+                // Whole module passed, exclude
+                TestFilter filter =
+                        new TestFilter(module.getAbi(), module.getName(), null /*test*/);
+                mIncludes.add(filter.toString());
+            } else {
+                for (ICaseResult testResultList : module.getResults()) {
+                    for (ITestResult testResult : testResultList.getResults(TestStatus.PASS)) {
+                        // Test passed, exclude
+                        TestFilter filter = new TestFilter(
+                                module.getAbi(), module.getName(), testResult.getFullName());
+                        mIncludes.add(filter.toString());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void includeFailed(IInvocationResult result) {
+        for (IModuleResult moduleResult : result.getModules()) {
+            for (ICaseResult caseResult : moduleResult.getResults()) {
+                for (ITestResult testResult : caseResult.getResults(TestStatus.FAIL)) {
+                    // Test failed, include for retry
+                    TestFilter filter = new TestFilter(moduleResult.getAbi(),
+                            moduleResult.getName(), testResult.getFullName());
+                    mIncludes.add(filter.toString());
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void excludePassed(IInvocationResult result) {
+        for (IModuleResult module : result.getModules()) {
+            if (module.isPassed()) {
+                // Whole module passed, exclude
+                TestFilter filter =
+                        new TestFilter(module.getAbi(), module.getName(), null /*test*/);
+                mExcludes.add(filter.toString());
+            } else {
+                for (ICaseResult testResultList : module.getResults()) {
+                    for (ITestResult testResult : testResultList.getResults(TestStatus.PASS)) {
+                        // Test passed, exclude
+                        TestFilter filter = new TestFilter(
+                                module.getAbi(), module.getName(), testResult.getFullName());
+                        mExcludes.add(filter.toString());
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void excludeFailed(IInvocationResult result) {
+        for (IModuleResult moduleResult : result.getModules()) {
+            for (ICaseResult caseResult : moduleResult.getResults()) {
+                for (ITestResult testResult : caseResult.getResults(TestStatus.FAIL)) {
+                    // Test failed, include for retry
+                    TestFilter filter = new TestFilter(moduleResult.getAbi(),
+                            moduleResult.getName(), testResult.getFullName());
+                    mExcludes.add(filter.toString());
+                }
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addIncludeFilter(String filter) {
+        mIncludes.add(filter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addAllIncludeFilters(Set<String> filters) {
+        mIncludes.addAll(filters);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addExcludeFilter(String filter) {
+        mExcludes.add(filter);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void addAllExcludeFilters(Set<String> filters) {
+        mExcludes.addAll(filters);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Set<String> getIncludeFilters() {
+        return new HashSet<String>(mIncludes);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public Set<String> getExcludeFilters() {
+        return new HashSet<String>(mExcludes);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void serialize(OutputStream stream) throws IOException {
+        KXmlSerializer serializer = new KXmlSerializer();
+        serializer.setOutput(stream, ENCODING);
+        serializer.startDocument(ENCODING, false);
+        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+        serializer.startTag(NS, PLAN_TAG);
+        serializer.attribute(NS, VERSION_ATTR, PLAN_VERSION);
+
+        ArrayList<String> sortedIncludes = new ArrayList<String>(mIncludes);
+        ArrayList<String> sortedExcludes = new ArrayList<String>(mExcludes);
+        Collections.sort(sortedIncludes);
+        Collections.sort(sortedExcludes);
+        for (String include : sortedIncludes) {
+            serializer.startTag(NS, ENTRY_TAG);
+            serializer.attribute(NS, INCLUDE_ATTR, include);
+            serializer.endTag(NS, ENTRY_TAG);
+        }
+        for (String exclude : sortedExcludes) {
+            serializer.startTag(NS, ENTRY_TAG);
+            serializer.attribute(NS, EXCLUDE_ATTR, exclude);
+            serializer.endTag(NS, ENTRY_TAG);
+        }
+
+        serializer.endTag(NS, PLAN_TAG);
+        serializer.endDocument();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    protected DefaultHandler createXmlHandler() {
+        return new EntryHandler();
+    }
+
+    /**
+     * SAX callback object. Handles parsing data from the xml tags.
+     */
+    private class EntryHandler extends DefaultHandler {
+
+        @Override
+        public void startElement(String uri, String localName, String name, Attributes attributes)
+                throws SAXException {
+            if (ENTRY_TAG.equals(localName)) {
+                String includeString = attributes.getValue(INCLUDE_ATTR);
+                String excludeString = attributes.getValue(EXCLUDE_ATTR);
+                if (includeString != null && excludeString != null) {
+                    throw new IllegalArgumentException(
+                            "Cannot specify include and exclude filter in the same element");
+                }
+                String abiString = attributes.getValue(ABI_ATTR);
+                String nameString = attributes.getValue(NAME_ATTR);
+
+                if (excludeString == null) {
+                    parseFilter(abiString, nameString, includeString, mIncludes);
+                } else {
+                    parseFilter(abiString, nameString, excludeString, mExcludes);
+                }
+            }
+        }
+
+        private void parseFilter(String abi, String name, String filter, Set<String> filterSet) {
+            if (name == null) {
+                // ignore name and abi attributes, 'filter' should contain all necessary parts
+                filterSet.add(filter);
+            } else {
+                // 'filter' is name of test. Build TestFilter and convert back to string
+                filterSet.add(new TestFilter(abi, name, filter).toString());
+            }
+        }
+    }
+}
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
index dfe67c1..cbb2020 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/UnitTests.java
@@ -24,6 +24,7 @@
 import com.android.compatibility.common.tradefed.testtype.CompatibilityTestTest;
 import com.android.compatibility.common.tradefed.testtype.ModuleDefTest;
 import com.android.compatibility.common.tradefed.testtype.ModuleRepoTest;
+import com.android.compatibility.common.tradefed.testtype.TestPlanTest;
 import com.android.compatibility.common.tradefed.util.OptionHelperTest;
 import com.android.compatibility.common.tradefed.util.CollectorUtilTest;
 
@@ -51,6 +52,7 @@
         addTestSuite(ModuleRepoTest.class);
         addTestSuite(PropertyCheckTest.class);
         addTestSuite(SettingsPreparerTest.class);
+        addTestSuite(TestPlanTest.class);
     }
 
     public static Test suite() {
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
index 24c7e3d..3b8e4cb 100644
--- a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/ModuleRepoTest.java
@@ -262,6 +262,26 @@
         assertArrayEquals(EXPECTED_MODULE_IDS, mRepo.getModuleIds());
     }
 
+    public void testIsPrepared() {
+        mRepo.initialize(3, mTestsDir, ABIS, DEVICE_TOKENS, TEST_ARGS, MODULE_ARGS, INCLUDES,
+                EXCLUDES, mBuild);
+        assertTrue("Should be initialized", mRepo.isInitialized());
+        mRepo.setPrepared(true);
+        mRepo.setPrepared(true);
+        mRepo.setPrepared(true); // each shard should call setPrepared() once
+        assertTrue(mRepo.isPrepared());
+    }
+
+    public void testIsNotPrepared() {
+        mRepo.initialize(3, mTestsDir, ABIS, DEVICE_TOKENS, TEST_ARGS, MODULE_ARGS, INCLUDES,
+                EXCLUDES, mBuild);
+        assertTrue("Should be initialized", mRepo.isInitialized());
+        mRepo.setPrepared(true);
+        mRepo.setPrepared(false); // mRepo should return false for setPrepared() after third call
+        mRepo.setPrepared(true);
+        assertFalse(mRepo.isPrepared());
+    }
+
     private void assertArrayEquals(Object[] expected, Object[] actual) {
         assertEquals(Arrays.asList(expected), Arrays.asList(actual));
     }
diff --git a/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestPlanTest.java b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestPlanTest.java
new file mode 100644
index 0000000..dd09635
--- /dev/null
+++ b/common/host-side/tradefed/tests/src/com/android/compatibility/common/tradefed/testtype/TestPlanTest.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.tradefed.testtype;
+
+import com.android.compatibility.common.util.TestFilter;
+import com.android.tradefed.util.FileUtil;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.HashSet;
+import java.util.Set;
+
+public class TestPlanTest extends TestCase {
+
+    private static final String ABI = "armeabi-v7a";
+    private static final String MODULE_A = "ModuleA";
+    private static final String MODULE_B = "ModuleB";
+    private static final String TEST_1 = "android.test.Foo#test1";
+    private static final String TEST_2 = "android.test.Foo#test2";
+    private static final String TEST_3 = "android.test.Foo#test3";
+
+    private static final String XML_BASE =
+            "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
+            "<TestPlan version=\"2.0\">\n" +
+            "%s\n" +
+            "</TestPlan>";
+    private static final String XML_ENTRY = "  <Entry %s/>\n";
+    private static final String XML_ATTR = "%s=\"%s\"";
+
+    public void testSerialization() throws Exception {
+        ITestPlan plan = new TestPlan();
+        plan.addIncludeFilter(new TestFilter(ABI, MODULE_A, TEST_1).toString());
+        Set<String> includeFilterSet = new HashSet<String>();
+        includeFilterSet.add(new TestFilter(ABI, MODULE_A, TEST_2).toString());
+        includeFilterSet.add(new TestFilter(ABI, MODULE_A, TEST_3).toString());
+        plan.addAllIncludeFilters(includeFilterSet); // add multiple include filters simultaneously
+        plan.addIncludeFilter(new TestFilter(null, MODULE_B, null).toString());
+        plan.addExcludeFilter(new TestFilter(null, MODULE_B, TEST_1).toString());
+        Set<String> excludeFilterSet = new HashSet<String>();
+        excludeFilterSet.add(new TestFilter(null, MODULE_B, TEST_2).toString());
+        excludeFilterSet.add(new TestFilter(null, MODULE_B, TEST_3).toString());
+        plan.addAllExcludeFilters(excludeFilterSet);
+
+        // Serialize to file
+        File planFile = FileUtil.createTempFile("test-plan-serialization", ".txt");
+        OutputStream planOutputStream = new FileOutputStream(planFile);
+        plan.serialize(planOutputStream);
+        planOutputStream.close();
+
+        // Parse plan and assert correctness
+        checkPlan(planFile);
+
+    }
+
+    public void testParsing() throws Exception {
+        File planFile = FileUtil.createTempFile("test-plan-parsing", ".txt");
+        FileWriter writer = new FileWriter(planFile);
+        Set<String> entries = new HashSet<String>();
+        entries.add(generateEntryXml(ABI, MODULE_A, TEST_1, true)); // include format 1
+        entries.add(generateEntryXml(ABI, MODULE_A, TEST_2, true));
+        entries.add(generateEntryXml(null, null,
+                new TestFilter(ABI, MODULE_A, TEST_3).toString(), true)); // include format 2
+        entries.add(generateEntryXml(null, MODULE_B, null, true));
+        entries.add(generateEntryXml(null, null,
+                new TestFilter(null, MODULE_B, TEST_1).toString(), false));
+        entries.add(generateEntryXml(null, null,
+                new TestFilter(null, MODULE_B, TEST_2).toString(), false));
+        entries.add(generateEntryXml(null, null,
+                new TestFilter(null, MODULE_B, TEST_3).toString(), false));
+        String xml = String.format(XML_BASE, String.join("\n", entries));
+        writer.write(xml);
+        writer.flush();
+        checkPlan(planFile);
+    }
+
+    private void checkPlan(File planFile) throws Exception {
+        InputStream planInputStream = new FileInputStream(planFile);
+        ITestPlan plan = new TestPlan();
+        plan.parse(planInputStream);
+        Set<String> planIncludes = plan.getIncludeFilters();
+        Set<String> planExcludes = plan.getExcludeFilters();
+        assertEquals("Expected 4 includes", 4, planIncludes.size());
+        assertTrue("Missing expected test include", planIncludes.contains(
+                new TestFilter(ABI, MODULE_A, TEST_1).toString()));
+        assertTrue("Missing expected test include", planIncludes.contains(
+                new TestFilter(ABI, MODULE_A, TEST_2).toString()));
+        assertTrue("Missing expected test include", planIncludes.contains(
+                new TestFilter(ABI, MODULE_A, TEST_3).toString()));
+        assertTrue("Missing expected module include", planIncludes.contains(
+                new TestFilter(null, MODULE_B, null).toString()));
+
+        assertEquals("Expected 3 excludes", 3, planExcludes.size());
+        assertTrue("Missing expected exclude", planExcludes.contains(
+                new TestFilter(null, MODULE_B, TEST_1).toString()));
+        assertTrue("Missing expected exclude", planExcludes.contains(
+                new TestFilter(null, MODULE_B, TEST_2).toString()));
+        assertTrue("Missing expected exclude", planExcludes.contains(
+                new TestFilter(null, MODULE_B, TEST_3).toString()));
+    }
+
+    // Helper for generating Entry XML tags
+    private String generateEntryXml(String abi, String name, String filter, boolean include) {
+        String filterType = (include) ? "include" : "exclude";
+        Set<String> attributes = new HashSet<String>();
+        if (filter != null) {
+            attributes.add(String.format(XML_ATTR, filterType, filter));
+        }
+        if (name != null) {
+            attributes.add(String.format(XML_ATTR, "name", name));
+        }
+        if (abi != null) {
+            attributes.add(String.format(XML_ATTR, "abi", abi));
+        }
+        return String.format(XML_ENTRY, String.join(" ", attributes));
+    }
+}
diff --git a/common/util/src/com/android/compatibility/common/util/CddTest.java b/common/util/src/com/android/compatibility/common/util/CddTest.java
new file mode 100644
index 0000000..34ee663
--- /dev/null
+++ b/common/util/src/com/android/compatibility/common/util/CddTest.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.util;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks the type of test with purpose of asserting CDD requirements.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.METHOD, ElementType.TYPE})
+public @interface CddTest {
+    String requirement();
+}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTestCase.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTestCase.java
index 7bd42b5..009d81b 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTestCase.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/DocumentsTestCase.java
@@ -51,10 +51,7 @@
         assertNotNull(mAbi);
         assertNotNull(mCtsBuild);
 
-        getDevice().uninstallPackage(CLIENT_PKG);
-
-        assertNull(getDevice().installPackage(
-                MigrationHelper.getTestFile(mCtsBuild, CLIENT_APK), false));
+        reinstallClientPackage();
     }
 
     @Override
@@ -68,4 +65,11 @@
             throws DeviceNotAvailableException {
         Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
     }
+
+    protected void reinstallClientPackage() throws Exception {
+        getDevice().uninstallPackage(CLIENT_PKG);
+
+        assertNull(getDevice().installPackage(
+                MigrationHelper.getTestFile(mCtsBuild, CLIENT_APK), false));
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
index ffc7597..889b20b 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ScopedDirectoryAccessTest.java
@@ -42,6 +42,14 @@
         runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest", "testNotAskedAgain");
     }
 
+    public void testDeniesOnceForAllClearedWhenPackageRemoved() throws Exception {
+        runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest",
+                "testRemovePackageStep1UserDenies");
+        reinstallClientPackage();
+        runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest",
+                "testRemovePackageStep2UserAcceptsDoNotClear");
+    }
+
     public void testDeniesOnceButAllowsAskingAgain() throws Exception {
         runDeviceTests(CLIENT_PKG, ".ScopedDirectoryAccessClientTest",
                 "testDeniesOnceButAllowsAskingAgain");
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
index e52af73..a26ec2d 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTestCase.java
@@ -150,7 +150,15 @@
         return result;
     }
 
+    /**
+     * Clears the DocumentsUI package data, unless test name ends on {@code DoNotClear}.
+     */
     protected void clearDocumentsUi() throws Exception {
+        final String testName = getName();
+        if (testName.endsWith("DoNotClear")) {
+            Log.d(TAG, "Not clearing DocumentsUI due to test name: " + testName);
+            return;
+        }
         executeShellCommand("pm clear com.android.documentsui");
     }
 }
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java
index a4d35fb..5747adf 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/ScopedDirectoryAccessClientTest.java
@@ -117,9 +117,9 @@
         if (!supportedHardware()) return;
 
         for (StorageVolume volume : getVolumes()) {
-            userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_PICTURES);
+            userAcceptsTest(volume, DIRECTORY_PICTURES);
             if (!volume.isPrimary()) {
-                userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_ROOT);
+                userAcceptsTest(volume, DIRECTORY_ROOT);
             }
         }
     }
@@ -133,7 +133,7 @@
         if (!output.isEmpty()) {
             fail("Command '" + command + "' failed: '" + output + "'");
         }
-        userAcceptsOpenExternalDirectoryTest(getPrimaryVolume(), DIRECTORY_PICTURES);
+        userAcceptsTest(getPrimaryVolume(), DIRECTORY_PICTURES);
     }
 
     public void testNotAskedAgain() throws Exception {
@@ -141,7 +141,7 @@
 
         for (StorageVolume volume : getVolumes()) {
             final String volumeDesc = volume.getDescription(getInstrumentation().getContext());
-            final Uri grantedUri = userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_PICTURES);
+            final Uri grantedUri = userAcceptsTest(volume, DIRECTORY_PICTURES);
 
             // Calls it again - since the permission has been granted, it should return right
             // away, without popping up the permissions dialog.
@@ -151,7 +151,7 @@
             assertEquals(grantedUri, newData.getData());
 
             // Make sure other directories still require user permission.
-            final Uri grantedUri2 = userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_ALARMS);
+            final Uri grantedUri2 = userAcceptsTest(volume, DIRECTORY_ALARMS);
             assertNotEqual(grantedUri, grantedUri2);
         }
     }
@@ -162,7 +162,7 @@
         for (StorageVolume volume : getVolumes()) {
             if (volume.isPrimary()) continue;
             final String volumeDesc = volume.getDescription(getInstrumentation().getContext());
-            final Uri grantedRootUri = userAcceptsOpenExternalDirectoryTest(volume, DIRECTORY_ROOT);
+            final Uri grantedRootUri = userAcceptsTest(volume, DIRECTORY_ROOT);
 
             // Calls it again - since the permission has been granted, it should return right
             // away, without popping up the permissions dialog.
@@ -204,7 +204,7 @@
                 assertActivityFailed();
 
                 // Third time is a charm...
-                userAcceptsOpenExternalDirectoryTest(volume, dir);
+                userAcceptsTest(volume, dir);
             }
         }
     }
@@ -216,33 +216,44 @@
         for (StorageVolume volume : getVolumes()) {
             for (String dir : dirs) {
                 if (volume.isPrimary() && dir == DIRECTORY_ROOT) continue;
-                // Rejects the first attempt...
-                UiAlertDialog dialog = openExternalDirectoryValidPath(volume, dir);
-                dialog.assertDoNotAskAgainVisibility(false);
-                dialog.noButton.click();
-                assertActivityFailed();
-
-                // ...and the second, checking the box
-                dialog = openExternalDirectoryValidPath(volume, dir);
-                UiObject checkbox = dialog.assertDoNotAskAgainVisibility(true);
-                assertTrue("checkbox should not be checkable", checkbox.isCheckable());
-                assertFalse("checkbox should not be checked", checkbox.isChecked());
-                checkbox.click();
-                assertTrue("checkbox should be checked", checkbox.isChecked()); // Sanity check
-                assertFalse("allow button should be disabled", dialog.yesButton.isEnabled());
-
-                dialog.noButton.click();
-                assertActivityFailed();
-
-                // Third strike out...
-                sendOpenExternalDirectoryIntent(volume, dir);
-                assertActivityFailed();
+                deniesOnceForAllTest(volume, dir);
             }
         }
     }
 
-    private Uri userAcceptsOpenExternalDirectoryTest(StorageVolume volume, String directoryName)
-            throws Exception {
+    private void deniesOnceForAllTest(StorageVolume volume, String dir) throws Exception {
+        // Rejects the first attempt...
+        UiAlertDialog dialog = openExternalDirectoryValidPath(volume, dir);
+        dialog.assertDoNotAskAgainVisibility(false);
+        dialog.noButton.click();
+        assertActivityFailed();
+
+        // ...and the second, checking the box
+        dialog = openExternalDirectoryValidPath(volume, dir);
+        UiObject checkbox = dialog.assertDoNotAskAgainVisibility(true);
+        assertTrue("checkbox should not be checkable", checkbox.isCheckable());
+        assertFalse("checkbox should not be checked", checkbox.isChecked());
+        checkbox.click();
+        assertTrue("checkbox should be checked", checkbox.isChecked()); // Sanity check
+        assertFalse("allow button should be disabled", dialog.yesButton.isEnabled());
+
+        dialog.noButton.click();
+        assertActivityFailed();
+
+        // Third strike out...
+        sendOpenExternalDirectoryIntent(volume, dir);
+        assertActivityFailed();
+    }
+
+    public void testRemovePackageStep1UserDenies() throws Exception {
+        deniesOnceForAllTest(getPrimaryVolume(), DIRECTORY_NOTIFICATIONS);
+    }
+
+    public void testRemovePackageStep2UserAcceptsDoNotClear() throws Exception {
+        userAcceptsTest(getPrimaryVolume(), DIRECTORY_NOTIFICATIONS);
+    }
+
+    private Uri userAcceptsTest(StorageVolume volume, String directoryName) throws Exception {
         // Asserts dialog contain the proper message.
         final UiAlertDialog dialog = openExternalDirectoryValidPath(volume, directoryName);
         final String message = dialog.messageText.getText();
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java
index 2d1197b..26d89c0 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceAdminPasswordTest.java
@@ -68,20 +68,22 @@
     }
 
     private void assertHasPassword() {
-        dpm.setPasswordMinimumLength(mAdminComponent, 1);
+        final int currentQuality = dpm.getPasswordQuality(mAdminComponent);
+        dpm.setPasswordQuality(mAdminComponent, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
         try {
             assertTrue("No password set", dpm.isActivePasswordSufficient());
         } finally {
-            dpm.setPasswordMinimumLength(mAdminComponent, 0);
+            dpm.setPasswordQuality(mAdminComponent, currentQuality);
         }
     }
 
     private void assertNoPassword() {
-        dpm.setPasswordMinimumLength(mAdminComponent, 1);
+        final int currentQuality = dpm.getPasswordQuality(mAdminComponent);
+        dpm.setPasswordQuality(mAdminComponent, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING);
         try {
             assertFalse("Password is set", dpm.isActivePasswordSufficient());
         } finally {
-            dpm.setPasswordMinimumLength(mAdminComponent, 0);
+            dpm.setPasswordQuality(mAdminComponent, currentQuality);
         }
     }
 
diff --git a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java
index a00b4eb..d7c3bcf 100644
--- a/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAdmin/src/com.android.cts.deviceadmin/DeviceOwnerPasswordTest.java
@@ -56,8 +56,9 @@
         dpm.setPasswordMinimumLength(mAdminComponent, 10);
         caseDescription = "minimum password length = 10";
         assertEquals(10, dpm.getPasswordMinimumLength(mAdminComponent));
-        assertFalse(dpm.isActivePasswordSufficient());
+        assertTrue(dpm.isActivePasswordSufficient()); // length not checked for this quality
 
+        // TODO(ascull): fix resetPassword() logic so these succeed
         assertPasswordFails("1234", caseDescription);
         assertPasswordFails("abcd", caseDescription);
         assertPasswordFails("abcd1234", caseDescription);
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
index c1c91da..50bcc60 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractBatterySaverModeTestCase.java
@@ -85,16 +85,24 @@
         assertsForegroundAlwaysHasNetworkAccess();
         assertBackgroundNetworkAccess(false);
 
-        // Make sure foreground app doesn't lose access upon enabling it.
+        // Make sure foreground app doesn't lose access upon Battery Saver.
         setBatterySaverMode(false);
         launchActivity();
         assertForegroundNetworkAccess();
         setBatterySaverMode(true);
         assertForegroundNetworkAccess();
+
+        // Although it should not have access while the screen is off.
+        turnScreenOff();
+        assertBackgroundNetworkAccess(false);
+        turnScreenOn();
+        assertForegroundNetworkAccess();
+
+        // Goes back to background state.
         finishActivity();
         assertBackgroundNetworkAccess(false);
 
-        // Same for foreground service.
+        // Make sure foreground service doesn't lose access upon enabling Battery Saver.
         setBatterySaverMode(false);
         startForegroundService();
         assertForegroundNetworkAccess();
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 8be1621..9245a6f 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -244,7 +244,7 @@
             if (isBackground(state.state)) {
                 return;
             }
-            Log.d(TAG, "App not on background state on attempt #" + i
+            Log.d(TAG, "App not on background state (" + state + ") on attempt #" + i
                     + "; sleeping 1s before trying again");
             SystemClock.sleep(SECOND_IN_MS);
         }
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
index 3e6bd33..881b3b4 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/DataSaverModeTest.java
@@ -98,16 +98,24 @@
         assertsForegroundAlwaysHasNetworkAccess();
         assertDataSaverStatusOnBackground(RESTRICT_BACKGROUND_STATUS_ENABLED);
 
-        // Make sure foreground app doesn't lose access upon enabling it.
+        // Make sure foreground app doesn't lose access upon enabling Data Saver.
         setRestrictBackground(false);
         launchActivity();
         assertForegroundNetworkAccess();
         setRestrictBackground(true);
         assertForegroundNetworkAccess();
+
+        // Although it should not have access while the screen is off.
+        turnScreenOff();
+        assertBackgroundNetworkAccess(false);
+        turnScreenOn();
+        assertForegroundNetworkAccess();
+
+        // Goes back to background state.
         finishActivity();
         assertBackgroundNetworkAccess(false);
 
-        // Same for foreground service.
+        // Make sure foreground service doesn't lose access upon enabling Data Saver.
         setRestrictBackground(false);
         startForegroundService();
         assertForegroundNetworkAccess();
diff --git a/hostsidetests/retaildemo/Android.mk b/hostsidetests/retaildemo/Android.mk
new file mode 100644
index 0000000..0aa5ee1
--- /dev/null
+++ b/hostsidetests/retaildemo/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := CtsRetailDemoHostTestCases
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := tools-common-prebuilt cts-tradefed tradefed-prebuilt
+
+LOCAL_CTS_TEST_PACKAGE := android.host.retaildemo
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+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/retaildemo/AndroidTest.xml b/hostsidetests/retaildemo/AndroidTest.xml
new file mode 100644
index 0000000..c174489
--- /dev/null
+++ b/hostsidetests/retaildemo/AndroidTest.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for the CTS retaildemo host tests">
+
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+        <option name="test-file-name" value="CtsRetailDemoApp.apk" />
+        <option name="cleanup-apks" value="true" />
+    </target_preparer>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsRetailDemoHostTestCases.jar" />
+    </test>
+
+</configuration>
\ No newline at end of file
diff --git a/hostsidetests/retaildemo/app/Android.mk b/hostsidetests/retaildemo/app/Android.mk
new file mode 100644
index 0000000..e91e1da
--- /dev/null
+++ b/hostsidetests/retaildemo/app/Android.mk
@@ -0,0 +1,35 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := tests
+# When built, explicitly put it in the data partition.
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+# tag this module as a cts test artifact
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_PACKAGE_NAME := CtsRetailDemoApp
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/retaildemo/app/AndroidManifest.xml b/hostsidetests/retaildemo/app/AndroidManifest.xml
new file mode 100644
index 0000000..b36ee1b
--- /dev/null
+++ b/hostsidetests/retaildemo/app/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.retaildemo">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.cts.retaildemo"
+            android:label="RetailDemo device side tests" />
+</manifest>
diff --git a/hostsidetests/retaildemo/app/src/com/android/cts/retaildemo/DemoUserTest.java b/hostsidetests/retaildemo/app/src/com/android/cts/retaildemo/DemoUserTest.java
new file mode 100644
index 0000000..bb20b1a
--- /dev/null
+++ b/hostsidetests/retaildemo/app/src/com/android/cts/retaildemo/DemoUserTest.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.retaildemo;
+
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.UserManager;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@SmallTest
+@RunWith(JUnit4.class)
+public class DemoUserTest {
+    private UserManager mUm;
+
+    @Before
+    public void setUp() {
+        mUm = InstrumentationRegistry.getContext().getSystemService(UserManager.class);
+    }
+
+    @Test
+    public void testIsDemoUser_success() {
+        assertTrue(mUm.isDemoUser());
+    }
+
+    @Test
+    public void testIsDemoUser_failure() {
+        assertFalse(mUm.isDemoUser());
+    }
+}
diff --git a/hostsidetests/retaildemo/src/android/host/retaildemo/BaseTestCase.java b/hostsidetests/retaildemo/src/android/host/retaildemo/BaseTestCase.java
new file mode 100644
index 0000000..5033b6d
--- /dev/null
+++ b/hostsidetests/retaildemo/src/android/host/retaildemo/BaseTestCase.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.host.retaildemo;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
+import com.android.ddmlib.testrunner.TestIdentifier;
+import com.android.ddmlib.testrunner.TestResult;
+import com.android.ddmlib.testrunner.TestResult.TestStatus;
+import com.android.ddmlib.testrunner.TestRunResult;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.result.CollectingTestListener;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.Map;
+
+public class BaseTestCase extends DeviceTestCase implements IBuildReceiver {
+    private static final String RETAIL_DEMO_TEST_PKG = "com.android.cts.retaildemo";
+    private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
+
+    private IBuildInfo mBuildInfo;
+    private CompatibilityBuildHelper mBuildHelper;
+
+    private ArrayList<Integer> mTestUsers;
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mBuildInfo = buildInfo;
+        mBuildHelper = new CompatibilityBuildHelper(mBuildInfo);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertNotNull(mBuildInfo); // ensure build has been set before test is run.
+        mTestUsers = new ArrayList<>();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        for (int userId : mTestUsers) {
+            getDevice().removeUser(userId);
+        }
+        super.tearDown();
+    }
+
+    protected int createDemoUser() throws DeviceNotAvailableException, IllegalStateException {
+        final String command = "pm create-user --ephemeral --demo "
+                + "TestUser_" + System.currentTimeMillis();
+        CLog.d("Starting command: " + command);
+        final String output = getDevice().executeShellCommand(command);
+        CLog.d("Output for command " + command + ": " + output);
+
+        if (output.startsWith("Success")) {
+            try {
+                int userId = Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim());
+                mTestUsers.add(userId);
+                return userId;
+            } catch (NumberFormatException e) {
+                CLog.e("Failed to parse result: %s", output);
+            }
+        } else {
+            CLog.e("Failed to create demo user: %s", output);
+        }
+        throw new IllegalStateException();
+    }
+
+    protected void installAppAsUser(String appFileName, int userId)
+            throws FileNotFoundException, DeviceNotAvailableException {
+        CLog.d("Installing app " + appFileName + " for user " + userId);
+        File apkFile = new File(mBuildHelper.getTestsDir(), appFileName);
+        final String result = getDevice().installPackageForUser(
+                apkFile, true, true, userId, "-t");
+        assertNull("Failed to install " + appFileName + " for user " + userId + ": " + result,
+                result);
+    }
+
+    protected boolean runDeviceTestsAsUser(String testClassName, String testMethodName, int userId)
+            throws Exception {
+        if (testClassName != null && testClassName.startsWith(".")) {
+            testClassName = RETAIL_DEMO_TEST_PKG + testClassName;
+        }
+
+        RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(
+                RETAIL_DEMO_TEST_PKG, RUNNER, getDevice().getIDevice());
+        if (testClassName != null && testMethodName != null) {
+            testRunner.setMethodName(testClassName, testMethodName);
+        } else if (testClassName != null) {
+            testRunner.setClassName(testClassName);
+        }
+
+        CollectingTestListener listener = new CollectingTestListener();
+        assertTrue(getDevice().runInstrumentationTestsAsUser(testRunner, userId, listener));
+
+        TestRunResult runResult = listener.getCurrentRunResults();
+        printTestResult(runResult);
+        return !runResult.hasFailedTests() && runResult.getNumTestsInState(TestStatus.PASSED) > 0;
+    }
+
+    private void printTestResult(TestRunResult runResult) {
+        for (Map.Entry<TestIdentifier, TestResult> testEntry :
+                runResult.getTestResults().entrySet()) {
+            TestResult testResult = testEntry.getValue();
+            CLog.d("Test " + testEntry.getKey() + ": " + testResult.getStatus());
+            if (testResult.getStatus() != TestStatus.PASSED) {
+                CLog.d(testResult.getStackTrace());
+            }
+        }
+    }
+}
diff --git a/hostsidetests/retaildemo/src/android/host/retaildemo/DemoModeTest.java b/hostsidetests/retaildemo/src/android/host/retaildemo/DemoModeTest.java
new file mode 100644
index 0000000..8252022
--- /dev/null
+++ b/hostsidetests/retaildemo/src/android/host/retaildemo/DemoModeTest.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.host.retaildemo;
+
+import static junit.framework.Assert.assertTrue;
+
+public class DemoModeTest extends BaseTestCase {
+    private static final String RETAIL_DEMO_TEST_APK = "CtsRetailDemoApp.apk";
+
+    public void testIsDemoUser_inPrimaryUser() throws Exception {
+        assertTrue(runDeviceTestsAsUser(
+                ".DemoUserTest", "testIsDemoUser_failure", getDevice().getPrimaryUserId()));
+    }
+
+    public void testIsDemoUser_inDemoUser() throws Exception {
+        final int demoUserId = createDemoUser();
+        getDevice().startUser(demoUserId);
+        installAppAsUser(RETAIL_DEMO_TEST_APK, demoUserId);
+        assertTrue(runDeviceTestsAsUser(
+                ".DemoUserTest", "testIsDemoUser_success", demoUserId));
+    }
+}
diff --git a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
index b74eba7..a8c35d2 100644
--- a/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
+++ b/hostsidetests/security/src/android/security/cts/SELinuxHostTest.java
@@ -27,6 +27,8 @@
 import com.android.tradefed.testtype.IBuildReceiver;
 import com.android.tradefed.testtype.IDeviceTest;
 
+import com.android.compatibility.common.util.CddTest;
+
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileReader;
@@ -123,6 +125,7 @@
      *
      * @throws Exception
      */
+    @CddTest(requirement="9.7")
     public void testGlobalEnforcing() throws Exception {
         CollectingOutputReceiver out = new CollectingOutputReceiver();
         mDevice.executeShellCommand("cat /sys/fs/selinux/enforce", out);
@@ -134,6 +137,7 @@
      *
      * @throws Exception
      */
+    @CddTest(requirement="9.7")
     @RestrictedBuildTest
     public void testAllDomainsEnforcing() throws Exception {
 
@@ -188,6 +192,7 @@
      *
      * @throws Exception
      */
+    @CddTest(requirement="9.7")
     public void testMLSAttributes() throws Exception {
         assertNotInAttribute("mlstrustedsubject", "untrusted_app");
         assertNotInAttribute("mlstrustedobject", "app_data_file");
@@ -198,6 +203,7 @@
      *
      * @throws Exception
      */
+    @CddTest(requirement="9.7")
     public void testValidSeappContexts() throws Exception {
 
         /* obtain seapp_contexts file from running device */
@@ -257,6 +263,7 @@
      *
      * @throws Exception
      */
+    @CddTest(requirement="9.7")
     public void testAospSeappContexts() throws Exception {
 
         /* obtain seapp_contexts file from running device */
@@ -276,6 +283,7 @@
      *
      * @throws Exception
      */
+    @CddTest(requirement="9.7")
     public void testAospFileContexts() throws Exception {
 
         /* retrieve the checkfc executable from jar */
@@ -311,6 +319,7 @@
      *
      * @throws Exception
      */
+    @CddTest(requirement="9.7")
     public void testAospPropertyContexts() throws Exception {
 
         /* obtain property_contexts file from running device */
@@ -330,6 +339,7 @@
      *
      * @throws Exception
      */
+    @CddTest(requirement="9.7")
     public void testAospServiceContexts() throws Exception {
 
         /* obtain service_contexts file from running device */
@@ -348,6 +358,7 @@
      *
      * @throws Exception
      */
+    @CddTest(requirement="9.7")
     public void testValidFileContexts() throws Exception {
 
         /* retrieve the checkfc executable from jar */
@@ -383,6 +394,7 @@
      *
      * @throws Exception
      */
+    @CddTest(requirement="9.7")
     public void testValidPropertyContexts() throws Exception {
 
         /* retrieve the checkfc executable from jar */
@@ -418,6 +430,7 @@
      *
      * @throws Exception
      */
+    @CddTest(requirement="9.7")
     public void testValidServiceContexts() throws Exception {
 
         /* retrieve the checkfc executable from jar */
@@ -453,6 +466,7 @@
      *
      * @throws Exception
      */
+    @CddTest(requirement="9.7")
     public void testNoBooleans() throws Exception {
 
         /* run sepolicy-analyze booleans check on policy file */
@@ -629,67 +643,80 @@
     }
 
     /* Init is always there */
+    @CddTest(requirement="9.7")
     public void testInitDomain() throws DeviceNotAvailableException {
         assertDomainOne("u:r:init:s0", "/init");
     }
 
     /* Ueventd is always there */
+    @CddTest(requirement="9.7")
     public void testUeventdDomain() throws DeviceNotAvailableException {
         assertDomainOne("u:r:ueventd:s0", "/sbin/ueventd");
     }
 
     /* Devices always have healthd */
+    @CddTest(requirement="9.7")
     public void testHealthdDomain() throws DeviceNotAvailableException {
         assertDomainOne("u:r:healthd:s0", "/sbin/healthd");
     }
 
     /* Servicemanager is always there */
+    @CddTest(requirement="9.7")
     public void testServicemanagerDomain() throws DeviceNotAvailableException {
         assertDomainOne("u:r:servicemanager:s0", "/system/bin/servicemanager");
     }
 
     /* Vold is always there */
+    @CddTest(requirement="9.7")
     public void testVoldDomain() throws DeviceNotAvailableException {
         assertDomainOne("u:r:vold:s0", "/system/bin/vold");
     }
 
     /* netd is always there */
+    @CddTest(requirement="9.7")
     public void testNetdDomain() throws DeviceNotAvailableException {
         assertDomainOne("u:r:netd:s0", "/system/bin/netd");
     }
 
     /* Debuggerd is always there */
+    @CddTest(requirement="9.7")
     public void testDebuggerdDomain() throws DeviceNotAvailableException {
         assertDomainN("u:r:debuggerd:s0", "/system/bin/debuggerd", "/system/bin/debuggerd64",
                 "debuggerd:signaller", "debuggerd64:signaller");
     }
 
     /* Surface flinger is always there */
+    @CddTest(requirement="9.7")
     public void testSurfaceflingerDomain() throws DeviceNotAvailableException {
         assertDomainOne("u:r:surfaceflinger:s0", "/system/bin/surfaceflinger");
     }
 
     /* Zygote is always running */
+    @CddTest(requirement="9.7")
     public void testZygoteDomain() throws DeviceNotAvailableException {
         assertDomainN("u:r:zygote:s0", "zygote", "zygote64");
     }
 
     /* Checks drmserver for devices that require it */
+    @CddTest(requirement="9.7")
     public void testDrmServerDomain() throws DeviceNotAvailableException {
         assertDomainZeroOrOne("u:r:drmserver:s0", "/system/bin/drmserver");
     }
 
     /* Installd is always running */
+    @CddTest(requirement="9.7")
     public void testInstalldDomain() throws DeviceNotAvailableException {
         assertDomainOne("u:r:installd:s0", "/system/bin/installd");
     }
 
     /* keystore is always running */
+    @CddTest(requirement="9.7")
     public void testKeystoreDomain() throws DeviceNotAvailableException {
         assertDomainOne("u:r:keystore:s0", "/system/bin/keystore");
     }
 
     /* System server better be running :-P */
+    @CddTest(requirement="9.7")
     public void testSystemServerDomain() throws DeviceNotAvailableException {
         assertDomainOne("u:r:system_server:s0", "system_server");
     }
@@ -698,26 +725,31 @@
      * Some OEMs do not use sdcardd so transient. Other OEMs have multiple sdcards
      * so they run the daemon multiple times.
      */
+    @CddTest(requirement="9.7")
     public void testSdcarddDomain() throws DeviceNotAvailableException {
         assertDomainHasExecutable("u:r:sdcardd:s0", "/system/bin/sdcard");
     }
 
     /* Watchdogd may or may not be there */
+    @CddTest(requirement="9.7")
     public void testWatchdogdDomain() throws DeviceNotAvailableException {
         assertDomainZeroOrOne("u:r:watchdogd:s0", "/sbin/watchdogd");
     }
 
     /* logd may or may not be there */
+    @CddTest(requirement="9.7")
     public void testLogdDomain() throws DeviceNotAvailableException {
         assertDomainZeroOrOne("u:r:logd:s0", "/system/bin/logd");
     }
 
     /* lmkd may or may not be there */
+    @CddTest(requirement="9.7")
     public void testLmkdDomain() throws DeviceNotAvailableException {
         assertDomainZeroOrOne("u:r:lmkd:s0", "/system/bin/lmkd");
     }
 
     /* Wifi may be off so cardinality of 0 or 1 is ok */
+    @CddTest(requirement="9.7")
     public void testWpaDomain() throws DeviceNotAvailableException {
         assertDomainZeroOrOne("u:r:wpa:s0", "/system/bin/wpa_supplicant");
     }
@@ -726,6 +758,7 @@
      * Nothing should be running in this domain, cardinality test is all thats
      * needed
      */
+    @CddTest(requirement="9.7")
     public void testInitShellDomain() throws DeviceNotAvailableException {
         assertDomainEmpty("u:r:init_shell:s0");
     }
@@ -734,6 +767,7 @@
      * Nothing should be running in this domain, cardinality test is all thats
      * needed
      */
+    @CddTest(requirement="9.7")
     public void testRecoveryDomain() throws DeviceNotAvailableException {
         assertDomainEmpty("u:r:recovery:s0");
     }
@@ -742,6 +776,7 @@
      * Nothing should be running in this domain, cardinality test is all thats
      * needed
      */
+    @CddTest(requirement="9.7")
     @RestrictedBuildTest
     public void testSuDomain() throws DeviceNotAvailableException {
         assertDomainEmpty("u:r:su:s0");
@@ -750,6 +785,7 @@
     /*
      * All kthreads should be in kernel context.
      */
+    @CddTest(requirement="9.7")
     public void testKernelDomain() throws DeviceNotAvailableException {
         String domain = "u:r:kernel:s0";
         List<ProcessDetails> procs = ProcessDetails.getProcMap(mDevice).get(domain);
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index 10a6792..b5460a6 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -329,6 +329,21 @@
 
         <activity android:name="android.app.stubs.KeyboardShortcutsActivity" />
 
+        <service
+            android:name="android.app.stubs.LiveWallpaper"
+            android:icon="@drawable/robot"
+            android:label="@string/wallpaper_title"
+            android:permission="android.permission.BIND_WALLPAPER">
+            <intent-filter>
+                <action android:name="android.service.wallpaper.WallpaperService">
+                </action>
+            </intent-filter>
+            <meta-data
+                android:name="android.service.wallpaper"
+                android:resource="@xml/wallpaper">
+            </meta-data>
+        </service>
+
     </application>
 
 </manifest>
diff --git a/tests/app/app/res/values/strings.xml b/tests/app/app/res/values/strings.xml
index 5e9e6d7..839e398 100644
--- a/tests/app/app/res/values/strings.xml
+++ b/tests/app/app/res/values/strings.xml
@@ -177,4 +177,10 @@
 I think so, so how about double this string, like copy and paste! </string>
     <string name="rectangle200">"M 0,0 l 200,0 l 0, 200 l -200, 0 z"</string>
     <string name="hello">Hello World</string>
+
+    <string name="wallpaper_description">Description</string>
+    <string name="wallpaper_collection">Collection</string>
+    <string name="wallpaper_title">Title</string>
+    <string name="wallpaper_context">Context</string>
+    <string name="wallpaper_context_uri">http://android.com</string>
 </resources>
diff --git a/tests/app/app/res/xml/wallpaper.xml b/tests/app/app/res/xml/wallpaper.xml
new file mode 100644
index 0000000..f70b20f
--- /dev/null
+++ b/tests/app/app/res/xml/wallpaper.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2016 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<wallpaper
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:thumbnail="@drawable/icon_red"
+    android:author="@string/wallpaper_collection"
+    android:description="@string/wallpaper_description"
+    android:showMetadataInPreview="true"
+    android:contextDescription="@string/wallpaper_context"
+    android:contextUri="@string/wallpaper_context_uri"
+/>
\ No newline at end of file
diff --git a/tests/app/app/src/android/app/stubs/LiveWallpaper.java b/tests/app/app/src/android/app/stubs/LiveWallpaper.java
new file mode 100644
index 0000000..5b8a82e
--- /dev/null
+++ b/tests/app/app/src/android/app/stubs/LiveWallpaper.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.stubs;
+
+import android.service.wallpaper.WallpaperService;
+
+public class LiveWallpaper extends WallpaperService {
+
+    @Override
+    public Engine onCreateEngine() {
+        return new Engine();
+    }
+}
diff --git a/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java b/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
index 2b84be6..fefa546 100644
--- a/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
+++ b/tests/app/src/android/app/cts/ActivityManagerMemoryClassTest.java
@@ -27,6 +27,8 @@
 import android.view.Display;
 import android.view.WindowManager;
 
+import com.android.compatibility.common.util.CddTest;
+
 import java.util.HashMap;
 import java.util.Map;
 
@@ -148,6 +150,7 @@
         }
     }
 
+    @CddTest(requirement="3.7")
     public void testGetMemoryClass() throws Exception {
         int memoryClass = getMemoryClass();
         int screenDensity = getScreenDensity();
diff --git a/tests/app/src/android/app/cts/DownloadManagerTest.java b/tests/app/src/android/app/cts/DownloadManagerTest.java
index 50eb96b..1bec983 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTest.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTest.java
@@ -34,6 +34,8 @@
 import android.util.Log;
 import android.webkit.cts.CtsTestServer;
 
+import com.android.compatibility.common.util.CddTest;
+
 import java.io.File;
 import java.util.Arrays;
 import java.util.HashSet;
@@ -181,6 +183,7 @@
         }
     }
 
+    @CddTest(requirement="7.6.1")
     public void testMinimumDownload() throws Exception {
         final DownloadCompleteReceiver receiver = new DownloadCompleteReceiver();
         try {
diff --git a/tests/app/src/android/app/cts/WallpaperInfoTest.java b/tests/app/src/android/app/cts/WallpaperInfoTest.java
new file mode 100644
index 0000000..1b30902
--- /dev/null
+++ b/tests/app/src/android/app/cts/WallpaperInfoTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.WallpaperInfo;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.service.wallpaper.WallpaperService;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class WallpaperInfoTest {
+
+    @Test
+    public void test() throws Exception {
+        Context context = InstrumentationRegistry.getTargetContext();
+        Intent intent = new Intent(WallpaperService.SERVICE_INTERFACE);
+        intent.setPackage("android.app.stubs");
+        PackageManager pm = context.getPackageManager();
+        List<ResolveInfo> result = pm.queryIntentServices(intent, PackageManager.GET_META_DATA);
+        assertEquals(1, result.size());
+        ResolveInfo info = result.get(0);
+        WallpaperInfo wallpaperInfo = new WallpaperInfo(context, info);
+        assertEquals("Title", wallpaperInfo.loadLabel(pm));
+        assertEquals("Description", wallpaperInfo.loadDescription(pm));
+        assertEquals("Collection", wallpaperInfo.loadAuthor(pm));
+        assertEquals("Context", wallpaperInfo.loadContextDescription(pm));
+        assertEquals("http://android.com", wallpaperInfo.loadContextUri(pm).toString());
+        assertEquals(true, wallpaperInfo.getShowMetadataInPreview());
+        assertNotNull(wallpaperInfo.loadIcon(pm));
+        assertNotNull(wallpaperInfo.loadThumbnail(pm));
+    }
+}
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index ebda3dd..ae5c2ea 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -2622,7 +2622,7 @@
             }
         }
 
-        mSession.stopRepeating();
+        stopPreview();
     }
 
     /**
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
index 8d141c4..a95f4f3 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
@@ -222,9 +222,11 @@
      * Does _not_ wait for the device to go idle
      */
     protected void stopPreview() throws Exception {
-        if (VERBOSE) Log.v(TAG, "Stopping preview");
         // Stop repeat, wait for captures to complete, and disconnect from surfaces
-        mSession.close();
+        if (mSession != null) {
+            if (VERBOSE) Log.v(TAG, "Stopping preview");
+            mSession.close();
+        }
     }
 
     /**
@@ -232,11 +234,13 @@
      * resulting in an idle device.
      */
     protected void stopPreviewAndDrain() throws Exception {
-        if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
         // Stop repeat, wait for captures to complete, and disconnect from surfaces
-        mSession.close();
-        mSessionListener.getStateWaiter().waitForState(BlockingSessionCallback.SESSION_CLOSED,
-                /*timeoutMs*/WAIT_FOR_RESULT_TIMEOUT_MS);
+        if (mSession != null) {
+            if (VERBOSE) Log.v(TAG, "Stopping preview and waiting for idle");
+            mSession.close();
+            mSessionListener.getStateWaiter().waitForState(BlockingSessionCallback.SESSION_CLOSED,
+                    /*timeoutMs*/WAIT_FOR_RESULT_TIMEOUT_MS);
+        }
     }
 
     /**
diff --git a/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java b/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java
index f30c7a3..a2df743 100644
--- a/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java
+++ b/tests/filesystem/src/android/filesystem/cts/RandomRWTest.java
@@ -18,6 +18,7 @@
 
 import android.cts.util.CtsAndroidTestCase;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.compatibility.common.util.DeviceReportLog;
 
 public class RandomRWTest extends CtsAndroidTestCase {
@@ -32,6 +33,7 @@
         super.tearDown();
     }
 
+    @CddTest(requirement="8.2")
     public void testRandomRead() throws Exception {
         final int READ_BUFFER_SIZE = 4 * 1024;
         final long fileSize = FileUtil.getFileSizeExceedingMemory(getContext(), READ_BUFFER_SIZE);
@@ -46,6 +48,7 @@
     }
 
     // It is taking too long in some device, and thus cannot run multiple times
+    @CddTest(requirement="8.2")
     public void testRandomUpdate() throws Exception {
         final int WRITE_BUFFER_SIZE = 4 * 1024;
         final long fileSize = 256 * 1024 * 1024;
diff --git a/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java b/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
index c19d03c..2db2f24 100644
--- a/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
+++ b/tests/filesystem/src/android/filesystem/cts/SequentialRWTest.java
@@ -25,6 +25,8 @@
 import com.android.compatibility.common.util.ResultUnit;
 import com.android.compatibility.common.util.Stat;
 
+import com.android.compatibility.common.util.CddTest;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -44,6 +46,7 @@
         super.tearDown();
     }
 
+    @CddTest(requirement="8.2")
     public void testSingleSequentialWrite() throws Exception {
         final long fileSize = FileUtil.getFileSizeExceedingMemory(getContext(), BUFFER_SIZE);
         if (fileSize == 0) { // not enough space, give up
@@ -85,6 +88,7 @@
                 NUMBER_REPETITION, REPORT_LOG_NAME, streamName);
     }
 
+    @CddTest(requirement="8.2")
     public void testSingleSequentialRead() throws Exception {
         final long fileSize = FileUtil.getFileSizeExceedingMemory(getContext(), BUFFER_SIZE);
         if (fileSize == 0) { // not enough space, give up
diff --git a/tests/tests/graphics/Android.mk b/tests/tests/graphics/Android.mk
index 7e26e07..d4d0d33 100644
--- a/tests/tests/graphics/Android.mk
+++ b/tests/tests/graphics/Android.mk
@@ -32,8 +32,6 @@
 # Tag this module as a cts test artifact
 LOCAL_COMPATIBILITY_SUITE := cts
 
-LOCAL_SDK_VERSION := current
-
 include $(BUILD_CTS_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/graphics/res/drawable/custom_animation_scale_list_drawable.xml b/tests/tests/graphics/res/drawable/custom_animation_scale_list_drawable.xml
new file mode 100644
index 0000000..1e9fbff
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/custom_animation_scale_list_drawable.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<com.android.internal.graphics.drawable.AnimationScaleListDrawable
+        xmlns:android="http://schemas.android.com/apk/res/android">
+    <item android:drawable="@drawable/vector_icon_create" />
+    <item android:drawable="@drawable/animation_vector_drawable_grouping_1" />
+</com.android.internal.graphics.drawable.AnimationScaleListDrawable>
diff --git a/tests/tests/graphics/res/drawable/layerdrawable_theme.xml b/tests/tests/graphics/res/drawable/layerdrawable_theme.xml
index 2a678ff..fac42b2 100644
--- a/tests/tests/graphics/res/drawable/layerdrawable_theme.xml
+++ b/tests/tests/graphics/res/drawable/layerdrawable_theme.xml
@@ -25,5 +25,6 @@
             android:dither="?attr/themeBoolean"
             android:src="?attr/themeNinePatch" />
     </item>
+    <item android:drawable="?attr/themeDrawable" />
 
-</layer-list>
\ No newline at end of file
+</layer-list>
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/CustomAnimationScaleListDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/CustomAnimationScaleListDrawableTest.java
new file mode 100644
index 0000000..3445641
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/CustomAnimationScaleListDrawableTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable.cts;
+
+import android.animation.ValueAnimator;
+import android.graphics.cts.R;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableContainer;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
+
+/**
+ * This test is used to verify that the CustomAnimationScaleListDrawable's current drawable depends
+ * on animation duration scale. When the scale is 0, it is a static drawable, otherwise, it is an
+ * animatable drawable.
+ */
+public class CustomAnimationScaleListDrawableTest extends AndroidTestCase {
+    @MediumTest
+    public void testNonZeroDurationScale() {
+        float originalScale = ValueAnimator.getDurationScale();
+        ValueAnimator.setDurationScale(2.0f);
+        Drawable dr = getContext().getDrawable(R.drawable.custom_animation_scale_list_drawable);
+        assertTrue(dr instanceof DrawableContainer);
+
+        assertTrue(dr.getCurrent() instanceof Animatable);
+        ValueAnimator.setDurationScale(originalScale);
+    }
+
+    @MediumTest
+    public void testZeroDurationScale() {
+        float originalScale = ValueAnimator.getDurationScale();
+        ValueAnimator.setDurationScale(0f);
+        Drawable dr = getContext().getDrawable(R.drawable.custom_animation_scale_list_drawable);
+        assertTrue(dr instanceof DrawableContainer);
+        assertFalse(dr.getCurrent() instanceof Animatable);
+        ValueAnimator.setDurationScale(originalScale);
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java
index 52bef55..7c6fe7c 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/ThemedDrawableTest.java
@@ -22,6 +22,7 @@
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.Shader.TileMode;
+import android.graphics.drawable.Drawable;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.GradientDrawable;
@@ -34,7 +35,6 @@
 
 import android.graphics.cts.R;
 
-@TargetApi(21)
 public class ThemedDrawableTest extends AndroidTestCase {
 
     @Override
@@ -150,9 +150,14 @@
         assertEquals(true, d.isAutoMirrored());
 
         BitmapDrawable bitmapDrawable  = (BitmapDrawable) d.getDrawable(0);
+        assertEquals(d, bitmapDrawable.getCallback());
         internalTestBitmapDrawable(bitmapDrawable);
 
         NinePatchDrawable ninePatchDrawable = (NinePatchDrawable) d.getDrawable(1);
+        assertEquals(d, ninePatchDrawable.getCallback());
         internalTestNinePatchDrawable(ninePatchDrawable);
+
+        Drawable themeDrawable = (Drawable) d.getDrawable(2);
+        assertEquals(d, themeDrawable.getCallback());
     }
 }
diff --git a/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java b/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java
index 33e82043..1468382 100644
--- a/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java
+++ b/tests/tests/graphics/src/android/opengl/cts/EglConfigTest.java
@@ -63,6 +63,8 @@
             EglConfigCtsActivity activity = launchActivity("android.graphics.cts",
                     EglConfigCtsActivity.class, extras);
             activity.waitToFinishDrawing();
+            // TODO(b/30948621): Remove the sleep below once b/30948621 is fixed.
+            Thread.sleep(500);
             activity.finish();
             mInstrumentation.waitForIdleSync();
         }
diff --git a/tests/tests/location/src/android/location/cts/GnssMeasurementWhenNoLocationTest.java b/tests/tests/location/src/android/location/cts/GnssMeasurementWhenNoLocationTest.java
index 0f4775e..32c54c9 100644
--- a/tests/tests/location/src/android/location/cts/GnssMeasurementWhenNoLocationTest.java
+++ b/tests/tests/location/src/android/location/cts/GnssMeasurementWhenNoLocationTest.java
@@ -135,7 +135,13 @@
         List<GnssMeasurementsEvent> events = mMeasurementListener.getEvents();
         Log.i(TAG, "Number of GPS measurement events received = " + events.size());
 
-        // Ensure that after getting a few (at least 2) measurements, that we still don't have
+        if (events.isEmpty()) {
+            SoftAssert.failOrWarning(isMeasurementTestStrict(), "No measurement events received",
+                    false);
+            return;  // All of the following checks rely on there being measurements
+        }
+
+        // Ensure that after getting a few (at least 2) measurement events, that we still don't have
         // location (i.e. that we got measurements before location.)  Fail, if strict, warn, if not.
         SoftAssert.failOrWarning(isMeasurementTestStrict(),
                 "Location was received before " + events.size() +
@@ -149,9 +155,8 @@
             return; // allow a (passing) return, if not strict, otherwise continue
         }
 
-        // If device is not indoors, verify
-        // 1) that we receive GPS measurements before being able to calculate the position solution
-        // 2) that mandatory fields of GnssMeasurement are in expected ranges.
+        // If device has received measurements also verify
+        // that mandatory fields of GnssMeasurement are in expected ranges.
         GnssMeasurementsEvent firstEvent = events.get(0);
         Collection<GnssMeasurement> gpsMeasurements = firstEvent.getMeasurements();
         int satelliteCount = gpsMeasurements.size();
diff --git a/tests/tests/location/src/android/location/cts/SoftAssert.java b/tests/tests/location/src/android/location/cts/SoftAssert.java
index 3913149..4349f07 100644
--- a/tests/tests/location/src/android/location/cts/SoftAssert.java
+++ b/tests/tests/location/src/android/location/cts/SoftAssert.java
@@ -156,7 +156,9 @@
         if (testIsStrict) {
             Assert.assertTrue(message, condition);
         } else {
-            failAsWarning("", message);
+            if (!condition) {
+                failAsWarning("", message);
+            }
         }
     }
 
diff --git a/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java b/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
index 0b2d875..217d8eb 100644
--- a/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
+++ b/tests/tests/location/src/android/location/cts/TestMeasurementUtil.java
@@ -28,8 +28,11 @@
 
 import junit.framework.Assert;
 
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
+import java.util.Set;
 
 /**
  * Helper class for GnssMeasurement Tests.
@@ -52,6 +55,21 @@
             " listener has failed, this indicates a platform bug. Please report the issue with" +
             " a full bugreport.";
 
+    // The valid Gnss navigation message type as listed in
+    // android/hardware/libhardware/include/hardware/gps.h
+    public static final Set<Integer> GNSS_NAVIGATION_MESSAGE_TYPE =
+        new HashSet<Integer>(Arrays.asList(
+            0x0101,
+            0x0102,
+            0x0103,
+            0x0104,
+            0x0301,
+            0x0501,
+            0x0502,
+            0x0601,
+            0x0602
+        ));
+
     /**
      * Check if test can be run on the current device.
      *
@@ -63,15 +81,12 @@
                                                     String testTag,
                                                     int minHardwareYear,
                                                     boolean isCtsVerifier) {
-       // TODO(sumitk): Enable this check once api 24 for N is avaiable.
-       /*
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
             Log.i(TAG, "This test is designed to work on N or newer. " +
                     "Test is being skipped because the platform version is being run in " +
                     Build.VERSION.SDK_INT);
             return false;
         }
-        */
 
         // If device does not have a GPS, skip the test.
         PackageManager pm = testLocationManager.getContext().getPackageManager();
@@ -670,9 +685,9 @@
         SoftAssert softAssert = new SoftAssert(TAG);
         for (GnssNavigationMessage message : events) {
             int type = message.getType();
-            softAssert.assertTrue("Gnss Navigation Message Type:expected [0x0101 - 0x0104]," +
-                            " actual = " + type,
-                    type >= 0x0101 && type <= 0x0104);
+            softAssert.assertTrue("Gnss Navigation Message Type:expected [" +
+                getGnssNavMessageTypes() + "] actual = " + type,
+                    GNSS_NAVIGATION_MESSAGE_TYPE.contains(type));
 
             // if message type == TYPE_L1CA, verify PRN & Data Size.
             int messageType = message.getType();
@@ -691,4 +706,14 @@
         }
         softAssert.assertAll();
     }
+
+    private static String getGnssNavMessageTypes() {
+        StringBuilder typesStr = new StringBuilder();
+        for (int type : GNSS_NAVIGATION_MESSAGE_TYPE) {
+            typesStr.append(String.format("0x%04X", type));
+            typesStr.append(", ");
+        }
+
+        return typesStr.length() > 2 ? typesStr.substring(0, typesStr.length() - 2) : "";
+    }
 }
diff --git a/tests/tests/os/src/android/os/cts/AsyncTaskTest.java b/tests/tests/os/src/android/os/cts/AsyncTaskTest.java
index efd1eed..5bf1f59 100644
--- a/tests/tests/os/src/android/os/cts/AsyncTaskTest.java
+++ b/tests/tests/os/src/android/os/cts/AsyncTaskTest.java
@@ -21,6 +21,7 @@
 import android.os.AsyncTask;
 import android.test.InstrumentationTestCase;
 
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 public class AsyncTaskTest extends InstrumentationTestCase {
@@ -30,7 +31,8 @@
     private static final long DURATION = 2000;
     private static final String[] PARAM = { "Test" };
 
-    private static MyAsyncTask mAsyncTask;
+    private static AsyncTask mAsyncTask;
+    private static MyAsyncTask mMyAsyncTask;
 
     public void testAsyncTask() throws Throwable {
         doTestAsyncTask(0);
@@ -43,51 +45,51 @@
     private void doTestAsyncTask(final long timeout) throws Throwable {
         startAsyncTask();
         if (timeout > 0) {
-            assertEquals(RESULT, mAsyncTask.get(DURATION, TimeUnit.MILLISECONDS).longValue());
+            assertEquals(RESULT, mMyAsyncTask.get(DURATION, TimeUnit.MILLISECONDS).longValue());
         } else {
-            assertEquals(RESULT, mAsyncTask.get().longValue());
+            assertEquals(RESULT, mMyAsyncTask.get().longValue());
         }
 
         // wait for the task to finish completely (including onPostResult()).
         new PollingCheck(DURATION) {
             protected boolean check() {
-                return mAsyncTask.getStatus() == AsyncTask.Status.FINISHED;
+                return mMyAsyncTask.getStatus() == AsyncTask.Status.FINISHED;
             }
         }.run();
 
-        assertTrue(mAsyncTask.isOnPreExecuteCalled);
-        assert(mAsyncTask.hasRun);
-        assertEquals(PARAM.length, mAsyncTask.parameters.length);
+        assertTrue(mMyAsyncTask.isOnPreExecuteCalled);
+        assert(mMyAsyncTask.hasRun);
+        assertEquals(PARAM.length, mMyAsyncTask.parameters.length);
         for (int i = 0; i < PARAM.length; i++) {
-            assertEquals(PARAM[i], mAsyncTask.parameters[i]);
+            assertEquals(PARAM[i], mMyAsyncTask.parameters[i]);
         }
         // even though the background task has run, the onPostExecute() may not have been
         // executed yet and the progress update may not have been processed. Wait until the task
         // has completed, which guarantees that onPostExecute has been called.
 
-        assertEquals(RESULT, mAsyncTask.postResult.longValue());
-        assertEquals(AsyncTask.Status.FINISHED, mAsyncTask.getStatus());
+        assertEquals(RESULT, mMyAsyncTask.postResult.longValue());
+        assertEquals(AsyncTask.Status.FINISHED, mMyAsyncTask.getStatus());
 
-        if (mAsyncTask.exception != null) {
-            throw mAsyncTask.exception;
+        if (mMyAsyncTask.exception != null) {
+            throw mMyAsyncTask.exception;
         }
 
         // wait for progress update to be processed (happens asynchronously)
         new PollingCheck(DURATION) {
             protected boolean check() {
-                return mAsyncTask.updateValue != null;
+                return mMyAsyncTask.updateValue != null;
             }
         }.run();
-        assertEquals(UPDATE_VALUE.length, mAsyncTask.updateValue.length);
+        assertEquals(UPDATE_VALUE.length, mMyAsyncTask.updateValue.length);
         for (int i = 0; i < UPDATE_VALUE.length; i++) {
-            assertEquals(UPDATE_VALUE[i], mAsyncTask.updateValue[i]);
+            assertEquals(UPDATE_VALUE[i], mMyAsyncTask.updateValue[i]);
         }
 
         runTestOnUiThread(new Runnable() {
             public void run() {
                 try {
                     // task should not be allowed to execute twice
-                    mAsyncTask.execute(PARAM);
+                    mMyAsyncTask.execute(PARAM);
                     fail("Failed to throw exception!");
                 } catch (IllegalStateException e) {
                     // expected
@@ -99,44 +101,81 @@
     public void testCancelWithInterrupt() throws Throwable {
         startAsyncTask();
         Thread.sleep(COMPUTE_TIME / 2);
-        assertTrue(mAsyncTask.cancel(true));
+        assertTrue(mMyAsyncTask.cancel(true));
         // already cancelled
-        assertFalse(mAsyncTask.cancel(true));
+        assertFalse(mMyAsyncTask.cancel(true));
         Thread.sleep(DURATION);
-        assertTrue(mAsyncTask.isCancelled());
-        assertTrue(mAsyncTask.isOnCancelledCalled);
-        assertNotNull(mAsyncTask.exception);
-        assertTrue(mAsyncTask.exception instanceof InterruptedException);
+        assertTrue(mMyAsyncTask.isCancelled());
+        assertTrue(mMyAsyncTask.isOnCancelledCalled);
+        assertNotNull(mMyAsyncTask.exception);
+        assertTrue(mMyAsyncTask.exception instanceof InterruptedException);
     }
 
     public void testCancel() throws Throwable {
         startAsyncTask();
         Thread.sleep(COMPUTE_TIME / 2);
-        assertTrue(mAsyncTask.cancel(false));
+        assertTrue(mMyAsyncTask.cancel(false));
         // already cancelled
-        assertFalse(mAsyncTask.cancel(false));
+        assertFalse(mMyAsyncTask.cancel(false));
         Thread.sleep(DURATION);
-        assertTrue(mAsyncTask.isCancelled());
-        assertTrue(mAsyncTask.isOnCancelledCalled);
-        assertNull(mAsyncTask.exception);
+        assertTrue(mMyAsyncTask.isCancelled());
+        assertTrue(mMyAsyncTask.isOnCancelledCalled);
+        assertNull(mMyAsyncTask.exception);
     }
 
     public void testCancelTooLate() throws Throwable {
         startAsyncTask();
         Thread.sleep(DURATION);
-        assertFalse(mAsyncTask.cancel(false));
-        assertTrue(mAsyncTask.isCancelled());
-        assertFalse(mAsyncTask.isOnCancelledCalled);
-        assertNull(mAsyncTask.exception);
+        assertFalse(mMyAsyncTask.cancel(false));
+        assertTrue(mMyAsyncTask.isCancelled());
+        assertFalse(mMyAsyncTask.isOnCancelledCalled);
+        assertNull(mMyAsyncTask.exception);
+    }
+
+    public void testCancellationWithException() throws Throwable {
+        final CountDownLatch readyToCancel = new CountDownLatch(1);
+        final CountDownLatch readyToThrow = new CountDownLatch(1);
+        final CountDownLatch calledOnCancelled = new CountDownLatch(1);
+        runTestOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mAsyncTask = new AsyncTask() {
+                    @Override
+                    protected Object doInBackground(Object... params) {
+                        readyToCancel.countDown();
+                        try {
+                            readyToThrow.await();
+                        } catch (InterruptedException e) {}
+                        // This exception is expected to be caught and ignored
+                        throw new RuntimeException();
+                    }
+
+                    @Override
+                    protected void onCancelled(Object o) {
+                        calledOnCancelled.countDown();
+                    }
+                };
+            }
+        });
+
+        mAsyncTask.execute();
+        if (!readyToCancel.await(5, TimeUnit.SECONDS)) {
+            fail("Test failure: doInBackground did not run in time.");
+        }
+        mAsyncTask.cancel(false);
+        readyToThrow.countDown();
+        if (!calledOnCancelled.await(5, TimeUnit.SECONDS)) {
+            fail("onCancelled not called!");
+        }
     }
 
     private void startAsyncTask() throws Throwable {
         runTestOnUiThread(new Runnable() {
             public void run() {
-                mAsyncTask = new MyAsyncTask();
-                assertEquals(AsyncTask.Status.PENDING, mAsyncTask.getStatus());
-                assertEquals(mAsyncTask, mAsyncTask.execute(PARAM));
-                assertEquals(AsyncTask.Status.RUNNING, mAsyncTask.getStatus());
+                mMyAsyncTask = new MyAsyncTask();
+                assertEquals(AsyncTask.Status.PENDING, mMyAsyncTask.getStatus());
+                assertEquals(mMyAsyncTask, mMyAsyncTask.execute(PARAM));
+                assertEquals(AsyncTask.Status.RUNNING, mMyAsyncTask.getStatus());
             }
         });
     }
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index ff84655..d167249 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -445,6 +445,10 @@
      * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
      */
     public void testConnectionRemoveExtras() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
         testConnectionPutExtras();
 
         mConnection.removeExtras(Arrays.asList(TEST_EXTRA_KEY));
@@ -458,6 +462,10 @@
      * {@link android.telecom.Call.Callback#onDetailsChanged(Call, Call.Details)}.
      */
     public void testConnectionRemoveExtras2() {
+        if (!mShouldTestTelecom) {
+            return;
+        }
+
         testConnectionPutExtras();
 
         mConnection.removeExtras(TEST_EXTRA_KEY);
diff --git a/tests/tests/toast/Android.mk b/tests/tests/toast/Android.mk
new file mode 100644
index 0000000..d9f15eb
--- /dev/null
+++ b/tests/tests/toast/Android.mk
@@ -0,0 +1,33 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsToastTestCases
+
+include $(BUILD_CTS_PACKAGE)
+
diff --git a/tests/tests/toast/AndroidManifest.xml b/tests/tests/toast/AndroidManifest.xml
new file mode 100644
index 0000000..1fa71c4
--- /dev/null
+++ b/tests/tests/toast/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="android.widget.toast.cts">
+
+    <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="26" />
+
+    <application>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.widget.toast.cts"
+        android:label="CTS tests for toast windows">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/toast/AndroidTest.xml b/tests/tests/toast/AndroidTest.xml
new file mode 100644
index 0000000..d0a5eed
--- /dev/null
+++ b/tests/tests/toast/AndroidTest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for Toast test cases">
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsToastTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.widget.toast.cts" />
+    </test>
+</configuration>
diff --git a/tests/tests/toast/src/android/widget/toast/cts/BaseToastTest.java b/tests/tests/toast/src/android/widget/toast/cts/BaseToastTest.java
new file mode 100644
index 0000000..fd75309
--- /dev/null
+++ b/tests/tests/toast/src/android/widget/toast/cts/BaseToastTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.toast.cts;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.content.Context;
+import android.graphics.PixelFormat;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.view.WindowManager;
+import android.widget.TextView;
+import android.widget.Toast;
+import org.junit.Before;
+
+/**
+ * Base class for toast tests.
+ */
+public abstract class BaseToastTest {
+    protected static final long TOAST_TIMEOUT_MILLIS = 5000; // 5 sec
+    protected static final long EVENT_TIMEOUT_MILLIS = 5000; // 5 sec
+
+    protected Context mContext;
+    protected Instrumentation mInstrumentation;
+    protected UiAutomation mUiAutomation;
+
+    @Before
+    public void setUp() {
+        mContext = InstrumentationRegistry.getContext();
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mUiAutomation = mInstrumentation.getUiAutomation();
+        waitForToastTimeout();
+    }
+
+    protected void waitForToastTimeout() {
+        SystemClock.sleep(TOAST_TIMEOUT_MILLIS);
+    }
+
+    protected void showToastsViaToastApis(int count) throws Exception {
+        Exception[] exceptions = new Exception[1];
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    try {
+                        for (int i = 0; i < count; i++) {
+                            Toast.makeText(mContext, getClass().getName(),
+                                    Toast.LENGTH_LONG).show();
+                        }
+                    } catch (Exception e) {
+                        exceptions[0] = e;
+                    }
+                });
+        if (exceptions[0] != null) {
+            throw exceptions[0];
+        }
+    }
+
+    protected void showToastsViaAddingWindow(int count, boolean focusable) throws Exception {
+        Exception[] exceptions = new Exception[1];
+        mInstrumentation.runOnMainSync(() -> {
+            try {
+                for (int i = 0; i < count; i++) {
+                    WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+                    params.height = WindowManager.LayoutParams.WRAP_CONTENT;
+                    params.width = WindowManager.LayoutParams.WRAP_CONTENT;
+                    params.format = PixelFormat.TRANSLUCENT;
+                    params.type = WindowManager.LayoutParams.TYPE_TOAST;
+                    params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
+                    if (!focusable) {
+                        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
+                    }
+
+                    TextView textView = new TextView(mContext);
+                    textView.setText(BaseToastTest.class.getName());
+
+                    WindowManager windowManager = mContext
+                            .getSystemService(WindowManager.class);
+                    windowManager.addView(textView, params);
+                }
+            } catch (Exception e) {
+                exceptions[0] = e;
+            }
+        });
+        if (exceptions[0] != null) {
+            throw exceptions[0];
+        }
+    }
+}
diff --git a/tests/tests/toast/src/android/widget/toast/cts/LegacyToastTest.java b/tests/tests/toast/src/android/widget/toast/cts/LegacyToastTest.java
new file mode 100644
index 0000000..4ac88b3
--- /dev/null
+++ b/tests/tests/toast/src/android/widget/toast/cts/LegacyToastTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.toast.cts;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.view.WindowManager;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.fail;
+
+/**
+ * Test whether toasts are properly shown. For apps targeting API 25+
+ * like this app the only way to add toast windows is via the dedicated
+ * toast APIs.
+ */
+@RunWith(AndroidJUnit4.class)
+public class LegacyToastTest extends BaseToastTest {
+    @Test
+    public void testAddSingleToastViaTestApisWhenUidFocused() throws Exception {
+        // Normal toast windows cannot be obtained vie the accessibility APIs because
+        // they are not touchable. In this case not crashing is good enough.
+        showToastsViaToastApis(1);
+    }
+
+    @Test
+    public void testAddTwoToastViaTestApisWhenUidFocused() throws Exception {
+        // Normal toast windows cannot be obtained vie the accessibility APIs because
+        // they are not touchable. In this case not crashing is good enough.
+        showToastsViaToastApis(2);
+
+        // Wait for the first one to expire
+        waitForToastTimeout();
+    }
+
+    @Test
+    public void testAddSingleToastViaAddingWindowApisWhenUidFocused() throws Exception {
+        try {
+            showToastsViaAddingWindow(1, false);
+            fail("Shouldn't be able to add toast windows directly");
+        } catch (WindowManager.BadTokenException e) {
+            /* expected */
+        }
+    }
+}
diff --git a/tests/tests/toastlegacy/Android.mk b/tests/tests/toastlegacy/Android.mk
new file mode 100644
index 0000000..ac5825d
--- /dev/null
+++ b/tests/tests/toastlegacy/Android.mk
@@ -0,0 +1,34 @@
+#
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_COMPATIBILITY_SUITE := cts
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner android-support-test
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    ../toast/src/android/widget/toast/cts/BaseToastTest.java
+
+LOCAL_PACKAGE_NAME := CtsToastLegacyTestCases
+
+include $(BUILD_CTS_PACKAGE)
+
diff --git a/tests/tests/toastlegacy/AndroidManifest.xml b/tests/tests/toastlegacy/AndroidManifest.xml
new file mode 100644
index 0000000..8529b5c
--- /dev/null
+++ b/tests/tests/toastlegacy/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="android.widget.toast.legacy.cts">
+
+    <uses-sdk android:minSdkVersion="25" android:targetSdkVersion="25" />
+
+    <application>
+        <activity android:name="android.widget.toast.cts.legacy.ToastActivity">
+        </activity>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.widget.toast.legacy.cts"
+        android:label="CTS tests for legacy toast windows">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
+
diff --git a/tests/tests/toastlegacy/AndroidTest.xml b/tests/tests/toastlegacy/AndroidTest.xml
new file mode 100644
index 0000000..a208077
--- /dev/null
+++ b/tests/tests/toastlegacy/AndroidTest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for Toast legacy test cases">
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.ApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsToastLegacyTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.widget.toast.legacy.cts" />
+    </test>
+</configuration>
diff --git a/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastActivity.java b/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastActivity.java
new file mode 100644
index 0000000..745385e
--- /dev/null
+++ b/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.toast.cts.legacy;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * Activity that shows toasts on demand.
+ */
+public class ToastActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+        View view = new View(this);
+        view.setBackgroundColor(Color.BLUE);
+        view.setLayoutParams(new ViewGroup.LayoutParams(
+                ViewGroup.LayoutParams.MATCH_PARENT,
+                ViewGroup.LayoutParams.MATCH_PARENT));
+        setContentView(view);
+    }
+}
diff --git a/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastTest.java b/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastTest.java
new file mode 100644
index 0000000..207e6ea
--- /dev/null
+++ b/tests/tests/toastlegacy/src/android/widget/toast/cts/legacy/ToastTest.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.toast.cts.legacy;
+
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.toast.cts.BaseToastTest;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test whether toasts are properly shown. For apps targeting SDK
+ * 25 and below a toast window can be added via the window APIs
+ * but it will be removed after a timeout if the UID that added
+ * the window is not focused. Also only a single toast window
+ * is allowed at a time.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ToastTest extends BaseToastTest {
+    @Rule
+    public final ActivityTestRule<ToastActivity> mActivityRule =
+            new ActivityTestRule<>(ToastActivity.class);
+
+    @Test
+    public void testAddSingleNotFocusableToastViaAddingWindowApisWhenUidFocused() throws Exception {
+        // Show a toast on top of the focused activity
+        showToastsViaAddingWindow(1, false);
+
+        // Wait for the toast to timeout
+        waitForToastTimeout();
+
+        // Finish the activity so the UID loses focus
+        finishActivity(false);
+
+        // Wait for the toast to timeout
+        waitForToastTimeout();
+
+        // Show another toast
+        showToastsViaAddingWindow(1, false);
+    }
+
+    @Test
+    public void testAddSingleFocusableToastViaAddingWindowApisWhenUidFocused() throws Exception {
+        // Show a toast on top of our activity
+        showToastsViaAddingWindow(1, true);
+
+        // Wait for the toast to timeout
+        waitForToastTimeout();
+
+        // Show a toast on top of our activity
+        showToastsViaAddingWindow(1, true);
+    }
+
+    @Test
+    public void testAddSingleToastViaAddingWindowApisWhenUidNotFocused() throws Exception {
+        // Finish the activity so the UID loses focus
+        finishActivity(false);
+
+        // Show a toast
+        showToastsViaAddingWindow(1, true);
+
+        // Wait for the toast to timeout
+        waitForToastTimeout();
+
+        // Show a toast on top of our activity
+        showToastsViaAddingWindow(1, true);
+    }
+
+    @Test
+    public void testAddTwoToastsViaToastApisWhenUidFocused() throws Exception {
+        // Finish the activity so the UID loses focus
+        finishActivity(false);
+
+        // Normal toast windows cannot be obtained vie the accessibility APIs because
+        // they are not touchable. In this case not crashing is good enough.
+        showToastsViaToastApis(2);
+
+        // Wait for the first one to expire
+        waitForToastTimeout();
+    }
+
+    @Test
+    public void testAddTwoToastsViaToastApisWhenUidNotFocused() throws Exception {
+        // Normal toast windows cannot be obtained vie the accessibility APIs because
+        // they are not touchable. In this case not crashing is good enough.
+        showToastsViaToastApis(2);
+
+        // Wait for the first one to expire
+        waitForToastTimeout();
+    }
+
+    @Test
+    public void testAddTwoToastsViaAddingWindowApisWhenUidFocusedQuickly() throws Exception {
+        try {
+            showToastsViaAddingWindow(2, false);
+            Assert.fail("Only one custom toast window at a time should be allowed");
+        } catch (WindowManager.BadTokenException e) {
+            /* expected */
+        } catch (Exception ex) {
+            Assert.fail("Unexpected exception when adding second toast window" + ex);
+        }
+    }
+
+    @Test
+    public void testAddTwoToastsViaAddingWindowApisWhenUidFocusedSlowly() throws Exception {
+        // Add one window
+        showToastsViaAddingWindow(1, true);
+
+        // Wait for the toast to timeout
+        waitForToastTimeout();
+
+        // Add another window
+        showToastsViaAddingWindow(1, true);
+    }
+
+    private void finishActivity(boolean waitForEvent) throws Exception {
+        if (waitForEvent) {
+            mUiAutomation.executeAndWaitForEvent(
+                    () -> mActivityRule.getActivity().finish(),
+                    (event) -> event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED
+                    , EVENT_TIMEOUT_MILLIS);
+        } else {
+            mActivityRule.getActivity().finish();
+        }
+    }
+}
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index ba1f3d2..7f7bd8c 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -19,6 +19,8 @@
     package="android.view.cts">
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
     <application android:label="Android TestCase"
                 android:icon="@drawable/size_48x48"
                 android:maxRecents="1"
diff --git a/tests/tests/view/src/android/view/cts/PixelCopyTests.java b/tests/tests/view/src/android/view/cts/PixelCopyTests.java
index 3fa3634..f8bf79a 100644
--- a/tests/tests/view/src/android/view/cts/PixelCopyTests.java
+++ b/tests/tests/view/src/android/view/cts/PixelCopyTests.java
@@ -23,9 +23,12 @@
 import android.graphics.SurfaceTexture;
 import android.opengl.GLSurfaceView;
 import android.opengl.GLSurfaceView.Renderer;
+import android.os.Debug;
+import android.os.Debug.MemoryInfo;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.LargeTest;
 import android.support.test.filters.MediumTest;
 import android.support.test.rule.ActivityTestRule;
 import android.util.Log;
@@ -171,6 +174,61 @@
     }
 
     @Test
+    @LargeTest
+    public void testNotLeaking() {
+        try {
+            CountDownLatch swapFence = new CountDownLatch(2);
+            GLSurfaceViewCtsActivity.setGlVersion(2);
+            GLSurfaceViewCtsActivity.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
+            GLSurfaceViewCtsActivity.setFixedSize(100, 100);
+            GLSurfaceViewCtsActivity.setRenderer(new QuadColorGLRenderer(
+                    Color.RED, Color.GREEN, Color.BLUE, Color.BLACK, swapFence));
+
+            GLSurfaceViewCtsActivity activity =
+                    mGLSurfaceViewActivityRule.launchActivity(null);
+
+            while (!swapFence.await(5, TimeUnit.MILLISECONDS)) {
+                activity.getView().requestRender();
+            }
+
+            // Test a fullsize copy
+            Bitmap bitmap = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+
+            MemoryInfo meminfoStart = new MemoryInfo();
+            MemoryInfo meminfoEnd = new MemoryInfo();
+
+            for (int i = 0; i < 1000; i++) {
+                if (i == 2) {
+                    // Not really the "start" but by having done a couple
+                    // we've fully initialized any state that may be required,
+                    // so memory usage should be stable now
+                    Debug.getMemoryInfo(meminfoStart);
+                }
+                if (i % 10 == 5) {
+                    Debug.getMemoryInfo(meminfoEnd);
+                    if (meminfoEnd.getTotalPss() - meminfoStart.getTotalPss() > 1000 /* kb */) {
+                        assertEquals("Memory leaked, iteration=" + i,
+                                meminfoStart.getTotalPss(), meminfoEnd.getTotalPss(),
+                                1000 /* kb */);
+                    }
+                }
+                SynchronousPixelCopy copyHelper = new SynchronousPixelCopy();
+                int result = copyHelper.request(activity.getView(), bitmap);
+                assertEquals("Copy request failed", PixelCopy.SUCCESS, result);
+                // Make sure nothing messed with the bitmap
+                assertEquals(100, bitmap.getWidth());
+                assertEquals(100, bitmap.getHeight());
+                assertEquals(Config.ARGB_8888, bitmap.getConfig());
+                assertBitmapQuadColor(bitmap,
+                        Color.RED, Color.GREEN, Color.BLUE, Color.BLACK);
+            }
+
+        } catch (InterruptedException e) {
+            fail("Interrupted, error=" + e.getMessage());
+        }
+    }
+
+    @Test
     public void testVideoProducer() throws InterruptedException {
         PixelCopyVideoSourceActivity activity =
                 mVideoSourceActivityRule.launchActivity(null);
diff --git a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
index da453dd..6444713 100644
--- a/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
+++ b/tests/tests/view/src/android/view/cts/SurfaceViewSyncTests.java
@@ -19,10 +19,12 @@
 import android.animation.PropertyValuesHolder;
 import android.animation.ValueAnimator;
 import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
 import android.media.MediaPlayer;
+import android.os.Environment;
 import android.support.test.InstrumentationRegistry;
 import android.support.test.rule.ActivityTestRule;
 import android.support.test.runner.AndroidJUnit4;
@@ -32,6 +34,7 @@
 import android.support.test.uiautomator.UiSelector;
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.Gravity;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
@@ -44,11 +47,19 @@
 import android.view.cts.surfacevalidator.ViewFactory;
 import android.widget.FrameLayout;
 
+import libcore.io.IoUtils;
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestName;
 import org.junit.runner.RunWith;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
 import static org.junit.Assert.*;
 
 @RunWith(AndroidJUnit4.class)
@@ -56,15 +67,21 @@
 @SuppressLint("RtlHardcoded")
 public class SurfaceViewSyncTests {
     private static final String TAG = "SurfaceViewSyncTests";
-    private static final int PERMISSION_DIALOG_WAIT_MS = 500;
+    private static final int PERMISSION_DIALOG_WAIT_MS = 1000;
 
+    /**
+     * Want to be especially sure we don't leave up the permission dialog, so try and dismiss both
+     * before and after test.
+     */
     @Before
+    @After
     public void setUp() throws UiObjectNotFoundException {
         // The permission dialog will be auto-opened by the activity - find it and accept
         UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
         UiSelector acceptButtonSelector = new UiSelector().resourceId("android:id/button1");
         UiObject acceptButton = uiDevice.findObject(acceptButtonSelector);
         if (acceptButton.waitForExists(PERMISSION_DIALOG_WAIT_MS)) {
+            Log.d(TAG, "found permission dialog, dismissing...");
             assertTrue(acceptButton.click());
         }
     }
@@ -80,6 +97,9 @@
     @Rule
     public ActivityTestRule mActivityRule = new ActivityTestRule<>(CapturedActivity.class);
 
+    @Rule
+    public TestName mName = new TestName();
+
     static ValueAnimator makeInfinite(ValueAnimator a) {
         a.setRepeatMode(ObjectAnimator.REVERSE);
         a.setRepeatCount(ObjectAnimator.INFINITE);
@@ -165,13 +185,76 @@
     };
 
     ///////////////////////////////////////////////////////////////////////////
+    // Bad frame capture
+    ///////////////////////////////////////////////////////////////////////////
+
+    private void saveFailureCaptures(SparseArray<Bitmap> failFrames) {
+        if (failFrames.size() == 0) return;
+
+        String directoryName = Environment.getExternalStorageDirectory()
+                + "/" + getClass().getSimpleName()
+                + "/" + mName.getMethodName();
+        File testDirectory = new File(directoryName);
+        if (testDirectory.exists()) {
+            String[] children = testDirectory.list();
+            if (children == null) {
+                return;
+            }
+            for (String file : children) {
+                new File(testDirectory, file).delete();
+            }
+        } else {
+            testDirectory.mkdirs();
+        }
+
+        for (int i = 0; i < failFrames.size(); i++) {
+            int frameNr = failFrames.keyAt(i);
+            Bitmap bitmap = failFrames.valueAt(i);
+
+            String bitmapName =  "frame_" + frameNr + ".png";
+            Log.d(TAG, "Saving file : " + bitmapName + " in directory : " + directoryName);
+
+            File file = new File(directoryName, bitmapName);
+            FileOutputStream fileStream = null;
+            try {
+                fileStream = new FileOutputStream(file);
+                bitmap.compress(Bitmap.CompressFormat.PNG, 85, fileStream);
+                fileStream.flush();
+            } catch (IOException e) {
+                e.printStackTrace();
+            } finally {
+                IoUtils.closeQuietly(fileStream);
+            }
+        }
+    }
+
+
+    ///////////////////////////////////////////////////////////////////////////
     // Tests
     ///////////////////////////////////////////////////////////////////////////
 
+    public void verifyTest(AnimationTestCase testCase) {
+        CapturedActivity.TestResult result = getActivity().runTest(testCase);
+        saveFailureCaptures(result.failures);
+
+        float failRatio = 1.0f * result.failFrames / (result.failFrames + result.passFrames);
+        assertTrue("Error: " + failRatio + " fail ratio - extremely high, is activity obstructed?",
+                failRatio < 0.95f);
+        assertTrue("Error: " + result.failFrames
+                + " incorrect frames observed - incorrect positioning",
+                result.failFrames == 0);
+        float framesPerSecond = 1.0f * result.passFrames
+                / TimeUnit.MILLISECONDS.toSeconds(CapturedActivity.CAPTURE_DURATION_MS);
+        assertTrue("Error, only " + result.passFrames
+                + " frames observed, virtual display only capturing at "
+                + framesPerSecond + " frames per second",
+                result.passFrames > 100);
+    }
+
     /** Draws a moving 10x10 black rectangle, validates 100 pixels of black are seen each frame */
     @Test
     public void testSmallRect() {
-        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+        verifyTest(new AnimationTestCase(
                 context -> new View(context) {
                     // draw a single pixel
                     final Paint sBlackPaint = new Paint();
@@ -191,8 +274,6 @@
                 new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
                 view -> makeInfinite(ObjectAnimator.ofInt(view, "offset", 10, 30)),
                 (blackishPixelCount, width, height) -> blackishPixelCount == 100));
-        assertTrue(result.passFrames > 100);
-        assertTrue(result.failFrames == 0);
     }
 
     /**
@@ -201,52 +282,44 @@
      */
     @Test
     public void testEmptySurfaceView() {
-        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+        verifyTest(new AnimationTestCase(
                 sEmptySurfaceViewFactory,
                 new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
                 sTranslateAnimationFactory,
                 (blackishPixelCount, width, height) ->
                         blackishPixelCount > 9000 && blackishPixelCount < 11000));
-        assertTrue(result.passFrames > 100);
-        assertTrue(result.failFrames == 0);
     }
 
     @Test
     public void testSurfaceViewSmallScale() {
-        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+        verifyTest(new AnimationTestCase(
                 sGreenSurfaceViewFactory,
                 new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
                 sSmallScaleAnimationFactory,
                 (blackishPixelCount, width, height) -> blackishPixelCount == 0));
-        assertTrue(result.passFrames > 100);
-        assertTrue(result.failFrames == 0);
     }
 
     @Test
     public void testSurfaceViewBigScale() {
-        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+        verifyTest(new AnimationTestCase(
                 sGreenSurfaceViewFactory,
                 new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
                 sBigScaleAnimationFactory,
                 (blackishPixelCount, width, height) -> blackishPixelCount == 0));
-        assertTrue(result.passFrames > 100);
-        assertTrue(result.failFrames == 0);
     }
 
     @Test
     public void testVideoSurfaceViewTranslate() {
-        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+        verifyTest(new AnimationTestCase(
                 sVideoViewFactory,
                 new FrameLayout.LayoutParams(640, 480, Gravity.LEFT | Gravity.TOP),
                 sTranslateAnimationFactory,
                 (blackishPixelCount, width, height) -> blackishPixelCount == 0));
-        assertTrue(result.passFrames > 100);
-        assertTrue(result.failFrames == 0);
     }
 
     @Test
     public void testVideoSurfaceViewRotated() {
-        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+        verifyTest(new AnimationTestCase(
                 sVideoViewFactory,
                 new FrameLayout.LayoutParams(100, 100, Gravity.LEFT | Gravity.TOP),
                 view -> makeInfinite(ObjectAnimator.ofPropertyValuesHolder(view,
@@ -254,13 +327,11 @@
                         PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 10f, 30f),
                         PropertyValuesHolder.ofFloat(View.ROTATION, 45f, 45f))),
                 (blackishPixelCount, width, height) -> blackishPixelCount == 0));
-        assertTrue(result.passFrames > 100);
-        assertTrue(result.failFrames == 0);
     }
 
     @Test
     public void testVideoSurfaceViewEdgeCoverage() {
-        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+        verifyTest(new AnimationTestCase(
                 sVideoViewFactory,
                 new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
                 view -> {
@@ -274,13 +345,11 @@
                             PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0, -y, 0, y, 0)));
                 },
                 (blackishPixelCount, width, height) -> blackishPixelCount == 0));
-        assertTrue(result.passFrames > 100);
-        assertTrue(result.failFrames == 0);
     }
 
     @Test
     public void testVideoSurfaceViewCornerCoverage() {
-        CapturedActivity.TestResult result = getActivity().runTest(new AnimationTestCase(
+        verifyTest(new AnimationTestCase(
                 sVideoViewFactory,
                 new FrameLayout.LayoutParams(640, 480, Gravity.CENTER),
                 view -> {
@@ -294,7 +363,5 @@
                             PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, -y, -y, y, y, -y)));
                 },
                 (blackishPixelCount, width, height) -> blackishPixelCount == 0));
-        assertTrue(result.passFrames > 100);
-        assertTrue(result.failFrames == 0);
     }
 }
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
index 6a23e02..07cb88c 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -19,7 +19,10 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
+import android.graphics.Bitmap;
 import android.graphics.Point;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.VirtualDisplay;
 import android.media.MediaPlayer;
@@ -30,6 +33,7 @@
 import android.os.Looper;
 import android.util.DisplayMetrics;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.Display;
 import android.view.View;
 import android.widget.FrameLayout;
@@ -40,10 +44,11 @@
     public static class TestResult {
         public int passFrames;
         public int failFrames;
+        public final SparseArray<Bitmap> failures = new SparseArray<>();
     }
 
     private static final String TAG = "CapturedActivity";
-    private static final long TIME_OUT_MS = 10000;
+    private static final long TIME_OUT_MS = 20000;
     private static final int PERMISSION_CODE = 1;
     private MediaProjectionManager mProjectionManager;
     private MediaProjection mMediaProjection;
@@ -52,9 +57,11 @@
     private SurfacePixelValidator mSurfacePixelValidator;
     private final Object mLock = new Object();
 
-    private static final long START_CAPTURE_DELAY_MS = 1000;
-    private static final long END_CAPTURE_DELAY_MS = START_CAPTURE_DELAY_MS + 4000;
-    private static final long END_DELAY_MS = END_CAPTURE_DELAY_MS + 500;
+    public static final long CAPTURE_DURATION_MS = 10000;
+
+    private static final long START_CAPTURE_DELAY_MS = 1500;
+    private static final long END_CAPTURE_DELAY_MS = START_CAPTURE_DELAY_MS + CAPTURE_DURATION_MS;
+    private static final long END_DELAY_MS = END_CAPTURE_DELAY_MS + 1000;
 
     private MediaPlayer mMediaPlayer;
 
@@ -64,6 +71,12 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        mOnWatch = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
+        if (mOnWatch) {
+            // Don't try and set up test/capture infrastructure - they're not supported
+            return;
+        }
+
         getWindow().getDecorView().setSystemUiVisibility(
                 View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
 
@@ -74,8 +87,6 @@
 
         mMediaPlayer = MediaPlayer.create(this, R.raw.colors_video);
         mMediaPlayer.setLooping(true);
-
-        mOnWatch = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
     }
 
     /**
@@ -97,6 +108,10 @@
 
     @Override
     public void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (mOnWatch) return;
+        getWindow().getDecorView().setSystemUiVisibility(
+                View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN);
+
         if (requestCode != PERMISSION_CODE) {
             throw new IllegalStateException("Unknown request code: " + requestCode);
         }
@@ -143,6 +158,7 @@
             display.getRealSize(size);
             display.getMetrics(metrics);
 
+
             mSurfacePixelValidator = new SurfacePixelValidator(CapturedActivity.this,
                     size, animationTestCase.getChecker());
             Log.d("MediaProjection", "Size is " + size.toString());
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
index 55bc251..f58b9cb 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/PixelCounter.rs
@@ -15,22 +15,18 @@
  */
 #pragma version(1)
 #pragma rs java_package_name(android.view.cts.surfacevalidator)
+#pragma rs reduce(countBlackishPixels) accumulator(countBlackishPixelsAccum) combiner(countBlackishPixelsCombiner)
 
-int WIDTH;
 uchar THRESHOLD;
 
-rs_allocation image;
-
-void countBlackishPixels(const int32_t *v_in, int *v_out){
-    int y = v_in[0];
-    v_out[0] = 0;
-
-    for(int i = 0 ; i < WIDTH; i++){
-        uchar4 pixel = rsGetElementAt_uchar4(image, i, y);
-        if (pixel.r < THRESHOLD
-                && pixel.g < THRESHOLD
-                && pixel.b < THRESHOLD) {
-            v_out[0]++;
-        }
+static void countBlackishPixelsAccum(int *accum, uchar4 pixel){
+    if (pixel.r < THRESHOLD
+            && pixel.g < THRESHOLD
+            && pixel.b < THRESHOLD) {
+        *accum += 1;
     }
 }
+
+static void countBlackishPixelsCombiner(int *accum, const int *other){
+    *accum += *other;
+}
diff --git a/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
index c9bff1d..5a30b77 100644
--- a/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
+++ b/tests/tests/view/src/android/view/cts/surfacevalidator/SurfacePixelValidator.java
@@ -16,6 +16,7 @@
 package android.view.cts.surfacevalidator;
 
 import android.content.Context;
+import android.graphics.Bitmap;
 import android.graphics.Point;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -25,11 +26,13 @@
 import android.renderscript.RenderScript;
 import android.renderscript.Type;
 import android.util.Log;
+import android.util.SparseArray;
 import android.view.Surface;
 import android.view.cts.surfacevalidator.PixelChecker;
 
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
+import java.util.ArrayList;
 
 public class SurfacePixelValidator {
     private static final String TAG = "SurfacePixelValidator";
@@ -43,6 +46,8 @@
     // If no channel is greater than this value, pixel will be considered 'blackish'.
     private static final short PIXEL_CHANNEL_THRESHOLD = 4;
 
+    private static final int MAX_CAPTURED_FAILURES = 5;
+
     private final int mWidth;
     private final int mHeight;
 
@@ -54,14 +59,13 @@
     private final RenderScript mRS;
 
     private final Allocation mInPixelsAllocation;
-    private final Allocation mInRowsAllocation;
-    private final Allocation mOutRowsAllocation;
     private final ScriptC_PixelCounter mScript;
 
 
     private final Object mResultLock = new Object();
     private int mResultSuccessFrames;
     private int mResultFailureFrames;
+    private SparseArray<Bitmap> mFirstFailures = new SparseArray<>(MAX_CAPTURED_FAILURES);
 
     private Runnable mConsumeRunnable = new Runnable() {
         int numSkipped = 0;
@@ -69,15 +73,10 @@
         public void run() {
             Trace.beginSection("consume buffer");
             mInPixelsAllocation.ioReceive();
-            mScript.set_image(mInPixelsAllocation);
             Trace.endSection();
 
-            Trace.beginSection("compare");
-            mScript.forEach_countBlackishPixels(mInRowsAllocation, mOutRowsAllocation);
-            Trace.endSection();
-
-            Trace.beginSection("sum");
-            int blackishPixelCount = sum1DIntAllocation(mOutRowsAllocation, mHeight);
+            Trace.beginSection("compare and sum");
+            int blackishPixelCount = mScript.reduce_countBlackishPixels(mInPixelsAllocation).get();
             Trace.endSection();
 
             boolean success = mPixelChecker.checkPixels(blackishPixelCount, mWidth, mHeight);
@@ -85,7 +84,6 @@
                 if (numSkipped < NUM_FIRST_FRAMES_SKIPPED) {
                     numSkipped++;
                 } else {
-
                     if (success) {
                         mResultSuccessFrames++;
                     } else {
@@ -93,6 +91,15 @@
                         int totalFramesSeen = mResultSuccessFrames + mResultFailureFrames;
                         Log.d(TAG, "Failure (pixel count = " + blackishPixelCount
                                 + ") occurred on frame " + totalFramesSeen);
+
+                        if (mFirstFailures.size() < MAX_CAPTURED_FAILURES) {
+                            Log.d(TAG, "Capturing bitmap #" + mFirstFailures.size());
+                            // error, worth looking at...
+                            Bitmap capture = Bitmap.createBitmap(mWidth, mHeight,
+                                    Bitmap.Config.ARGB_8888);
+                            mInPixelsAllocation.copyTo(capture);
+                            mFirstFailures.put(totalFramesSeen, capture);
+                        }
                     }
                 }
             }
@@ -113,9 +120,6 @@
         mScript = new ScriptC_PixelCounter(mRS);
 
         mInPixelsAllocation = createBufferQueueAllocation();
-        mInRowsAllocation = createInputRowIndexAllocation();
-        mOutRowsAllocation = createOutputRowAllocation();
-        mScript.set_WIDTH(mWidth);
         mScript.set_THRESHOLD(PIXEL_CHANNEL_THRESHOLD);
 
         mInPixelsAllocation.setOnBufferAvailableListener(
@@ -126,41 +130,10 @@
         return mInPixelsAllocation.getSurface();
     }
 
-    static private int sum1DIntAllocation(Allocation array, int length) {
-        //Get the values returned from the function
-        int[] returnValue = new int[length];
-        array.copyTo(returnValue);
-        int sum = 0;
-        //If any row had any different pixels, then it fails
-        for (int i = 0; i < length; i++) {
-            sum += returnValue[i];
-        }
-        return sum;
-    }
-
-    /**
-     * Creates an allocation where the values in it are the indices of each row
-     */
-    private Allocation createInputRowIndexAllocation() {
-        //Create an array with the index of each row
-        int[] inputIndices = new int[mHeight];
-        for (int i = 0; i < mHeight; i++) {
-            inputIndices[i] = i;
-        }
-        //Create the allocation from that given array
-        Allocation inputAllocation = Allocation.createSized(mRS, Element.I32(mRS),
-                inputIndices.length, Allocation.USAGE_SCRIPT);
-        inputAllocation.copyFrom(inputIndices);
-        return inputAllocation;
-    }
-
-    private Allocation createOutputRowAllocation() {
-        return Allocation.createSized(mRS, Element.I32(mRS), mHeight, Allocation.USAGE_SCRIPT);
-    }
-
     private Allocation createBufferQueueAllocation() {
         return Allocation.createAllocations(mRS, Type.createXY(mRS,
-                Element.U8_4(mRS), mWidth, mHeight),
+                Element.RGBA_8888(mRS)
+                /*Element.U32(mRS)*/, mWidth, mHeight),
                 Allocation.USAGE_SCRIPT | Allocation.USAGE_IO_INPUT,
                 1)[0];
     }
@@ -177,6 +150,10 @@
             // Caller should only call this
             testResult.failFrames = mResultFailureFrames;
             testResult.passFrames = mResultSuccessFrames;
+
+            for (int i = 0; i < mFirstFailures.size(); i++) {
+                testResult.failures.put(mFirstFailures.keyAt(i), mFirstFailures.valueAt(i));
+            }
         }
         mWorkerThread.quitSafely();
     }
diff --git a/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java b/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
index a77d648..e09e0d6 100644
--- a/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/ServiceWorkerClientTest.java
@@ -16,6 +16,7 @@
 
 package android.webkit.cts;
 
+import android.cts.util.NullWebViewUtils;
 import android.cts.util.PollingCheck;
 import android.test.ActivityInstrumentationTestCase2;
 
@@ -136,6 +137,10 @@
 
     // Test correct invocation of shouldInterceptRequest for Service Workers.
     public void testServiceWorkerClientInterceptCallback() throws Exception {
+        if (!NullWebViewUtils.isWebViewAvailable()) {
+            return;
+        }
+
         final InterceptServiceWorkerClient mInterceptServiceWorkerClient =
                 new InterceptServiceWorkerClient();
         ServiceWorkerController swController = ServiceWorkerController.getInstance();
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewStartupTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewStartupTest.java
index a4ebaca..3b67d84 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewStartupTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewStartupTest.java
@@ -20,7 +20,6 @@
 import android.content.Context;
 import android.cts.util.NullWebViewUtils;
 import android.cts.util.PollingCheck;
-import android.os.StrictMode;
 import android.test.ActivityInstrumentationTestCase2;
 import android.test.UiThreadTest;
 import android.util.Log;
@@ -89,21 +88,4 @@
         assertTrue(m.matches());
         assertEquals("42", m.group(1)); // value got incremented
     }
-
-    @UiThreadTest
-    public void testStrictModeNotViolatedOnStartup() throws Throwable {
-        StrictMode.ThreadPolicy oldPolicy = StrictMode.getThreadPolicy();
-        StrictMode.ThreadPolicy testPolicy = new StrictMode.ThreadPolicy.Builder()
-            .detectDiskReads()
-            .penaltyLog()
-            .penaltyDeath()
-            .build();
-        StrictMode.setThreadPolicy(testPolicy);
-        try {
-            mActivity.createAndAttachWebView();
-        } finally {
-            StrictMode.setThreadPolicy(oldPolicy);
-        }
-    }
-
 }
diff --git a/tools/cts-api-coverage/src/Android.mk b/tools/cts-api-coverage/src/Android.mk
index d1fe4ed..8c038dc 100644
--- a/tools/cts-api-coverage/src/Android.mk
+++ b/tools/cts-api-coverage/src/Android.mk
@@ -23,6 +23,10 @@
 LOCAL_JAVA_RESOURCE_DIRS := res 
 LOCAL_JAR_MANIFEST := MANIFEST.mf
 
+LOCAL_STATIC_JAVA_LIBRARIES := \
+  compatibility-host-util \
+  dexlib2
+
 LOCAL_MODULE := cts-api-coverage
 LOCAL_MODULE_TAGS := optional
 
diff --git a/tools/cts-api-coverage/src/MANIFEST.mf b/tools/cts-api-coverage/src/MANIFEST.mf
index b6aa831..63d6674 100644
--- a/tools/cts-api-coverage/src/MANIFEST.mf
+++ b/tools/cts-api-coverage/src/MANIFEST.mf
@@ -1,2 +1,3 @@
 Manifest-Version: 1.0
 Main-Class: com.android.cts.apicoverage.CtsApiCoverage
+Class-Path: tradefed-prebuilt.jar
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CddCoverage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CddCoverage.java
new file mode 100644
index 0000000..3d1a07d
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CddCoverage.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.apicoverage;
+
+import java.lang.String;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.List;
+import java.util.Set;
+
+/** Representation of the entire CDD. */
+class CddCoverage {
+
+    private final Map<String, CddRequirement> requirements = new HashMap<>();
+
+    public void addCddRequirement(CddRequirement cddRequirement) {
+        requirements.put(cddRequirement.getRequirementId(), cddRequirement);
+    }
+
+    public Collection<CddRequirement> getCddRequirements() {
+        return Collections.unmodifiableCollection(requirements.values());
+    }
+
+    public void addCoverage(String cddRequirementId, TestMethod testMethod) {
+        if (!requirements.containsKey(cddRequirementId)) {
+            requirements.put(cddRequirementId, new CddRequirement(cddRequirementId));
+        }
+
+        requirements.get(cddRequirementId).addTestMethod(testMethod);
+    }
+
+    static class CddRequirement {
+        private final String mRequirementId;
+        private final List<TestMethod> mtestMethods;
+
+        CddRequirement(String requirementId) {
+            this.mRequirementId = requirementId;
+            this.mtestMethods = new ArrayList<>();
+        }
+
+        @Override
+        public boolean equals(Object other) {
+            if (other == null) {
+                return false;
+            } else if (!(other instanceof CddRequirement)) {
+                return false;
+            } else {
+                return mRequirementId.equals(((CddRequirement)other).mRequirementId);
+            }
+        }
+
+        @Override
+        public int hashCode() {
+            return mRequirementId.hashCode();
+        }
+
+        @Override
+        public String toString() {
+            return String.format("Requirement %s %s", mRequirementId, mtestMethods);
+        }
+
+        public String getRequirementId() { return mRequirementId; }
+
+        public void addTestMethod(TestMethod testMethod) {
+            mtestMethods.add(testMethod);
+        }
+
+        public Collection<TestMethod> getTestMethods() {
+            return Collections.unmodifiableCollection(mtestMethods);
+        }
+    }
+
+    static class TestMethod {
+        private final String mTestModule;
+        private final String mTestClass;
+        private final String mTestMethod;
+
+        TestMethod(String testModule, String testClass, String testMethod) {
+            this.mTestModule = testModule;
+            this.mTestClass = testClass;
+            this.mTestMethod = testMethod;
+        }
+
+        public String getTestModule() { return mTestModule; }
+
+        public String getTestClass() { return mTestClass; }
+
+        public String getTestMethod() { return mTestMethod; }
+
+        @Override
+        public String toString() {
+            return String.format("%s %s#%s", mTestModule, mTestClass, mTestMethod);
+        }
+    }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
index 6b69a57..2b57c76 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
@@ -16,6 +16,19 @@
 
 package com.android.cts.apicoverage;
 
+import com.android.compatibility.common.util.CddTest;
+
+import org.jf.dexlib2.DexFileFactory;
+import org.jf.dexlib2.DexFileFactory.DexFileNotFound;
+import org.jf.dexlib2.DexFileFactory.MultipleDexFilesException;
+import org.jf.dexlib2.Opcodes;
+import org.jf.dexlib2.iface.Annotation;
+import org.jf.dexlib2.iface.AnnotationElement;
+import org.jf.dexlib2.iface.ClassDef;
+import org.jf.dexlib2.iface.DexFile;
+import org.jf.dexlib2.iface.Method;
+import org.jf.dexlib2.iface.value.StringEncodedValue;
+
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
 import org.xml.sax.XMLReader;
@@ -29,7 +42,9 @@
 import java.io.OutputStream;
 import java.util.Arrays;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.List;
+import java.util.Set;
 
 import javax.xml.transform.TransformerException;
 
@@ -52,6 +67,10 @@
 
     private static final int FORMAT_HTML = 2;
 
+    private static final String CDD_REQUIREMENT_ANNOTATION = "Lcom/android/compatibility/common/util/CddTest;";
+
+    private static final String CDD_REQUIREMENT_ELEMENT_NAME = "requirement";
+
     private static void printUsage() {
         System.out.println("Usage: cts-api-coverage [OPTION]... [APK]...");
         System.out.println();
@@ -70,6 +89,7 @@
         System.out.println("  -a PATH                path to the API XML file");
         System.out.println("  -p PACKAGENAMEPREFIX   report coverage only for package that start with");
         System.out.println("  -t TITLE               report title");
+        System.out.println("  -a API                 the Android API Level");
         System.out.println();
         System.exit(1);
     }
@@ -82,6 +102,7 @@
         String apiXmlPath = "";
         PackageFilter packageFilter = new PackageFilter();
         String reportTitle = "CTS API Coverage";
+        int apiLevel = Integer.MAX_VALUE;
 
         for (int i = 0; i < args.length; i++) {
             if (args[i].startsWith("-")) {
@@ -106,6 +127,8 @@
                     packageFilter.addPrefixToFilter(getExpectedArg(args, ++i));
                 } else if ("-t".equals(args[i])) {
                     reportTitle = getExpectedArg(args, ++i);
+                } else if ("-a".equals(args[i])) {
+                    apiLevel = Integer.parseInt(getExpectedArg(args, ++i));
                 } else {
                     printUsage();
                 }
@@ -131,12 +154,16 @@
          */
 
         ApiCoverage apiCoverage = getEmptyApiCoverage(apiXmlPath);
+        CddCoverage cddCoverage = getEmptyCddCoverage();
         // Add superclass information into api coverage.
         apiCoverage.resolveSuperClasses();
         for (File testApk : testApks) {
             addApiCoverage(apiCoverage, testApk, dexDeps);
+            addCddCoverage(cddCoverage, testApk, apiLevel);
         }
-        outputCoverageReport(apiCoverage, testApks, outputFile, format, packageFilter, reportTitle);
+
+        outputCoverageReport(apiCoverage, cddCoverage, testApks, outputFile,
+            format, packageFilter, reportTitle);
     }
 
     /** Get the argument or print out the usage and exit. */
@@ -202,8 +229,106 @@
         }
     }
 
-    private static void outputCoverageReport(ApiCoverage apiCoverage, List<File> testApks,
-            File outputFile, int format, PackageFilter packageFilter, String reportTitle)
+    private static void addCddCoverage(CddCoverage cddCoverage, File testSource, int api)
+            throws IOException {
+
+        if (testSource.getName().endsWith(".apk")) {
+            addCddApkCoverage(cddCoverage, testSource, api);
+        } else if (testSource.getName().endsWith(".jar")) {
+            addCddJarCoverage(cddCoverage, testSource);
+        } else {
+            System.err.println("Unsupported file type for CDD coverage: " + testSource.getPath());
+        }
+    }
+
+    private static void addCddJarCoverage(CddCoverage cddCoverage, File testSource)
+            throws IOException {
+
+        Collection<Class<?>> classes = JarTestFinder.getClasses(testSource);
+        for (Class<?> c : classes) {
+            for (java.lang.reflect.Method m : c.getMethods()) {
+                if (m.isAnnotationPresent(CddTest.class)) {
+                    CddTest cddTest = m.getAnnotation(CddTest.class);
+                    CddCoverage.TestMethod testMethod =
+                            new CddCoverage.TestMethod(
+                                    testSource.getName(), c.getName(), m.getName());
+                    cddCoverage.addCoverage(cddTest.requirement(), testMethod);
+                }
+            }
+        }
+    }
+
+    private static void addCddApkCoverage(
+        CddCoverage cddCoverage, File testSource, int api)
+            throws IOException {
+
+        DexFile dexFile = null;
+        try {
+            dexFile = DexFileFactory.loadDexFile(
+                testSource, null /*dexEntry*/, Opcodes.forApi(api));
+        } catch (IOException | DexFileFactory.DexFileNotFound e) {
+            System.err.println("Unable to load dex file: " + testSource.getPath());
+            return;
+        }
+
+        String moduleName = testSource.getName();
+        for (ClassDef classDef : dexFile.getClasses()) {
+            String className = classDef.getType();
+            handleAnnotations(
+                cddCoverage, moduleName, className, null /*methodName*/,
+                classDef.getAnnotations());
+
+            for (Method method : classDef.getMethods()) {
+                String methodName = method.getName();
+                handleAnnotations(
+                    cddCoverage, moduleName, className, methodName, method.getAnnotations());
+            }
+        }
+    }
+
+    private static void handleAnnotations(
+            CddCoverage cddCoverage, String moduleName, String className,
+                    String methodName, Set<? extends Annotation> annotations) {
+        for (Annotation annotation : annotations) {
+            if (annotation.getType().equals(CDD_REQUIREMENT_ANNOTATION)) {
+                for (AnnotationElement annotationElement : annotation.getElements()) {
+                    if (annotationElement.getName().equals(CDD_REQUIREMENT_ELEMENT_NAME)) {
+                        String cddRequirement =
+                                ((StringEncodedValue) annotationElement.getValue()).getValue();
+                        CddCoverage.TestMethod testMethod =
+                                new CddCoverage.TestMethod(
+                                        moduleName, dexToJavaName(className), methodName);
+                        cddCoverage.addCoverage(cddRequirement, testMethod);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Given a string like Landroid/app/cts/DownloadManagerTest;
+     * return android.app.cts.DownloadManagerTest.
+     */
+    private static String dexToJavaName(String dexName) {
+        if (!dexName.startsWith("L") || !dexName.endsWith(";")) {
+            return dexName;
+        }
+        dexName = dexName.replace('/', '.');
+        if (dexName.length() > 2) {
+            dexName = dexName.substring(1, dexName.length() - 1);
+        }
+        return dexName;
+    }
+
+    private static CddCoverage getEmptyCddCoverage() {
+        CddCoverage cddCoverage = new CddCoverage();
+        // TODO(nicksauer): Read in the valid list of requirements
+        return cddCoverage;
+    }
+
+    private static void outputCoverageReport(ApiCoverage apiCoverage, CddCoverage cddCoverage,
+            List<File> testApks, File outputFile, int format, PackageFilter packageFilter,
+            String reportTitle)
                 throws IOException, TransformerException, InterruptedException {
 
         OutputStream out = outputFile != null
@@ -213,15 +338,17 @@
         try {
             switch (format) {
                 case FORMAT_TXT:
-                    TextReport.printTextReport(apiCoverage, packageFilter, out);
+                    TextReport.printTextReport(apiCoverage, cddCoverage, packageFilter, out);
                     break;
 
                 case FORMAT_XML:
-                    XmlReport.printXmlReport(testApks, apiCoverage, packageFilter, reportTitle, out);
+                    XmlReport.printXmlReport(testApks, apiCoverage, cddCoverage,
+                        packageFilter, reportTitle, out);
                     break;
 
                 case FORMAT_HTML:
-                    HtmlReport.printHtmlReport(testApks, apiCoverage, packageFilter, reportTitle, out);
+                    HtmlReport.printHtmlReport(testApks, apiCoverage, cddCoverage,
+                        packageFilter, reportTitle, out);
                     break;
             }
         } finally {
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/HtmlReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/HtmlReport.java
index 0e6b54a..f53a884 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/HtmlReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/HtmlReport.java
@@ -37,7 +37,8 @@
 class HtmlReport {
 
     public static void printHtmlReport(final List<File> testApks, final ApiCoverage apiCoverage,
-            final PackageFilter packageFilter, final String reportTitle, final OutputStream out)
+            final CddCoverage cddCoverage, final PackageFilter packageFilter,
+            final String reportTitle, final OutputStream out)
                 throws IOException, TransformerException {
         final PipedOutputStream xmlOut = new PipedOutputStream();
         final PipedInputStream xmlIn = new PipedInputStream(xmlOut);
@@ -45,7 +46,8 @@
         Thread t = new Thread(new Runnable() {
             @Override
             public void run() {
-                XmlReport.printXmlReport(testApks, apiCoverage, packageFilter, reportTitle, xmlOut);
+                XmlReport.printXmlReport(
+                    testApks, apiCoverage, cddCoverage, packageFilter, reportTitle, xmlOut);
 
                 // Close the output stream to avoid "Write dead end" errors.
                 try {
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/JarTestFinder.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/JarTestFinder.java
new file mode 100644
index 0000000..2aa3e72
--- /dev/null
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/JarTestFinder.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.apicoverage;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.lang.reflect.Modifier;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+
+/** Representation of the entire CDD. */
+class JarTestFinder {
+
+    public static Collection<Class<?>> getClasses(File jarTestFile)
+            throws IllegalArgumentException  {
+        List<Class<?>> classes = new ArrayList<>();
+
+        try (JarFile jarFile = new JarFile(jarTestFile)) {
+            Enumeration<JarEntry> e = jarFile.entries();
+
+            URL[] urls = {
+                new URL(String.format("jar:file:%s!/", jarTestFile.getAbsolutePath()))
+            };
+            URLClassLoader cl = URLClassLoader.newInstance(urls, JarTestFinder.class.getClassLoader());
+
+            while (e.hasMoreElements()) {
+                JarEntry je = e.nextElement();
+                if (je.isDirectory() || !je.getName().endsWith(".class")
+                        || je.getName().contains("$")) {
+                    continue;
+                }
+                String className = getClassName(je.getName());
+                if (!className.endsWith("Test")) {
+                    continue;
+                }
+                try {
+                    Class<?> cls = cl.loadClass(className);
+                    int modifiers = cls.getModifiers();
+                    if (!Modifier.isStatic(modifiers)
+                            && !Modifier.isPrivate(modifiers)
+                            && !Modifier.isProtected(modifiers)
+                            && !Modifier.isInterface(modifiers)
+                            && !Modifier.isAbstract(modifiers)) {
+
+                        classes.add(cls);
+                    }
+                } catch (ClassNotFoundException | Error x) {
+                    System.err.println(
+                            String.format("Cannot find test class %s from %s",
+                                    className, jarTestFile.getName()));
+                    x.printStackTrace();
+                }
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        }
+        return classes;
+    }
+
+    private static String getClassName(String name) {
+        // -6 because of .class
+        return name.substring(0, name.length() - 6).replace('/', '.');
+    }
+}
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
index 3adc020..978a652 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
@@ -27,8 +27,8 @@
  */
 class TextReport {
 
-    public static void printTextReport(ApiCoverage api, PackageFilter packageFilter,
-            OutputStream outputStream) {
+    public static void printTextReport(ApiCoverage api, CddCoverage CddCoverage,
+            PackageFilter packageFilter, OutputStream outputStream) {
         PrintStream out = new PrintStream(outputStream);
 
         CoverageComparator comparator = new CoverageComparator();
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
index 2ba16cf..e6ac3af 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
@@ -33,7 +33,8 @@
 class XmlReport {
 
     public static void printXmlReport(List<File> testApks, ApiCoverage apiCoverage,
-            PackageFilter packageFilter, String reportTitle, OutputStream outputStream) {
+            CddCoverage cddCoverage, PackageFilter packageFilter, String reportTitle,
+            OutputStream outputStream) {
         PrintStream out = new PrintStream(outputStream);
         out.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
         out.println("<?xml-stylesheet type=\"text/xsl\"  href=\"api-coverage.xsl\"?>");
@@ -137,6 +138,20 @@
         }
 
         out.println("</api>");
+        out.println("<cdd>");
+        for (CddCoverage.CddRequirement requirement : cddCoverage.getCddRequirements()) {
+            out.println("<requirement id=\"" + requirement.getRequirementId() + "\">");
+            for (CddCoverage.TestMethod method : requirement.getTestMethods()) {
+                out.print("<test module=\"" + method.getTestModule()
+                        + "\" class=\"" + method.getTestClass() + "\" ");
+                if (method.getTestMethod() != null) {
+                    out.print("method=\"" + method.getTestMethod() + "\"");
+                }
+                out.println("/>" );
+            }
+            out.println("</requirement>");
+        }
+        out.println("</cdd>");
         out.println("<total numCovered=\"" + totalCoveredMethods + "\" "
                 + "numTotal=\"" + totalMethods + "\" "
                 + "coveragePercentage=\""