Merge "add unit test for DynamicConfigHandler"
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index c17b98b..07bc383 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -169,7 +169,6 @@
     CtsLocationTestCases \
     CtsLocation2TestCases \
     CtsMediaStressTestCases \
-    CtsMediaTestCases \
     CtsMidiTestCases \
     CtsNativeOpenGLTestCases \
     CtsNdefTestCases \
diff --git a/apps/CameraITS/tests/scene1/test_exposure.py b/apps/CameraITS/tests/scene1/test_exposure.py
index d217bdb..26c398d 100644
--- a/apps/CameraITS/tests/scene1/test_exposure.py
+++ b/apps/CameraITS/tests/scene1/test_exposure.py
@@ -36,11 +36,13 @@
     THRESHOLD_MIN_LEVEL = 0.1
     THRESHOLD_MAX_LEVEL = 0.9
     THRESHOLD_MAX_LEVEL_DIFF = 0.025
+    THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE = 0.05
 
     mults = []
     r_means = []
     g_means = []
     b_means = []
+    threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
@@ -57,13 +59,18 @@
             req = its.objects.manual_capture_request(s*m, e/m)
             cap = cam.do_capture(req)
             img = its.image.convert_capture_to_rgb_image(cap)
-            its.image.write_image(img, "%s_mult=%02d.jpg" % (NAME, m))
+            its.image.write_image(img, "%s_mult=%3.2f.jpg" % (NAME, m))
             tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
             rgb_means = its.image.compute_image_means(tile)
             r_means.append(rgb_means[0])
             g_means.append(rgb_means[1])
             b_means.append(rgb_means[2])
-            m = m + 4
+            # Test 3 steps per 2x gain
+            m = m * pow(2, 1.0 / 3)
+
+        # Allow more threshold for devices with wider exposure range
+        if m >= 64.0:
+            threshold_max_level_diff = THRESHOLD_MAX_LEVEL_DIFF_WIDE_RANGE
 
     # Draw a plot.
     pylab.plot(mults, r_means, 'r')
@@ -83,7 +90,7 @@
         max_diff = max_val - min_val
         print "Channel %d line fit (y = mx+b): m = %f, b = %f" % (chan, m, b)
         print "Channel max %f min %f diff %f" % (max_val, min_val, max_diff)
-        assert(max_diff < THRESHOLD_MAX_LEVEL_DIFF)
+        assert(max_diff < threshold_max_level_diff)
         assert(b > THRESHOLD_MIN_LEVEL and b < THRESHOLD_MAX_LEVEL)
         for v in values:
             assert(v > THRESHOLD_MIN_LEVEL and v < THRESHOLD_MAX_LEVEL)
diff --git a/apps/CameraITS/tests/scene1/test_param_noise_reduction.py b/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
index 2c8d73b..35cfc07 100644
--- a/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
@@ -36,11 +36,10 @@
     NAME = os.path.basename(__file__).split(".")[0]
 
     RELATIVE_ERROR_TOLERANCE = 0.1
-
-    # List of variances for Y,U,V.
+    # List of variances for R,G,B.
     variances = [[],[],[]]
 
-    # Reference (baseline) variance for each of Y,U,V.
+    # Reference (baseline) variance for each of R,G,B.
     ref_variance = []
 
     nr_modes_reported = []
@@ -56,16 +55,15 @@
         req = its.objects.manual_capture_request(s, e)
         req["android.noiseReduction.mode"] = 0
         cap = cam.do_capture(req)
+        rgb_image = its.image.convert_capture_to_rgb_image(cap)
         its.image.write_image(
-                its.image.convert_capture_to_rgb_image(cap),
+                rgb_image,
                 "%s_low_gain.jpg" % (NAME))
-        planes = its.image.convert_capture_to_planes(cap)
-        for j in range(3):
-            img = planes[j]
-            tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
-            ref_variance.append(its.image.compute_image_variances(tile)[0])
+        rgb_tile = its.image.get_image_patch(rgb_image, 0.45, 0.45, 0.1, 0.1)
+        ref_variance = its.image.compute_image_variances(rgb_tile)
         print "Ref variances:", ref_variance
 
+        e, s = its.target.get_target_exposure_combos(cam)["maxSensitivity"]
         # NR modes 0, 1, 2, 3, 4 with high gain
         for mode in range(5):
             # Skip unavailable modes
@@ -75,22 +73,22 @@
                     variances[channel].append(0)
                 continue;
 
-            e, s = its.target.get_target_exposure_combos(cam)["maxSensitivity"]
             req = its.objects.manual_capture_request(s, e)
             req["android.noiseReduction.mode"] = mode
             cap = cam.do_capture(req)
+            rgb_image = its.image.convert_capture_to_rgb_image(cap)
             nr_modes_reported.append(
                     cap["metadata"]["android.noiseReduction.mode"])
             its.image.write_image(
-                    its.image.convert_capture_to_rgb_image(cap),
+                    rgb_image,
                     "%s_high_gain_nr=%d.jpg" % (NAME, mode))
-            planes = its.image.convert_capture_to_planes(cap)
-            for j in range(3):
-                img = planes[j]
-                tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
-                variance = its.image.compute_image_variances(tile)[0]
-                variances[j].append(variance / ref_variance[j])
-        print "Variances with NR mode [0,1,2,3,4]:", variances
+            rgb_tile = its.image.get_image_patch(
+                    rgb_image, 0.45, 0.45, 0.1, 0.1)
+            rgb_vars = its.image.compute_image_variances(rgb_tile)
+            for chan in range(3):
+                variance = rgb_vars[chan]
+                variances[chan].append(variance / ref_variance[chan])
+        print "Variances with NR mode [0,1,2]:", variances
 
     # Draw a plot.
     for j in range(3):
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java
index f8f1a9a..6dbf8cc 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/sensors/SignificantMotionTestActivity.java
@@ -302,8 +302,9 @@
      * It cannot be reused.
      */
     private class TriggerVerifier extends TriggerEventListener {
-        private volatile CountDownLatch mCountDownLatch = new CountDownLatch(1);
+        private volatile CountDownLatch mCountDownLatch;
         private volatile TriggerEventRegistry mEventRegistry;
+        private volatile long mTimestampForTriggeredEvent = 0;
 
         // TODO: refactor out if needed
         private class TriggerEventRegistry {
@@ -329,10 +330,7 @@
         }
 
         public long getTimeStampForTriggerEvent() {
-            if (mEventRegistry != null && mEventRegistry.triggerEvent != null) {
-                return mEventRegistry.triggerEvent.timestamp;
-            }
-            return 0;
+            return mTimestampForTriggeredEvent;
         }
 
         public String verifyEventTriggered() throws Throwable {
@@ -388,9 +386,16 @@
         }
 
         private TriggerEventRegistry awaitForEvent() throws InterruptedException {
+            mCountDownLatch = new CountDownLatch(1);
             mCountDownLatch.await(TRIGGER_MAX_DELAY_SECONDS, TimeUnit.SECONDS);
             TriggerEventRegistry registry = mEventRegistry;
 
+            // Save the last timestamp when the event triggered.
+            if (mEventRegistry != null && mEventRegistry.triggerEvent != null) {
+                mTimestampForTriggeredEvent = mEventRegistry.triggerEvent.timestamp;
+            }
+
+            mEventRegistry = null;
             playSound();
             return registry != null ? registry : new TriggerEventRegistry(null, 0);
         }
diff --git a/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTest.java b/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTest.java
index 78da8ac..c8a2d8e 100644
--- a/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTest.java
+++ b/hostsidetests/atrace/src/android/atrace/cts/AtraceHostTest.java
@@ -208,7 +208,8 @@
                     installResult);
 
             // capture a launch of the app with async tracing
-            String atraceArgs = "-a " + TEST_PKG + " -c -b 16000 view"; // TODO: zipping
+            // content traced by 'view' tag tested below, 'sched' used to ensure tgid printed
+            String atraceArgs = "-a " + TEST_PKG + " -c -b 16000 view sched"; // TODO: zipping
             getDevice().executeShellCommand("atrace --async_stop " + atraceArgs);
             getDevice().executeShellCommand("atrace --async_start " + atraceArgs);
             getDevice().executeShellCommand("am start " + TEST_PKG);
diff --git a/hostsidetests/theme/Android.mk b/hostsidetests/theme/Android.mk
index 71027c7..188bf7a 100644
--- a/hostsidetests/theme/Android.mk
+++ b/hostsidetests/theme/Android.mk
@@ -29,6 +29,8 @@
 
 LOCAL_CTS_TEST_PACKAGE := android.host.theme
 
+LOCAL_SDK_VERSION := current
+
 include $(BUILD_CTS_HOST_JAVA_LIBRARY)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/hostsidetests/theme/README b/hostsidetests/theme/README
new file mode 100644
index 0000000..bce711a
--- /dev/null
+++ b/hostsidetests/theme/README
@@ -0,0 +1,73 @@
+* Copyright (C) 2015 The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+*      http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+
+
+INTRODUCTION
+
+The Android theme tests ensure that the Holo and Material themes have not been
+modified. They consist of API-specific sets of reference images representing
+specific themes and widgets that must be identical across devices. To pass the
+theme tests, a device must be able to generate images that are identical to the
+reference images.
+
+NOTE: Reference images should only be updated by the CTS test maintainers. Any
+      modifications to the reference images will invalidate the test results.
+
+
+INSTRUCTIONS
+
+I. Generating reference images (CTS maintainers only)
+
+Reference images are typically only generated for new API revisions. To
+generate a new set of reference images, do the following:
+
+  1. Connect one device for each DPI bucket (ldpi, xxxhdpi, etc.) that you wish
+     to generate references images for. Confirm that all devices are connected
+     with:
+
+     adb devices
+
+  2. Image generation occurs on all devices in parallel. Resulting sets of
+     reference images are saved in assets/<api>/<dpi>.zip and will overwrite
+     any existing sets. Image generation may be started using:
+
+     .cts/hostsidetests/theme/generate_images.sh
+
+A complete collection of reference images for a given API revision must include
+a set for each possible DPI bucket (tvdpi, xxhdpi, etc.) that may be tested.
+
+
+II. Building theme tests
+
+1. If you have not already built the CTS tests, run an initial make:
+
+   make cts -j32
+
+2. Subsequent changes to the theme tests, including changes to the reference
+   images, may be built using mmm:
+
+   mmm cts/hostsidetests/theme -j32
+
+
+III. Running theme tests
+
+1. Connect the device that you wish to test. Confirm that is is connected with:
+
+   adb devices
+
+2. Run the theme tests using cts-tradefed:
+
+   cts-tradefed run cts -c android.theme.cts.ThemeHostTest
+
+3. Wait for the tests to complete. This should take less than five minutes.
diff --git a/hostsidetests/theme/android_device.py b/hostsidetests/theme/android_device.py
new file mode 100644
index 0000000..97b5fdd
--- /dev/null
+++ b/hostsidetests/theme/android_device.py
@@ -0,0 +1,107 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+import re
+import sys
+import threading
+import subprocess
+import time
+
+# class for running android device from python
+# it will fork the device processor
+class androidDevice(object):
+    def __init__(self, adbDevice):
+        self._adbDevice = adbDevice
+
+    def runAdbCommand(self, cmd):
+        self.waitForAdbDevice()
+        adbCmd = "adb -s %s %s" %(self._adbDevice, cmd)
+        adbProcess = subprocess.Popen(adbCmd.split(" "), bufsize = -1, stdout = subprocess.PIPE)
+        return adbProcess.communicate()
+
+    def runShellCommand(self, cmd):
+        return self.runAdbCommand("shell " + cmd)
+
+    def waitForAdbDevice(self):
+        os.system("adb -s %s wait-for-device" %self._adbDevice)
+
+    def waitForBootComplete(self, timeout = 240):
+        boot_complete = False
+        attempts = 0
+        wait_period = 5
+        while not boot_complete and (attempts*wait_period) < timeout:
+            (output, err) = self.runShellCommand("getprop dev.bootcomplete")
+            output = output.strip()
+            if output == "1":
+                boot_complete = True
+            else:
+                time.sleep(wait_period)
+                attempts += 1
+        if not boot_complete:
+            print "***boot not complete within timeout. will proceed to the next step"
+        return boot_complete
+
+    def installApk(self, apkPath):
+        (out, err) = self.runAdbCommand("install -r -d -g " + apkPath)
+        result = out.split()
+        return (out, err, "Success" in result)
+
+    def uninstallApk(self, package):
+        (out, err) = self.runAdbCommand("uninstall " + package)
+        result = out.split()
+        return "Success" in result
+
+    def runInstrumentationTest(self, option):
+        return self.runShellCommand("am instrument -w " + option)
+
+    def isProcessAlive(self, processName):
+        (out, err) = self.runShellCommand("ps")
+        names = out.split()
+        # very lazy implementation as it does not filter out things like uid
+        # should work mostly unless processName is too simple to overlap with
+        # uid. So only use name like com.android.xyz
+        return processName in names
+
+    def getDensity(self):
+        if "emulator" in self._adbDevice:
+          return int(self.runShellCommand("getprop qemu.sf.lcd_density")[0])
+        else:
+          return int(self.runShellCommand("getprop ro.sf.lcd_density")[0])
+
+    def getSdkLevel(self):
+        return int(self.runShellCommand("getprop ro.build.version.sdk")[0])
+
+    def getOrientation(self):
+        return int(self.runShellCommand("dumpsys | grep SurfaceOrientation")[0].split()[1])
+
+def runAdbDevices():
+    devices = subprocess.check_output(["adb", "devices"])
+    devices = devices.split('\n')[1:]
+
+    deviceSerial = []
+
+    for device in devices:
+        if device is not "":
+            info = device.split('\t')
+            if info[1] == "device":
+                deviceSerial.append(info[0])
+
+    return deviceSerial
+
+if __name__ == '__main__':
+    main(sys.argv)
diff --git a/hostsidetests/theme/app/Android.mk b/hostsidetests/theme/app/Android.mk
index 70623cb..1be2983 100644
--- a/hostsidetests/theme/app/Android.mk
+++ b/hostsidetests/theme/app/Android.mk
@@ -26,6 +26,8 @@
 
 LOCAL_PROGUARD_ENABLED := disabled
 
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-test
+
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 #Flags to tell the Android Asset Packaging Tool not to strip for some densities
diff --git a/hostsidetests/theme/app/AndroidManifest.xml b/hostsidetests/theme/app/AndroidManifest.xml
index 81a4d9d..4e81ab0 100755
--- a/hostsidetests/theme/app/AndroidManifest.xml
+++ b/hostsidetests/theme/app/AndroidManifest.xml
@@ -22,9 +22,10 @@
 
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name=".HoloDeviceActivity">
+        <activity android:name=".ThemeDeviceActivity">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
@@ -37,6 +38,13 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
+        <activity android:name=".GenerateImagesActivity"
+                  android:exported="true" />
     </application>
 
+    <!--  self-instrumenting test package. -->
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.theme.app"
+                     android:label="Generates Theme reference images"/>
+
 </manifest>
diff --git a/hostsidetests/theme/app/res/layout/holo_test.xml b/hostsidetests/theme/app/res/layout/theme_test.xml
similarity index 100%
rename from hostsidetests/theme/app/res/layout/holo_test.xml
rename to hostsidetests/theme/app/res/layout/theme_test.xml
diff --git a/hostsidetests/theme/app/res/values/strings.xml b/hostsidetests/theme/app/res/values/strings.xml
index a69a2e0..d9d6602 100644
--- a/hostsidetests/theme/app/res/values/strings.xml
+++ b/hostsidetests/theme/app/res/values/strings.xml
@@ -14,8 +14,6 @@
      limitations under the License.
 -->
 <resources>
-    <string name="holo_test_utilities">Holo Test Utilities</string>
-
     <string name="display_info">Display Info</string>
     <string name="display_info_text">Density DPI: %1$d\nDensity Bucket: %2$s\nWidth DP: %3$d\nHeight DP: %4$d</string>
 
diff --git a/hostsidetests/theme/app/src/android/theme/app/DisplayInfoActivity.java b/hostsidetests/theme/app/src/android/theme/app/DisplayInfoActivity.java
index 5255698..530675d 100644
--- a/hostsidetests/theme/app/src/android/theme/app/DisplayInfoActivity.java
+++ b/hostsidetests/theme/app/src/android/theme/app/DisplayInfoActivity.java
@@ -25,7 +25,8 @@
 import android.widget.TextView;
 
 /**
- * An activity to display information about the device, including density bucket and dimensions.
+ * An activity to display information about the device, including density
+ * bucket and dimensions.
  */
 public class DisplayInfoActivity extends Activity {
 
diff --git a/hostsidetests/theme/app/src/android/theme/app/GenerateBitmapTask.java b/hostsidetests/theme/app/src/android/theme/app/GenerateBitmapTask.java
new file mode 100644
index 0000000..05b6dcf
--- /dev/null
+++ b/hostsidetests/theme/app/src/android/theme/app/GenerateBitmapTask.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.theme.app;
+
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.CompressFormat;
+import android.graphics.Canvas;
+import android.os.AsyncTask;
+import android.os.Environment;
+import android.util.Log;
+import android.view.View;
+
+import java.io.File;
+import java.io.FileOutputStream;
+
+/**
+ * A task which gets the UI element to render to a bitmap and then saves that
+ * as a PNG asynchronously.
+ */
+class GenerateBitmapTask extends AsyncTask<Void, Void, Boolean> {
+    private static final String TAG = "GenerateBitmapTask";
+
+    private final View mView;
+    private final File mOutDir;
+
+    private Bitmap mBitmap;
+
+    protected final String mName;
+
+    public GenerateBitmapTask(View view, File outDir, String name) {
+        mView = view;
+        mOutDir = outDir;
+        mName = name;
+    }
+
+    @Override
+    protected void onPreExecute() {
+        if (mView.getWidth() == 0 || mView.getHeight() == 0) {
+            Log.e(TAG, "Unable to draw view due to incorrect size: " + mName);
+            return;
+        }
+
+        mBitmap = Bitmap.createBitmap(mView.getWidth(), mView.getHeight(),
+                Bitmap.Config.ARGB_8888);
+
+        final Canvas canvas = new Canvas(mBitmap);
+        mView.draw(canvas);
+    }
+
+    @Override
+    protected Boolean doInBackground(Void... ignored) {
+        final Bitmap bitmap = mBitmap;
+        if (bitmap == null) {
+            return false;
+        }
+
+        final File file = new File(mOutDir, mName + ".png");
+        if (file.exists() && !file.canWrite()) {
+            Log.e(TAG, "Unable to write file: " + file.getAbsolutePath());
+            return false;
+        }
+
+        boolean success = false;
+        try {
+            FileOutputStream stream = null;
+            try {
+                stream = new FileOutputStream(file);
+                success = bitmap.compress(CompressFormat.PNG, 100, stream);
+            } finally {
+                if (stream != null) {
+                    stream.flush();
+                    stream.close();
+                }
+            }
+        } catch (Exception e) {
+            Log.e(TAG, e.getMessage());
+        } finally {
+            bitmap.recycle();
+        }
+
+        return success;
+    }
+}
diff --git a/hostsidetests/theme/app/src/android/theme/app/GenerateImagesActivity.java b/hostsidetests/theme/app/src/android/theme/app/GenerateImagesActivity.java
new file mode 100644
index 0000000..e7f5aa2
--- /dev/null
+++ b/hostsidetests/theme/app/src/android/theme/app/GenerateImagesActivity.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.theme.app;
+
+import android.Manifest.permission;
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Build.VERSION;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.util.Log;
+import android.view.WindowManager.LayoutParams;
+
+import java.io.File;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Generates images by iterating through all themes and launching instances of
+ * {@link ThemeDeviceActivity}.
+ */
+public class GenerateImagesActivity extends Activity {
+    private static final String TAG = "GenerateImagesActivity";
+
+    private static final String OUT_DIR = "cts-theme-assets";
+    private static final int REQUEST_CODE = 1;
+
+    private final CountDownLatch mLatch = new CountDownLatch(1);
+
+    private File mOutputDir;
+    private int mCurrentTheme;
+    private String mFinishReason;
+    private boolean mFinishSuccess;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON
+                | LayoutParams.FLAG_TURN_SCREEN_ON
+                | LayoutParams.FLAG_DISMISS_KEYGUARD);
+
+        mOutputDir = new File(Environment.getExternalStorageDirectory(), OUT_DIR);
+        ThemeTestUtils.deleteDirectory(mOutputDir);
+        mOutputDir.mkdirs();
+
+        if (!mOutputDir.exists()) {
+            finish("Failed to create output directory " + mOutputDir.getAbsolutePath(), false);
+            return;
+        }
+
+        final boolean canDisableKeyguard = checkCallingOrSelfPermission(
+                permission.DISABLE_KEYGUARD) == PackageManager.PERMISSION_GRANTED;
+        if (!canDisableKeyguard) {
+            finish("Not granted permission to disable keyguard", false);
+            return;
+        }
+
+        new KeyguardCheck(this) {
+            @Override
+            public void onSuccess() {
+                generateNextImage();
+            }
+
+            @Override
+            public void onFailure() {
+                finish("Device is locked", false);
+            }
+        }.start();
+    }
+
+    private void finish(String reason, boolean success) {
+        mFinishSuccess = success;
+        mFinishReason = reason;
+
+        Log.i(TAG, (success ? "OKAY" : "FAIL") + ":" + reason);
+        finish();
+    }
+
+    public boolean isFinishSuccess() {
+        return mFinishSuccess;
+    }
+
+    public String getFinishReason() {
+        return mFinishReason;
+    }
+
+    static abstract class KeyguardCheck implements Runnable {
+        private static final int MAX_RETRIES = 3;
+        private static final int RETRY_DELAY = 500;
+
+        private final Handler mHandler;
+        private final KeyguardManager mKeyguard;
+
+        private int mRetries;
+
+        public KeyguardCheck(Context context) {
+            mHandler = new Handler(context.getMainLooper());
+            mKeyguard = (KeyguardManager) context.getSystemService(KEYGUARD_SERVICE);
+        }
+
+        public void start() {
+            mRetries = 0;
+
+            mHandler.removeCallbacks(this);
+            mHandler.post(this);
+        }
+
+        public void cancel() {
+            mHandler.removeCallbacks(this);
+        }
+
+        @Override
+        public void run() {
+            if (!mKeyguard.isKeyguardLocked()) {
+                onSuccess();
+            } else if (mRetries < MAX_RETRIES) {
+                mRetries++;
+                mHandler.postDelayed(this, RETRY_DELAY);
+            } else {
+                onFailure();
+            }
+
+        }
+
+        public abstract void onSuccess();
+        public abstract void onFailure();
+    }
+
+    public File getOutputDir() {
+        return mOutputDir;
+    }
+
+    /**
+     * Starts the activity to generate the next image.
+     */
+    private boolean generateNextImage() {
+        final ThemeDeviceActivity.Theme theme = ThemeDeviceActivity.THEMES[mCurrentTheme];
+        if (theme.apiLevel > VERSION.SDK_INT) {
+            Log.v(TAG, "Skipping theme \"" + theme.name
+                    + "\" (requires API " + theme.apiLevel + ")");
+            return false;
+        }
+
+        Log.v(TAG, "Generating images for theme \"" + theme.name + "\"...");
+
+        final Intent intent = new Intent(this, ThemeDeviceActivity.class);
+        intent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+        intent.putExtra(ThemeDeviceActivity.EXTRA_THEME, mCurrentTheme);
+        intent.putExtra(ThemeDeviceActivity.EXTRA_OUTPUT_DIR, mOutputDir.getAbsolutePath());
+        startActivityForResult(intent, REQUEST_CODE);
+        return true;
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (resultCode != RESULT_OK) {
+            Log.i(TAG, "FAIL:Failed to generate images for theme " + mCurrentTheme);
+            finish();
+            return;
+        }
+
+        // Keep trying themes until one works.
+        boolean success = false;
+        while (++mCurrentTheme < ThemeDeviceActivity.THEMES.length && !success) {
+            success = generateNextImage();
+        }
+
+        // If we ran out of themes, we're done.
+        if (!success) {
+            finish("Image generation complete!", true);
+        }
+    }
+
+    public void finish() {
+        mLatch.countDown();
+        super.finish();
+    }
+
+    public void waitForCompletion() throws InterruptedException {
+        mLatch.await();
+    }
+}
diff --git a/hostsidetests/theme/app/src/android/theme/app/HoloDeviceActivity.java b/hostsidetests/theme/app/src/android/theme/app/HoloDeviceActivity.java
deleted file mode 100644
index 8ae9fc8..0000000
--- a/hostsidetests/theme/app/src/android/theme/app/HoloDeviceActivity.java
+++ /dev/null
@@ -1,331 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.theme.app;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.CompressFormat;
-import android.graphics.Canvas;
-import android.os.AsyncTask;
-import android.os.Environment;
-import android.os.Bundle;
-import android.os.Handler;
-import android.theme.app.modifiers.DatePickerModifier;
-import android.theme.app.modifiers.ProgressBarModifier;
-import android.theme.app.modifiers.SearchViewModifier;
-import android.theme.app.modifiers.TimePickerModifier;
-import android.theme.app.modifiers.ViewCheckedModifier;
-import android.theme.app.modifiers.ViewPressedModifier;
-import android.theme.app.R;
-import android.theme.app.ReferenceViewGroup;
-import android.util.Log;
-import android.view.View;
-import android.widget.CheckBox;
-import android.widget.DatePicker;
-import android.widget.LinearLayout;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.lang.Override;
-
-/**
- * A activity which display various UI elements with Holo theme.
- */
-public class HoloDeviceActivity extends Activity {
-
-    public static final String EXTRA_THEME = "holo_theme_extra";
-
-    private static final String TAG = HoloDeviceActivity.class.getSimpleName();
-
-    /**
-     * The duration of the CalendarView adjustement to settle to its final position.
-     */
-    private static final long CALENDAR_VIEW_ADJUSTMENT_DURATION = 540;
-
-    private Theme mTheme;
-
-    private ReferenceViewGroup mViewGroup;
-
-    private int mLayoutIndex;
-
-    @Override
-    protected void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
-
-        mTheme = THEMES[getIntent().getIntExtra(EXTRA_THEME, 0)];
-        setTheme(mTheme.mId);
-        setContentView(R.layout.holo_test);
-        mViewGroup = (ReferenceViewGroup) findViewById(R.id.reference_view_group);
-    }
-
-    @Override
-    protected void onResume() {
-        super.onResume();
-        setNextLayout();
-    }
-
-    @Override
-    protected void onPause() {
-        if (!isFinishing()) {
-            // The Activity got paused for some reasons, for finish it as the host won't move on to
-            // the next theme otherwise.
-            Log.w(TAG, "onPause called without a call to finish().");
-            finish();
-        }
-        super.onPause();
-    }
-
-    @Override
-    protected void onDestroy() {
-        if (mLayoutIndex != LAYOUTS.length) {
-            Log.w(TAG, "Not all layouts got rendered: " + mLayoutIndex);
-        }
-        Log.i(TAG, "OKAY:" + mTheme.mName);
-        super.onDestroy();
-    }
-
-    /**
-     * Sets the next layout in the UI.
-     */
-    private void setNextLayout() {
-        if (mLayoutIndex >= LAYOUTS.length) {
-            finish();
-            return;
-        }
-        final Layout layout = LAYOUTS[mLayoutIndex++];
-        final String layoutName = String.format("%s_%s", mTheme.mName, layout.mName);
-
-        mViewGroup.removeAllViews();
-        final View view = getLayoutInflater().inflate(layout.mId, mViewGroup, false);
-        if (layout.mModifier != null) {
-            layout.mModifier.modifyView(view);
-        }
-        mViewGroup.addView(view);
-        view.setFocusable(false);
-
-        final Runnable generateBitmapRunnable = new Runnable() {
-            @Override
-            public void run() {
-                new GenerateBitmapTask(view, layoutName).execute();
-            }
-        };
-
-        if (view instanceof DatePicker) {
-            // DatePicker uses a CalendarView that has a non-configurable adjustment duration of
-            // 540ms
-            view.postDelayed(generateBitmapRunnable, CALENDAR_VIEW_ADJUSTMENT_DURATION);
-        } else {
-            view.post(generateBitmapRunnable);
-        }
-    }
-
-    /**
-     * A task which gets the UI element to render to a bitmap and then saves that as a png
-     * asynchronously
-     */
-    private class GenerateBitmapTask extends AsyncTask<Void, Void, Boolean> {
-
-        private final View mView;
-
-        private final String mName;
-
-        public GenerateBitmapTask(final View view, final String name) {
-            super();
-            mView = view;
-            mName = name;
-        }
-
-        @Override
-        protected Boolean doInBackground(Void... ignored) {
-            if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
-                Log.i(TAG, "External storage for saving bitmaps is not mounted");
-                return false;
-            }
-            if (mView.getWidth() == 0 || mView.getHeight() == 0) {
-                Log.w(TAG, "Unable to draw View due to incorrect size: " + mName);
-                return false;
-            }
-
-            final Bitmap bitmap = Bitmap.createBitmap(
-                    mView.getWidth(), mView.getHeight(), Bitmap.Config.ARGB_8888);
-            final Canvas canvas = new Canvas(bitmap);
-
-            mView.draw(canvas);
-            final File dir = new File(Environment.getExternalStorageDirectory(), "cts-holo-assets");
-            dir.mkdirs();
-            boolean success = false;
-            try {
-                final File file = new File(dir, mName + ".png");
-                FileOutputStream stream = null;
-                try {
-                    stream = new FileOutputStream(file);
-                    success = bitmap.compress(CompressFormat.PNG, 100, stream);
-                } finally {
-                    if (stream != null) {
-                        stream.close();
-                    }
-                }
-            } catch (Exception e) {
-                Log.e(TAG, e.getMessage());
-            } finally {
-                bitmap.recycle();
-            }
-            return success;
-        }
-
-        @Override
-        protected void onPostExecute(Boolean success) {
-            setNextLayout();
-        }
-    }
-
-    /**
-     * A class to encapsulate information about a holo theme.
-     */
-    private static class Theme {
-
-        public final int mId;
-
-        public final String mName;
-
-        private Theme(int id, String name) {
-            mId = id;
-            mName = name;
-        }
-    }
-
-    private static final Theme[] THEMES = {
-            new Theme(android.R.style.Theme_Holo,
-                    "holo"),
-            new Theme(android.R.style.Theme_Holo_Dialog,
-                    "holo_dialog"),
-            new Theme(android.R.style.Theme_Holo_Dialog_MinWidth,
-                    "holo_dialog_minwidth"),
-            new Theme(android.R.style.Theme_Holo_Dialog_NoActionBar,
-                    "holo_dialog_noactionbar"),
-            new Theme(android.R.style.Theme_Holo_Dialog_NoActionBar_MinWidth,
-                    "holo_dialog_noactionbar_minwidth"),
-            new Theme(android.R.style.Theme_Holo_DialogWhenLarge,
-                    "holo_dialogwhenlarge"),
-            new Theme(android.R.style.Theme_Holo_DialogWhenLarge_NoActionBar,
-                    "holo_dialogwhenlarge_noactionbar"),
-            new Theme(android.R.style.Theme_Holo_InputMethod,
-                    "holo_inputmethod"),
-            new Theme(android.R.style.Theme_Holo_Light,
-                    "holo_light"),
-            new Theme(android.R.style.Theme_Holo_Light_DarkActionBar,
-                    "holo_light_darkactionbar"),
-            new Theme(android.R.style.Theme_Holo_Light_Dialog,
-                    "holo_light_dialog"),
-            new Theme(android.R.style.Theme_Holo_Light_Dialog_MinWidth,
-                    "holo_light_dialog_minwidth"),
-            new Theme(android.R.style.Theme_Holo_Light_Dialog_NoActionBar,
-                    "holo_light_dialog_noactionbar"),
-            new Theme(android.R.style.Theme_Holo_Light_Dialog_NoActionBar_MinWidth,
-                    "holo_light_dialog_noactionbar_minwidth"),
-            new Theme(android.R.style.Theme_Holo_Light_DialogWhenLarge,
-                    "holo_light_dialogwhenlarge"),
-            new Theme(android.R.style.Theme_Holo_Light_DialogWhenLarge_NoActionBar,
-                    "holo_light_dialogwhenlarge_noactionbar"),
-            new Theme(android.R.style.Theme_Holo_Light_NoActionBar,
-                    "holo_light_noactionbar"),
-            new Theme(android.R.style.Theme_Holo_Light_NoActionBar_Fullscreen,
-                    "holo_light_noactionbar_fullscreen"),
-            new Theme(android.R.style.Theme_Holo_Light_Panel,
-                    "holo_light_panel"),
-            new Theme(android.R.style.Theme_Holo_NoActionBar,
-                    "holo_noactionbar"),
-            new Theme(android.R.style.Theme_Holo_NoActionBar_Fullscreen,
-                    "holo_noactionbar_fullscreen"),
-            new Theme(android.R.style.Theme_Holo_Panel,
-                    "holo_panel"),
-            new Theme(android.R.style.Theme_Holo_Wallpaper,
-                    "holo_wallpaper"),
-            new Theme(android.R.style.Theme_Holo_Wallpaper_NoTitleBar,
-                    "holo_wallpaper_notitlebar")
-    };
-
-    /**
-     * A class to encapsulate information about a holo layout.
-     */
-    private static class Layout {
-
-        public final int mId;
-
-        public final String mName;
-
-        public final LayoutModifier mModifier;
-
-        private Layout(int id, String name, LayoutModifier modifier) {
-            mId = id;
-            mName = name;
-            mModifier = modifier;
-        }
-    }
-
-    private static final Layout[] LAYOUTS = {
-            new Layout(R.layout.button, "button", null),
-            new Layout(R.layout.button, "button_pressed", new ViewPressedModifier()),
-            new Layout(R.layout.checkbox, "checkbox", null),
-            new Layout(R.layout.checkbox, "checkbox_checked", new ViewCheckedModifier()),
-            new Layout(R.layout.chronometer, "chronometer", null),
-            new Layout(R.layout.color_blue_bright, "color_blue_bright", null),
-            new Layout(R.layout.color_blue_dark, "color_blue_dark", null),
-            new Layout(R.layout.color_blue_light, "color_blue_light", null),
-            new Layout(R.layout.color_green_dark, "color_green_dark", null),
-            new Layout(R.layout.color_green_light, "color_green_light", null),
-            new Layout(R.layout.color_orange_dark, "color_orange_dark", null),
-            new Layout(R.layout.color_orange_light, "color_orange_light", null),
-            new Layout(R.layout.color_purple, "color_purple", null),
-            new Layout(R.layout.color_red_dark, "color_red_dark", null),
-            new Layout(R.layout.color_red_light, "color_red_light", null),
-            new Layout(R.layout.datepicker, "datepicker", new DatePickerModifier()),
-            new Layout(R.layout.display_info, "display_info", null),
-            new Layout(R.layout.edittext, "edittext", null),
-            new Layout(R.layout.progressbar_horizontal_0, "progressbar_horizontal_0", null),
-            new Layout(R.layout.progressbar_horizontal_100, "progressbar_horizontal_100", null),
-            new Layout(R.layout.progressbar_horizontal_50, "progressbar_horizontal_50", null),
-            new Layout(R.layout.progressbar_large, "progressbar_large", new ProgressBarModifier()),
-            new Layout(R.layout.progressbar_small, "progressbar_small", new ProgressBarModifier()),
-            new Layout(R.layout.progressbar, "progressbar", new ProgressBarModifier()),
-            new Layout(R.layout.radiobutton_checked, "radiobutton_checked", null),
-            new Layout(R.layout.radiobutton, "radiobutton", null),
-            new Layout(R.layout.radiogroup_horizontal, "radiogroup_horizontal", null),
-            new Layout(R.layout.radiogroup_vertical, "radiogroup_vertical", null),
-            new Layout(R.layout.ratingbar_0, "ratingbar_0", null),
-            new Layout(R.layout.ratingbar_2point5, "ratingbar_2point5", null),
-            new Layout(R.layout.ratingbar_5, "ratingbar_5", null),
-            new Layout(R.layout.ratingbar_0, "ratingbar_0_pressed", new ViewPressedModifier()),
-            new Layout(R.layout.ratingbar_2point5, "ratingbar_2point5_pressed", new ViewPressedModifier()),
-            new Layout(R.layout.ratingbar_5, "ratingbar_5_pressed", new ViewPressedModifier()),
-            new Layout(R.layout.searchview, "searchview_query", new SearchViewModifier(SearchViewModifier.QUERY)),
-            new Layout(R.layout.searchview, "searchview_query_hint", new SearchViewModifier(SearchViewModifier.QUERY_HINT)),
-            new Layout(R.layout.seekbar_0, "seekbar_0", null),
-            new Layout(R.layout.seekbar_100, "seekbar_100", null),
-            new Layout(R.layout.seekbar_50, "seekbar_50", null),
-            new Layout(R.layout.spinner, "spinner", null),
-            new Layout(R.layout.switch_button_checked, "switch_button_checked", null),
-            new Layout(R.layout.switch_button, "switch_button", null),
-            new Layout(R.layout.textview, "textview", null),
-            new Layout(R.layout.timepicker, "timepicker", new TimePickerModifier()),
-            new Layout(R.layout.togglebutton_checked, "togglebutton_checked", null),
-            new Layout(R.layout.togglebutton, "togglebutton", null),
-            new Layout(R.layout.zoomcontrols, "zoomcontrols", null),
-    };
-}
diff --git a/hostsidetests/theme/app/src/android/theme/app/ReferenceImagesTest.java b/hostsidetests/theme/app/src/android/theme/app/ReferenceImagesTest.java
new file mode 100644
index 0000000..7569252
--- /dev/null
+++ b/hostsidetests/theme/app/src/android/theme/app/ReferenceImagesTest.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.theme.app;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+import java.io.File;
+
+/**
+ * Activity test case used to instrument generation of reference images.
+ */
+public class ReferenceImagesTest extends ActivityInstrumentationTestCase2<GenerateImagesActivity> {
+
+    public ReferenceImagesTest() {
+        super(GenerateImagesActivity.class);
+    }
+
+    public void testGenerateReferenceImages() throws Exception {
+        setActivityInitialTouchMode(true);
+
+        final GenerateImagesActivity activity = getActivity();
+        activity.waitForCompletion();
+
+        assertTrue(activity.getFinishReason(), activity.isFinishSuccess());
+
+        final File outputDir = activity.getOutputDir();
+        final File outputZip = new File(outputDir.getParentFile(), outputDir.getName() + ".zip");
+        if (outputZip.exists()) {
+            // Remove any old test results.
+            outputZip.delete();
+        }
+
+        ThemeTestUtils.compressDirectory(outputDir, outputZip);
+        ThemeTestUtils.deleteDirectory(outputDir);
+
+        assertTrue("Generated reference image ZIP", outputZip.exists());
+    }
+}
diff --git a/hostsidetests/theme/app/src/android/theme/app/ThemeDeviceActivity.java b/hostsidetests/theme/app/src/android/theme/app/ThemeDeviceActivity.java
new file mode 100644
index 0000000..d8b1f30
--- /dev/null
+++ b/hostsidetests/theme/app/src/android/theme/app/ThemeDeviceActivity.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.theme.app;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Bundle;
+import android.theme.app.modifiers.DatePickerModifier;
+import android.theme.app.modifiers.ProgressBarModifier;
+import android.theme.app.modifiers.SearchViewModifier;
+import android.theme.app.modifiers.TimePickerModifier;
+import android.theme.app.modifiers.ViewCheckedModifier;
+import android.theme.app.modifiers.ViewPressedModifier;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager.LayoutParams;
+import android.widget.DatePicker;
+
+import java.io.File;
+import java.lang.Override;
+
+/**
+ * A activity which display various UI elements with non-modifiable themes.
+ */
+public class ThemeDeviceActivity extends Activity {
+    public static final String EXTRA_THEME = "theme";
+    public static final String EXTRA_OUTPUT_DIR = "outputDir";
+
+    private static final String TAG = "ThemeDeviceActivity";
+
+    /**
+     * The duration of the CalendarView adjustment to settle to its final
+     * position.
+     */
+    private static final long CALENDAR_VIEW_ADJUSTMENT_DURATION = 540;
+
+    private Theme mTheme;
+    private ReferenceViewGroup mViewGroup;
+    private File mOutputDir;
+    private int mLayoutIndex;
+    private boolean mIsRunning;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        final Intent intent = getIntent();
+        final int themeIndex = intent.getIntExtra(EXTRA_THEME, -1);
+        if (themeIndex < 0) {
+            Log.e(TAG, "No theme specified");
+            finish();
+        }
+
+        final String outputDir = intent.getStringExtra(EXTRA_OUTPUT_DIR);
+        if (outputDir == null) {
+            Log.e(TAG, "No output directory specified");
+            finish();
+        }
+
+        mOutputDir = new File(outputDir);
+        mTheme = THEMES[themeIndex];
+
+        setTheme(mTheme.id);
+        setContentView(R.layout.theme_test);
+
+        mViewGroup = (ReferenceViewGroup) findViewById(R.id.reference_view_group);
+
+        getWindow().addFlags(LayoutParams.FLAG_KEEP_SCREEN_ON
+                | LayoutParams.FLAG_TURN_SCREEN_ON
+                | LayoutParams.FLAG_DISMISS_KEYGUARD );
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        mIsRunning = true;
+
+        setNextLayout();
+    }
+
+    @Override
+    protected void onPause() {
+        mIsRunning = false;
+
+        if (!isFinishing()) {
+            // The activity paused for some reason, likely a system crash
+            // dialog. Finish it so we can move to the next theme.
+            Log.w(TAG, "onPause() called without a call to finish()", new RuntimeException());
+            finish();
+        }
+
+        super.onPause();
+    }
+
+    @Override
+    protected void onDestroy() {
+        if (mLayoutIndex < LAYOUTS.length) {
+            Log.e(TAG, "Not all layouts got rendered: " + mLayoutIndex);
+            setResult(RESULT_CANCELED);
+        }
+
+        super.onDestroy();
+    }
+
+    /**
+     * Sets the next layout in the UI.
+     */
+    private void setNextLayout() {
+        if (mLayoutIndex >= LAYOUTS.length) {
+            setResult(RESULT_OK);
+            finish();
+            return;
+        }
+
+        mViewGroup.removeAllViews();
+
+        final Layout layout = LAYOUTS[mLayoutIndex++];
+        final String layoutName = String.format("%s_%s", mTheme.name, layout.name);
+        final View view = getLayoutInflater().inflate(layout.id, mViewGroup, false);
+        if (layout.modifier != null) {
+            layout.modifier.modifyView(view);
+        }
+
+        mViewGroup.addView(view);
+        view.setFocusable(false);
+
+        Log.v(TAG, "Rendering layout " + layoutName
+                + " (" + mLayoutIndex + "/" + LAYOUTS.length + ")");
+
+        final Runnable generateBitmapRunnable = new Runnable() {
+            @Override
+            public void run() {
+                new BitmapTask(view, layoutName).execute();
+            }
+        };
+
+        if (view instanceof DatePicker) {
+            // The Holo-styled DatePicker uses a CalendarView that has a
+            // non-configurable adjustment duration of 540ms.
+            view.postDelayed(generateBitmapRunnable, CALENDAR_VIEW_ADJUSTMENT_DURATION);
+        } else {
+            view.post(generateBitmapRunnable);
+        }
+    }
+
+    private class BitmapTask extends GenerateBitmapTask {
+        public BitmapTask(View view, String name) {
+            super(view, mOutputDir, name);
+        }
+
+        @Override
+        protected void onPostExecute(Boolean success) {
+            if (success && mIsRunning) {
+                setNextLayout();
+            } else {
+                Log.e(TAG, "Failed to render view to bitmap: " + mName + " (activity running? "
+                        + mIsRunning + ")");
+                finish();
+            }
+        }
+    }
+
+    /**
+     * A class to encapsulate information about a theme.
+     */
+    static class Theme {
+        public final int id;
+        public final int apiLevel;
+        public final String name;
+
+        private Theme(int id, int apiLevel, String name) {
+            this.id = id;
+            this.apiLevel = apiLevel;
+            this.name = name;
+        }
+    }
+
+    // List of themes to verify.
+    static final Theme[] THEMES = {
+            // Holo
+            new Theme(android.R.style.Theme_Holo,
+                    Build.VERSION_CODES.HONEYCOMB, "holo"),
+            new Theme(android.R.style.Theme_Holo_Dialog,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_dialog"),
+            new Theme(android.R.style.Theme_Holo_Dialog_MinWidth,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_dialog_minwidth"),
+            new Theme(android.R.style.Theme_Holo_Dialog_NoActionBar,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_dialog_noactionbar"),
+            new Theme(android.R.style.Theme_Holo_Dialog_NoActionBar_MinWidth,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_dialog_noactionbar_minwidth"),
+            new Theme(android.R.style.Theme_Holo_DialogWhenLarge,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_dialogwhenlarge"),
+            new Theme(android.R.style.Theme_Holo_DialogWhenLarge_NoActionBar,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_dialogwhenlarge_noactionbar"),
+            new Theme(android.R.style.Theme_Holo_InputMethod,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_inputmethod"),
+            new Theme(android.R.style.Theme_Holo_NoActionBar,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_noactionbar"),
+            new Theme(android.R.style.Theme_Holo_NoActionBar_Fullscreen,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_noactionbar_fullscreen"),
+            new Theme(android.R.style.Theme_Holo_NoActionBar_Overscan,
+                    Build.VERSION_CODES.JELLY_BEAN_MR2, "holo_noactionbar_overscan"),
+            new Theme(android.R.style.Theme_Holo_NoActionBar_TranslucentDecor,
+                    Build.VERSION_CODES.KITKAT, "holo_noactionbar_translucentdecor"),
+            new Theme(android.R.style.Theme_Holo_Panel,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_panel"),
+            new Theme(android.R.style.Theme_Holo_Wallpaper,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_wallpaper"),
+            new Theme(android.R.style.Theme_Holo_Wallpaper_NoTitleBar,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_wallpaper_notitlebar"),
+
+            // Holo Light
+            new Theme(android.R.style.Theme_Holo_Light,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_light"),
+            new Theme(android.R.style.Theme_Holo_Light_DarkActionBar,
+                    Build.VERSION_CODES.ICE_CREAM_SANDWICH, "holo_light_darkactionbar"),
+            new Theme(android.R.style.Theme_Holo_Light_Dialog,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_light_dialog"),
+            new Theme(android.R.style.Theme_Holo_Light_Dialog_MinWidth,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_light_dialog_minwidth"),
+            new Theme(android.R.style.Theme_Holo_Light_Dialog_NoActionBar,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_light_dialog_noactionbar"),
+            new Theme(android.R.style.Theme_Holo_Light_Dialog_NoActionBar_MinWidth,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_light_dialog_noactionbar_minwidth"),
+            new Theme(android.R.style.Theme_Holo_Light_DialogWhenLarge,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_light_dialogwhenlarge"),
+            new Theme(android.R.style.Theme_Holo_Light_DialogWhenLarge_NoActionBar,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_light_dialogwhenlarge_noactionbar"),
+            new Theme(android.R.style.Theme_Holo_Light_NoActionBar,
+                    Build.VERSION_CODES.HONEYCOMB_MR2, "holo_light_noactionbar"),
+            new Theme(android.R.style.Theme_Holo_Light_NoActionBar_Fullscreen,
+                    Build.VERSION_CODES.HONEYCOMB_MR2, "holo_light_noactionbar_fullscreen"),
+            new Theme(android.R.style.Theme_Holo_Light_NoActionBar_Overscan,
+                    Build.VERSION_CODES.JELLY_BEAN_MR2, "holo_light_noactionbar_overscan"),
+            new Theme(android.R.style.Theme_Holo_Light_NoActionBar_TranslucentDecor,
+                    Build.VERSION_CODES.KITKAT, "holo_light_noactionbar_translucentdecor"),
+            new Theme(android.R.style.Theme_Holo_Light_Panel,
+                    Build.VERSION_CODES.HONEYCOMB, "holo_light_panel"),
+
+            // Material
+            new Theme(android.R.style.Theme_Material,
+                    Build.VERSION_CODES.LOLLIPOP, "material"),
+            new Theme(android.R.style.Theme_Material_Dialog,
+                    Build.VERSION_CODES.LOLLIPOP, "material_dialog"),
+            new Theme(android.R.style.Theme_Material_Dialog_Alert,
+                    Build.VERSION_CODES.LOLLIPOP, "material_dialog_alert"),
+            new Theme(android.R.style.Theme_Material_Dialog_MinWidth,
+                    Build.VERSION_CODES.LOLLIPOP, "material_dialog_minwidth"),
+            new Theme(android.R.style.Theme_Material_Dialog_NoActionBar,
+                    Build.VERSION_CODES.LOLLIPOP, "material_dialog_noactionbar"),
+            new Theme(android.R.style.Theme_Material_Dialog_NoActionBar_MinWidth,
+                    Build.VERSION_CODES.LOLLIPOP, "material_dialog_noactionbar_minwidth"),
+            new Theme(android.R.style.Theme_Material_Dialog_Presentation,
+                    Build.VERSION_CODES.LOLLIPOP, "material_dialog_presentation"),
+            new Theme(android.R.style.Theme_Material_DialogWhenLarge,
+                    Build.VERSION_CODES.LOLLIPOP, "material_dialogwhenlarge"),
+            new Theme(android.R.style.Theme_Material_DialogWhenLarge_NoActionBar,
+                    Build.VERSION_CODES.LOLLIPOP, "material_dialogwhenlarge_noactionbar"),
+            new Theme(android.R.style.Theme_Material_InputMethod,
+                    Build.VERSION_CODES.LOLLIPOP, "material_inputmethod"),
+            new Theme(android.R.style.Theme_Material_NoActionBar,
+                    Build.VERSION_CODES.LOLLIPOP, "material_noactionbar"),
+            new Theme(android.R.style.Theme_Material_NoActionBar_Fullscreen,
+                    Build.VERSION_CODES.LOLLIPOP, "material_noactionbar_fullscreen"),
+            new Theme(android.R.style.Theme_Material_NoActionBar_Overscan,
+                    Build.VERSION_CODES.LOLLIPOP, "material_noactionbar_overscan"),
+            new Theme(android.R.style.Theme_Material_NoActionBar_TranslucentDecor,
+                    Build.VERSION_CODES.LOLLIPOP, "material_noactionbar_translucentdecor"),
+            new Theme(android.R.style.Theme_Material_Panel,
+                    Build.VERSION_CODES.LOLLIPOP, "material_panel"),
+            new Theme(android.R.style.Theme_Material_Settings,
+                    Build.VERSION_CODES.LOLLIPOP, "material_settings"),
+            new Theme(android.R.style.Theme_Material_Voice,
+                    Build.VERSION_CODES.LOLLIPOP, "material_voice"),
+            new Theme(android.R.style.Theme_Material_Wallpaper,
+                    Build.VERSION_CODES.LOLLIPOP, "material_wallpaper"),
+            new Theme(android.R.style.Theme_Material_Wallpaper_NoTitleBar,
+                    Build.VERSION_CODES.LOLLIPOP, "material_wallpaper_notitlebar"),
+
+            // Material Light
+            new Theme(android.R.style.Theme_Material_Light,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light"),
+            new Theme(android.R.style.Theme_Material_Light_DarkActionBar,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_darkactionbar"),
+            new Theme(android.R.style.Theme_Material_Light_Dialog,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_dialog"),
+            new Theme(android.R.style.Theme_Material_Light_Dialog_Alert,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_dialog_alert"),
+            new Theme(android.R.style.Theme_Material_Light_Dialog_MinWidth,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_dialog_minwidth"),
+            new Theme(android.R.style.Theme_Material_Light_Dialog_NoActionBar,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_dialog_noactionbar"),
+            new Theme(android.R.style.Theme_Material_Light_Dialog_NoActionBar_MinWidth,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_dialog_noactionbar_minwidth"),
+            new Theme(android.R.style.Theme_Material_Light_Dialog_Presentation,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_dialog_presentation"),
+            new Theme(android.R.style.Theme_Material_Light_DialogWhenLarge,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_dialogwhenlarge"),
+            new Theme(android.R.style.Theme_Material_Light_DialogWhenLarge_NoActionBar,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_dialogwhenlarge_noactionbar"),
+            new Theme(android.R.style.Theme_Material_Light_LightStatusBar,
+                    Build.VERSION_CODES.M, "material_light_lightstatusbar"),
+            new Theme(android.R.style.Theme_Material_Light_NoActionBar,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_noactionbar"),
+            new Theme(android.R.style.Theme_Material_Light_NoActionBar_Fullscreen,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_noactionbar_fullscreen"),
+            new Theme(android.R.style.Theme_Material_Light_NoActionBar_Overscan,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_noactionbar_overscan"),
+            new Theme(android.R.style.Theme_Material_Light_NoActionBar_TranslucentDecor,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_noactionbar_translucentdecor"),
+            new Theme(android.R.style.Theme_Material_Light_Panel,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_panel"),
+            new Theme(android.R.style.Theme_Material_Light_Voice,
+                    Build.VERSION_CODES.LOLLIPOP, "material_light_voice")
+    };
+
+    /**
+     * A class to encapsulate information about a layout.
+     */
+    private static class Layout {
+        public final int id;
+        public final String name;
+        public final LayoutModifier modifier;
+
+        private Layout(int id, String name) {
+            this(id, name, null);
+        }
+
+        private Layout(int id, String name, LayoutModifier modifier) {
+            this.id = id;
+            this.name = name;
+            this.modifier = modifier;
+        }
+    }
+
+    // List of layouts to verify for each theme.
+    private static final Layout[] LAYOUTS = {
+            new Layout(R.layout.button, "button"),
+            new Layout(R.layout.button, "button_pressed",
+                    new ViewPressedModifier()),
+            new Layout(R.layout.checkbox, "checkbox"),
+            new Layout(R.layout.checkbox, "checkbox_checked",
+                    new ViewCheckedModifier()),
+            new Layout(R.layout.chronometer, "chronometer"),
+            new Layout(R.layout.color_blue_bright, "color_blue_bright"),
+            new Layout(R.layout.color_blue_dark, "color_blue_dark"),
+            new Layout(R.layout.color_blue_light, "color_blue_light"),
+            new Layout(R.layout.color_green_dark, "color_green_dark"),
+            new Layout(R.layout.color_green_light, "color_green_light"),
+            new Layout(R.layout.color_orange_dark, "color_orange_dark"),
+            new Layout(R.layout.color_orange_light, "color_orange_light"),
+            new Layout(R.layout.color_purple, "color_purple"),
+            new Layout(R.layout.color_red_dark, "color_red_dark"),
+            new Layout(R.layout.color_red_light, "color_red_light"),
+            new Layout(R.layout.datepicker, "datepicker",
+                    new DatePickerModifier()),
+            new Layout(R.layout.display_info, "display_info"),
+            new Layout(R.layout.edittext, "edittext"),
+            new Layout(R.layout.progressbar_horizontal_0, "progressbar_horizontal_0"),
+            new Layout(R.layout.progressbar_horizontal_100, "progressbar_horizontal_100"),
+            new Layout(R.layout.progressbar_horizontal_50, "progressbar_horizontal_50"),
+            new Layout(R.layout.progressbar_large, "progressbar_large",
+                    new ProgressBarModifier()),
+            new Layout(R.layout.progressbar_small, "progressbar_small",
+                    new ProgressBarModifier()),
+            new Layout(R.layout.progressbar, "progressbar",
+                    new ProgressBarModifier()),
+            new Layout(R.layout.radiobutton_checked, "radiobutton_checked"),
+            new Layout(R.layout.radiobutton, "radiobutton"),
+            new Layout(R.layout.radiogroup_horizontal, "radiogroup_horizontal"),
+            new Layout(R.layout.radiogroup_vertical, "radiogroup_vertical"),
+            new Layout(R.layout.ratingbar_0, "ratingbar_0"),
+            new Layout(R.layout.ratingbar_2point5, "ratingbar_2point5"),
+            new Layout(R.layout.ratingbar_5, "ratingbar_5"),
+            new Layout(R.layout.ratingbar_0, "ratingbar_0_pressed",
+                    new ViewPressedModifier()),
+            new Layout(R.layout.ratingbar_2point5, "ratingbar_2point5_pressed",
+                    new ViewPressedModifier()),
+            new Layout(R.layout.ratingbar_5, "ratingbar_5_pressed",
+                    new ViewPressedModifier()),
+            new Layout(R.layout.searchview, "searchview_query",
+                    new SearchViewModifier(SearchViewModifier.QUERY)),
+            new Layout(R.layout.searchview, "searchview_query_hint",
+                    new SearchViewModifier(SearchViewModifier.QUERY_HINT)),
+            new Layout(R.layout.seekbar_0, "seekbar_0"),
+            new Layout(R.layout.seekbar_100, "seekbar_100"),
+            new Layout(R.layout.seekbar_50, "seekbar_50"),
+            new Layout(R.layout.spinner, "spinner"),
+            new Layout(R.layout.switch_button_checked, "switch_button_checked"),
+            new Layout(R.layout.switch_button, "switch_button"),
+            new Layout(R.layout.textview, "textview"),
+            new Layout(R.layout.timepicker, "timepicker",
+                    new TimePickerModifier()),
+            new Layout(R.layout.togglebutton_checked, "togglebutton_checked"),
+            new Layout(R.layout.togglebutton, "togglebutton"),
+            new Layout(R.layout.zoomcontrols, "zoomcontrols"),
+    };
+}
diff --git a/hostsidetests/theme/app/src/android/theme/app/ThemeTestUtils.java b/hostsidetests/theme/app/src/android/theme/app/ThemeTestUtils.java
new file mode 100644
index 0000000..4daca6c
--- /dev/null
+++ b/hostsidetests/theme/app/src/android/theme/app/ThemeTestUtils.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.theme.app;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+
+public class ThemeTestUtils {
+
+    /**
+     * Compresses the contents of a directory to a ZIP file.
+     *
+     * @param dir the directory to compress
+     * @return {@code true} on success, {@code false} on failure
+     */
+    public static boolean compressDirectory(File dir, File file) throws IOException {
+        if (dir == null || !dir.exists() || file == null || file.exists()) {
+            return false;
+        }
+
+        final File[] srcFiles = dir.listFiles();
+        if (srcFiles.length == 0) {
+            return false;
+        }
+
+        final ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(file));
+        final byte[] data = new byte[4096];
+        for (int i = 0; i < srcFiles.length; i++) {
+            final FileInputStream fileIn = new FileInputStream(srcFiles[i]);
+            final ZipEntry entry = new ZipEntry(srcFiles[i].getName());
+            zipOut.putNextEntry(entry);
+
+            int count;
+            while ((count = fileIn.read(data, 0, data.length)) != -1) {
+                zipOut.write(data, 0, count);
+                zipOut.flush();
+            }
+
+            zipOut.closeEntry();
+            fileIn.close();
+        }
+
+        zipOut.close();
+        return true;
+    }
+
+    /**
+     * Recursively deletes a directory and its contents.
+     *
+     * @param dir the directory to delete
+     * @return {@code true} on success, {@code false} on failure
+     */
+    public static boolean deleteDirectory(File dir) {
+        final File files[] = dir.listFiles();
+        if (files != null) {
+            for (File file : files) {
+                deleteDirectory(file);
+            }
+        }
+        return dir.delete();
+    }
+}
diff --git a/hostsidetests/theme/assets/23/400dpi.zip b/hostsidetests/theme/assets/23/400dpi.zip
new file mode 100644
index 0000000..be0891f
--- /dev/null
+++ b/hostsidetests/theme/assets/23/400dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/23/560dpi.zip b/hostsidetests/theme/assets/23/560dpi.zip
new file mode 100644
index 0000000..cf0a559
--- /dev/null
+++ b/hostsidetests/theme/assets/23/560dpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/23/hdpi.zip b/hostsidetests/theme/assets/23/hdpi.zip
new file mode 100644
index 0000000..80c12a7
--- /dev/null
+++ b/hostsidetests/theme/assets/23/hdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/23/ldpi.zip b/hostsidetests/theme/assets/23/ldpi.zip
new file mode 100644
index 0000000..937914a
--- /dev/null
+++ b/hostsidetests/theme/assets/23/ldpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/23/mdpi.zip b/hostsidetests/theme/assets/23/mdpi.zip
new file mode 100644
index 0000000..f842676
--- /dev/null
+++ b/hostsidetests/theme/assets/23/mdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/23/tvdpi.zip b/hostsidetests/theme/assets/23/tvdpi.zip
new file mode 100644
index 0000000..77386e5
--- /dev/null
+++ b/hostsidetests/theme/assets/23/tvdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/23/xhdpi.zip b/hostsidetests/theme/assets/23/xhdpi.zip
new file mode 100644
index 0000000..a8310d5
--- /dev/null
+++ b/hostsidetests/theme/assets/23/xhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/assets/23/xxhdpi.zip b/hostsidetests/theme/assets/23/xxhdpi.zip
new file mode 100644
index 0000000..f88711f
--- /dev/null
+++ b/hostsidetests/theme/assets/23/xxhdpi.zip
Binary files differ
diff --git a/hostsidetests/theme/generate_images.sh b/hostsidetests/theme/generate_images.sh
new file mode 100755
index 0000000..9bcc3e5
--- /dev/null
+++ b/hostsidetests/theme/generate_images.sh
@@ -0,0 +1,55 @@
+#!/bin/sh
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# This script is used to generate reference images for the CTS theme tests.
+# See the accompanying README file for more information.
+
+# retry <command> <tries> <message> <delay>
+function retry {
+  RETRY="0"
+  while true; do
+    if (("$RETRY" >= "$2")); then
+      echo $OUTPUT
+      exit
+    fi
+
+    OUTPUT=`$1 |& grep error`
+
+    if [ -z "$OUTPUT" ]; then
+      break
+    fi
+
+    echo $3
+    sleep $4
+    RETRY=$[$RETRY + 1]
+  done
+}
+
+themeApkPath="$ANDROID_HOST_OUT/cts/android-cts/repository/testcases/CtsThemeDeviceApp.apk"
+outDir="$ANDROID_BUILD_TOP/cts/hostsidetests/theme/assets"
+exe="$ANDROID_BUILD_TOP/cts/hostsidetests/theme/run_theme_capture_device.py"
+
+if [ -z "$ANDROID_BUILD_TOP" ]; then
+  echo "Missing environment variables. Did you run build/envsetup.sh and lunch?"
+  exit
+fi
+
+if [ ! -e "$themeApkPath" ]; then
+  echo "Couldn't find test APK. Did you run make cts?"
+  exit
+fi
+
+adb devices
+python $exe $themeApkPath $outDir
diff --git a/hostsidetests/theme/run_theme_capture_device.py b/hostsidetests/theme/run_theme_capture_device.py
new file mode 100755
index 0000000..23171db
--- /dev/null
+++ b/hostsidetests/theme/run_theme_capture_device.py
@@ -0,0 +1,170 @@
+#!/usr/bin/env python
+#
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+import os
+import sys
+import threading
+import time
+import Queue
+sys.path.append(sys.path[0])
+from android_device import *
+
+CTS_THEME_dict = {
+    120 : "ldpi",
+    160 : "mdpi",
+    213 : "tvdpi",
+    240 : "hdpi",
+    320 : "xhdpi",
+    400 : "400dpi",
+    480 : "xxhdpi",
+    560 : "560dpi",
+    640 : "xxxhdpi",
+}
+
+OUT_FILE = "/sdcard/cts-theme-assets.zip"
+
+# pass a function with number of instances to be executed in parallel
+# each thread continues until config q is empty.
+def executeParallel(tasks, setup, q, numberThreads):
+    class ParallelExecutor(threading.Thread):
+        def __init__(self, tasks, q):
+            threading.Thread.__init__(self)
+            self._q = q
+            self._tasks = tasks
+            self._setup = setup
+            self._result = 0
+
+        def run(self):
+            try:
+                while True:
+                    config = q.get(block=True, timeout=2)
+                    for t in self._tasks:
+                        try:
+                            if t(self._setup, config):
+                                self._result += 1
+                        except KeyboardInterrupt:
+                            raise
+                        except:
+                            print "Failed to execute thread:", sys.exc_info()[0]
+                    q.task_done()
+            except KeyboardInterrupt:
+                raise
+            except Queue.Empty:
+                pass
+
+        def getResult(self):
+            return self._result
+
+    result = 0;
+    threads = []
+    for i in range(numberThreads):
+        t = ParallelExecutor(tasks, q)
+        t.start()
+        threads.append(t)
+    for t in threads:
+        t.join()
+        result += t.getResult()
+    return result;
+
+def printAdbResult(device, out, err):
+    print "device: " + device
+    if out is not None:
+        print "out:\n" + out
+    if err is not None:
+        print "err:\n" + err
+
+def getResDir(outPath, resName):
+    resDir = outPath + "/" + resName
+    return resDir
+
+def doCapturing(setup, deviceSerial):
+    (themeApkPath, outPath) = setup
+
+    print "Found device: " + deviceSerial
+    device = androidDevice(deviceSerial)
+
+    # outPath = outPath + "/%d/" % (device.getSdkLevel()) + deviceSerial
+    outPath = outPath + "/%d" % (device.getSdkLevel())
+    density = device.getDensity()
+    resName = CTS_THEME_dict[density]
+
+    device.uninstallApk("android.theme.app")
+
+    (out, err, success) = device.installApk(themeApkPath)
+    if not success:
+        print "Failed to install APK on " + deviceSerial
+        printAdbResult(device, out, err)
+        return False
+
+    print "Generating images on " + deviceSerial + "..."
+    try:
+        (out, err) = device.runInstrumentationTest("android.theme.app/android.support.test.runner.AndroidJUnitRunner")
+    except KeyboardInterrupt:
+        raise
+    except:
+        (out, err) = device.runInstrumentationTest("android.theme.app/android.test.InstrumentationTestRunner")
+
+    # Detect test failure and abort.
+    if "FAILURES!!!" in out.split():
+        printAdbResult(deviceSerial, out, err)
+        return False
+
+    # Make sure that the run is complete by checking the process itself
+    print "Waiting for " + deviceSerial + "..."
+    waitTime = 0
+    while device.isProcessAlive("android.theme.app"):
+        time.sleep(1)
+        waitTime = waitTime + 1
+        if waitTime > 180:
+            print "Timed out"
+            break
+
+    time.sleep(10)
+    resDir = getResDir(outPath, resName)
+
+    print "Pulling images from " + deviceSerial + " to " + resDir + ".zip"
+    device.runAdbCommand("pull " + OUT_FILE + " " + resDir + ".zip")
+    device.runAdbCommand("shell rm -rf " + OUT_FILE)
+    return True
+
+def main(argv):
+    if len(argv) < 3:
+        print "run_theme_capture_device.py themeApkPath outDir"
+        sys.exit(1)
+    themeApkPath = argv[1]
+    outPath = os.path.abspath(argv[2])
+    os.system("mkdir -p " + outPath)
+
+    tasks = []
+    tasks.append(doCapturing)
+
+    devices = runAdbDevices();
+    numberThreads = len(devices)
+
+    configQ = Queue.Queue()
+    for device in devices:
+        configQ.put(device)
+    setup = (themeApkPath, outPath)
+    result = executeParallel(tasks, setup, configQ, numberThreads)
+
+    if result > 0:
+        print 'Generated reference images for %(count)d devices' % {"count": result}
+    else:
+        print 'Failed to generate reference images'
+
+if __name__ == '__main__':
+    main(sys.argv)
diff --git a/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java b/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
index ba880d7..63c7472 100644
--- a/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
+++ b/hostsidetests/theme/src/android/theme/cts/ComparisonTask.java
@@ -23,6 +23,7 @@
 import java.awt.Color;
 import java.awt.image.BufferedImage;
 import java.io.File;
+import java.io.IOException;
 import java.lang.String;
 import java.util.concurrent.Callable;
 
@@ -32,70 +33,62 @@
  * Compares the images generated by the device with the reference images.
  */
 public class ComparisonTask implements Callable<Boolean> {
-
-    private static final String TAG = ComparisonTask.class.getSimpleName();
+    private static final String TAG = "ComparisonTask";
 
     private static final int IMAGE_THRESHOLD = 2;
 
-    private static final String STORAGE_PATH_DEVICE = "/sdcard/cts-holo-assets/%s.png";
-
     private final ITestDevice mDevice;
+    private final File mExpected;
+    private final File mActual;
 
-    private final File mReference;
-
-    private final String mName;
-
-    public ComparisonTask(ITestDevice device, File reference, String name) {
+    public ComparisonTask(ITestDevice device, File expected, File actual) {
         mDevice = device;
-        mReference = reference;
-        mName = name;
+        mExpected = expected;
+        mActual = actual;
     }
 
     public Boolean call() {
         boolean success = false;
-        File generated = null;
+
         try {
-            generated = File.createTempFile("gen_" + mName, ".png");
-
-            final String remoteGenerated = String.format(STORAGE_PATH_DEVICE, mName);
-            if (!mDevice.doesFileExist(remoteGenerated)) {
-                Log.logAndDisplay(LogLevel.ERROR, TAG, "File " + remoteGenerated + " have not been saved on device");
-                return false;
-            }
-            mDevice.pullFile(remoteGenerated, generated);
-
-            final BufferedImage ref = ImageIO.read(mReference);
-            final BufferedImage gen = ImageIO.read(generated);
-            if (compare(ref, gen, IMAGE_THRESHOLD)) {
+            final BufferedImage expected = ImageIO.read(mExpected);
+            final BufferedImage actual = ImageIO.read(mActual);
+            if (compare(expected, actual, IMAGE_THRESHOLD)) {
                 success = true;
             } else {
-                File diff = File.createTempFile("diff_" + mName, ".png");
-                createDiff(ref, gen, diff);
+                final File diff = File.createTempFile("diff_" + mExpected.getName(), ".png");
+                createDiff(expected, actual, diff);
                 Log.logAndDisplay(LogLevel.INFO, TAG, "Diff created: " + diff.getPath());
             }
-        } catch (Exception e) {
-            Log.logAndDisplay(LogLevel.ERROR, TAG, String.format(STORAGE_PATH_DEVICE, mName));
+        } catch (IOException e) {
             Log.logAndDisplay(LogLevel.ERROR, TAG, e.toString());
             e.printStackTrace();
-        } finally {
-            if (generated != null) {
-                generated.delete();
-            }
         }
+
         return success;
     }
 
-    private static boolean compare(BufferedImage reference, BufferedImage generated, int threshold) {
-        final int w = generated.getWidth();
-        final int h = generated.getHeight();
-        if (w != reference.getWidth() || h != reference.getHeight()) {
+    /**
+     * Verifies that the pixels of reference and generated images are similar
+     * within a specified threshold.
+     *
+     * @param expected expected image
+     * @param actual actual image
+     * @param threshold maximum difference per channel
+     * @return {@code true} if the images are similar, false otherwise
+     */
+    private static boolean compare(BufferedImage expected, BufferedImage actual,
+            int threshold) {
+        final int w = actual.getWidth();
+        final int h = actual.getHeight();
+        if (w != expected.getWidth() || h != expected.getHeight()) {
             return false;
         }
 
         for (int i = 0; i < w; i++) {
             for (int j = 0; j < h; j++) {
-                final int p1 = reference.getRGB(i, j);
-                final int p2 = generated.getRGB(i, j);
+                final int p1 = expected.getRGB(i, j);
+                final int p2 = actual.getRGB(i, j);
                 final int dr = (p1 & 0x000000FF) - (p2 & 0x000000FF);
                 final int dg = ((p1 & 0x0000FF00) - (p2 & 0x0000FF00)) >> 8;
                 final int db = ((p1 & 0x00FF0000) - (p2 & 0x00FF0000)) >> 16;
@@ -112,45 +105,49 @@
         return true;
     }
 
-    private static void createDiff(BufferedImage image1, BufferedImage image2, File out)
-            throws Exception {
-        final int w1 = image1.getWidth();
-        final int h1 = image1.getHeight();
-        final int w2 = image2.getWidth();
-        final int h2 = image2.getHeight();
+    private static void createDiff(BufferedImage expected, BufferedImage actual, File out)
+            throws IOException {
+        final int w1 = expected.getWidth();
+        final int h1 = expected.getHeight();
+        final int w2 = actual.getWidth();
+        final int h2 = actual.getHeight();
         final int width = Math.max(w1, w2);
         final int height = Math.max(h1, h2);
+
         // The diff will contain image1, image2 and the difference between the two.
-        final BufferedImage diff = new BufferedImage(width * 3, height, BufferedImage.TYPE_INT_ARGB);
+        final BufferedImage diff = new BufferedImage(
+                width * 3, height, BufferedImage.TYPE_INT_ARGB);
 
         for (int i = 0; i < width; i++) {
             for (int j = 0; j < height; j++) {
                 final boolean inBounds1 = i < w1 && j < h1;
                 final boolean inBounds2 = i < w2 && j < h2;
-                int color1 = Color.WHITE.getRGB();
-                int color2 = Color.WHITE.getRGB();
-                int color3;
+                int colorExpected = Color.WHITE.getRGB();
+                int colorActual = Color.WHITE.getRGB();
+                int colorDiff;
                 if (inBounds1 && inBounds2) {
-                    color1 = image1.getRGB(i, j);
-                    color2 = image2.getRGB(i, j);
-                    color3 = color1 == color2 ? color1 : Color.RED.getRGB();
+                    colorExpected = expected.getRGB(i, j);
+                    colorActual = actual.getRGB(i, j);
+                    colorDiff = colorExpected == colorActual ? colorExpected : Color.RED.getRGB();
                 } else if (inBounds1 && !inBounds2) {
-                    color1 = image1.getRGB(i, j);
-                    color3 = Color.BLUE.getRGB();
+                    colorExpected = expected.getRGB(i, j);
+                    colorDiff = Color.BLUE.getRGB();
                 } else if (!inBounds1 && inBounds2) {
-                    color2 = image2.getRGB(i, j);
-                    color3 = Color.GREEN.getRGB();
+                    colorActual = actual.getRGB(i, j);
+                    colorDiff = Color.GREEN.getRGB();
                 } else {
-                    color3 = Color.MAGENTA.getRGB();
+                    colorDiff = Color.MAGENTA.getRGB();
                 }
+
                 int x = i;
-                diff.setRGB(x, j, color1);
+                diff.setRGB(x, j, colorExpected);
                 x += width;
-                diff.setRGB(x, j, color2);
+                diff.setRGB(x, j, colorActual);
                 x += width;
-                diff.setRGB(x, j, color3);
+                diff.setRGB(x, j, colorDiff);
             }
         }
+
         ImageIO.write(diff, "png", out);
     }
 
diff --git a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
index 7c4d63a..e17e205 100644
--- a/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
+++ b/hostsidetests/theme/src/android/theme/cts/ThemeHostTest.java
@@ -21,8 +21,8 @@
 import com.android.cts.util.TimeoutReq;
 import com.android.ddmlib.Log;
 import com.android.ddmlib.Log.LogLevel;
-import com.android.ddmlib.IShellOutputReceiver;
 import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IAbi;
@@ -30,145 +30,44 @@
 import com.android.tradefed.testtype.IBuildReceiver;
 
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
+import java.io.IOException;
 import java.io.InputStream;
 import java.lang.String;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Scanner;
-import java.util.concurrent.Callable;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ExecutorCompletionService;
 import java.util.concurrent.ExecutorService;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipInputStream;
 
 /**
- * Test to check the Holo theme has not been changed.
+ * Test to check non-modifiable themes have not been changed.
  */
 public class ThemeHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+    private static final String LOG_TAG = "ThemeHostTest";
+    private static final String APK_NAME = "CtsThemeDeviceApp";
+    private static final String APP_PACKAGE_NAME = "android.theme.app";
 
-    private static final String TAG = ThemeHostTest.class.getSimpleName();
-
-    private static final int ADB_TIMEOUT = 60 * 60 * 1000;//60mins in ms
-
-    /** The package name of the APK. */
-    private static final String PACKAGE = "android.theme.app";
-
-    /** The file name of the APK. */
-    private static final String APK = "CtsThemeDeviceApp.apk";
+    private static final String GENERATED_ASSETS_ZIP = "/sdcard/cts-theme-assets.zip";
 
     /** The class name of the main activity in the APK. */
-    private static final String CLASS = "HoloDeviceActivity";
+    private static final String CLASS = "GenerateImagesActivity";
 
     /** The command to launch the main activity. */
     private static final String START_CMD = String.format(
-            "am start -W -a android.intent.action.MAIN -n %s/%s.%s", PACKAGE, PACKAGE, CLASS);
+            "am start -W -a android.intent.action.MAIN -n %s/%s.%s", APP_PACKAGE_NAME,
+            APP_PACKAGE_NAME, CLASS);
 
-    private static final String CLEAR_GENERATED_CMD = "rm -rf /sdcard/cts-holo-assets/*.png";
-
-    private static final String STOP_CMD = String.format("am force-stop %s", PACKAGE);
-
+    private static final String CLEAR_GENERATED_CMD = "rm -rf %s/*.png";
+    private static final String STOP_CMD = String.format("am force-stop %s", APP_PACKAGE_NAME);
     private static final String HARDWARE_TYPE_CMD = "dumpsys | grep android.hardware.type";
-
     private static final String DENSITY_PROP_DEVICE = "ro.sf.lcd_density";
-
     private static final String DENSITY_PROP_EMULATOR = "qemu.sf.lcd_density";
 
-    // Intent extras
-    protected final static String INTENT_STRING_EXTRA = " --es %s %s";
-
-    protected final static String INTENT_BOOLEAN_EXTRA = " --ez %s %b";
-
-    protected final static String INTENT_INTEGER_EXTRA = " --ei %s %d";
-
-    // Intent extra keys
-    private static final String EXTRA_THEME = "holo_theme_extra";
-
-    private static final String[] THEMES = {
-            "holo",
-            "holo_dialog",
-            "holo_dialog_minwidth",
-            "holo_dialog_noactionbar",
-            "holo_dialog_noactionbar_minwidth",
-            "holo_dialogwhenlarge",
-            "holo_dialogwhenlarge_noactionbar",
-            "holo_inputmethod",
-            "holo_light",
-            "holo_light_darkactionbar",
-            "holo_light_dialog",
-            "holo_light_dialog_minwidth",
-            "holo_light_dialog_noactionbar",
-            "holo_light_dialog_noactionbar_minwidth",
-            "holo_light_dialogwhenlarge",
-            "holo_light_dialogwhenlarge_noactionbar",
-            "holo_light_noactionbar",
-            "holo_light_noactionbar_fullscreen",
-            "holo_light_panel",
-            "holo_noactionbar",
-            "holo_noactionbar_fullscreen",
-            "holo_panel",
-            "holo_wallpaper",
-            "holo_wallpaper_notitlebar",
-    };
-
-    private final int NUM_THEMES = THEMES.length;
-
-    private static final String[] LAYOUTS = {
-            "button",
-            "button_pressed",
-            "checkbox",
-            "checkbox_checked",
-            "chronometer",
-            "color_blue_bright",
-            "color_blue_dark",
-            "color_blue_light",
-            "color_green_dark",
-            "color_green_light",
-            "color_orange_dark",
-            "color_orange_light",
-            "color_purple",
-            "color_red_dark",
-            "color_red_light",
-            "datepicker",
-            "display_info",
-            "edittext",
-            "progressbar_horizontal_0",
-            "progressbar_horizontal_100",
-            "progressbar_horizontal_50",
-            "progressbar_large",
-            "progressbar_small",
-            "progressbar",
-            "radiobutton_checked",
-            "radiobutton",
-            "radiogroup_horizontal",
-            "radiogroup_vertical",
-            "ratingbar_0",
-            "ratingbar_2point5",
-            "ratingbar_5",
-            "ratingbar_0_pressed",
-            "ratingbar_2point5_pressed",
-            "ratingbar_5_pressed",
-            "searchview_query",
-            "searchview_query_hint",
-            "seekbar_0",
-            "seekbar_100",
-            "seekbar_50",
-            "spinner",
-            "switch_button_checked",
-            "switch_button",
-            "textview",
-            "timepicker",
-            "togglebutton_checked",
-            "togglebutton",
-            "zoomcontrols",
-    };
-
-    private final int NUM_LAYOUTS = LAYOUTS.length;
-
-    private final HashMap<String, File> mReferences = new HashMap<String, File>();
+    private final HashMap<String, File> mReferences = new HashMap<>();
 
     /** The ABI to use. */
     private IAbi mAbi;
@@ -197,32 +96,21 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        // Get the device, this gives a handle to run commands and install APKs.
+
         mDevice = getDevice();
-        // Remove any previously installed versions of this APK.
-        mDevice.uninstallPackage(PACKAGE);
+        mDevice.uninstallPackage(APP_PACKAGE_NAME);
+
         // Get the APK from the build.
-        File app = mBuild.getTestApp(APK);
-        // Get the ABI flag.
-        String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
-        // Install the APK on the device.
+        final File app = mBuild.getTestApp(String.format("%s.apk", APK_NAME));
+        final String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+
         mDevice.installPackage(app, false, options);
-        // Remove previously generated images.
-        mDevice.executeShellCommand(CLEAR_GENERATED_CMD);
-        final String densityProp;
 
-        if (mDevice.getSerialNumber().startsWith("emulator-")) {
-            densityProp = DENSITY_PROP_EMULATOR;
-        } else {
-            densityProp = DENSITY_PROP_DEVICE;
-        }
+        final String density = getDensityBucketForDevice(mDevice);
+        final String zipFile = String.format("/%s.zip", density);
+        Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Loading resources from " + zipFile);
 
-        final String zip = String.format("/%s.zip",
-                getDensityBucket(Integer.parseInt(mDevice.getProperty(densityProp))));
-        Log.logAndDisplay(LogLevel.INFO, TAG, "Loading resources from " + zip);
-
-
-        final InputStream zipStream = this.getClass().getResourceAsStream(zip);
+        final InputStream zipStream = ThemeHostTest.class.getResourceAsStream(zipFile);
         if (zipStream != null) {
             final ZipInputStream in = new ZipInputStream(zipStream);
             try {
@@ -232,21 +120,28 @@
                     final String name = ze.getName();
                     final File tmp = File.createTempFile("ref_" + name, ".png");
                     final FileOutputStream out = new FileOutputStream(tmp);
+
                     int count;
                     while ((count = in.read(buffer)) != -1) {
                         out.write(buffer, 0, count);
                     }
+
                     out.flush();
                     out.close();
                     mReferences.put(name, tmp);
                 }
+            } catch (IOException e) {
+                Log.logAndDisplay(LogLevel.ERROR, LOG_TAG, "Failed to unzip assets: " + zipFile);
             } finally {
                 in.close();
             }
+        } else {
+            Log.logAndDisplay(LogLevel.ERROR, LOG_TAG, "Failed to get resource: " + zipFile);
         }
 
-        mExecutionService = Executors.newFixedThreadPool(2);// 2 worker threads
-        mCompletionService = new ExecutorCompletionService<Boolean>(mExecutionService);
+        final int numCores = Runtime.getRuntime().availableProcessors();
+        mExecutionService = Executors.newFixedThreadPool(numCores * 2);
+        mCompletionService = new ExecutorCompletionService<>(mExecutionService);
     }
 
     @Override
@@ -255,86 +150,148 @@
         for (File ref : mReferences.values()) {
             ref.delete();
         }
+
         mExecutionService.shutdown();
+
         // Remove the APK.
-        mDevice.uninstallPackage(PACKAGE);
+        mDevice.uninstallPackage(APP_PACKAGE_NAME);
+
         // Remove generated images.
         mDevice.executeShellCommand(CLEAR_GENERATED_CMD);
+
         super.tearDown();
     }
 
     @TimeoutReq(minutes = 60)
-    public void testHoloThemes() throws Exception {
-        if (checkHardwareTypeSkipTest(
-                mDevice.executeShellCommand(HARDWARE_TYPE_CMD).trim())) {
-            Log.logAndDisplay(LogLevel.INFO, TAG, "Skipped HoloThemes test for watch and TV");
+    public void testThemes() throws Exception {
+        if (checkHardwareTypeSkipTest(mDevice.executeShellCommand(HARDWARE_TYPE_CMD).trim())) {
+            Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Skipped themes test for watch");
             return;
         }
 
         if (mReferences.isEmpty()) {
-            Log.logAndDisplay(LogLevel.INFO, TAG,
-                    "Skipped HoloThemes test due to no reference images");
+            Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Skipped themes test due to no reference images");
             return;
         }
 
+        Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Generating device images...");
+
+        assertTrue("Aborted image generation", generateDeviceImages());
+
+        // Pull ZIP file from remote device.
+        final File localZip = File.createTempFile("generated", ".zip");
+        mDevice.pullFile(GENERATED_ASSETS_ZIP, localZip);
+
         int numTasks = 0;
-        for (int i = 0; i < NUM_THEMES; i++) {
-            final String themeName = THEMES[i];
-            runCapture(i, themeName);
-            for (int j = 0; j < NUM_LAYOUTS; j++) {
-                final String name = String.format("%s_%s", themeName, LAYOUTS[j]);
-                final File ref = mReferences.get(name + ".png");
-                if (!ref.exists()) {
-                    Log.logAndDisplay(LogLevel.INFO, TAG,
-                            "Skipping theme test due to missing reference for reference image " +
-                            name);
-                    continue;
+
+        Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Extracting generated images...");
+
+        // Extract generated images to temporary files.
+        final byte[] data = new byte[4096];
+        final ZipInputStream zipInput = new ZipInputStream(new FileInputStream(localZip));
+        ZipEntry entry;
+        while ((entry = zipInput.getNextEntry()) != null) {
+            final String name = entry.getName();
+            final File expected = mReferences.get(name);
+            if (expected != null && expected.exists()) {
+                final File actual = File.createTempFile("actual_" + name, ".png");
+                final FileOutputStream pngOutput = new FileOutputStream(actual);
+
+                int count;
+                while ((count = zipInput.read(data, 0, data.length)) != -1) {
+                    pngOutput.write(data, 0, count);
                 }
-                mCompletionService.submit(new ComparisonTask(mDevice, ref, name));
+
+                pngOutput.flush();
+                pngOutput.close();
+
+                mCompletionService.submit(new ComparisonTask(mDevice, expected, actual));
                 numTasks++;
+            } else {
+                Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Missing reference image for " + name);
             }
+
+            zipInput.closeEntry();
         }
+
+        zipInput.close();
+
+        Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Waiting for comparison tasks...");
+
         int failures = 0;
-        for (int i = 0; i < numTasks; i++) {
+        for (int i = numTasks; i > 0; i--) {
             failures += mCompletionService.take().get() ? 0 : 1;
         }
+
         assertTrue(failures + " failures in theme test", failures == 0);
+
+        Log.logAndDisplay(LogLevel.INFO, LOG_TAG, "Finished!");
     }
 
-    private void runCapture(int themeId, String themeName) throws Exception {
-        final StringBuilder sb = new StringBuilder(START_CMD);
-        sb.append(String.format(INTENT_INTEGER_EXTRA, EXTRA_THEME, themeId));
-        final String startCommand = sb.toString();
+    private boolean generateDeviceImages() throws Exception {
         // Clear logcat
         mDevice.executeAdbCommand("logcat", "-c");
+
         // Stop any existing instances
         mDevice.executeShellCommand(STOP_CMD);
-        // Start activity
-        mDevice.executeShellCommand(startCommand);
 
+        // Start activity
+        mDevice.executeShellCommand(START_CMD);
+
+        Log.logAndDisplay(LogLevel.VERBOSE, LOG_TAG, "Starting image generation...");
+
+        boolean aborted = false;
         boolean waiting = true;
         do {
             // Dump logcat.
             final String logs = mDevice.executeAdbCommand(
                     "logcat", "-v", "brief", "-d", CLASS + ":I", "*:S");
+
             // Search for string.
             final Scanner in = new Scanner(logs);
             while (in.hasNextLine()) {
                 final String line = in.nextLine();
                 if (line.startsWith("I/" + CLASS)) {
                     final String[] lineSplit = line.split(":");
-                    final String s = lineSplit[1].trim();
-                    final String themeNameGenerated = lineSplit[2].trim();
-                    if (s.equals("OKAY") && themeNameGenerated.equals(themeName)) {
-                        waiting = false;
+                    if (lineSplit.length >= 3) {
+                        final String cmd = lineSplit[1].trim();
+                        final String arg = lineSplit[2].trim();
+                        switch (cmd) {
+                            case "FAIL":
+                                Log.logAndDisplay(LogLevel.WARN, LOG_TAG, line);
+                                Log.logAndDisplay(LogLevel.WARN, LOG_TAG, "Aborting! Check host logs for details.");
+                                aborted = true;
+                                // fall-through
+                            case "OKAY":
+                                waiting = false;
+                                break;
+                        }
                     }
                 }
             }
             in.close();
-        } while (waiting);
+        } while (waiting && !aborted);
+
+        Log.logAndDisplay(LogLevel.VERBOSE, LOG_TAG, "Image generation completed!");
+
+        return !aborted;
     }
 
-    private static String getDensityBucket(int density) {
+    private static String getDensityBucketForDevice(ITestDevice device) {
+        final String densityProp;
+        if (device.getSerialNumber().startsWith("emulator-")) {
+            densityProp = DENSITY_PROP_EMULATOR;
+        } else {
+            densityProp = DENSITY_PROP_DEVICE;
+        }
+
+        final int density;
+        try {
+            density = Integer.parseInt(device.getProperty(densityProp));
+        } catch (DeviceNotAvailableException e) {
+            return "unknown";
+        }
+
         switch (density) {
             case 120:
                 return "ldpi";
@@ -363,9 +320,7 @@
         if (hardwareTypeString.contains("android.hardware.type.watch")) {
             return true;
         }
-        if (hardwareTypeString.contains("android.hardware.type.television")) {
-            return true;
-        }
+
         return false;
     }
 }
diff --git a/tests/sample/src/android/sample/cts/SampleJUnit4DeviceTest.java b/tests/sample/src/android/sample/cts/SampleJUnit4DeviceTest.java
new file mode 100755
index 0000000..c8863b3
--- /dev/null
+++ b/tests/sample/src/android/sample/cts/SampleJUnit4DeviceTest.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.sample.cts;
+
+import org.junit.Assert;
+import org.junit.runner.RunWith;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import android.app.Activity;
+import android.sample.SampleDeviceActivity;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.ActivityInstrumentationTestCase2;
+
+/**
+ * A simple compatibility test which tests the SharedPreferences API.
+ *
+ * This test uses {@link ActivityTestRule} to instrument the
+ * {@link android.sample.SampleDeviceActivity}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class SampleJUnit4DeviceTest {
+
+    private static final String KEY = "foo";
+
+    private static final String VALUE = "bar";
+
+    @Rule
+    public ActivityTestRule<SampleDeviceActivity> mActivityRule =
+        new ActivityTestRule(SampleDeviceActivity.class);
+
+
+    /**
+     * This inserts the key value pair and assert they can be retrieved. Then it clears the
+     * preferences and asserts they can no longer be retrieved.
+     *
+     * @throws Exception
+     */
+    @Test
+    public void shouldSaveSharedPreferences() throws Exception {
+        // Save the key value pair to the preferences and assert they were saved.
+        mActivityRule.getActivity().savePreference(KEY, VALUE);
+        Assert.assertEquals("Preferences were not saved", VALUE,
+            mActivityRule.getActivity().getPreference(KEY));
+
+        // Clear the shared preferences and assert the data was removed.
+        mActivityRule.getActivity().clearPreferences();
+        Assert.assertNull("Preferences were not cleared",
+            mActivityRule.getActivity().getPreference(KEY));
+    }
+}
diff --git a/tests/tests/assist/AndroidManifest.xml b/tests/tests/assist/AndroidManifest.xml
index 97bd874..fefdf54 100644
--- a/tests/tests/assist/AndroidManifest.xml
+++ b/tests/tests/assist/AndroidManifest.xml
@@ -23,8 +23,9 @@
 
     <application>
       <uses-library android:name="android.test.runner" />
-      <activity android:name="android.assist.TestStartActivity"
-                android:label="Assist Target">
+      <activity android:name="android.assist.cts.TestStartActivity"
+                android:label="Assist Structure Test App"
+                android:theme="@android:style/Theme.Material.Light">
           <intent-filter>
               <action android:name="android.intent.action.TEST_START_ACTIVITY_ASSIST_STRUCTURE" />
               <action android:name="android.intent.action.TEST_START_ACTIVITY_DISABLE_CONTEXT" />
diff --git a/tests/tests/assist/common/src/android/assist/common/Utils.java b/tests/tests/assist/common/src/android/assist/common/Utils.java
index 8d555da..4bf9412 100644
--- a/tests/tests/assist/common/src/android/assist/common/Utils.java
+++ b/tests/tests/assist/common/src/android/assist/common/Utils.java
@@ -46,9 +46,11 @@
 
     /** Flag Secure Test intent constants */
     public static final String FLAG_SECURE_HASRESUMED = ACTION_PREFIX + "flag_secure_hasResumed";
+    public static final String ASSIST_STRUCTURE_HASRESUMED = ACTION_PREFIX
+            + "assist_structure_hasResumed";
 
     /** Two second timeout for getting back assist context */
-    public static final int TIMEOUT_MS = 2 * 1000; // TODO(awlee): what is the timeout
+    public static final int TIMEOUT_MS = 2 * 1000;
     public static final int ACTIVITY_ONRESUME_TIMEOUT_MS = 4000;
     public static final String EXTRA_REGISTER_RECEIVER = "register_receiver";
 
@@ -66,10 +68,9 @@
      */
     public static final String getTestActivity(String testCaseType) {
         switch (testCaseType) {
-            case ASSIST_STRUCTURE:
-                return "service.AssistStructureActivity";
             case DISABLE_CONTEXT:
                 return "service.DisableContextActivity";
+            case ASSIST_STRUCTURE:
             case FLAG_SECURE:
             case LIFECYCLE:
                 return "service.DelayedAssistantActivity";
@@ -84,9 +85,11 @@
     public static final ComponentName getTestAppComponent(String testCaseType) {
         switch (testCaseType) {
             case ASSIST_STRUCTURE:
-            case DISABLE_CONTEXT:
                 return new ComponentName(
                         "android.assist.testapp", "android.assist.testapp.TestApp");
+            case DISABLE_CONTEXT:
+                return new ComponentName(
+                        "android.assist.testapp", "android.assist.testapp.DisableContextActivity");
             case FLAG_SECURE:
                 return new ComponentName(
                         "android.assist.testapp", "android.assist.testapp.SecureActivity");
diff --git a/tests/tests/assist/res/layout/test_app.xml b/tests/tests/assist/res/layout/test_app.xml
new file mode 100644
index 0000000..3fbfd6d
--- /dev/null
+++ b/tests/tests/assist/res/layout/test_app.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/welcome" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="350dp"
+        android:orientation="vertical"
+        android:layout_gravity="bottom">
+        <FrameLayout
+            android:id="@+id/card1"
+            android:layout_width="match_parent"
+            android:layout_height="150dp"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:layout_marginTop="16dp"
+            android:elevation="3dp">
+        </FrameLayout>
+        <View
+            android:id="@+id/card2"
+            android:layout_width="match_parent"
+            android:layout_height="200dp"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:layout_marginTop="16dp"
+            android:elevation="3dp"/>
+    </LinearLayout>
+</RelativeLayout>
\ No newline at end of file
diff --git a/tests/tests/assist/res/values/strings.xml b/tests/tests/assist/res/values/strings.xml
new file mode 100644
index 0000000..ae4f16e
--- /dev/null
+++ b/tests/tests/assist/res/values/strings.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<resources>
+    <string name="welcome">Hello there!</string>
+</resources>
diff --git a/tests/tests/assist/service/AndroidManifest.xml b/tests/tests/assist/service/AndroidManifest.xml
index cdbeef0..5a22d31 100644
--- a/tests/tests/assist/service/AndroidManifest.xml
+++ b/tests/tests/assist/service/AndroidManifest.xml
@@ -31,14 +31,7 @@
               <action android:name="android.service.voice.VoiceInteractionService" />
           </intent-filter>
       </service>
-      <activity android:name=".AssistStructureActivity" >
-          <intent-filter>
-              <action android:name="android.intent.action.START_TEST_ASSIST_STRUCTURE" />
-              <category android:name="android.intent.category.DEFAULT" />
-          </intent-filter>
-      </activity>
-      <activity android:name=".DisableContextActivity"
-                android:label="Disabled Context Activity">
+      <activity android:name=".DisableContextActivity" >
           <intent-filter>
               <action android:name="android.intent.action.START_TEST_DISABLE_CONTEXT" />
               <category android:name="android.intent.category.DEFAULT" />
@@ -47,6 +40,7 @@
       <activity android:name=".DelayedAssistantActivity"
                 android:label="Delay Assistant Start Activity">
           <intent-filter>
+              <action android:name="android.intent.action.START_TEST_ASSIST_STRUCTURE" />
               <action android:name="android.intent.action.START_TEST_LIFECYCLE" />
               <action android:name="android.intent.action.START_TEST_FLAG_SECURE" />
               <category android:name="android.intent.category.DEFAULT" />
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/DelayedAssistantActivity.java b/tests/tests/assist/service/src/android/voiceinteraction/service/DelayedAssistantActivity.java
index 31d1694..b7d67f1 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/DelayedAssistantActivity.java
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/DelayedAssistantActivity.java
@@ -24,7 +24,7 @@
 import android.util.Log;
 
 public class DelayedAssistantActivity extends Activity {
-    static final String TAG = "DelatyedAssistantActivity";
+    static final String TAG = "DelayedAssistantActivity";
 
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/DisableContextActivity.java b/tests/tests/assist/service/src/android/voiceinteraction/service/DisableContextActivity.java
index 52ba7ac..2fd540b 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/DisableContextActivity.java
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/DisableContextActivity.java
@@ -22,9 +22,6 @@
 import android.os.Bundle;
 import android.util.Log;
 
-/**
- * TODO(awlee): Change context on/off settings and test
- */
 public class DisableContextActivity extends Activity {
     static final String TAG = "DisableContextActivity";
 
@@ -38,8 +35,8 @@
         super.onStart();
         Intent intent = new Intent();
         intent.setComponent(new ComponentName(this, MainInteractionService.class));
+        Log.i(TAG, "Starting service.");
         finish();
-        ComponentName serviceName = startService(intent);
-        Log.i(TAG, "Started service: " + serviceName);
+        startService(intent);
     }
 }
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
index 7530933..fc19e1c 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionService.java
@@ -51,7 +51,7 @@
     }
 
     private void maybeStart() {
-       if (mIntent == null || !mReady) {
+        if (mIntent == null || !mReady) {
             Log.wtf(TAG, "Can't start session because either intent is null or onReady() "
                     + "has not been called yet. mIntent = " + mIntent + ", mReady = " + mReady);
         } else {
@@ -60,18 +60,18 @@
                     Log.i(TAG, "Registering receiver to start session later");
                     if (mBroadcastReceiver == null) {
                         mBroadcastReceiver = new MainInteractionServiceBroadcastReceiver();
-                        registerReceiver(mBroadcastReceiver, 
+                        registerReceiver(mBroadcastReceiver,
                                 new IntentFilter(Utils.BROADCAST_INTENT_START_ASSIST));
                     }
                     sendBroadcast(new Intent(Utils.ASSIST_RECEIVER_REGISTERED));
-                } else {
-                    Log.i(TAG, "Yay! about to start session");
-                    showSession(new Bundle(), VoiceInteractionSession.SHOW_WITH_ASSIST |
-                                VoiceInteractionSession.SHOW_WITH_SCREENSHOT);
-                }
+              } else {
+                  Log.i(TAG, "Yay! about to start session");
+                  showSession(new Bundle(), VoiceInteractionSession.SHOW_WITH_ASSIST |
+                          VoiceInteractionSession.SHOW_WITH_SCREENSHOT);
+              }
             } else {
                 Log.wtf(TAG, "**** Not starting MainInteractionService because" +
-                    " it is not set as the current voice interaction service");
+                        " it is not set as the current voice interaction service");
             }
         }
     }
@@ -79,6 +79,7 @@
     private class MainInteractionServiceBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
+            Log.i(MainInteractionService.TAG, "Recieved broadcast to start session now.");
             if (intent.getAction().equals(Utils.BROADCAST_INTENT_START_ASSIST)) {
                 showSession(new Bundle(), SHOW_WITH_ASSIST | SHOW_WITH_SCREENSHOT);
             }
@@ -91,4 +92,4 @@
             unregisterReceiver(mBroadcastReceiver);
         }
     }
-}
+}
\ No newline at end of file
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
index f297b3e..38d03f8 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
+++ b/tests/tests/assist/service/src/android/voiceinteraction/service/MainInteractionSession.java
@@ -92,7 +92,7 @@
         /*@Nullable*/ AssistContent content) {
         Log.i(TAG, "onHandleAssist");
         Log.i(TAG,
-            String.format("Bundle: %s, Structure: %s, Content: %s", data, structure, content));
+                String.format("Bundle: %s, Structure: %s, Content: %s", data, structure, content));
         super.onHandleAssist(data, structure, content);
 
         // send to test to verify that this is accurate.
diff --git a/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java b/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java
index 763ecef..8ff235b 100644
--- a/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistStructureTest.java
@@ -18,7 +18,15 @@
 
 import android.assist.common.Utils;
 
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
 import android.provider.Settings;
+import android.util.Log;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 
 /**
@@ -26,10 +34,11 @@
  */
 
 public class AssistStructureTest extends AssistTestBase {
-    static final String TAG = "AssistStructureTest";
-
     private static final String TEST_CASE_TYPE = Utils.ASSIST_STRUCTURE;
 
+    private BroadcastReceiver mReceiver;
+    private CountDownLatch mHasResumedLatch = new CountDownLatch(1);
+
     public AssistStructureTest() {
         super();
     }
@@ -37,13 +46,59 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        setUpAndRegisterReceiver();
         startTestActivity(TEST_CASE_TYPE);
-        waitForBroadcast();
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        super.tearDown();
+        if (mReceiver != null) {
+            mContext.unregisterReceiver(mReceiver);
+            mReceiver = null;
+        }
+    }
+
+    private void setUpAndRegisterReceiver() {
+        if (mReceiver != null) {
+            mContext.unregisterReceiver(mReceiver);
+        }
+        mReceiver = new AssistStructureTestBroadcastReceiver();
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(Utils.ASSIST_STRUCTURE_HASRESUMED);
+        filter.addAction(Utils.ASSIST_RECEIVER_REGISTERED);
+        mContext.registerReceiver(mReceiver, filter);
+    }
+
+    private void waitForOnResume() throws Exception {
+        Log.i(TAG, "waiting for onResume() before continuing");
+        if (!mHasResumedLatch.await(Utils.ACTIVITY_ONRESUME_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("Activity failed to resume in " + Utils.ACTIVITY_ONRESUME_TIMEOUT_MS + "msec");
+        }
     }
 
     public void testAssistStructure() throws Exception {
+        mTestActivity.start3pApp(TEST_CASE_TYPE);
+        mTestActivity.startTest(TEST_CASE_TYPE);
+        waitForAssistantToBeReady();
+        waitForOnResume();
+        startSession();
+        waitForContext();
         verifyAssistDataNullness(false, false, false, false);
+
         verifyAssistStructure(Utils.getTestAppComponent(TEST_CASE_TYPE),
-                    false /*FLAG_SECURE set*/);
+                false /*FLAG_SECURE set*/);
+    }
+
+    private class AssistStructureTestBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Utils.ASSIST_STRUCTURE_HASRESUMED)) {
+                mHasResumedLatch.countDown();
+            } else if (action.equals(Utils.ASSIST_RECEIVER_REGISTERED)) {
+                mAssistantReadyLatch.countDown();
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/tests/tests/assist/src/android/assist/cts/AssistTest.java b/tests/tests/assist/src/android/assist/cts/AssistTest.java
deleted file mode 100644
index 5241c4e..0000000
--- a/tests/tests/assist/src/android/assist/cts/AssistTest.java
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.assist.cts;
-
-import android.assist.TestStartActivity;
-
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.Bundle;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-
-import junit.framework.Assert;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-import android.assist.common.Utils;
-
-public class AssistTest extends ActivityInstrumentationTestCase2<TestStartActivity> {
-    static final String TAG = "AssistTest";
-    private static final int TIMEOUT_MS = 2 * 1000;
-
-    public AssistTest() {
-        super(TestStartActivity.class);
-    }
-}
diff --git a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
index a7e7087..25460e5 100644
--- a/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
+++ b/tests/tests/assist/src/android/assist/cts/AssistTestBase.java
@@ -16,16 +16,19 @@
 
 package android.assist.cts;
 
-import android.assist.TestStartActivity;
+import android.assist.cts.TestStartActivity;
 import android.assist.common.Utils;
 
 import android.app.assist.AssistContent;
 import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
+import android.app.assist.AssistStructure.WindowNode;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.cts.util.SystemUtil;
 import android.graphics.Bitmap;
@@ -34,6 +37,11 @@
 import android.provider.Settings;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.TextView;
 
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -49,7 +57,9 @@
     protected Bundle mAssistBundle;
     protected Context mContext;
     protected CountDownLatch mLatch, mAssistantReadyLatch;
+
     private String mTestName;
+    private View mView;
 
     public AssistTestBase() {
         super(TestStartActivity.class);
@@ -61,9 +71,9 @@
         mAssistantReadyLatch = new CountDownLatch(1);
         mContext = getInstrumentation().getTargetContext();
         SystemUtil.runShellCommand(getInstrumentation(),
-            "settings put secure assist_structure_enabled 1");
+                "settings put secure assist_structure_enabled 1");
         SystemUtil.runShellCommand(getInstrumentation(),
-            "settings put secure assist_screenshot_enabled 1");
+                "settings put secure assist_screenshot_enabled 1");
         logContextAndScreenshotSetting();
     }
 
@@ -80,7 +90,7 @@
         mTestName = testName;
         intent.setAction("android.intent.action.TEST_START_ACTIVITY_" + testName);
         intent.setComponent(new ComponentName(getInstrumentation().getContext(),
-            TestStartActivity.class));
+                TestStartActivity.class));
         setActivityIntent(intent);
         mTestActivity = getActivity();
     }
@@ -122,13 +132,14 @@
 
         if (!mLatch.await(Utils.TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
             fail("Failed to receive broadcast in " + Utils.TIMEOUT_MS + "msec");
-            return false;
         }
+        Log.i(TAG, "Received broadcast with all information.");
         return true;
     }
 
     /**
      * Checks that the nullness of values are what we expect.
+     *
      * @param isBundleNull True if assistBundle should be null.
      * @param isStructureNull True if assistStructure should be null.
      * @param isContentNull True if assistContent should be null.
@@ -159,7 +170,7 @@
     }
 
     /**
-     * Traverses and compares the view heirarchy of the backgroundApp and the view we expect.
+     * Verifies the view hierarchy of the backgroundApp matches the assist structure.
      *
      * @param backgroundApp ComponentName of app the assistant is invoked upon
      * @param isSecureWindow Denotes whether the activity has FLAG_SECURE set
@@ -169,26 +180,154 @@
         assertEquals(backgroundApp.flattenToString(),
             mAssistStructure.getActivityComponent().flattenToString());
 
-        int numWindows = mAssistStructure.getWindowNodeCount();
-        assertEquals(1, numWindows);
-        for (int i = 0; i < numWindows; i++) {
-            AssistStructure.ViewNode node = mAssistStructure.getWindowNodeAt(i).getRootViewNode();
-            // TODO: Actually traverse the view heirarchy and verify it matches what we expect
-            // If isSecureWindow, will not have any children.
-        }
+        Log.i(TAG, "Traversing down structure for: " + backgroundApp.flattenToString());
+        mView = mTestActivity.findViewById(android.R.id.content).getRootView();
+        verifyHierarchy(mAssistStructure, isSecureWindow);
     }
 
     protected void logContextAndScreenshotSetting() {
         Log.i(TAG, "Context is: " + Settings.Secure.getString(
             mContext.getContentResolver(), "assist_structure_enabled"));
         Log.i(TAG, "Screenshot is: " + Settings.Secure.getString(
-            mContext.getContentResolver(), "assist_screenshot_enabled"));
+                mContext.getContentResolver(), "assist_screenshot_enabled"));
+    }
+
+    /**
+     * Recursively traverse and compare properties in the View hierarchy with the Assist Structure.
+     */
+    public void verifyHierarchy(AssistStructure structure, boolean isSecureWindow) {
+        Log.i(TAG, "verifyHierarchy");
+        Window mWindow = mTestActivity.getWindow();
+
+        int numWindows = structure.getWindowNodeCount();
+        // TODO: multiple windows?
+        assertEquals("Number of windows don't match", 1, numWindows);
+
+        for (int i = 0; i < numWindows; i++) {
+            AssistStructure.WindowNode windowNode = structure.getWindowNodeAt(i);
+            Log.i(TAG, "Title: " + windowNode.getTitle());
+            // verify top level window bounds are as big as the screen and pinned to 0,0
+            assertEquals("Window left position wrong: was " + windowNode.getLeft(),
+                    windowNode.getLeft(), 0);
+            assertEquals("Window top position wrong: was " + windowNode.getTop(),
+                    windowNode.getTop(), 0);
+
+            traverseViewAndStructure(
+                    mView,
+                    windowNode.getRootViewNode(),
+                    isSecureWindow);
+        }
+    }
+
+    private void traverseViewAndStructure(View parentView, ViewNode parentNode,
+            boolean isSecureWindow) {
+        ViewGroup parentGroup;
+
+        if (parentView == null && parentNode == null) {
+            Log.i(TAG, "Views are null, done traversing this branch.");
+            return;
+        } else if (parentNode == null || parentView == null) {
+            fail(String.format("Views don't match. View: %s, Node: %s", parentView, parentNode));
+        }
+
+        // Debugging
+        Log.i(TAG, "parentView is of type: " + parentView.getClass().getName());
+        if (parentView instanceof ViewGroup) {
+            for (int childInt = 0; childInt < ((ViewGroup) parentView).getChildCount();
+                    childInt++) {
+                Log.i(TAG,
+                        "viewchild" + childInt + " is of type: "
+                        + ((ViewGroup) parentView).getChildAt(childInt).getClass().getName());
+            }
+        }
+        if (parentView.getId() > 0) {
+            Log.i(TAG, "View ID: "
+                    + mTestActivity.getResources().getResourceEntryName(parentView.getId()));
+        }
+
+        Log.i(TAG, "parentNode is of type: " + parentNode.getClassName());
+        for (int nodeInt = 0; nodeInt < parentNode.getChildCount(); nodeInt++) {
+            Log.i(TAG,
+                    "nodechild" + nodeInt + " is of type: "
+                    + parentNode.getChildAt(nodeInt).getClassName());
+        }
+            Log.i(TAG, "Node ID: " + parentNode.getIdEntry());
+
+        int numViewChildren = 0;
+        int numNodeChildren = 0;
+        if (parentView instanceof ViewGroup) {
+            numViewChildren = ((ViewGroup) parentView).getChildCount();
+        }
+        numNodeChildren = parentNode.getChildCount();
+
+        if  (isSecureWindow) {
+            assertEquals("Secure window should only traverse root node.", 0, numNodeChildren);
+            isSecureWindow = false;
+            return;
+        } else {
+            assertEquals("Number of children did not match.", numViewChildren, numNodeChildren);
+        }
+
+        verifyViewProperties(parentView, parentNode);
+
+        if (parentView instanceof ViewGroup) {
+            parentGroup = (ViewGroup) parentView;
+
+            // TODO: set a max recursion level
+            for (int i = numNodeChildren - 1; i >= 0; i--) {
+                View childView = parentGroup.getChildAt(i);
+                ViewNode childNode = parentNode.getChildAt(i);
+
+                // if isSecureWindow, should not have reached this point.
+                assertFalse(isSecureWindow);
+                traverseViewAndStructure(childView, childNode, isSecureWindow);
+            }
+        }
+    }
+
+    /**
+     * Compare view properties of the view hierarchy with that reported in the assist structure.
+     */
+    private void verifyViewProperties(View parentView, ViewNode parentNode) {
+        assertEquals("Left positions do not match.", parentView.getLeft(), parentNode.getLeft());
+        assertEquals("Top positions do not match.", parentView.getTop(), parentNode.getTop());
+
+        int viewId = parentView.getId();
+
+        if (viewId > 0) {
+            if (parentNode.getIdEntry() != null) {
+                assertEquals("View IDs do not match.",
+                        mTestActivity.getResources().getResourceEntryName(viewId),
+                        parentNode.getIdEntry());
+            }
+        } else {
+            assertNull("View Node should not have an ID.", parentNode.getIdEntry());
+        }
+
+        assertEquals("Scroll X does not match.",
+                parentView.getScrollX(), parentNode.getScrollX());
+
+        assertEquals("Scroll Y does not match.",
+                parentView.getScrollY(), parentNode.getScrollY());
+
+        assertEquals("Heights do not match.", parentView.getHeight(),
+                parentNode.getHeight());
+
+        assertEquals("Widths do not match.", parentView.getWidth(), parentNode.getWidth());
+
+        // TODO: handle unicode/i18n
+        if (parentView instanceof TextView) {
+            assertEquals("Text in TextView does not match.",
+                    ((TextView) parentView).getText().toString(), parentNode.getText());
+        } else {
+            assertNull(parentNode.getText());
+        }
     }
 
     class TestResultsReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equalsIgnoreCase(Utils.BROADCAST_ASSIST_DATA_INTENT)) { // not necessary?
+            if (intent.getAction().equalsIgnoreCase(Utils.BROADCAST_ASSIST_DATA_INTENT)) {
                 Log.i(TAG, "Received broadcast with assist data.");
                 Bundle assistData = intent.getExtras();
                 AssistTestBase.this.mAssistBundle = assistData.getBundle(Utils.ASSIST_BUNDLE_KEY);
diff --git a/tests/tests/assist/src/android/assist/cts/DisableContextTest.java b/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
index 9b29407..dc28879 100644
--- a/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
+++ b/tests/tests/assist/src/android/assist/cts/DisableContextTest.java
@@ -16,7 +16,6 @@
 
 package android.assist.cts;
 
-import android.assist.TestStartActivity;
 import android.assist.common.Utils;
 
 import android.app.Activity;
@@ -31,7 +30,6 @@
 import android.os.Bundle;
 import android.provider.Settings;
 import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
 
 import java.lang.Override;
 import java.util.concurrent.CountDownLatch;
@@ -97,4 +95,4 @@
 
         verifyAssistDataNullness(true, true, true, true);
     }
-}
+}
\ No newline at end of file
diff --git a/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java b/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
index 40bf7a7..2e9932e 100644
--- a/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
+++ b/tests/tests/assist/src/android/assist/cts/FlagSecureTest.java
@@ -31,7 +31,6 @@
  * invoked on an app with FLAG_SECURE set.
  */
 public class FlagSecureTest extends AssistTestBase {
-
     static final String TAG = "FlagSecureTest";
 
     private static final String TEST_CASE_TYPE = Utils.FLAG_SECURE;
@@ -78,8 +77,13 @@
     }
 
     public void testSecureActivity() throws Exception {
+        mTestActivity.startTest(TEST_CASE_TYPE);
+        waitForAssistantToBeReady();
+        mTestActivity.start3pApp(TEST_CASE_TYPE);
+        waitForOnResume();
+        startSession();
+        waitForContext();
         verifyAssistDataNullness(false, false, false, false);
-
         // verify that we have only the root window and not its children.
         verifyAssistStructure(Utils.getTestAppComponent(TEST_CASE_TYPE), true);
     }
diff --git a/tests/tests/assist/src/android/assist/cts/LifecycleTest.java b/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
index 19a1be5..7451017 100644
--- a/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
+++ b/tests/tests/assist/src/android/assist/cts/LifecycleTest.java
@@ -16,7 +16,7 @@
 
 package android.assist.cts;
 
-import android.assist.TestStartActivity;
+import android.assist.cts.TestStartActivity;
 import android.assist.common.Utils;
 
 import android.app.Activity;
diff --git a/tests/tests/assist/src/android/assist/TestStartActivity.java b/tests/tests/assist/src/android/assist/cts/TestStartActivity.java
similarity index 89%
rename from tests/tests/assist/src/android/assist/TestStartActivity.java
rename to tests/tests/assist/src/android/assist/cts/TestStartActivity.java
index df9b534..16c924f 100644
--- a/tests/tests/assist/src/android/assist/TestStartActivity.java
+++ b/tests/tests/assist/src/android/assist/cts/TestStartActivity.java
@@ -14,15 +14,21 @@
  * limitations under the License.
  */
 
-package android.assist;
+package android.assist.cts;
 
 import android.assist.common.Utils;
 
 import android.app.Activity;
+import android.app.assist.AssistStructure;
+import android.app.assist.AssistStructure.ViewNode;
 import android.content.Intent;
 import android.content.ComponentName;
 import android.os.Bundle;
 import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.TextView;
 
 public class TestStartActivity extends Activity {
     static final String TAG = "TestStartActivity";
@@ -31,6 +37,7 @@
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         Log.i(TAG, " in onCreate");
+        setContentView(R.layout.test_app);
     }
 
     @Override
diff --git a/tests/tests/assist/testapp/AndroidManifest.xml b/tests/tests/assist/testapp/AndroidManifest.xml
index 8d6169c..f14cf22 100644
--- a/tests/tests/assist/testapp/AndroidManifest.xml
+++ b/tests/tests/assist/testapp/AndroidManifest.xml
@@ -22,7 +22,7 @@
         <uses-library android:name="android.test.runner" />
 
         <activity android:name="TestApp"
-                android:label="Assist Test App"
+                android:label="Assist Structure Test App"
                 android:theme="@android:style/Theme.Material.Light">
           <intent-filter>
               <action android:name="android.intent.action.TEST_APP_ASSIST_STRUCTURE" />
@@ -30,8 +30,16 @@
               <category android:name="android.intent.category.VOICE" />
           </intent-filter>
         </activity>
+        <activity android:name="DisableContextActivity"
+            android:label="Disable Context Test Activity"
+            android:theme="@android:style/Theme.Material.Light">
+            <intent-filter>
+                <action android:name="android.intent.action.TEST_APP_DISABLE_CONTEXT" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
         <activity android:name="SecureActivity"
-                  android:label="Secure Test App"
+                  android:label="Secure Test Activity"
                   android:theme="@android:style/Theme.Material.Light">
             <intent-filter>
                 <action android:name="android.intent.action.TEST_APP_FLAG_SECURE" />
diff --git a/tests/tests/assist/testapp/res/layout/secure_app.xml b/tests/tests/assist/testapp/res/layout/secure_app.xml
index 9169a37..3b72ad6 100644
--- a/tests/tests/assist/testapp/res/layout/secure_app.xml
+++ b/tests/tests/assist/testapp/res/layout/secure_app.xml
@@ -14,12 +14,12 @@
      limitations under the License.
 -->
 <RelativeLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:tools="http://schemas.android.com/tools"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
     <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/welcome" />
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/welcome" />
 </RelativeLayout>
\ No newline at end of file
diff --git a/tests/tests/assist/testapp/res/layout/test_app.xml b/tests/tests/assist/testapp/res/layout/test_app.xml
index 9169a37..3fbfd6d 100644
--- a/tests/tests/assist/testapp/res/layout/test_app.xml
+++ b/tests/tests/assist/testapp/res/layout/test_app.xml
@@ -13,13 +13,35 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<RelativeLayout
-        xmlns:android="http://schemas.android.com/apk/res/android"
-        xmlns:tools="http://schemas.android.com/tools"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent">
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
     <TextView
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:text="@string/welcome" />
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:text="@string/welcome" />
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="350dp"
+        android:orientation="vertical"
+        android:layout_gravity="bottom">
+        <FrameLayout
+            android:id="@+id/card1"
+            android:layout_width="match_parent"
+            android:layout_height="150dp"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:layout_marginTop="16dp"
+            android:elevation="3dp">
+        </FrameLayout>
+        <View
+            android:id="@+id/card2"
+            android:layout_width="match_parent"
+            android:layout_height="200dp"
+            android:layout_marginStart="8dp"
+            android:layout_marginEnd="8dp"
+            android:layout_marginTop="16dp"
+            android:elevation="3dp"/>
+    </LinearLayout>
 </RelativeLayout>
\ No newline at end of file
diff --git a/tests/tests/assist/testapp/res/values/strings.xml b/tests/tests/assist/testapp/res/values/strings.xml
index a245b36..ae4f16e 100644
--- a/tests/tests/assist/testapp/res/values/strings.xml
+++ b/tests/tests/assist/testapp/res/values/strings.xml
@@ -1,4 +1,18 @@
 <?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
 <resources>
     <string name="welcome">Hello there!</string>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/tests/tests/assist/service/src/android/voiceinteraction/service/AssistStructureActivity.java b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/DisableContextActivity.java
similarity index 65%
rename from tests/tests/assist/service/src/android/voiceinteraction/service/AssistStructureActivity.java
rename to tests/tests/assist/testapp/src/android/voiceinteraction/testapp/DisableContextActivity.java
index 784d63b..ae570d8 100644
--- a/tests/tests/assist/service/src/android/voiceinteraction/service/AssistStructureActivity.java
+++ b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/DisableContextActivity.java
@@ -14,29 +14,25 @@
  * limitations under the License.
  */
 
-package android.assist.service;
+package android.assist.testapp;
 
 import android.app.Activity;
 import android.content.Intent;
-import android.content.ComponentName;
 import android.os.Bundle;
 import android.util.Log;
 
-public class AssistStructureActivity extends Activity {
-    static final String TAG = "VoiceInteractionMain";
+public class DisableContextActivity extends Activity {
+    static final String TAG = "TestApp";
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        Log.i(TAG, "TestApp created");
+        setContentView(R.layout.test_app);
     }
 
     @Override
-    public void onStart() {
-        super.onStart();
-        Intent intent = new Intent();
-        intent.setComponent(new ComponentName(this, MainInteractionService.class));
-        Log.i(TAG, "Starting service.");
-        finish();
-        startService(intent);
+    public void onResume() {
+        super.onResume();
     }
-}
+}
\ No newline at end of file
diff --git a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/SecureActivity.java b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/SecureActivity.java
index d9b2ff2..708061e 100644
--- a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/SecureActivity.java
+++ b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/SecureActivity.java
@@ -16,11 +16,16 @@
 
 package android.assist.testapp;
 
+import android.assist.common.Utils;
+
 import android.app.Activity;
 import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
 
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
 import android.view.WindowManager;
 
 public class SecureActivity extends Activity {
@@ -37,7 +42,15 @@
     @Override
     protected void onResume() {
         super.onResume();
-        Log.i(TAG, "Activity has resumed");     
-        sendBroadcast(new Intent("android.intent.action.flag_secure_hasResumed"));
+        Log.i(TAG, "Activity has resumed");
+        final View layout = findViewById(android.R.id.content);
+        ViewTreeObserver vto = layout.getViewTreeObserver();
+        vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+            @Override
+            public void onGlobalLayout() {
+                layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                sendBroadcast(new Intent(Utils.FLAG_SECURE_HASRESUMED));
+            }
+        });
     }
 }
diff --git a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/TestApp.java b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/TestApp.java
index 85a9342..ff56ea8 100644
--- a/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/TestApp.java
+++ b/tests/tests/assist/testapp/src/android/voiceinteraction/testapp/TestApp.java
@@ -16,25 +16,41 @@
 
 package android.assist.testapp;
 
+import android.assist.common.Utils;
+
 import android.app.Activity;
+import android.content.Intent;
 import android.os.Bundle;
 import android.util.Log;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+
+import java.lang.Override;
 
 public class TestApp extends Activity {
     static final String TAG = "TestApp";
 
-    Bundle mTestinfo = new Bundle();
-    Bundle mTotalInfo = new Bundle();
 
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         Log.i(TAG, "TestApp created");
-        getLayoutInflater().inflate(R.layout.test_app, null);
+        setContentView(R.layout.test_app);
     }
 
     @Override
     public void onResume() {
         super.onResume();
+        Log.i(TAG, "TestApp has resumed");
+        final View layout = findViewById(android.R.id.content);
+        ViewTreeObserver vto = layout.getViewTreeObserver();
+        vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
+            @Override
+            public void onGlobalLayout() {
+                layout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                sendBroadcast(new Intent(Utils.ASSIST_STRUCTURE_HASRESUMED));
+            }
+        });
     }
 }
\ No newline at end of file
diff --git a/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java b/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
index 30c78a8..d4b6c63 100644
--- a/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
+++ b/tests/tests/content/src/android/content/res/cts/ConfigurationTest.java
@@ -22,6 +22,7 @@
 import android.content.res.Configuration;
 import android.os.Parcel;
 import android.test.AndroidTestCase;
+import android.util.LocaleList;
 import android.view.View;
 
 public class ConfigurationTest extends AndroidTestCase {
@@ -40,7 +41,7 @@
         mConfig = new Configuration();
         mConfig.fontScale = 2;
         mConfig.mcc = mConfig.mnc = 1;
-        mConfig.locale = Locale.getDefault();
+        mConfig.setLocale(Locale.getDefault());
         mConfig.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
         mConfig.keyboard = Configuration.KEYBOARD_NOKEYS;
         mConfig.keyboardHidden = Configuration.KEYBOARDHIDDEN_NO;
@@ -93,6 +94,27 @@
         cfg2.touchscreen = 2;
         assertEquals(1, cfg1.compareTo(cfg2));
 
+        cfg1.setLocales(LocaleList.forLanguageTags("fr"));
+        cfg2.setLocales(LocaleList.forLanguageTags("fr,en"));
+        assertTrue(cfg1.compareTo(cfg2) < 0);
+        cfg1.setLocales(LocaleList.forLanguageTags("fr,en"));
+        cfg2.setLocales(LocaleList.forLanguageTags("fr"));
+        assertTrue(cfg1.compareTo(cfg2) > 0);
+
+        cfg1.setLocales(LocaleList.forLanguageTags("fr,en"));
+        cfg2.setLocales(LocaleList.forLanguageTags("fr,en-US"));
+        assertTrue(cfg1.compareTo(cfg2) < 0);
+        cfg1.setLocales(LocaleList.forLanguageTags("fr,en-US"));
+        cfg2.setLocales(LocaleList.forLanguageTags("fr,en"));
+        assertTrue(cfg1.compareTo(cfg2) > 0);
+
+        cfg1.locale = Locale.forLanguageTag("en");
+        cfg2.locale = Locale.forLanguageTag("en-Shaw");
+        assertTrue(cfg1.compareTo(cfg2) < 0);
+        cfg1.locale = Locale.forLanguageTag("en-Shaw");
+        cfg2.locale = Locale.forLanguageTag("en");
+        assertTrue(cfg1.compareTo(cfg2) > 0);
+
         cfg1.locale = new Locale("", "", "2");
         cfg2.locale = new Locale("", "", "3");
         assertEquals(-1, cfg1.compareTo(cfg2));
@@ -114,6 +136,13 @@
         cfg2.locale = new Locale("2", "", "");
         assertEquals(1, cfg1.compareTo(cfg2));
 
+        cfg1.locale = new Locale("");
+        cfg2.locale = null;
+        assertTrue(cfg1.compareTo(cfg2) < 0);
+        cfg1.locale = null;
+        cfg2.locale = new Locale("");
+        assertTrue(cfg1.compareTo(cfg2) > 0);
+
         cfg1.mnc = 2;
         cfg2.mnc = 3;
         assertEquals(-1, cfg1.compareTo(cfg2));
@@ -160,6 +189,11 @@
                 | ActivityInfo.CONFIG_MNC
                 | ActivityInfo.CONFIG_LOCALE
                 | ActivityInfo.CONFIG_LAYOUT_DIRECTION, mConfigDefault, config);
+        config.setLocales(LocaleList.forLanguageTags("fr,en"));
+        doConfigCompare(ActivityInfo.CONFIG_MCC
+                | ActivityInfo.CONFIG_MNC
+                | ActivityInfo.CONFIG_LOCALE
+                | ActivityInfo.CONFIG_LAYOUT_DIRECTION, mConfigDefault, config);
         config.screenLayout = 1;
         doConfigCompare(ActivityInfo.CONFIG_MCC
                 | ActivityInfo.CONFIG_MNC
@@ -282,6 +316,7 @@
         assertFalse(temp.equals(mConfigDefault));
         temp.setToDefaults();
         assertTrue(temp.equals(mConfigDefault));
+        assertTrue(temp.getLocales().isEmpty());
     }
 
     public void testToString() {
@@ -289,47 +324,62 @@
     }
 
     public void testWriteToParcel() {
-        assertWriteToParcel(createConfig(null), Parcel.obtain());
+        assertWriteToParcel(createConfig((Locale) null), Parcel.obtain());
+        assertWriteToParcel(createConfig(new Locale("")), Parcel.obtain());
         assertWriteToParcel(createConfig(Locale.JAPAN), Parcel.obtain());
+        assertWriteToParcel(createConfig(Locale.forLanguageTag("en-Shaw")), Parcel.obtain());
+        assertWriteToParcel(createConfig(LocaleList.forLanguageTags("fr,en-US")), Parcel.obtain());
     }
 
     public void testSetLocale() {
         Configuration config = new Configuration();
 
+        config.setLocale(null);
+        assertNull(config.locale);
+        assertTrue(config.getLocales().isEmpty());
+
         config.setLocale(Locale.getDefault());
         assertEquals(Locale.getDefault(), config.locale);
-        assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
+        assertEquals(new LocaleList(Locale.getDefault()), config.getLocales());
 
         config.setLocale(Locale.ENGLISH);
         assertEquals(Locale.ENGLISH, config.locale);
+        assertEquals(new LocaleList(Locale.ENGLISH), config.getLocales());
         assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
 
         config.setLocale(Locale.US);
         assertEquals(Locale.US, config.locale);
+        assertEquals(new LocaleList(Locale.US), config.getLocales());
         assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
 
         final Locale arEGLocale = new Locale("ar", "EG");
         config.setLocale(arEGLocale);
         assertEquals(arEGLocale, config.locale);
+        assertEquals(new LocaleList(arEGLocale), config.getLocales());
         assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
 
-        final Locale faFALocale = new Locale("fa", "FA");
-        config.setLocale(faFALocale);
-        assertEquals(faFALocale, config.locale);
+        final Locale faIRLocale = new Locale("fa", "IR");
+        config.setLocale(faIRLocale);
+        assertEquals(faIRLocale, config.locale);
+        assertEquals(new LocaleList(faIRLocale), config.getLocales());
         assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
 
         final Locale iwILLocale = new Locale("iw", "IL");
         config.setLocale(iwILLocale);
         assertEquals(iwILLocale, config.locale);
+        assertEquals(new LocaleList(iwILLocale), config.getLocales());
+        assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
+
+        final Locale urPKLocale = new Locale("ur", "PK");
+        config.setLocale(urPKLocale);
+        assertEquals(urPKLocale, config.locale);
+        assertEquals(new LocaleList(urPKLocale), config.getLocales());
         assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
     }
 
     public void testSetGetLayoutDirection() {
         Configuration config = new Configuration();
 
-        config.setLayoutDirection(Locale.getDefault());
-        assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
-
         config.setLayoutDirection(Locale.ENGLISH);
         assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
 
@@ -340,21 +390,230 @@
         config.setLayoutDirection(arEGLocale);
         assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
 
-        final Locale faFALocale = new Locale("fa", "FA");
-        config.setLayoutDirection(faFALocale);
+        final Locale faIRLocale = new Locale("fa", "IR");
+        config.setLayoutDirection(faIRLocale);
         assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
 
         final Locale iwILLocale = new Locale("iw", "IL");
         config.setLayoutDirection(iwILLocale);
         assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
+
+        final Locale urPKLocale = new Locale("ur", "PK");
+        config.setLayoutDirection(urPKLocale);
+        assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
+    }
+
+    public void testFixUpLocaleList() {
+        Configuration config = new Configuration();
+
+        config.setLocales(LocaleList.forLanguageTags("fr"));
+        config.locale = null;
+        assertEquals(LocaleList.getEmptyLocaleList(), config.getLocales());
+
+        config.setLocales(LocaleList.forLanguageTags("fr,en"));
+        config.locale = Locale.forLanguageTag("en");
+        assertEquals(LocaleList.forLanguageTags("en"), config.getLocales());
+
+        config.setLocales(LocaleList.forLanguageTags("fr,en"));
+        config.locale = Locale.forLanguageTag("fr");
+        assertEquals(LocaleList.forLanguageTags("fr,en"), config.getLocales());
+    }
+
+    public void testSetTo_localeFixUp() {
+        Configuration config1 = new Configuration();
+        Configuration config2 = new Configuration();
+        config2.locale = Locale.FRENCH;
+
+        config1.setTo(config2);
+        assertEquals(Locale.FRENCH, config1.locale);
+        assertEquals(new LocaleList(Locale.FRENCH), config1.getLocales());
+        assertEquals(new LocaleList(Locale.FRENCH), config2.getLocales());
+    }
+
+    public void testToString_localeFixUp() {
+        Configuration config1 = new Configuration();
+        Configuration config2 = new Configuration();
+        config1.setLocales(LocaleList.forLanguageTags("fr,en"));
+        config1.locale = Locale.forLanguageTag("en");
+        config2.setLocales(LocaleList.forLanguageTags("en"));
+
+        assertEquals(config1.toString(), config2.toString());
+    }
+
+    public void testUpdateFrom_localeFixUp() {
+        Configuration config1, config2;
+        int changed;
+
+        config1 = new Configuration();
+        config2 = new Configuration();
+        config1.locale = Locale.FRENCH;
+        changed = config1.updateFrom(config2);
+        assertEquals(0, changed);
+        assertEquals(Locale.FRENCH, config1.locale);
+        assertEquals(new LocaleList(Locale.FRENCH), config1.getLocales());
+
+        config1 = new Configuration();
+        config2 = new Configuration();
+        config2.locale = Locale.FRENCH;
+        changed = config1.updateFrom(config2);
+        assertEquals(ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_LAYOUT_DIRECTION, changed);
+        assertEquals(Locale.FRENCH, config1.locale);
+        assertEquals(new LocaleList(Locale.FRENCH), config1.getLocales());
+        assertEquals(new LocaleList(Locale.FRENCH), config2.getLocales());
+
+        config1 = new Configuration();
+        config2 = new Configuration();
+        config1.setLocales(LocaleList.forLanguageTags("en,fr"));
+        config1.locale = Locale.forLanguageTag("fr");
+        config2.setLocales(LocaleList.forLanguageTags("en,de"));
+        config2.locale = Locale.forLanguageTag("fr");
+        changed = config1.updateFrom(config2);
+        assertEquals(0, changed);
+        assertEquals(Locale.forLanguageTag("fr"), config1.locale);
+        assertEquals(LocaleList.forLanguageTags("fr"), config1.getLocales());
+        assertEquals(LocaleList.forLanguageTags("fr"), config2.getLocales());
+    }
+
+    public void testUpdateFrom_layoutDirection() {
+        Configuration config1, config2;
+        int changed;
+
+        config1 = new Configuration();
+        config2 = new Configuration();
+        config1.setLocales(LocaleList.forLanguageTags("fr,en"));
+        config2.setLocales(LocaleList.forLanguageTags("de,en"));
+        changed = config1.updateFrom(config2);
+        assertTrue((changed & ActivityInfo.CONFIG_LAYOUT_DIRECTION) != 0);
+
+        config1 = new Configuration();
+        config2 = new Configuration();
+        config1.setLocales(LocaleList.forLanguageTags("fr,en"));
+        config2.setLocales(LocaleList.forLanguageTags("fr,de"));
+        changed = config1.updateFrom(config2);
+        assertEquals(0, changed & ActivityInfo.CONFIG_LAYOUT_DIRECTION);
+    }
+
+    public void testDiff_localeFixUp() {
+        Configuration config1 = new Configuration();
+        Configuration config2 = new Configuration();
+        config1.setLocales(LocaleList.forLanguageTags("en,fr"));
+        config1.locale = Locale.forLanguageTag("fr");
+        config2.setLocales(LocaleList.forLanguageTags("en,de"));
+        config2.locale = Locale.forLanguageTag("fr");
+
+        int diff = config1.diff(config2);
+        assertEquals(0, diff);
+    }
+
+    public void testCompareTo_localeFixUp() {
+        Configuration config1 = new Configuration();
+        Configuration config2 = new Configuration();
+        config1.setLocales(LocaleList.forLanguageTags("en,fr"));
+        config2.setLocales(LocaleList.forLanguageTags("en,fr"));
+        assertEquals(0, config1.compareTo(config2));
+        config1.locale = new Locale("2");
+        config2.locale = new Locale("3");
+        assertEquals(-1, config1.compareTo(config2));
+    }
+
+    public void testSetLocales_null() {
+        Configuration config = new Configuration();
+        config.setLocales(null);
+        assertNull(config.locale);
+        assertNotNull(config.getLocales());
+        assertTrue(config.getLocales().isEmpty());
+        assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
+    }
+
+    public void testSetLocales_emptyList() {
+        Configuration config = new Configuration();
+        config.setLocales(LocaleList.getEmptyLocaleList());
+        assertNull(config.locale);
+        assertNotNull(config.getLocales());
+        assertTrue(config.getLocales().isEmpty());
+        assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
+    }
+
+    public void testSetLocales_oneLtr() {
+        Configuration config = new Configuration();
+        Locale loc = Locale.forLanguageTag("en");
+        LocaleList ll = new LocaleList(loc);
+        config.setLocales(ll);
+        assertEquals(loc, config.locale);
+        assertEquals(ll, config.getLocales());
+        assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
+    }
+
+    public void testSetLocales_oneRtl() {
+        Configuration config = new Configuration();
+        Locale loc = Locale.forLanguageTag("az-Arab");
+        LocaleList ll = new LocaleList(loc);
+        config.setLocales(ll);
+        assertEquals(loc, config.locale);
+        assertEquals(ll, config.getLocales());
+        assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
+    }
+
+    public void testSetLocales_twoLocales() {
+        Configuration config = new Configuration();
+        Locale rtlLoc = Locale.forLanguageTag("az-Arab");
+        Locale ltrLoc = Locale.forLanguageTag("en");
+        LocaleList ll = LocaleList.forLanguageTags("az-Arab,en");
+        config.setLocales(ll);
+        assertEquals(rtlLoc, config.locale);
+        assertEquals(ll, config.getLocales());
+        assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
+    }
+
+    public void testSetLocales_overridesLocale() {
+        Configuration config = new Configuration();
+        config.locale = Locale.forLanguageTag("en");
+        LocaleList ll = LocaleList.forLanguageTags("az-Arab,en");
+        config.setLocales(ll);
+
+        assertEquals(Locale.forLanguageTag("az-Arab"), config.locale);
+        assertEquals(ll, config.getLocales());
+        assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
+    }
+
+    public void testSetLocales_overridesSetLocale() {
+        Configuration config = new Configuration();
+        config.setLocale(Locale.forLanguageTag("en"));
+        LocaleList ll = LocaleList.forLanguageTags("az-Arab,en");
+        config.setLocales(ll);
+
+        assertEquals(Locale.forLanguageTag("az-Arab"), config.locale);
+        assertEquals(ll, config.getLocales());
+        assertEquals(View.LAYOUT_DIRECTION_RTL, config.getLayoutDirection());
+    }
+
+    public void testSetLocale_overridesSetLocales() {
+        Configuration config = new Configuration();
+        config.setLocales(LocaleList.forLanguageTags("az-Arab,en"));
+        config.setLocale(Locale.ENGLISH);
+
+        assertEquals(Locale.ENGLISH, config.locale);
+        assertEquals(new LocaleList(Locale.ENGLISH), config.getLocales());
+        assertEquals(View.LAYOUT_DIRECTION_LTR, config.getLayoutDirection());
+    }
+
+    private Configuration createConfig(LocaleList list) {
+        Configuration config = createConfig();
+        config.setLocales(list);
+        return config;
     }
 
     private Configuration createConfig(Locale locale) {
+        Configuration config = createConfig();
+        config.locale = locale;
+        return config;
+    }
+
+    private Configuration createConfig() {
         Configuration config = new Configuration();
         config.fontScale = 13.37f;
         config.mcc = 0;
         config.mnc = 1;
-        config.locale = locale;
         config.touchscreen = Configuration.TOUCHSCREEN_STYLUS;
         config.keyboard = Configuration.KEYBOARD_UNDEFINED;
         config.keyboardHidden = Configuration.KEYBOARDHIDDEN_YES;
diff --git a/tests/tests/graphics/res/drawable/custom_drawable.xml b/tests/tests/graphics/res/drawable/custom_drawable.xml
new file mode 100644
index 0000000..cfb9bdb
--- /dev/null
+++ b/tests/tests/graphics/res/drawable/custom_drawable.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<drawable xmlns:android="http://schemas.android.com/apk/res/android"
+    class="android.graphics.drawable.cts.CustomDrawableTest$CustomDrawable"
+    android:color="#ffff0000" />
diff --git a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
index 80e0253..c813bdb 100644
--- a/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/PaintTest.java
@@ -36,6 +36,7 @@
 import android.os.Build;
 import android.test.AndroidTestCase;
 import android.text.SpannedString;
+import android.util.LocaleList;
 import android.util.Log;
 
 import java.util.Locale;
@@ -600,30 +601,73 @@
         // Check default
         assertEquals(defaultLocale, p.getTextLocale());
 
-        // Check setter / getter
+        // Check setter / getters
         p.setTextLocale(Locale.US);
         assertEquals(Locale.US, p.getTextLocale());
+        assertEquals(new LocaleList(Locale.US), p.getTextLocales());
 
         p.setTextLocale(Locale.CHINESE);
         assertEquals(Locale.CHINESE, p.getTextLocale());
+        assertEquals(new LocaleList(Locale.CHINESE), p.getTextLocales());
 
         p.setTextLocale(Locale.JAPANESE);
         assertEquals(Locale.JAPANESE, p.getTextLocale());
+        assertEquals(new LocaleList(Locale.JAPANESE), p.getTextLocales());
 
         p.setTextLocale(Locale.KOREAN);
         assertEquals(Locale.KOREAN, p.getTextLocale());
+        assertEquals(new LocaleList(Locale.KOREAN), p.getTextLocales());
 
         // Check reverting back to default
         p.setTextLocale(defaultLocale);
         assertEquals(defaultLocale, p.getTextLocale());
+        assertEquals(new LocaleList(defaultLocale), p.getTextLocales());
 
         // Check that we cannot pass a null locale
         try {
             p.setTextLocale(null);
-            assertFalse(true);
+            fail("Setting the text locale to null should throw");
+        } catch (Throwable e) {
+            assertEquals(IllegalArgumentException.class, e.getClass());
         }
-        catch (IllegalArgumentException iae) {
-            // OK !!
+    }
+
+    public void testAccessTextLocales() {
+        Paint p = new Paint();
+
+        final LocaleList defaultLocales = LocaleList.getDefault();
+
+        // Check default
+        assertEquals(defaultLocales, p.getTextLocales());
+
+        // Check setter / getters for a one-member locale list
+        p.setTextLocales(new LocaleList(Locale.CHINESE));
+        assertEquals(Locale.CHINESE, p.getTextLocale());
+        assertEquals(new LocaleList(Locale.CHINESE), p.getTextLocales());
+
+        // Check setter / getters for a two-member locale list
+        p.setTextLocales(LocaleList.forLanguageTags("fr,de"));
+        assertEquals(Locale.forLanguageTag("fr"), p.getTextLocale());
+        assertEquals(LocaleList.forLanguageTags("fr,de"), p.getTextLocales());
+
+        // Check reverting back to default
+        p.setTextLocales(defaultLocales);
+        assertEquals(defaultLocales, p.getTextLocales());
+
+        // Check that we cannot pass a null locale list
+        try {
+            p.setTextLocales(null);
+            fail("Setting the text locale list to null should throw");
+        } catch (Throwable e) {
+            assertEquals(IllegalArgumentException.class, e.getClass());
+        }
+
+        // Check that we cannot pass an empty locale list
+        try {
+            p.setTextLocales(new LocaleList());
+            fail("Setting the text locale list to an empty list should throw");
+        } catch (Throwable e) {
+            assertEquals(IllegalArgumentException.class, e.getClass());
         }
     }
 
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/CustomDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/CustomDrawableTest.java
new file mode 100644
index 0000000..2d3ffd9
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/CustomDrawableTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics.drawable.cts;
+
+import com.android.cts.graphics.R;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.drawable.Drawable;
+import android.test.AndroidTestCase;
+import android.util.AttributeSet;
+
+import java.io.IOException;
+
+public class CustomDrawableTest extends AndroidTestCase {
+
+    public void testInflation() {
+        Drawable dr = getContext().getDrawable(R.drawable.custom_drawable);
+        assertTrue(dr instanceof CustomDrawable);
+        assertEquals(Color.RED, ((CustomDrawable) dr).getColor());
+    }
+
+    public static class CustomDrawable extends Drawable {
+        private static final int[] ATTRS = new int[] { android.R.attr.color };
+
+        private int mColor;
+
+        @Override
+        public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme)
+                throws XmlPullParserException, IOException {
+            super.inflate(r, parser, attrs, theme);
+
+            final TypedArray ta;
+            if (theme != null) {
+                ta = theme.obtainStyledAttributes(attrs, ATTRS, 0, 0);
+            } else {
+                ta = r.obtainAttributes(attrs, ATTRS);
+            }
+
+            mColor = ta.getColor(0, Color.BLACK);
+        }
+
+        public int getColor() {
+            return mColor;
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+
+        }
+
+        @Override
+        public void setAlpha(int alpha) {
+
+        }
+
+        @Override
+        public void setColorFilter(ColorFilter colorFilter) {
+
+        }
+
+        @Override
+        public int getOpacity() {
+            return PixelFormat.OPAQUE;
+        }
+    }
+}
diff --git a/tests/tests/graphics/src/android/graphics/drawable/cts/LayerDrawableTest.java b/tests/tests/graphics/src/android/graphics/drawable/cts/LayerDrawableTest.java
index a2f9ddf..1d7f623 100644
--- a/tests/tests/graphics/src/android/graphics/drawable/cts/LayerDrawableTest.java
+++ b/tests/tests/graphics/src/android/graphics/drawable/cts/LayerDrawableTest.java
@@ -640,6 +640,21 @@
         assertTrue(mockDrawable2.hasCalledOnBoundsChange());
     }
 
+    public void testJumpToCurrentState() {
+        MockDrawable mockDrawable1 = new MockDrawable();
+        MockDrawable mockDrawable2 = new MockDrawable();
+        Drawable[] array = new Drawable[] { mockDrawable1, mockDrawable2 };
+        LayerDrawable layerDrawable = new LayerDrawable(array);
+
+        assertFalse(mockDrawable1.hasCalledJumpToCurrentState());
+        assertFalse(mockDrawable2.hasCalledJumpToCurrentState());
+
+        layerDrawable.jumpToCurrentState();
+
+        assertTrue(mockDrawable1.hasCalledJumpToCurrentState());
+        assertTrue(mockDrawable2.hasCalledJumpToCurrentState());
+    }
+
     public void testSetLevel() {
         MockDrawable mockDrawable1 = new MockDrawable();
         MockDrawable mockDrawable2 = new MockDrawable();
@@ -1400,7 +1415,7 @@
         private boolean mCalledSetState = false;
         private boolean mCalledOnLevelChange = false;
         private boolean mCalledOnBoundsChange = false;
-
+        private boolean mCalledJumpToCurrentState = false;
 
         private boolean mCalledDraw = false;
 
@@ -1474,11 +1489,23 @@
             mCalledSetState = false;
             mCalledOnLevelChange = false;
             mCalledOnBoundsChange = false;
+            mCalledJumpToCurrentState = false;
 
             mCalledDraw = false;
         }
 
         @Override
+        public void jumpToCurrentState() {
+            super.jumpToCurrentState();
+
+            mCalledJumpToCurrentState = true;
+        }
+
+        public boolean hasCalledJumpToCurrentState() {
+            return mCalledJumpToCurrentState;
+        }
+
+        @Override
         protected boolean onStateChange(int[] state) {
             increasePadding();
             return mIsStateful;
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
index 3e76fbc..0e7eb43 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -1993,12 +1993,12 @@
     private long[] getExposureTimeTestValues() {
         long[] testValues = new long[DEFAULT_NUM_EXPOSURE_TIME_STEPS + 1];
         long maxExpTime = mStaticInfo.getExposureMaximumOrDefault(DEFAULT_EXP_TIME_NS);
-        long minxExpTime = mStaticInfo.getExposureMinimumOrDefault(DEFAULT_EXP_TIME_NS);
+        long minExpTime = mStaticInfo.getExposureMinimumOrDefault(DEFAULT_EXP_TIME_NS);
 
-        long range = maxExpTime - minxExpTime;
+        long range = maxExpTime - minExpTime;
         double stepSize = range / (double)DEFAULT_NUM_EXPOSURE_TIME_STEPS;
         for (int i = 0; i < testValues.length; i++) {
-            testValues[i] = minxExpTime + (long)(stepSize * i);
+            testValues[i] = maxExpTime - (long)(stepSize * i);
             testValues[i] = mStaticInfo.getExposureClampToRange(testValues[i]);
         }
 
@@ -2047,7 +2047,7 @@
         }
         int[] testValues = new int[numSteps + 1];
         for (int i = 0; i < testValues.length; i++) {
-            testValues[i] = minSensitivity + stepSize * i;
+            testValues[i] = maxSensitivity - stepSize * i;
             testValues[i] = mStaticInfo.getSensitivityClampToRange(testValues[i]);
         }
 
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
index a6cf613..9f85fd8 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/RecordingTest.java
@@ -821,13 +821,21 @@
                     getAvailableMinFrameDurationsForFormatChecked(ImageFormat.JPEG);
             for (int i = mOrderedStillSizes.size() - 2; i >= 0; i--) {
                 Size candidateSize = mOrderedStillSizes.get(i);
-                Long jpegFrameDuration = minFrameDurationMap.get(candidateSize);
-                assertTrue("Cannot find minimum frame duration for jpeg size " + candidateSize,
-                        jpegFrameDuration != null);
-                if (candidateSize.getWidth() <= videoSz.getWidth() &&
-                        candidateSize.getHeight() <= videoSz.getHeight() &&
-                        jpegFrameDuration <= videoFrameDuration) {
-                    videoSnapshotSz = candidateSize;
+                if (mStaticInfo.isHardwareLevelLegacy()) {
+                    // Legacy level doesn't report min frame duration
+                    if (candidateSize.getWidth() <= videoSz.getWidth() &&
+                            candidateSize.getHeight() <= videoSz.getHeight()) {
+                        videoSnapshotSz = candidateSize;
+                    }
+                } else {
+                    Long jpegFrameDuration = minFrameDurationMap.get(candidateSize);
+                    assertTrue("Cannot find minimum frame duration for jpeg size " + candidateSize,
+                            jpegFrameDuration != null);
+                    if (candidateSize.getWidth() <= videoSz.getWidth() &&
+                            candidateSize.getHeight() <= videoSz.getHeight() &&
+                            jpegFrameDuration <= videoFrameDuration) {
+                        videoSnapshotSz = candidateSize;
+                    }
                 }
             }
 
diff --git a/tests/tests/hardware/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java b/tests/tests/hardware/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
index aa34de3..d1ca19a 100644
--- a/tests/tests/hardware/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
+++ b/tests/tests/hardware/src/android/hardware/multiprocess/camera/cts/CameraEvictionTest.java
@@ -16,6 +16,7 @@
 
 package android.hardware.multiprocess.camera.cts;
 
+import android.app.Activity;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
@@ -112,7 +113,8 @@
         super.setUp();
 
         mCompleted = false;
-        mContext = getActivity();
+        getActivity();
+        mContext = getInstrumentation().getTargetContext();
         System.setProperty("dexmaker.dexcache", mContext.getCacheDir().toString());
         mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE);
         mErrorServiceConnection = new ErrorLoggingService.ErrorServiceConnection(mContext);
@@ -232,6 +234,7 @@
         assertTrue("Remote camera service exited early", timeoutExceptionHit);
         android.os.Process.killProcess(mProcessPid);
         mProcessPid = -1;
+        forceCtsActivityToTop();
     }
 
     /**
@@ -337,6 +340,19 @@
         assertTrue("Remote camera service exited early", timeoutExceptionHit);
         android.os.Process.killProcess(mProcessPid);
         mProcessPid = -1;
+        forceCtsActivityToTop();
+    }
+
+    /**
+     * Ensure the CTS activity becomes foreground again instead of launcher.
+     */
+    private void forceCtsActivityToTop() throws InterruptedException {
+        Thread.sleep(WAIT_TIME);
+        Activity a = getActivity();
+        Intent activityIntent = new Intent(a, CameraCtsActivity.class);
+        activityIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        a.startActivity(activityIntent);
+        Thread.sleep(WAIT_TIME);
     }
 
     /**
@@ -389,15 +405,15 @@
     public void startRemoteProcess(java.lang.Class<?> klass, String processName)
             throws InterruptedException {
         // Ensure no running activity process with same name
-        String cameraActivityName = mContext.getPackageName() + ":" + processName;
+        Activity a = getActivity();
+        String cameraActivityName = a.getPackageName() + ":" + processName;
         List<ActivityManager.RunningAppProcessInfo> list =
                 mActivityManager.getRunningAppProcesses();
         assertEquals(-1, getPid(cameraActivityName, list));
 
         // Start activity in a new top foreground process
-        Intent activityIntent = new Intent(mContext, klass);
-        activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-        mContext.startActivity(activityIntent);
+        Intent activityIntent = new Intent(a, klass);
+        a.startActivity(activityIntent);
         Thread.sleep(WAIT_TIME);
 
         // Fail if activity isn't running
diff --git a/tests/tests/media/Android.mk b/tests/tests/media/Android.mk
index 2019da3..bbf1f04 100644
--- a/tests/tests/media/Android.mk
+++ b/tests/tests/media/Android.mk
@@ -34,6 +34,8 @@
 LOCAL_MODULE_TAGS := optional
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
 
 # include both the 32 and 64 bit versions
 LOCAL_MULTILIB := both
@@ -50,6 +52,8 @@
 #LOCAL_SDK_VERSION := current
 LOCAL_JAVA_LIBRARIES += android.test.runner org.apache.http.legacy
 
-include $(BUILD_CTS_PACKAGE)
+LOCAL_COMPATIBILITY_SUITE := cts_v2
+
+include $(BUILD_PACKAGE)
 
 include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/tests/media/AndroidTest.xml b/tests/tests/media/AndroidTest.xml
new file mode 100644
index 0000000..64a048b
--- /dev/null
+++ b/tests/tests/media/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Base config for CTS package preparer">
+    <include name="common-config" />
+    <option name="apk-installer:test-file-name" value="CtsMediaTestCases.apk" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.DynamicConfigPusher">
+        <option name="target" value="device" />
+        <option name="module-name" value="CtsMediaTestCases"/>
+        <option name="version-name" value="1.0"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.InstrumentationTest" >
+        <option name="package" value="com.android.cts.media" />
+        <option name="runner" value="android.support.test.runner.AndroidJUnitRunner" />
+    </test>
+</configuration>
diff --git a/tests/tests/media/DynamicConfig.xml b/tests/tests/media/DynamicConfig.xml
new file mode 100644
index 0000000..702157d
--- /dev/null
+++ b/tests/tests/media/DynamicConfig.xml
@@ -0,0 +1,29 @@
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<DynamicConfig>
+    <Config key="DecoderTest-VIDEO_URL">http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&amp;key=ik0&amp;user=android-device-test</Config>
+    <Config key="StreamingMediaPlayerTest-testHTTP_H264Base_AAC_Video1">http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=667AEEF54639926662CE62361400B8F8C1753B3F.15F46C382C68A9F121BA17BF1F56BEDEB4B06091&amp;key=ik0&amp;user=android-device-test</Config>
+    <Config key="StreamingMediaPlayerTest-testHTTP_H264Base_AAC_Video2">http://www.youtube.com/api/manifest/hls_variant/id/0168724d02bd9945/itag/5/source/youtube/playlist_type/DVR/ip/0.0.0.0/ipbits/0/expire/19000000000/sparams/ip,ipbits,expire,id,itag,source,playlist_type/signature/773AB8ACC68A96E5AA481996AD6A1BBCB70DCB87.95733B544ACC5F01A1223A837D2CF04DF85A3360/key/ik0/file/m3u8</Config>
+    <Config key="MediaCodecCapabilitiesTest-testAvcHigh31">http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=22&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=179525311196616BD8E1381759B0E5F81A9E91B5.C4A50E44059FEBCC6BBC78E3B3A4E0E0065777&amp;key=ik0</Config>
+    <Config key="StreamingMediaPlayerTest-testHTTP_MPEG4SP_AAC_Video2">http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=70E979A621001201BC18622BDBF914FA870BDA40.6E78890B80F4A33A18835F775B1FF64F0A4D0003&amp;key=ik0&amp;user=android-device-test</Config>
+    <Config key="StreamingMediaPlayerTest-testHTTP_MPEG4SP_AAC_Video1">http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=17&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=837198AAADF6F36BA6B2D324F690A7C5B7AFE3FF.7138CE5E36D718220726C1FC305497FF2D082249&amp;key=ik0&amp;user=android-device-test</Config>
+    <Config key="MediaCodecCapabilitiesTest-testAvcBaseline12">http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=160&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=9EDCA0B395B8A949C511FD5E59B9F805CFF797FD.702DE9BA7AF96785FD6930AD2DD693A0486C880E&amp;key=ik0</Config>
+    <Config key="DecoderTest-AUDIO_URL">http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=18&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=46A04ED550CA83B79B60060BA80C79FDA5853D26.49582D382B4A9AFAA163DED38D2AE531D85603C0&amp;key=ik0&amp;user=android-device-test</Config>
+    <Config key="MediaCodecCapabilitiesTest-testAvcBaseline30">http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=18&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=7DCDE3A6594D0B91A27676A3CDC3A87B149F82EA.7A83031734CB1EDCE06766B6228842F954927960&amp;key=ik0</Config>
+    <Config key="MediaCodecCapabilitiesTest-testAvcHigh40">http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=137&amp;source=youtube&amp;user=android-device-test&amp;sparams=ip,ipbits,expire,id,itag,source,user&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;signature=B0976085596DD42DEA3F08307F76587241CB132B.043B719C039E8B92F45391ADC0BE3665E2332930&amp;key=ik0</Config>
+    <Config key="StreamingMediaPlayerTest-testHTTP_H263_AMR_Video2">http://redirector.gvt1.com/videoplayback?id=c80658495af60617&amp;itag=13&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=508D82AB36939345BF6B8D0623CB6CABDD9C64C3.9B3336A96846DF38E5343C46AA57F6CF2956E427&amp;key=ik0&amp;user=android-device-test</Config>
+    <Config key="StreamingMediaPlayerTest-testHTTP_H263_AMR_Video1">http://redirector.gvt1.com/videoplayback?id=271de9756065677e&amp;itag=13&amp;source=youtube&amp;ip=0.0.0.0&amp;ipbits=0&amp;expire=19000000000&amp;sparams=ip,ipbits,expire,id,itag,source&amp;signature=5729247E22691EBB3E804DDD523EC42DC17DD8CE.443B81C1E8E6D64E4E1555F568BA46C206507D78&amp;key=ik0&amp;user=android-device-test</Config>
+</DynamicConfig>
diff --git a/tests/tests/media/res/raw/heap_oob_flac.mp3 b/tests/tests/media/res/raw/heap_oob_flac.mp3
new file mode 100644
index 0000000..ae542d0
--- /dev/null
+++ b/tests/tests/media/res/raw/heap_oob_flac.mp3
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/DecoderTest.java b/tests/tests/media/src/android/media/cts/DecoderTest.java
index 6765051..c5bdce8 100755
--- a/tests/tests/media/src/android/media/cts/DecoderTest.java
+++ b/tests/tests/media/src/android/media/cts/DecoderTest.java
@@ -16,6 +16,7 @@
 
 package android.media.cts;
 
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
 import com.android.cts.media.R;
 
 import android.content.Context;
@@ -65,20 +66,6 @@
     private MediaCodecTunneledPlayer mMediaCodecPlayer;
     private static final int SLEEP_TIME_MS = 1000;
     private static final long PLAY_TIME_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.MINUTES);
-    private static final Uri AUDIO_URL = Uri.parse(
-            "http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26."
-                + "49582D382B4A9AFAA163DED38D2AE531D85603C0"
-                + "&key=ik0&user=android-device-test");  // H.264 Base + AAC
-    private static final Uri VIDEO_URL = Uri.parse(
-            "http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26."
-                + "49582D382B4A9AFAA163DED38D2AE531D85603C0"
-                + "&key=ik0&user=android-device-test");  // H.264 Base + AAC
 
     @Override
     protected void setUp() throws Exception {
@@ -1959,8 +1946,11 @@
         mMediaCodecPlayer = new MediaCodecTunneledPlayer(
                 getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
 
-        mMediaCodecPlayer.setAudioDataSource(AUDIO_URL, null);
-        mMediaCodecPlayer.setVideoDataSource(VIDEO_URL, null);
+        DynamicConfigDeviceSide config = new DynamicConfigDeviceSide("CtsMediaTestCases");
+        Uri audio_url = Uri.parse(config.getConfig("DecoderTest-AUDIO_URL"));
+        mMediaCodecPlayer.setAudioDataSource(audio_url, null);
+        Uri video_url = Uri.parse(config.getConfig("DecoderTest-VIDEO_URL"));
+        mMediaCodecPlayer.setVideoDataSource(video_url, null);
         assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
         assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
 
@@ -1998,8 +1988,11 @@
         mMediaCodecPlayer = new MediaCodecTunneledPlayer(
                 getActivity().getSurfaceHolder(), true, am.generateAudioSessionId());
 
-        mMediaCodecPlayer.setAudioDataSource(AUDIO_URL, null);
-        mMediaCodecPlayer.setVideoDataSource(VIDEO_URL, null);
+        DynamicConfigDeviceSide config = new DynamicConfigDeviceSide("CtsMediaTestCases");
+        Uri audio_url = Uri.parse(config.getConfig("DecoderTest-AUDIO_URL"));
+        mMediaCodecPlayer.setAudioDataSource(audio_url, null);
+        Uri video_url = Uri.parse(config.getConfig("DecoderTest-VIDEO_URL"));
+        mMediaCodecPlayer.setVideoDataSource(video_url, null);
         assertTrue("MediaCodecPlayer.start() failed!", mMediaCodecPlayer.start());
         assertTrue("MediaCodecPlayer.prepare() failed!", mMediaCodecPlayer.prepare());
 
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index 813af0f2..812f009d 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -36,6 +36,8 @@
 import android.os.Build;
 import android.util.Log;
 
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+
 import java.io.IOException;
 import java.util.HashSet;
 import java.util.Set;
@@ -172,39 +174,28 @@
             return; // skip
         }
 
-        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=160&source=youtube&user=android-device-test"
-                + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&signature=9EDCA0B395B8A949C511FD5E59B9F805CFF797FD."
-                + "702DE9BA7AF96785FD6930AD2DD693A0486C880E"
-                + "&key=ik0", 256, 144, PLAY_TIME_MS);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("MediaCodecCapabilitiesTest-testAvcBaseline12");
+        playVideoWithRetries(url, 256, 144, PLAY_TIME_MS);
     }
 
     public void testAvcBaseline30() throws Exception {
         if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileBaseline, AVCLevel3)) {
             return; // skip
         }
-        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=18&source=youtube&user=android-device-test"
-                + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&signature=7DCDE3A6594D0B91A27676A3CDC3A87B149F82EA."
-                + "7A83031734CB1EDCE06766B6228842F954927960"
-                + "&key=ik0", 640, 360, PLAY_TIME_MS);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("MediaCodecCapabilitiesTest-testAvcBaseline30");
+        playVideoWithRetries(url, 640, 360, PLAY_TIME_MS);
     }
 
     public void testAvcHigh31() throws Exception {
         if (!checkDecoder(MIMETYPE_VIDEO_AVC, AVCProfileHigh, AVCLevel31)) {
             return; // skip
         }
-        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=22&source=youtube&user=android-device-test"
-                + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&signature=179525311196616BD8E1381759B0E5F81A9E91B5."
-                + "C4A50E44059FEBCC6BBC78E3B3A4E0E0065777"
-                + "&key=ik0", 1280, 720, PLAY_TIME_MS);
+
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("MediaCodecCapabilitiesTest-testAvcHigh31");
+        playVideoWithRetries(url, 1280, 720, PLAY_TIME_MS);
     }
 
     public void testAvcHigh40() throws Exception {
@@ -215,13 +206,10 @@
             MediaUtils.skipTest(TAG, "fragmented mp4 not supported");
             return;
         }
-        playVideoWithRetries("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=137&source=youtube&user=android-device-test"
-                + "&sparams=ip,ipbits,expire,id,itag,source,user"
-                + "&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&signature=B0976085596DD42DEA3F08307F76587241CB132B."
-                + "043B719C039E8B92F45391ADC0BE3665E2332930"
-                + "&key=ik0", 1920, 1080, PLAY_TIME_MS);
+
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("MediaCodecCapabilitiesTest-testAvcHigh40");
+        playVideoWithRetries(url, 1920, 1080, PLAY_TIME_MS);
     }
 
     public void testHevcMain1() throws Exception {
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index 86f0313..d5b2907 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -95,6 +95,37 @@
         }
     }
 
+    public void testFlacHeapOverflow() throws Exception {
+        testIfMediaServerDied(R.raw.heap_oob_flac);
+    }
+
+    private void testIfMediaServerDied(int res) throws Exception {
+        mMediaPlayer.setOnErrorListener(new MediaPlayer.OnErrorListener() {
+            @Override
+            public boolean onError(MediaPlayer mp, int what, int extra) {
+                assertTrue(mp == mMediaPlayer);
+                assertTrue("mediaserver process died", what != MediaPlayer.MEDIA_ERROR_SERVER_DIED);
+                return false;
+            }
+        });
+
+        mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+            @Override
+            public void onCompletion(MediaPlayer mp) {
+                assertTrue(mp == mMediaPlayer);
+                mOnCompletionCalled.signal();
+            }
+        });
+
+        AssetFileDescriptor afd = mResources.openRawResourceFd(res);
+        mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+        afd.close();
+        mMediaPlayer.prepare();
+        mMediaPlayer.start();
+        mOnCompletionCalled.waitForSignal();
+        mMediaPlayer.release();
+    }
+
     // Bug 13652927
     public void testVorbisCrash() throws Exception {
         MediaPlayer mp = mMediaPlayer;
diff --git a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
index 7497da2..a6c80ad 100644
--- a/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/StreamingMediaPlayerTest.java
@@ -26,6 +26,8 @@
 import android.util.Log;
 import android.webkit.cts.CtsTestServer;
 
+import com.android.compatibility.common.util.DynamicConfigDeviceSide;
+
 import java.io.IOException;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -73,49 +75,36 @@
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=5729247E22691EBB3E804DDD523EC42DC17DD8CE"
-                + ".443B81C1E8E6D64E4E1555F568BA46C206507D78"
-                + "&key=ik0&user=android-device-test", 176, 144);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("StreamingMediaPlayerTest-testHTTP_H263_AMR_Video1");
+        playVideoTest(url, 176, 144);
     }
     public void testHTTP_H263_AMR_Video2() throws Exception {
         if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_H263, MediaFormat.MIMETYPE_AUDIO_AMR_NB)) {
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=13&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=508D82AB36939345BF6B8D0623CB6CABDD9C64C3"
-                + ".9B3336A96846DF38E5343C46AA57F6CF2956E427"
-                + "&key=ik0&user=android-device-test", 176, 144);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("StreamingMediaPlayerTest-testHTTP_H263_AMR_Video2");
+        playVideoTest(url, 176, 144);
     }
 
     public void testHTTP_MPEG4SP_AAC_Video1() throws Exception {
         if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
             return; // skip
         }
-
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=837198AAADF6F36BA6B2D324F690A7C5B7AFE3FF"
-                + ".7138CE5E36D718220726C1FC305497FF2D082249"
-                + "&key=ik0&user=android-device-test", 176, 144);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("StreamingMediaPlayerTest-testHTTP_MPEG4SP_AAC_Video1");
+        playVideoTest(url, 176, 144);
     }
     public void testHTTP_MPEG4SP_AAC_Video2() throws Exception {
         if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_MPEG4)) {
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=17&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=70E979A621001201BC18622BDBF914FA870BDA40"
-                + ".6E78890B80F4A33A18835F775B1FF64F0A4D0003"
-                + "&key=ik0&user=android-device-test", 176, 144);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("StreamingMediaPlayerTest-testHTTP_MPEG4SP_AAC_Video2");
+        playVideoTest(url, 176, 144);
     }
 
     public void testHTTP_H264Base_AAC_Video1() throws Exception {
@@ -123,24 +112,18 @@
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=271de9756065677e"
-                + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=667AEEF54639926662CE62361400B8F8C1753B3F"
-                + ".15F46C382C68A9F121BA17BF1F56BEDEB4B06091"
-                + "&key=ik0&user=android-device-test", 640, 360);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("StreamingMediaPlayerTest-testHTTP_H264Base_AAC_Video1");
+        playVideoTest(url, 640, 360);
     }
     public void testHTTP_H264Base_AAC_Video2() throws Exception {
         if (!MediaUtils.checkDecoder(MediaFormat.MIMETYPE_VIDEO_AVC)) {
             return; // skip
         }
 
-        playVideoTest("http://redirector.c.youtube.com/videoplayback?id=c80658495af60617"
-                + "&itag=18&source=youtube&ip=0.0.0.0&ipbits=0&expire=19000000000"
-                + "&sparams=ip,ipbits,expire,id,itag,source"
-                + "&signature=46A04ED550CA83B79B60060BA80C79FDA5853D26"
-                + ".49582D382B4A9AFAA163DED38D2AE531D85603C0"
-                + "&key=ik0&user=android-device-test", 640, 360);
+        String url = new DynamicConfigDeviceSide("CtsMediaTestCases")
+                .getConfig("StreamingMediaPlayerTest-testHTTP_H264Base_AAC_Video2");
+        playVideoTest(url, 640, 360);
     }
 
     // Streaming HLS video from YouTube
diff --git a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
index 7a01e83..2843d21 100644
--- a/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/FileSystemPermissionTest.java
@@ -787,7 +787,9 @@
                 new File("/dev/ashmem"),
                 new File("/dev/binder"),
                 new File("/dev/card0"),       // b/13159510
+                new File("/dev/renderD128"),
                 new File("/dev/dri/card0"),   // b/13159510
+                new File("/dev/dri/renderD128"),
                 new File("/dev/felica"),     // b/11142586
                 new File("/dev/felica_ant"), // b/11142586
                 new File("/dev/felica_cen"), // b/11142586
diff --git a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
index c6f4049..f8b3cc4 100644
--- a/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
+++ b/tests/tests/text/src/android/text/cts/StaticLayoutTest.java
@@ -975,4 +975,25 @@
             assertEquals(testLabel, 6, layout.getOffsetToRightOf(7));
         }
     }
+
+    public void testGetOffsetForHorizontal_Multilines() {
+        // Emoticons for surrogate pairs tests.
+        String testString = "\uD83D\uDE00\uD83D\uDE01\uD83D\uDE02\uD83D\uDE03\uD83D\uDE04";
+        final float width = mDefaultPaint.measureText(testString, 0, 6);
+        StaticLayout layout = new StaticLayout(testString, mDefaultPaint, (int)width,
+                DEFAULT_ALIGN, SPACE_MULTI, SPACE_ADD, true);
+        // We expect the line break to be after the third emoticon, but we allow flexibility of the
+        // line break algorithm as long as the break is within the string. These other cases might
+        // happen if for example the font has kerning between emoticons.
+        final int lineBreakOffset = layout.getOffsetForHorizontal(1, 0.0f);
+        assertEquals(0, layout.getLineForOffset(lineBreakOffset - 1));
+
+        assertEquals(0, layout.getOffsetForHorizontal(0, 0.0f));
+        assertEquals(lineBreakOffset - 2, layout.getOffsetForHorizontal(0, width));
+        assertEquals(lineBreakOffset - 2, layout.getOffsetForHorizontal(0, width * 2));
+
+        final int lineCount = layout.getLineCount();
+        assertEquals(testString.length(), layout.getOffsetForHorizontal(lineCount - 1, width));
+        assertEquals(testString.length(), layout.getOffsetForHorizontal(lineCount - 1, width * 2));
+    }
 }
diff --git a/tests/tests/util/src/android/util/cts/LocaleListTest.java b/tests/tests/util/src/android/util/cts/LocaleListTest.java
index f7abfdf9..1703591 100644
--- a/tests/tests/util/src/android/util/cts/LocaleListTest.java
+++ b/tests/tests/util/src/android/util/cts/LocaleListTest.java
@@ -31,7 +31,15 @@
         assertNull(ll.get(1));
         assertNull(ll.get(10));
 
-        ll = new LocaleList(null);
+        ll = new LocaleList((Locale) null);
+        assertNotNull(ll);
+        assertTrue(ll.isEmpty());
+        assertEquals(0, ll.size());
+        assertNull(ll.getPrimary());
+        assertNull(ll.get(1));
+        assertNull(ll.get(10));
+
+        ll = new LocaleList((Locale[]) null);
         assertNotNull(ll);
         assertTrue(ll.isEmpty());
         assertEquals(0, ll.size());
@@ -49,8 +57,7 @@
     }
 
     public void testOneMemberLocaleList() {
-        final Locale[] la = {Locale.US};
-        final LocaleList ll = new LocaleList(la);
+        final LocaleList ll = new LocaleList(Locale.US);
         assertNotNull(ll);
         assertFalse(ll.isEmpty());
         assertEquals(1, ll.size());
@@ -93,4 +100,95 @@
             assertEquals(IllegalArgumentException.class, e.getClass());
         }
     }
+
+    public void testEquals() {
+        final LocaleList empty = new LocaleList();
+        final LocaleList anotherEmpty = new LocaleList();
+        LocaleList oneMember = new LocaleList(Locale.US);
+        LocaleList sameOneMember = new LocaleList(new Locale("en", "US"));
+        LocaleList differentOneMember = new LocaleList(Locale.FRENCH);
+        Locale[] la = {Locale.US, Locale.FRENCH};
+        LocaleList twoMember = new LocaleList(la);
+
+        assertFalse(empty.equals(null));
+        assertFalse(oneMember.equals(null));
+
+        assertFalse(empty.equals(new Object()));
+
+        assertTrue(empty.equals(empty));
+        assertTrue(oneMember.equals(oneMember));
+
+        assertFalse(empty.equals(oneMember));
+        assertFalse(oneMember.equals(twoMember));
+
+        assertFalse(oneMember.equals(differentOneMember));
+
+        assertTrue(empty.equals(anotherEmpty));
+        assertTrue(oneMember.equals(sameOneMember));
+    }
+
+    public void testHashCode() {
+        final LocaleList empty = new LocaleList();
+        final LocaleList anotherEmpty = new LocaleList();
+        Locale[] la1 = {Locale.US};
+        LocaleList oneMember = new LocaleList(la1);
+        LocaleList sameOneMember = new LocaleList(la1);
+
+        assertEquals(empty.hashCode(), anotherEmpty.hashCode());
+        assertEquals(oneMember.hashCode(), sameOneMember.hashCode());
+    }
+
+    public void testToString() {
+        LocaleList ll = new LocaleList();
+        assertEquals("[]", ll.toString());
+
+        final Locale[] la1 = {Locale.US};
+        ll = new LocaleList(la1);
+        assertEquals("["+Locale.US.toString()+"]", ll.toString());
+
+        final Locale[] la2 = {Locale.US, Locale.FRENCH};
+        ll = new LocaleList(la2);
+        assertEquals("["+Locale.US.toString()+","+Locale.FRENCH.toString()+"]", ll.toString());
+    }
+
+    public void testToLanguageTags() {
+        LocaleList ll = new LocaleList();
+        assertEquals("", ll.toLanguageTags());
+
+        final Locale[] la1 = {Locale.US};
+        ll = new LocaleList(la1);
+        assertEquals(Locale.US.toLanguageTag(), ll.toLanguageTags());
+
+        final Locale[] la2 = {Locale.US, Locale.FRENCH};
+        ll = new LocaleList(la2);
+        assertEquals(Locale.US.toLanguageTag()+","+Locale.FRENCH.toLanguageTag(),
+                ll.toLanguageTags());
+    }
+
+    public void testGetEmptyLocaleList() {
+        LocaleList empty = LocaleList.getEmptyLocaleList();
+        LocaleList anotherEmpty = LocaleList.getEmptyLocaleList();
+        LocaleList constructedEmpty = new LocaleList();
+
+        assertEquals(constructedEmpty, empty);
+        assertSame(empty, anotherEmpty);
+    }
+
+    public void testForLanguageTags() {
+        assertEquals(LocaleList.getEmptyLocaleList(), LocaleList.forLanguageTags(null));
+        assertEquals(LocaleList.getEmptyLocaleList(), LocaleList.forLanguageTags(""));
+
+        assertEquals(new LocaleList(Locale.forLanguageTag("en-US")),
+                LocaleList.forLanguageTags("en-US"));
+
+        final Locale[] la = {Locale.forLanguageTag("en-PH"), Locale.forLanguageTag("en-US")};
+        assertEquals(new LocaleList(la), LocaleList.forLanguageTags("en-PH,en-US"));
+    }
+
+    public void testGetDefault() {
+        LocaleList ll = LocaleList.getDefault();
+        assertNotNull(ll);
+        assertTrue(ll.size() >= 1);
+        assertEquals(Locale.getDefault(), ll.getPrimary());
+    }
 }
diff --git a/tests/tests/widget/src/android/widget/cts/AdapterViewTest.java b/tests/tests/widget/src/android/widget/cts/AdapterViewTest.java
index 6a2240e..ccbdd56 100644
--- a/tests/tests/widget/src/android/widget/cts/AdapterViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/AdapterViewTest.java
@@ -317,13 +317,8 @@
             //expected
         }
 
-        try {
-            assertEquals(AdapterView.INVALID_POSITION,
-                    mAdapterView.getPositionForView(new ImageView(mActivity)));
-            fail("Should throw NullPointerException");
-        } catch (NullPointerException e) {
-            //expected
-        }
+        assertEquals(AdapterView.INVALID_POSITION,
+                mAdapterView.getPositionForView(new ImageView(mActivity)));
     }
 
     public void testChangeFocusable() {
diff --git a/tests/tests/widget/src/android/widget/cts/TextViewTest.java b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
index 3130a26..5c75dd4 100644
--- a/tests/tests/widget/src/android/widget/cts/TextViewTest.java
+++ b/tests/tests/widget/src/android/widget/cts/TextViewTest.java
@@ -74,6 +74,7 @@
 import android.text.style.URLSpan;
 import android.text.util.Linkify;
 import android.util.DisplayMetrics;
+import android.util.LocaleList;
 import android.util.TypedValue;
 import android.view.ContextMenu;
 import android.view.ContextMenu.ContextMenuInfo;
@@ -4040,6 +4041,41 @@
         }
     }
 
+    public void testTextLocales() {
+        TextView tv = new TextView(mActivity);
+        assertEquals(Locale.getDefault(), tv.getTextLocale());
+        assertEquals(LocaleList.getDefault(), tv.getTextLocales());
+
+        tv.setTextLocale(Locale.CHINESE);
+        assertEquals(Locale.CHINESE, tv.getTextLocale());
+        assertEquals(new LocaleList(Locale.CHINESE), tv.getTextLocales());
+
+        tv.setTextLocales(LocaleList.forLanguageTags("en,ja"));
+        assertEquals(Locale.forLanguageTag("en"), tv.getTextLocale());
+        assertEquals(LocaleList.forLanguageTags("en,ja"), tv.getTextLocales());
+
+        try {
+            tv.setTextLocale(null);
+            fail("Setting the text locale to null should throw");
+        } catch (Throwable e) {
+            assertEquals(IllegalArgumentException.class, e.getClass());
+        }
+
+        try {
+            tv.setTextLocales(null);
+            fail("Setting the text locales to null should throw");
+        } catch (Throwable e) {
+            assertEquals(IllegalArgumentException.class, e.getClass());
+        }
+
+        try {
+            tv.setTextLocales(new LocaleList());
+            fail("Setting the text locale to an empty list should throw");
+        } catch (Throwable e) {
+            assertEquals(IllegalArgumentException.class, e.getClass());
+        }
+    }
+
     public void testAllCapsLocalization() {
         String testString = "abcdefghijklmnopqrstuvwxyz";