Merge "CameraITS: Update writer socket when it's closed" into mnc-dev
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 8e0d83d..5042a74 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -70,12 +70,15 @@
 
 cts_support_packages := \
     CtsAccelerationTestStubs \
+    CtsAlarmClockService \
     CtsAppTestStubs \
     CtsAtraceTestApp \
     CtsCertInstallerApp \
     CtsDeviceAdmin \
     CtsDeviceOpenGl \
     CtsWifiConfigCreator \
+    CtsDeviceAndProfileOwnerApp \
+    CtsDeviceInfo \
     CtsDeviceOwnerApp \
     CtsDeviceTaskswitchingAppA \
     CtsDeviceTaskswitchingAppB \
@@ -86,6 +89,7 @@
     CtsIntentSenderApp \
     CtsLauncherAppsTests \
     CtsLauncherAppsTestsSupport \
+    CtsLeanbackJank \
     CtsManagedProfileApp \
     CtsMonkeyApp \
     CtsMonkeyApp2 \
@@ -148,6 +152,7 @@
     CtsGraphics2TestCases \
     CtsHardwareTestCases \
     CtsJankTestCases \
+    CtsLeanbackJankTestCases \
     CtsJobSchedulerDeviceTestCases \
     CtsJniTestCases \
     CtsKeystoreTestCases \
@@ -211,6 +216,7 @@
     CtsHostUi \
     CtsMonkeyTestCases \
     CtsThemeHostTestCases \
+    CtsUsageHostTestCases \
     CtsSecurityHostTestCases \
     CtsUsbTests
 
diff --git a/apps/CameraITS/tests/scene1/test_param_noise_reduction.py b/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
index 27da8fc..f4f0d56 100644
--- a/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1/test_param_noise_reduction.py
@@ -17,10 +17,11 @@
 import its.device
 import its.objects
 import its.target
-import pylab
-import os.path
 import matplotlib
 import matplotlib.pyplot
+import numpy
+import os.path
+import pylab
 
 def main():
     """Test that the android.noiseReduction.mode param is applied when set.
@@ -34,6 +35,8 @@
     """
     NAME = os.path.basename(__file__).split(".")[0]
 
+    RELATIVE_ERROR_TOLERANCE = 0.1
+
     # List of variances for Y,U,V.
     variances = [[],[],[]]
 
@@ -96,18 +99,27 @@
 
     assert(nr_modes_reported == [0,1,2,3,4])
 
-    # Check that the variance of the NR=0 image is higher than for the
-    # NR=1 and NR=2 images.
     for j in range(3):
-        for mode in [1,2]:
-            if its.caps.noise_reduction_mode(props, mode):
-                assert(variances[j][mode] < variances[j][0])
-                # Variance of MINIMAL should be higher than for FAST, HQ
-                if its.caps.noise_reduction_mode(props, 3):
-                    assert(variances[j][mode] < variances[j][3])
-                # Variance of ZSL should be higher than for FAST, HQ
-                if its.caps.noise_reduction_mode(props, 4):
-                    assert(variances[j][mode] < variances[j][4])
+        # Smaller variance is better
+        # Verify OFF(0) is not better than FAST(1)
+        assert(variances[j][0] >
+               variances[j][1] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+        # Verify FAST(1) is not better than HQ(2)
+        assert(variances[j][1] >
+               variances[j][2] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+        # Verify HQ(2) is better than OFF(0)
+        assert(variances[j][0] > variances[j][2])
+        if its.caps.noise_reduction_mode(props, 3):
+            # Verify OFF(0) is not better than MINIMAL(3)
+            assert(variances[j][0] >
+                   variances[j][3] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+            # Verify MINIMAL(3) is not better than HQ(2)
+            assert(variances[j][3] >
+                   variances[j][2] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+        if its.caps.noise_reduction_mode(props, 4):
+            # Verify ZSL(4) is close to OFF(0)
+            assert(numpy.isclose(variances[j][4], variances[j][0],
+                                 RELATIVE_ERROR_TOLERANCE))
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py b/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
index c2657c7..aeeae33 100644
--- a/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
+++ b/apps/CameraITS/tests/scene1/test_reprocess_noise_reduction.py
@@ -20,6 +20,7 @@
 import math
 import matplotlib
 import matplotlib.pyplot
+import numpy
 import os.path
 import pylab
 
@@ -37,6 +38,8 @@
 
     NAME = os.path.basename(__file__).split(".")[0]
 
+    RELATIVE_ERROR_TOLERANCE = 0.1
+
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
 
@@ -78,7 +81,7 @@
             print "Ref variances:", ref_variance
 
             for nr_mode in range(5):
-                # Skipp unavailable modes
+                # Skip unavailable modes
                 if not its.caps.noise_reduction_mode(props, nr_mode):
                     nr_modes_reported.append(nr_mode)
                     variances.append(0)
@@ -111,21 +114,28 @@
             matplotlib.pyplot.savefig("%s_plot_%s_variances.png" %
                                       (NAME, reprocess_format))
 
-            assert(nr_modes_reported == [0,1,2,3,4]
+            assert(nr_modes_reported == [0,1,2,3,4])
 
-            # Check that the variances of the NR=0 and NR=3 and NR=4 images are
-            # higher than for the NR=1 and NR=2 images.
-            for channel in range(3):
-                for nr_mode in [1, 2]:
-                    if its.caps.noise_reduction_mode(props, nr_mode):
-                        assert(variances[nr_mode][channel] <
-                               variances[0][channel])
-                        if its.caps.noise_reduction_mode(props, 3):
-                            assert(variances[nr_mode][channel] <
-                                   variances[3][channel])
-                        if its.caps.noise_reduction_mode(props, 4):
-                            assert(variances[nr_mode][channel] <
-                                   variances[4][channel])
+            for j in range(3):
+                # Smaller variance is better
+                # Verify OFF(0) is not better than FAST(1)
+                assert(variances[0][j] >
+                       variances[1][j] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+                # Verify FAST(1) is not better than HQ(2)
+                assert(variances[1][j] >
+                       variances[2][j] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+                # Verify HQ(2) is better than OFF(0)
+                assert(variances[0][j] > variances[2][j])
+                if its.caps.noise_reduction_mode(props, 3):
+                    # Verify OFF(0) is not better than MINIMAL(3)
+                    assert(variances[0][j] >
+                           variances[3][j] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+                    # Verify MINIMAL(3) is not better than HQ(2)
+                    assert(variances[3][j] >
+                           variances[2][j] * (1.0 - RELATIVE_ERROR_TOLERANCE))
+                # Verify ZSL(4) is close to OFF(0)
+                assert(numpy.isclose(variances[4][j], variances[0][j],
+                                     RELATIVE_ERROR_TOLERANCE))
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
index 77208e7..910cda2 100644
--- a/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
+++ b/apps/CameraITS/tests/scene3/test_reprocess_edge_enhancement.py
@@ -20,6 +20,7 @@
 import math
 import matplotlib
 import matplotlib.pyplot
+import numpy
 import os.path
 import pylab
 
@@ -153,28 +154,48 @@
             print "Sharpness with EE mode [0,1,2,3] for %s reprocess:" % \
                 (reprocess_format) , sharpnesses
 
-        # Verify reported modes for regular results
-        assert(edge_mode_reported_regular == [0,1,2,3])
-        # Verify the images for all modes are not too blurrier than OFF.
-        for edge_mode in [1, 2, 3]:
-            if its.caps.edge_mode(props, edge_mode):
-                assert(sharpness_regular[edge_mode] >
-                    sharpness_regular[0] *
-                    (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
-        # Verify the image for ZSL are not too sharper than OFF
-        assert(sharpness_regular[3] <
-                sharpness_regular[0] * (1.0 + THRESHOLD_RELATIVE_SHARPNESS_DIFF))
 
-        # Verify sharpness of reprocess captures are similar to sharpness of
-        # regular captures.
+        # Verify HQ(2) is sharper than OFF(0)
+        assert(sharpness_regular[2] > sharpness_regular[0])
+
+        # Verify ZSL(3) is similar to OFF(0)
+        assert(numpy.isclose(sharpness_regular[3], sharpness_regular[0],
+                             THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+
+        # Verify OFF(0) is not sharper than FAST(1)
+        assert(sharpness_regular[1] >
+               sharpness_regular[0] * (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+
+        # Verify FAST(1) is not sharper than HQ(2)
+        assert(sharpness_regular[2] >
+               sharpness_regular[1] * (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+
         for reprocess_format in range(len(reprocess_formats)):
-            assert(edge_mode_reported_reprocess[reprocess_format] == [0,1,2,3])
-            for edge_mode in range(4):
-                if its.caps.edge_mode(props, edge_mode):
-                    assert(sharpnesses_reprocess[reprocess_format][edge_mode] >=
-                        (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF) *
-                        sharpnesses_reprocess[reprocess_format][0] *
-                        sharpness_regular[edge_mode] / sharpness_regular[0])
+            # Verify HQ(2) is sharper than OFF(0)
+            assert(sharpnesses_reprocess[reprocess_format][2] >
+                   sharpnesses_reprocess[reprocess_format][0])
+
+            # Verify ZSL(3) is similar to OFF(0)
+            assert(numpy.isclose(sharpnesses_reprocess[reprocess_format][3],
+                                 sharpnesses_reprocess[reprocess_format][0],
+                                 THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+
+            # Verify OFF(0) is not sharper than FAST(1)
+            assert(sharpnesses_reprocess[reprocess_format][1] >
+                   sharpnesses_reprocess[reprocess_format][0] *
+                   (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+
+            # Verify FAST(1) is not sharper than HQ(2)
+            assert(sharpnesses_reprocess[reprocess_format][2] >
+                   sharpnesses_reprocess[reprocess_format][1] *
+                   (1.0 - THRESHOLD_RELATIVE_SHARPNESS_DIFF))
+
+            # Verify reprocessing HQ(2) is similar to regular HQ(2) relative to
+            # OFF(0)
+            assert(numpy.isclose(sharpness_reprocess[reprocess_format][2] /
+                                    sharpness_reprocess[reprocess_format][0],
+                                 sharpness_regular[2] / sharpness_regular[0],
+                                 THRESHOLD_RELATIVE_SHARPNESS_DIFF))
 
 if __name__ == '__main__':
     main()
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index cb39f3e..39302c1 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -56,6 +56,7 @@
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
     <uses-permission android:name="com.android.providers.tv.permission.READ_EPG_DATA" />
     <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
+    <uses-permission android:name="android.permission.USE_FINGERPRINT"/>
 
     <!-- Needed by the Audio Quality Verifier to store the sound samples that will be mailed. -->
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
@@ -352,6 +353,28 @@
             <meta-data android:name="test_category" android:value="@string/test_category_security" />
         </activity>
 
+        <activity android:name=".security.FingerprintBoundKeysTest"
+                android:label="@string/sec_fingerprint_bound_key_test"
+                android:configChanges="keyboardHidden|orientation|screenSize" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_security" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
+        </activity>
+        <activity android:name=".security.ScreenLockBoundKeysTest"
+                android:label="@string/sec_lock_bound_key_test"
+                android:configChanges="keyboardHidden|orientation|screenSize" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.cts.intent.category.MANUAL_TEST" />
+            </intent-filter>
+            <meta-data android:name="test_category" android:value="@string/test_category_security" />
+            <meta-data android:name="test_excluded_features"
+                       android:value="android.hardware.type.television:android.software.leanback:android.hardware.type.watch" />
+        </activity>
         <activity android:name=".security.LockConfirmBypassTest"
                 android:label="@string/lock_confirm_test_title"
                 android:configChanges="keyboardHidden|orientation|screenSize" >
diff --git a/apps/CtsVerifier/res/layout/sec_screen_lock_keys_main.xml b/apps/CtsVerifier/res/layout/sec_screen_lock_keys_main.xml
new file mode 100644
index 0000000..af53335
--- /dev/null
+++ b/apps/CtsVerifier/res/layout/sec_screen_lock_keys_main.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2011 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:padding="10dip"
+        >
+
+    <Button android:id="@+id/sec_start_test_button"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            android:text="@string/sec_start_test"
+            />
+
+    <include android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_alignParentBottom="true"
+            layout="@layout/pass_fail_buttons"
+            />
+
+</RelativeLayout>
+
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index f93f09f..2fedfbe 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -89,11 +89,13 @@
     </string>
     <string name="car_dock_test">Car Dock Test</string>
     <string name="car_dock_test_desc">This test ensures that car mode opens the app associated with
-        car dock when going into car mode. Click on "Enable Car Mode" to start the test. Clicking on
-        the button will either bring up a disambiguation dialog asking which app to open or
-        immediately open the CAR_DOCK application. Select the "CTS Verifier" app if the dialog pops up,
-        which open the CAR_DOCK application. In the CAR_DOCK application, press the home button, which
-        will enable the pass button if the framework correctly tries to open the CAR_DOCk app again.</string>
+        car dock when going into car mode.\n\n
+        Click on "Enable Car Mode" to start the test. Clicking on the button will either bring up a
+        disambiguation dialog asking which app to open or immediately open the CAR_DOCK application.
+        Select the "CTS Verifier" app and then "Always" if the dialog pops up.
+        This will open the CAR_DOCK application.\n\n
+        In the CAR_DOCK application, press the home button, which will enable the pass button if the
+        framework correctly tries to open the CAR_DOCK app again.</string>
     <string name="car_mode_enable">Enable Car Mode</string>
     <string name="car_dock_activity_text">Press the Home button</string>
     <string name="da_no_policy">1. Press the \"Generate Policy\" to create a random device
@@ -124,6 +126,24 @@
     <string name="da_lock_success">It appears the screen was locked successfully!</string>
     <string name="da_lock_error">It does not look like the screen was locked...</string>
 
+    <!-- Strings for lock bound keys test -->
+    <string name="sec_lock_bound_key_test">Lock Bound Keys Test</string>
+    <string name="sec_lock_bound_key_test_info">
+        This test ensures that Keystore cryptographic keys that are bound to lock screen authentication
+        are unusable without a recent enough authentication. You need to set up a screen lock in order to
+        complete this test. If available, this test should be run by using fingerprint authentication
+        as well as PIN/pattern/password authentication.
+    </string>
+    <string name="sec_fingerprint_bound_key_test">Fingerprint Bound Keys Test</string>
+    <string name="sec_fingerprint_bound_key_test_info">
+        This test ensures that Keystore cryptographic keys that are bound to fingerprint authentication
+        are unusable without an authentication. You need to set up a fingerprint order to
+        complete this test.
+    </string>
+    <string name="sec_fp_dialog_message">Authenticate now with fingerprint</string>
+    <string name="sec_fp_auth_failed">Authentication failed</string>
+    <string name="sec_start_test">Start Test</string>
+
     <!-- Strings for BluetoothActivity -->
     <string name="bluetooth_test">Bluetooth Test</string>
     <string name="bluetooth_test_info">The Bluetooth Control tests check whether or not the device
@@ -1671,10 +1691,10 @@
 
     <!-- String for the bundled TV app Tests -->
 
-    <string name="tv_input_discover_test">Third-party TV input test</string>
+    <string name="tv_input_discover_test">3rd-party TV input test</string>
     <string name="tv_input_discover_test_info">
     Verify that the bundled TV app launches via Intent and calls the proper API to discover
-    third-party TV inputs.
+    3rd-party TV inputs.
     </string>
     <string name="tv_input_discover_test_go_to_setup">
     Select the \"Launch TV app\" button and set up the newly installed TV input:
@@ -1695,7 +1715,7 @@
     to the \"Dummy\" channel.
     </string>
     <string name="tv_input_discover_test_verify_global_search">
-    Verify the TV app provides query results for third-party input\'s channels and programs in
+    Verify the TV app provides query results for 3rd-party input\'s channels and programs in
     global search results.
     </string>
     <string name="tv_input_discover_test_go_to_epg">
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
new file mode 100644
index 0000000..70899c6
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
@@ -0,0 +1,247 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.security;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.Manifest;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.DialogFragment;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.util.Log;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.UserNotAuthenticatedException;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+public class FingerprintBoundKeysTest extends PassFailButtons.Activity {
+    private static final String TAG = "FingerprintBoundKeysTest";
+
+    /** Alias for our key in the Android Key Store. */
+    private static final String KEY_NAME = "my_key";
+    private static final byte[] SECRET_BYTE_ARRAY = new byte[] {1, 2, 3, 4, 5, 6};
+    private static final int AUTHENTICATION_DURATION_SECONDS = 5;
+    private static final int CONFIRM_CREDENTIALS_REQUEST_CODE = 1;
+    private static final int FINGERPRINT_PERMISSION_REQUEST_CODE = 0;
+
+    private FingerprintManager mFingerprintManager;
+    private KeyguardManager mKeyguardManager;
+    private FingerprintAuthDialogFragment mFingerprintDialog;
+    private Cipher mCipher;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.sec_screen_lock_keys_main);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.sec_fingerprint_bound_key_test, R.string.sec_fingerprint_bound_key_test_info, -1);
+        getPassButton().setEnabled(false);
+        requestPermissions(new String[]{Manifest.permission.USE_FINGERPRINT},
+                FINGERPRINT_PERMISSION_REQUEST_CODE);
+    }
+
+    @Override
+    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] state) {
+        if (requestCode == FINGERPRINT_PERMISSION_REQUEST_CODE && state[0] == PackageManager.PERMISSION_GRANTED) {
+            mFingerprintManager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE);
+            mKeyguardManager = (KeyguardManager) getSystemService(KeyguardManager.class);
+
+            try {
+                KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+                keyStore.load(null);
+                SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null);
+                mCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+                            + KeyProperties.BLOCK_MODE_CBC + "/"
+                            + KeyProperties.ENCRYPTION_PADDING_PKCS7);
+
+                mCipher.init(Cipher.ENCRYPT_MODE, secretKey);
+            } catch (KeyPermanentlyInvalidatedException e) {
+                createKey();
+                showToast("The key has been invalidated, please try again.\n");
+            } catch (NoSuchPaddingException | KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
+                    | NoSuchAlgorithmException | InvalidKeyException e) {
+                throw new RuntimeException("Failed to init Cipher", e);
+            }
+
+            Button startTestButton = (Button) findViewById(R.id.sec_start_test_button);
+            startTestButton.setOnClickListener(new OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    if (tryEncrypt()) {
+                        showToast("Test failed. Key accessible without auth.");
+                    } else {
+                        showAuthenticationScreen();
+                    }
+                }
+
+            });
+
+            if (!mKeyguardManager.isKeyguardSecure()) {
+                // Show a message that the user hasn't set up a lock screen.
+                showToast( "Secure lock screen hasn't been set up.\n"
+                                + "Go to 'Settings -> Security -> Screen lock' to set up a lock screen");
+                startTestButton.setEnabled(false);
+            } else if (!mFingerprintManager.hasEnrolledFingerprints()) {
+                showToast("No fingerprints enrolled.\n"
+                                + "Go to 'Settings -> Security -> Fingerprint' to set up a fingerprint");
+                startTestButton.setEnabled(false);
+            } else {
+                createKey();
+            }
+        }
+    }
+
+    /**
+     * Creates a symmetric key in the Android Key Store which can only be used after the user has
+     * authenticated with device credentials within the last X seconds.
+     */
+    private void createKey() {
+        // Generate a key to decrypt payment credentials, tokens, etc.
+        // This will most likely be a registration step for the user when they are setting up your app.
+        try {
+            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+            keyStore.load(null);
+            KeyGenerator keyGenerator = KeyGenerator.getInstance(
+                    KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
+
+            // Set the alias of the entry in Android KeyStore where the key will appear
+            // and the constrains (purposes) in the constructor of the Builder
+            keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
+                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+                    .setUserAuthenticationRequired(true)
+                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
+                    .build());
+            keyGenerator.generateKey();
+        } catch (NoSuchAlgorithmException | NoSuchProviderException
+                | InvalidAlgorithmParameterException | KeyStoreException
+                | CertificateException | IOException e) {
+            throw new RuntimeException("Failed to create a symmetric key", e);
+        }
+    }
+
+    /**
+     * Tries to encrypt some data with the generated key in {@link #createKey} which is
+     * only works if the user has just authenticated via device credentials.
+     */
+    private boolean tryEncrypt() {
+        try {
+            mCipher.doFinal(SECRET_BYTE_ARRAY);
+            return true;
+        } catch (BadPaddingException | IllegalBlockSizeException e) {
+            return false;
+        }
+    }
+
+    private void showAuthenticationScreen() {
+        mFingerprintDialog = new FingerprintAuthDialogFragment();
+        mFingerprintDialog.show(getFragmentManager(), "fingerprint_dialog");
+    }
+
+    private void showToast(String message) {
+        Toast.makeText(this, message, Toast.LENGTH_LONG)
+            .show();
+    }
+
+    public class FingerprintAuthDialogFragment extends DialogFragment {
+
+        private CancellationSignal mCancellationSignal;
+        private FingerprintManagerCallback mFingerprintManagerCallback;
+        private boolean mSelfCancelled;
+
+        class FingerprintManagerCallback extends FingerprintManager.AuthenticationCallback {
+            @Override
+            public void onAuthenticationError(int errMsgId, CharSequence errString) {
+                if (!mSelfCancelled) {
+                    showToast(errString.toString());
+                }
+            }
+
+            @Override
+            public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
+                showToast(helpString.toString());
+            }
+
+            @Override
+            public void onAuthenticationFailed() {
+                showToast(getString(R.string.sec_fp_auth_failed));
+            }
+
+            @Override
+            public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
+                if (tryEncrypt()) {
+                    showToast("Test passed.");
+                    getPassButton().setEnabled(true);
+                    FingerprintAuthDialogFragment.this.dismiss();
+                } else {
+                    showToast("Test failed. Key not accessible after auth");
+                }
+            }
+        }
+
+        @Override
+        public void onDismiss(DialogInterface dialog) {
+            mCancellationSignal.cancel();
+            mSelfCancelled = true;
+        }
+
+        @Override
+        public Dialog onCreateDialog(Bundle savedInstanceState) {
+            mCancellationSignal = new CancellationSignal();
+            mSelfCancelled = false;
+            mFingerprintManagerCallback = new FingerprintManagerCallback();
+            mFingerprintManager.authenticate(new FingerprintManager.CryptoObject(mCipher),
+                    mCancellationSignal, 0, mFingerprintManagerCallback, null);
+            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+            builder.setMessage(R.string.sec_fp_dialog_message);
+            return builder.create();
+        }
+
+    }
+}
+
+
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/ScreenLockBoundKeysTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/ScreenLockBoundKeysTest.java
new file mode 100644
index 0000000..618b99a
--- /dev/null
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/ScreenLockBoundKeysTest.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.verifier.security;
+
+import com.android.cts.verifier.PassFailButtons;
+import com.android.cts.verifier.R;
+
+import android.app.AlertDialog;
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.keystore.KeyPermanentlyInvalidatedException;
+import android.security.keystore.KeyProperties;
+import android.security.keystore.UserNotAuthenticatedException;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.Button;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.KeyGenerator;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.SecretKey;
+
+public class ScreenLockBoundKeysTest extends PassFailButtons.Activity {
+
+    /** Alias for our key in the Android Key Store. */
+    private static final String KEY_NAME = "my_key";
+    private static final byte[] SECRET_BYTE_ARRAY = new byte[] {1, 2, 3, 4, 5, 6};
+    private static final int AUTHENTICATION_DURATION_SECONDS = 5;
+    private static final int CONFIRM_CREDENTIALS_REQUEST_CODE = 1;
+
+    private KeyguardManager mKeyguardManager;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.sec_screen_lock_keys_main);
+        setPassFailButtonClickListeners();
+        setInfoResources(R.string.sec_lock_bound_key_test, R.string.sec_lock_bound_key_test_info, -1);
+
+        mKeyguardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
+
+        getPassButton().setEnabled(false);
+
+        Button startTestButton = (Button) findViewById(R.id.sec_start_test_button);
+        startTestButton.setOnClickListener(new OnClickListener() {
+            @Override
+            public void onClick(View v) {
+                showToast("Test running...");
+                v.postDelayed(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (tryEncrypt()) {
+                            showToast("Test failed. Key accessible without auth.");
+                        } else {
+                            showAuthenticationScreen();
+                        }
+                    }
+                },
+                AUTHENTICATION_DURATION_SECONDS * 1000);
+            }
+
+        });
+
+        if (!mKeyguardManager.isKeyguardSecure()) {
+            // Show a message that the user hasn't set up a lock screen.
+            Toast.makeText(this,
+                    "Secure lock screen hasn't set up.\n"
+                            + "Go to 'Settings -> Security -> Screenlock' to set up a lock screen",
+                    Toast.LENGTH_LONG).show();
+            startTestButton.setEnabled(false);
+            return;
+        }
+
+        createKey();
+    }
+
+    /**
+     * Creates a symmetric key in the Android Key Store which can only be used after the user has
+     * authenticated with device credentials within the last X seconds.
+     */
+    private void createKey() {
+        // Generate a key to decrypt payment credentials, tokens, etc.
+        // This will most likely be a registration step for the user when they are setting up your app.
+        try {
+            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+            keyStore.load(null);
+            KeyGenerator keyGenerator = KeyGenerator.getInstance(
+                    KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
+
+            // Set the alias of the entry in Android KeyStore where the key will appear
+            // and the constrains (purposes) in the constructor of the Builder
+            keyGenerator.init(new KeyGenParameterSpec.Builder(KEY_NAME,
+                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+                    .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
+                    .setUserAuthenticationRequired(true)
+                            // Require that the user has unlocked in the last 30 seconds
+                    .setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS)
+                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
+                    .build());
+            keyGenerator.generateKey();
+        } catch (NoSuchAlgorithmException | NoSuchProviderException
+                | InvalidAlgorithmParameterException | KeyStoreException
+                | CertificateException | IOException e) {
+            throw new RuntimeException("Failed to create a symmetric key", e);
+        }
+    }
+
+    /**
+     * Tries to encrypt some data with the generated key in {@link #createKey} which is
+     * only works if the user has just authenticated via device credentials.
+     */
+    private boolean tryEncrypt() {
+        try {
+            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+            keyStore.load(null);
+            SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null);
+            Cipher cipher = Cipher.getInstance(
+                    KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/"
+                            + KeyProperties.ENCRYPTION_PADDING_PKCS7);
+
+            // Try encrypting something, it will only work if the user authenticated within
+            // the last AUTHENTICATION_DURATION_SECONDS seconds.
+            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
+            cipher.doFinal(SECRET_BYTE_ARRAY);
+            return true;
+        } catch (UserNotAuthenticatedException e) {
+            // User is not authenticated, let's authenticate with device credentials.
+            return false;
+        } catch (KeyPermanentlyInvalidatedException e) {
+            // This happens if the lock screen has been disabled or reset after the key was
+            // generated after the key was generated.
+            createKey();
+            Toast.makeText(this, "Set up lockscreen after test ran. Retry the test.\n"
+                            + e.getMessage(),
+                    Toast.LENGTH_LONG).show();
+            return false;
+        } catch (BadPaddingException | IllegalBlockSizeException | KeyStoreException |
+                CertificateException | UnrecoverableKeyException | IOException
+                | NoSuchPaddingException | NoSuchAlgorithmException | InvalidKeyException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private void showAuthenticationScreen() {
+        // Create the Confirm Credentials screen. You can customize the title and description. Or
+        // we will provide a generic one for you if you leave it null
+        Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(null, null);
+        if (intent != null) {
+            startActivityForResult(intent, CONFIRM_CREDENTIALS_REQUEST_CODE);
+        }
+    }
+
+    private void showToast(String message) {
+        Toast.makeText(this, message, Toast.LENGTH_LONG)
+            .show();
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        super.onActivityResult(requestCode, resultCode, data);
+        switch (requestCode) {
+            case CONFIRM_CREDENTIALS_REQUEST_CODE:
+                if (resultCode == RESULT_OK) {
+                    if (tryEncrypt()) {
+                        showToast("Test passed.");
+                        getPassButton().setEnabled(true);
+                    } else {
+                        showToast("Test failed. Key not accessible after auth");
+                    }
+                }
+        }
+    }
+}
+
diff --git a/build/config.mk b/build/config.mk
index ffc71ba..56d4ae6 100644
--- a/build/config.mk
+++ b/build/config.mk
@@ -47,3 +47,4 @@
 BUILD_CTS_DEQP_PACKAGE := cts/build/test_deqp_package.mk
 BUILD_CTS_SUPPORT_PACKAGE := cts/build/support_package.mk
 BUILD_CTS_MODULE_TEST_CONFIG := cts/build/module_test_config.mk
+BUILD_CTS_DEVICE_INFO_PACKAGE := cts/build/device_info_package.mk
diff --git a/build/device_info_package.mk b/build/device_info_package.mk
new file mode 100644
index 0000000..600f6a1
--- /dev/null
+++ b/build/device_info_package.mk
@@ -0,0 +1,67 @@
+# 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.
+
+#
+# Builds a package which can be instrumented to retrieve information about the device under test.
+#
+
+DEVICE_INFO_PACKAGE := com.android.compatibility.common.deviceinfo
+DEVICE_INFO_INSTRUMENT := com.android.compatibility.common.deviceinfo.DeviceInfoInstrument
+DEVICE_INFO_PERMISSIONS += android.permission.WRITE_EXTERNAL_STORAGE
+DEVICE_INFO_ACTIVITIES += $(DEVICE_INFO_PACKAGE).GenericDeviceInfo
+
+# Add the base device info
+LOCAL_STATIC_JAVA_LIBRARIES += compatibility-device-info
+
+# Generator of APK manifests.
+MANIFEST_GENERATOR_JAR := $(HOST_OUT_JAVA_LIBRARIES)/compatibility-manifest-generator.jar
+MANIFEST_GENERATOR := java -jar $(MANIFEST_GENERATOR_JAR)
+
+# Generate the manifest
+manifest_xml := $(call intermediates-dir-for,APPS,$(LOCAL_PACKAGE_NAME))/AndroidManifest.xml
+$(manifest_xml): PRIVATE_INFO_PERMISSIONS := $(foreach permission, $(DEVICE_INFO_PERMISSIONS),-r $(permission))
+$(manifest_xml): PRIVATE_INFO_ACTIVITIES := $(foreach activity,$(DEVICE_INFO_ACTIVITIES),-a $(activity))
+$(manifest_xml): PRIVATE_PACKAGE := $(DEVICE_INFO_PACKAGE)
+$(manifest_xml): PRIVATE_INSTRUMENT := $(DEVICE_INFO_INSTRUMENT)
+
+# Regenerate manifest.xml if the generator jar, */cts-device-info/Android.mk, or this file is changed.
+$(manifest_xml): $(MANIFEST_GENERATOR_JAR) $(LOCAL_PATH)/Android.mk cts/build/device_info_package.mk
+	$(hide) echo Generating manifest for $(PRIVATE_NAME)
+	$(hide) mkdir -p $(dir $@)
+	$(hide) $(MANIFEST_GENERATOR) \
+						$(PRIVATE_INFO_PERMISSIONS) \
+						$(PRIVATE_INFO_ACTIVITIES) \
+						-p $(PRIVATE_PACKAGE) \
+						-i $(PRIVATE_INSTRUMENT) \
+						-o $@
+
+# Reset variables
+DEVICE_INFO_PACKAGE :=
+DEVICE_INFO_INSTRUMENT :=
+DEVICE_INFO_PERMISSIONS :=
+DEVICE_INFO_ACTIVITIES :=
+
+LOCAL_FULL_MANIFEST_FILE := $(manifest_xml)
+# Disable by default
+LOCAL_DEX_PREOPT := false
+LOCAL_PROGUARD_ENABLED := disabled
+
+# Don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# And when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/common/device-side/device-info/Android.mk b/common/device-side/device-info/Android.mk
new file mode 100644
index 0000000..8c5052b
--- /dev/null
+++ b/common/device-side/device-info/Android.mk
@@ -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.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := compatibility-device-info
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivity.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivity.java
new file mode 100644
index 0000000..62e512c
--- /dev/null
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivity.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.deviceinfo;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Environment;
+import android.text.TextUtils;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Collect device information on target device and write to a JSON file.
+ */
+public abstract class DeviceInfoActivity extends Activity {
+
+    /** Device info result code: collector failed to complete. */
+    private static final int DEVICE_INFO_RESULT_FAILED = -2;
+    /** Device info result code: collector completed with error. */
+    private static final int DEVICE_INFO_RESULT_ERROR = -1;
+    /** Device info result code: collector has started but not completed. */
+    private static final int DEVICE_INFO_RESULT_STARTED = 0;
+    /** Device info result code: collector completed success. */
+    private static final int DEVICE_INFO_RESULT_OK = 1;
+
+    private static final int MAX_STRING_VALUE_LENGTH = 1000;
+    private static final int MAX_ARRAY_LENGTH = 1000;
+
+    private static final String LOG_TAG = "DeviceInfoActivity";
+
+    private CountDownLatch mDone = new CountDownLatch(1);
+    private JsonWriter mJsonWriter = null;
+    private String mResultFilePath = null;
+    private String mErrorMessage = "Collector has started.";
+    private int mResultCode = DEVICE_INFO_RESULT_STARTED;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        if (createFilePath()) {
+            createJsonWriter();
+            startJsonWriter();
+            collectDeviceInfo();
+            closeJsonWriter();
+
+            if (mResultCode == DEVICE_INFO_RESULT_STARTED) {
+                mResultCode = DEVICE_INFO_RESULT_OK;
+            }
+        }
+
+        Intent data = new Intent();
+        if (mResultCode == DEVICE_INFO_RESULT_OK) {
+            data.setData(Uri.parse(mResultFilePath));
+            setResult(RESULT_OK, data);
+        } else {
+            data.setData(Uri.parse(mErrorMessage));
+            setResult(RESULT_CANCELED, data);
+        }
+
+        mDone.countDown();
+        finish();
+    }
+
+    /**
+     * Method to collect device information.
+     */
+    protected abstract void collectDeviceInfo();
+
+    void waitForActivityToFinish() {
+        try {
+            mDone.await();
+        } catch (Exception e) {
+            failed("Exception while waiting for activity to finish: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Returns the error message if collector did not complete successfully.
+     */
+    String getErrorMessage() {
+        if (mResultCode == DEVICE_INFO_RESULT_OK) {
+            return null;
+        }
+        return mErrorMessage;
+    }
+
+    /**
+     * Returns the path to the json file if collector completed successfully.
+     */
+    String getResultFilePath() {
+        if (mResultCode == DEVICE_INFO_RESULT_OK) {
+            return mResultFilePath;
+        }
+        return null;
+    }
+
+    private void error(String message) {
+        mResultCode = DEVICE_INFO_RESULT_ERROR;
+        mErrorMessage = message;
+        Log.e(LOG_TAG, message);
+    }
+
+    private void failed(String message) {
+        mResultCode = DEVICE_INFO_RESULT_FAILED;
+        mErrorMessage = message;
+        Log.e(LOG_TAG, message);
+    }
+
+    private boolean createFilePath() {
+        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+            failed("External storage is not mounted");
+            return false;
+        }
+        final File dir = new File(Environment.getExternalStorageDirectory(), "device-info-files");
+        if (!dir.mkdirs() && !dir.isDirectory()) {
+            failed("Cannot create directory for device info files");
+            return false;
+        }
+
+        // Create file at /sdcard/device-info-files/<class_name>.deviceinfo.json
+        final File jsonFile = new File(dir, getClass().getSimpleName() + ".deviceinfo.json");
+        try {
+            jsonFile.createNewFile();
+        } catch (Exception e) {
+            failed("Cannot create file to collect device info");
+            return false;
+        }
+        mResultFilePath = jsonFile.getAbsolutePath();
+        return true;
+    }
+
+    private void createJsonWriter() {
+        try {
+            FileOutputStream out = new FileOutputStream(mResultFilePath);
+            mJsonWriter = new JsonWriter(new OutputStreamWriter(out, StandardCharsets.UTF_8));
+            // TODO(agathaman): remove to make json output less pretty
+            mJsonWriter.setIndent("  ");
+        } catch (Exception e) {
+            failed("Failed to create JSON writer: " + e.getMessage());
+        }
+    }
+
+    private void startJsonWriter() {
+        try {
+            mJsonWriter.beginObject();
+        } catch (Exception e) {
+            failed("Failed to begin JSON object: " + e.getMessage());
+        }
+    }
+
+    private void closeJsonWriter() {
+        try {
+            mJsonWriter.endObject();
+            mJsonWriter.close();
+        } catch (Exception e) {
+            failed("Failed to close JSON object: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Start a new group of result.
+     */
+    public void startGroup(String name) {
+        try {
+            mJsonWriter.name(name);
+            mJsonWriter.beginObject();
+        } catch (Exception e) {
+            error("Failed to begin JSON group: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Complete adding result to the last started group.
+     */
+    public void endGroup() {
+        try {
+            mJsonWriter.endObject();
+        } catch (Exception e) {
+            error("Failed to end JSON group: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Add a double value result.
+     */
+    public void addResult(String name, double value) {
+        checkName(name);
+        try {
+            mJsonWriter.name(name).value(value);
+        } catch (Exception e) {
+            error("Failed to add result for type double: " + e.getMessage());
+        }
+    }
+
+    /**
+    * Add a long value result.
+    */
+    public void addResult(String name, long value) {
+        checkName(name);
+        try {
+            mJsonWriter.name(name).value(value);
+        } catch (Exception e) {
+            error("Failed to add result for type long: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Add an int value result.
+     */
+    public void addResult(String name, int value) {
+        checkName(name);
+        try {
+            mJsonWriter.name(name).value((Number) value);
+        } catch (Exception e) {
+            error("Failed to add result for type int: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Add a boolean value result.
+     */
+    public void addResult(String name, boolean value) {
+        checkName(name);
+        try {
+            mJsonWriter.name(name).value(value);
+        } catch (Exception e) {
+            error("Failed to add result for type boolean: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Add a String value result.
+     */
+    public void addResult(String name, String value) {
+        checkName(name);
+        try {
+            mJsonWriter.name(name).value(checkString(value));
+        } catch (Exception e) {
+            error("Failed to add result for type String: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Add a double array result.
+     */
+    public void addArray(String name, double[] list) {
+        checkName(name);
+        try {
+            mJsonWriter.name(name);
+            mJsonWriter.beginArray();
+            for (double value : checkArray(list)) {
+                mJsonWriter.value(value);
+            }
+            mJsonWriter.endArray();
+        } catch (Exception e) {
+            error("Failed to add result array for type double: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Add a long array result.
+     */
+    public void addArray(String name, long[] list) {
+        checkName(name);
+        try {
+        mJsonWriter.name(name);
+        mJsonWriter.beginArray();
+        for (long value : checkArray(list)) {
+            mJsonWriter.value(value);
+        }
+            mJsonWriter.endArray();
+        } catch (Exception e) {
+            error("Failed to add result array for type long: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Add an int array result.
+     */
+    public void addArray(String name, int[] list) {
+        checkName(name);
+        try {
+            mJsonWriter.name(name);
+            mJsonWriter.beginArray();
+            for (int value : checkArray(list)) {
+                mJsonWriter.value((Number) value);
+            }
+            mJsonWriter.endArray();
+        } catch (Exception e) {
+            error("Failed to add result array for type int: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Add a boolean array result.
+     */
+    public void addArray(String name, boolean[] list) {
+        checkName(name);
+        try {
+            mJsonWriter.name(name);
+            mJsonWriter.beginArray();
+            for (boolean value : checkArray(list)) {
+                mJsonWriter.value(value);
+            }
+            mJsonWriter.endArray();
+        } catch (Exception e) {
+            error("Failed to add result array for type boolean: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Add a String array result.
+     */
+    public void addArray(String name, String[] list) {
+        checkName(name);
+        try {
+            mJsonWriter.name(name);
+            mJsonWriter.beginArray();
+            for (String value : checkArray(list)) {
+                mJsonWriter.value(checkString(value));
+            }
+            mJsonWriter.endArray();
+        } catch (Exception e) {
+            error("Failed to add result array for type Sting: " + e.getMessage());
+        }
+    }
+
+    private static boolean[] checkArray(boolean[] values) {
+        if (values.length > MAX_ARRAY_LENGTH) {
+            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
+        } else {
+            return values;
+        }
+    }
+
+    private static double[] checkArray(double[] values) {
+        if (values.length > MAX_ARRAY_LENGTH) {
+            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
+        } else {
+            return values;
+        }
+    }
+
+    private static int[] checkArray(int[] values) {
+        if (values.length > MAX_ARRAY_LENGTH) {
+            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
+        } else {
+            return values;
+        }
+    }
+
+    private static long[] checkArray(long[] values) {
+        if (values.length > MAX_ARRAY_LENGTH) {
+            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
+        } else {
+            return values;
+        }
+    }
+
+    private static String[] checkArray(String[] values) {
+        if (values.length > MAX_ARRAY_LENGTH) {
+            return Arrays.copyOf(values, MAX_ARRAY_LENGTH);
+        } else {
+            return values;
+        }
+    }
+
+    private static String checkString(String value) {
+        if (value.length() > MAX_STRING_VALUE_LENGTH) {
+            return value.substring(0, MAX_STRING_VALUE_LENGTH);
+        }
+        return value;
+    }
+
+    private static String checkName(String value) {
+        if (TextUtils.isEmpty(value)) {
+            throw new NullPointerException();
+        }
+        return value;
+    }
+}
+
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoInstrument.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoInstrument.java
new file mode 100644
index 0000000..f3af0bc
--- /dev/null
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/DeviceInfoInstrument.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 com.android.compatibility.common.deviceinfo;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * An instrumentation that runs all activities that extends DeviceInfoActivity.
+ */
+public class DeviceInfoInstrument extends Instrumentation {
+
+    private static final String LOG_TAG = "ExtendedDeviceInfo";
+    private static final int DEVICE_INFO_ACTIVITY_REQUEST = 1;
+
+    private Bundle mBundle = new Bundle();
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        start();
+    }
+
+    @Override
+    public void onStart() {
+        try {
+            Context context = getContext();
+            ActivityInfo[] activities = context.getPackageManager().getPackageInfo(
+                    context.getPackageName(), PackageManager.GET_ACTIVITIES).activities;
+
+            for (ActivityInfo activityInfo : activities) {
+                Class cls = Class.forName(activityInfo.name);
+                if (cls != DeviceInfoActivity.class &&
+                        DeviceInfoActivity.class.isAssignableFrom(cls)) {
+                    runActivity(activityInfo.name);
+                }
+            }
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Exception occurred while running activities.", e);
+            // Returns INSTRUMENTATION_CODE: 0
+            finish(Activity.RESULT_CANCELED, mBundle);
+        }
+        // Returns INSTRUMENTATION_CODE: -1
+        finish(Activity.RESULT_OK, mBundle);
+    }
+
+    /**
+     * Runs a device info activity and return the file path where the results are written to.
+     */
+    private void runActivity(String activityName) throws Exception {
+        Intent intent = new Intent();
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setClassName(this.getContext(), activityName);
+
+        DeviceInfoActivity activity = (DeviceInfoActivity) startActivitySync(intent);
+        waitForIdleSync();
+        activity.waitForActivityToFinish();
+
+        String className = Class.forName(activityName).getSimpleName();
+        String errorMessage = activity.getErrorMessage();
+        if (TextUtils.isEmpty(errorMessage)) {
+            mBundle.putString(className, activity.getResultFilePath());
+        } else {
+            mBundle.putString(className, errorMessage);
+            throw new Exception(errorMessage);
+        }
+    }
+}
+
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.java
new file mode 100644
index 0000000..f6e7c57
--- /dev/null
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/GenericDeviceInfo.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 com.android.compatibility.common.deviceinfo;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.FeatureInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.UserManager;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.Display;
+import android.view.WindowManager;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Scanner;
+import java.util.Set;
+
+import com.android.compatibility.common.deviceinfo.DeviceInfoActivity;
+
+/**
+ * Generic device info collector.
+ */
+public class GenericDeviceInfo extends DeviceInfoActivity {
+
+    public static final String BUILD_ID = "build_id";
+    public static final String BUILD_PRODUCT = "build_product";
+    public static final String BUILD_DEVICE = "build_device";
+    public static final String BUILD_BOARD = "build_board";
+    public static final String BUILD_MANUFACTURER = "build_manufacturer";
+    public static final String BUILD_BRAND = "build_brand";
+    public static final String BUILD_MODEL = "build_model";
+    public static final String BUILD_TYPE = "build_type";
+    public static final String BUILD_FINGERPRINT = "build_fingerprint";
+    public static final String BUILD_ABI = "build_abi";
+    public static final String BUILD_ABI2 = "build_abi2";
+    public static final String BUILD_ABIS = "build_abis";
+    public static final String BUILD_ABIS_32 = "build_abis_32";
+    public static final String BUILD_ABIS_64 = "build_abis_64";
+    public static final String BUILD_SERIAL = "build_serial";
+    public static final String BUILD_VERSION_RELEASE = "build_version_release";
+    public static final String BUILD_VERSION_SDK = "build_version_sdk";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void collectDeviceInfo() {
+        addResult(BUILD_ID, Build.ID);
+        addResult(BUILD_PRODUCT, Build.PRODUCT);
+        addResult(BUILD_DEVICE, Build.DEVICE);
+        addResult(BUILD_BOARD, Build.BOARD);
+        addResult(BUILD_MANUFACTURER, Build.MANUFACTURER);
+        addResult(BUILD_BRAND, Build.BRAND);
+        addResult(BUILD_MODEL, Build.MODEL);
+        addResult(BUILD_TYPE, Build.TYPE);
+        addResult(BUILD_FINGERPRINT, Build.FINGERPRINT);
+        addResult(BUILD_ABI, Build.CPU_ABI);
+        addResult(BUILD_ABI2, Build.CPU_ABI2);
+        addResult(BUILD_ABIS, TextUtils.join(",", Build.SUPPORTED_ABIS));
+        addResult(BUILD_ABIS_32, TextUtils.join(",", Build.SUPPORTED_32_BIT_ABIS));
+        addResult(BUILD_ABIS_64, TextUtils.join(",", Build.SUPPORTED_64_BIT_ABIS));
+        addResult(BUILD_SERIAL, Build.SERIAL);
+        addResult(BUILD_VERSION_RELEASE, Build.VERSION.RELEASE);
+        addResult(BUILD_VERSION_SDK, Build.VERSION.SDK);
+    }
+}
\ No newline at end of file
diff --git a/common/device-side/device-info/tests/Android.mk b/common/device-side/device-info/tests/Android.mk
new file mode 100644
index 0000000..e24c5c1
--- /dev/null
+++ b/common/device-side/device-info/tests/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-info
+
+LOCAL_JAVA_LIBRARIES := android.test.runner
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE := compatibility-device-info-tests
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
diff --git a/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivityTest.java b/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivityTest.java
new file mode 100644
index 0000000..8a8a66c
--- /dev/null
+++ b/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/DeviceInfoActivityTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.deviceinfo;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+/**
+ * Test for {@link DeviceInfoActivity}.
+ */
+public class DeviceInfoActivityTest extends ActivityInstrumentationTestCase2<SampleDeviceInfo> {
+
+    private static final String EXPECTED_FILE_PATH =
+            "/storage/emulated/0/device-info-files/SampleDeviceInfo.deviceinfo.json";
+
+    private SampleDeviceInfo mActivity;
+
+    public DeviceInfoActivityTest() {
+        super(SampleDeviceInfo.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // Start the activity and get a reference to it.
+        mActivity = getActivity();
+        // Wait for the activity to finish.
+        mActivity.waitForActivityToFinish();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mActivity = null;
+        super.tearDown();
+    }
+
+    public void testJsonFile() throws IOException {
+        String resultFilePath = mActivity.getResultFilePath();
+        // Check file path exist
+        assertNotNull("Expected a non-null resultFilePath", resultFilePath);
+        // Check file path location
+        assertEquals("Incorrect file path", EXPECTED_FILE_PATH, resultFilePath);
+        // Check json file content
+        String jsonContent = readFile(resultFilePath);
+        assertEquals("Incorrect json output", ExampleObjects.sampleDeviceInfoJson(), jsonContent);
+    }
+
+    private String readFile(String filePath) throws IOException {
+        InputStreamReader inputStreamReader = new InputStreamReader(
+                new FileInputStream(filePath), "UTF-8");
+        StringBuilder stringBuilder = new StringBuilder();
+        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
+        String line;
+        while((line = bufferedReader.readLine()) != null) {
+            stringBuilder.append(line);
+            stringBuilder.append('\n');
+        }
+        bufferedReader.close();
+        return stringBuilder.toString();
+    }
+}
+
diff --git a/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/ExampleObjects.java b/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/ExampleObjects.java
new file mode 100644
index 0000000..92e7df1
--- /dev/null
+++ b/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/ExampleObjects.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.compatibility.common.deviceinfo;
+
+/**
+ * Example Objects for {@link DeviceInfoActivity} test package.
+ */
+public final class ExampleObjects {
+
+    private static final int MAX_LENGTH = 1000;
+
+    private static final String SAMPLE_DEVICE_INFO_JSON = "{\n" +
+        "  \"foo\": {\n" +
+        "    \"foo_boolean\": true,\n" +
+        "    \"bar\": {\n" +
+        "      \"bar_string\": [\n" +
+        "        \"bar-string-1\",\n" +
+        "        \"bar-string-2\",\n" +
+        "        \"bar-string-3\"\n" +
+        "      ],\n" +
+        "      \"bar_boolean\": [\n" +
+        "        true,\n" +
+        "        false\n" +
+        "      ],\n" +
+        "      \"bar_double\": [\n" +
+        "        1.7976931348623157E308,\n" +
+        "        4.9E-324\n" +
+        "      ],\n" +
+        "      \"bar_int\": [\n" +
+        "        2147483647,\n" +
+        "        -2147483648\n" +
+        "      ],\n" +
+        "      \"bar_long\": [\n" +
+        "        9223372036854775807,\n" +
+        "        -9223372036854775808\n" +
+        "      ]\n" +
+        "    },\n" +
+        "    \"foo_double\": 1.7976931348623157E308,\n" +
+        "    \"foo_int\": 2147483647,\n" +
+        "    \"foo_long\": 9223372036854775807,\n" +
+        "    \"foo_string\": \"foo-string\",\n" +
+        "    \"long_string\": \"%s\",\n" +
+        "    \"long_int_array\": [\n%s" +
+        "    ]\n" +
+        "  }\n" +
+        "}\n";
+
+    public static String sampleDeviceInfoJson() {
+        StringBuilder longStringSb = new StringBuilder();
+        StringBuilder longArraySb = new StringBuilder();
+        int lastNum = MAX_LENGTH - 1;
+        for (int i = 0; i < MAX_LENGTH; i++) {
+            longStringSb.append("a");
+            longArraySb.append(String.format("      %d%s\n", i, ((i == lastNum)? "" : ",")));
+        }
+        return String.format(SAMPLE_DEVICE_INFO_JSON,
+            longStringSb.toString(), longArraySb.toString());
+    }
+}
\ No newline at end of file
diff --git a/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/SampleDeviceInfo.java b/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/SampleDeviceInfo.java
new file mode 100644
index 0000000..7da9951
--- /dev/null
+++ b/common/device-side/device-info/tests/src/com/android/compatibility/common/deviceinfo/SampleDeviceInfo.java
@@ -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.
+ */
+package com.android.compatibility.common.deviceinfo;
+
+import android.os.Bundle;
+
+import java.lang.StringBuilder;
+
+/**
+ * Sample device info collector.
+ */
+public class SampleDeviceInfo extends DeviceInfoActivity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void collectDeviceInfo() {
+        boolean[] booleans = {Boolean.TRUE, Boolean.FALSE};
+        double[] doubles = {Double.MAX_VALUE, Double.MIN_VALUE};
+        int[] ints = {Integer.MAX_VALUE, Integer.MIN_VALUE};
+        long[] longs = {Long.MAX_VALUE, Long.MIN_VALUE};
+
+        // Group Foo
+        startGroup("foo");
+        addResult("foo_boolean", Boolean.TRUE);
+
+        // Group Bar
+        startGroup("bar");
+        addArray("bar_string", new String[] {
+                "bar-string-1",
+                "bar-string-2",
+                "bar-string-3"});
+
+        addArray("bar_boolean", booleans);
+        addArray("bar_double", doubles);
+        addArray("bar_int", ints);
+        addArray("bar_long", longs);
+        endGroup(); // bar
+
+        addResult("foo_double", Double.MAX_VALUE);
+        addResult("foo_int", Integer.MAX_VALUE);
+        addResult("foo_long", Long.MAX_VALUE);
+        addResult("foo_string", "foo-string");
+
+        StringBuilder sb = new StringBuilder();
+        int[] arr = new int[1001];
+        for (int i = 0; i < 1001; i++) {
+            sb.append("a");
+            arr[i] = i;
+        }
+        addResult("long_string", sb.toString());
+        addArray("long_int_array", arr);
+
+        endGroup(); // foo
+    }
+}
+
diff --git a/common/host-side/manifest-generator/Android.mk b/common/host-side/manifest-generator/Android.mk
new file mode 100644
index 0000000..ca08928
--- /dev/null
+++ b/common/host-side/manifest-generator/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_STATIC_JAVA_LIBRARIES := kxml2-2.3.0
+
+LOCAL_JAR_MANIFEST := MANIFEST.mf
+
+LOCAL_CLASSPATH := $(HOST_JDK_TOOLS_JAR)
+
+LOCAL_MODULE := compatibility-manifest-generator
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/common/host-side/manifest-generator/MANIFEST.mf b/common/host-side/manifest-generator/MANIFEST.mf
new file mode 100644
index 0000000..4f62631
--- /dev/null
+++ b/common/host-side/manifest-generator/MANIFEST.mf
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: com.android.compatibility.common.generator.ManifestGenerator
diff --git a/common/host-side/manifest-generator/src/com/android/compatibility/common/generator/ManifestGenerator.java b/common/host-side/manifest-generator/src/com/android/compatibility/common/generator/ManifestGenerator.java
new file mode 100644
index 0000000..e23e254
--- /dev/null
+++ b/common/host-side/manifest-generator/src/com/android/compatibility/common/generator/ManifestGenerator.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.generator;
+
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.kxml2.io.KXmlSerializer;
+
+public class ManifestGenerator {
+
+    private static final String USAGE = "Usage: "
+        + "manifest-generator -n NAME -p PACKAGE_NAME -o OUTPUT_FILE -i INSTRUMENT_NAME "
+        + "[-r PERMISSION]+ [-a ACTIVITY]+";
+    private static final String MANIFEST = "manifest";
+    private static final String USES_SDK = "uses-sdk";
+    private static final String USES_PERMISSION = "uses-permission";
+    private static final String APPLICATION = "application";
+    private static final String INSTRUMENTATION = "instrumentation";
+    private static final String ACTIVITY = "activity";
+
+    public static void main(String[] args) {
+        String pkgName = null;
+        String instrumentName = null;
+        List<String> permissions = new ArrayList<>();
+        List<String> activities = new ArrayList<>();
+        String output = null;
+
+        for (int i = 0; i < args.length - 1; i++) {
+            if (args[i].equals("-p")) {
+                pkgName = args[++i];
+            } else if (args[i].equals("-a")) {
+                activities.add(args[++i]);
+            } else if (args[i].equals("-o")) {
+                output = args[++i];
+            } else if (args[i].equals("-i")) {
+                instrumentName = args[++i];
+            } else if (args[i].equals("-r")) {
+                permissions.add(args[++i]);
+            }
+        }
+
+        if (pkgName == null) {
+            error("Missing package name");
+        } else if (instrumentName == null) {
+            error("Missing instrumentation name");
+        } else if (activities.isEmpty()) {
+            error("No activities");
+        } else if (output == null) {
+            error("Missing output file");
+        }
+
+        FileOutputStream out = null;
+        try {
+          out = new FileOutputStream(output);
+          generate(out, pkgName, instrumentName, permissions, activities);
+        } catch (Exception e) {
+          System.err.println("Couldn't create manifest file");
+        } finally {
+          if (out != null) {
+              try {
+                  out.close();
+              } catch (Exception e) {
+                  // Ignore
+              }
+          }
+        }
+    }
+
+    /*package*/ static void generate(OutputStream out, String pkgName, String instrumentName,
+            List<String> permissions, List<String> activities) throws Exception {
+        final String ns = null;
+        KXmlSerializer serializer = new KXmlSerializer();
+        serializer.setOutput(out, "UTF-8");
+        serializer.startDocument("UTF-8", true);
+        serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
+        serializer.startTag(ns, MANIFEST);
+        serializer.attribute(ns, "xmlns:android", "http://schemas.android.com/apk/res/android");
+        serializer.attribute(ns, "package", pkgName);
+        serializer.startTag(ns, USES_SDK);
+        serializer.attribute(ns, "android:minSdkVersion", "8");
+        serializer.endTag(ns, USES_SDK);
+        for (String permission : permissions) {
+            serializer.startTag(ns, USES_PERMISSION);
+            serializer.attribute(ns, "android:name", permission);
+            serializer.endTag(ns, USES_PERMISSION);
+        }
+        serializer.startTag(ns, APPLICATION);
+        for (String activity : activities) {
+            serializer.startTag(ns, ACTIVITY);
+            serializer.attribute(ns, "android:name", activity);
+            serializer.endTag(ns, ACTIVITY);
+        }
+        serializer.endTag(ns, APPLICATION);
+        serializer.startTag(ns, INSTRUMENTATION);
+        serializer.attribute(ns, "android:name", instrumentName);
+        serializer.attribute(ns, "android:targetPackage", pkgName);
+        serializer.endTag(ns, INSTRUMENTATION);
+        serializer.endTag(ns, MANIFEST);
+        serializer.endDocument();
+        out.flush();
+    }
+
+    private static void error(String message) {
+        System.err.println(message);
+        System.err.println(USAGE);
+        System.exit(1);
+    }
+
+}
diff --git a/common/host-side/manifest-generator/tests/Android.mk b/common/host-side/manifest-generator/tests/Android.mk
new file mode 100644
index 0000000..2eb5d2f
--- /dev/null
+++ b/common/host-side/manifest-generator/tests/Android.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := compatibility-manifest-generator junit
+
+LOCAL_MODULE := compatibility-manifest-generator-tests
+
+LOCAL_MODULE_TAGS := optional
+
+include $(BUILD_HOST_JAVA_LIBRARY)
diff --git a/common/host-side/manifest-generator/tests/src/com/android/compatibility/common/generator/ManifestGeneratorTest.java b/common/host-side/manifest-generator/tests/src/com/android/compatibility/common/generator/ManifestGeneratorTest.java
new file mode 100644
index 0000000..457bbb8
--- /dev/null
+++ b/common/host-side/manifest-generator/tests/src/com/android/compatibility/common/generator/ManifestGeneratorTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compatibility.common.generator;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.TestCase;
+
+/** Unit tests for {@link ManifestGenerator}. */
+public class ManifestGeneratorTest extends TestCase {
+
+    private static final String PACKAGE = "test.package";
+    private static final String INSTRUMENT = "test.package.TestInstrument";
+    private static final String MANIFEST = "<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>\r\n"
+        + "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\" "
+        + "package=\"test.package\">\r\n"
+        + "  <uses-sdk android:minSdkVersion=\"8\" />\r\n"
+        + "%s"
+        + "  <application>\r\n"
+        + "%s"
+        + "  </application>\r\n"
+        + "  <instrumentation android:name=\"test.package.TestInstrument\" "
+        + "android:targetPackage=\"test.package\" />\r\n"
+        + "</manifest>";
+    private static final String PERMISSION = "  <uses-permission android:name=\"%s\" />\r\n";
+    private static final String PERMISSION_A = "android.permission.PermissionA";
+    private static final String PERMISSION_B = "android.permission.PermissionB";
+    private static final String ACTIVITY = "    <activity android:name=\"%s\" />\r\n";
+    private static final String ACTIVITY_A = "test.package.ActivityA";
+    private static final String ACTIVITY_B = "test.package.ActivityB";
+
+    public void testManifest() throws Exception {
+        List<String> permissions = new ArrayList<>();
+        permissions.add(PERMISSION_A);
+        permissions.add(PERMISSION_B);
+        List<String> activities = new ArrayList<>();
+        activities.add(ACTIVITY_A);
+        activities.add(ACTIVITY_B);
+        OutputStream output = new OutputStream() {
+            private StringBuilder string = new StringBuilder();
+            @Override
+            public void write(int b) throws IOException {
+                this.string.append((char) b);
+            }
+
+            @Override
+            public String toString(){
+                return this.string.toString();
+            }
+        };
+        ManifestGenerator.generate(output, PACKAGE, INSTRUMENT, permissions, activities);
+        String permissionXml = String.format(PERMISSION, PERMISSION_A)
+                + String.format(PERMISSION, PERMISSION_B);
+        String activityXml = String.format(ACTIVITY, ACTIVITY_A)
+                + String.format(ACTIVITY, ACTIVITY_B);
+        String expected = String.format(MANIFEST, permissionXml, activityXml);
+        assertEquals("Wrong manifest output", expected, output.toString());
+    }
+
+}
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AdoptableHostTest.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AdoptableHostTest.java
new file mode 100644
index 0000000..e256f76
--- /dev/null
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AdoptableHostTest.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.appsecurity;
+
+import static com.android.cts.appsecurity.SplitTests.ABI_TO_APK;
+import static com.android.cts.appsecurity.SplitTests.APK;
+import static com.android.cts.appsecurity.SplitTests.APK_mdpi;
+import static com.android.cts.appsecurity.SplitTests.APK_xxhdpi;
+import static com.android.cts.appsecurity.SplitTests.CLASS;
+import static com.android.cts.appsecurity.SplitTests.PKG;
+
+import com.android.cts.appsecurity.SplitTests.BaseInstallMultiple;
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IAbiReceiver;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.Arrays;
+
+/**
+ * Set of tests that verify behavior of adopted storage media, if supported.
+ */
+public class AdoptableHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
+    private IAbi mAbi;
+    private CtsBuildHelper mCtsBuild;
+
+    @Override
+    public void setAbi(IAbi abi) {
+        mAbi = abi;
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        assertNotNull(mAbi);
+        assertNotNull(mCtsBuild);
+
+        getDevice().uninstallPackage(PKG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        getDevice().uninstallPackage(PKG);
+    }
+
+    public void testApps() throws Exception {
+        if (!hasAdoptable()) return;
+        final String diskId = getAdoptionDisk();
+        try {
+            final String abi = mAbi.getName();
+            final String apk = ABI_TO_APK.get(abi);
+            assertNotNull("Failed to find APK for ABI " + abi, apk);
+
+            // Install simple app on internal
+            new InstallMultiple().useNaturalAbi().addApk(APK).addApk(apk).run();
+            runDeviceTests(PKG, CLASS, "testDataInternal");
+            runDeviceTests(PKG, CLASS, "testDataWrite");
+            runDeviceTests(PKG, CLASS, "testDataRead");
+            runDeviceTests(PKG, CLASS, "testNative");
+
+            // Adopt that disk!
+            assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
+            final LocalVolumeInfo vol = getAdoptionVolume();
+
+            // Move app and verify
+            assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " " + vol.uuid));
+            runDeviceTests(PKG, CLASS, "testDataNotInternal");
+            runDeviceTests(PKG, CLASS, "testDataRead");
+            runDeviceTests(PKG, CLASS, "testNative");
+
+            // Unmount, remount and verify
+            getDevice().executeShellCommand("sm unmount " + vol.volId);
+            getDevice().executeShellCommand("sm mount " + vol.volId);
+            runDeviceTests(PKG, CLASS, "testDataNotInternal");
+            runDeviceTests(PKG, CLASS, "testDataRead");
+            runDeviceTests(PKG, CLASS, "testNative");
+
+            // Move app back and verify
+            assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " internal"));
+            runDeviceTests(PKG, CLASS, "testDataInternal");
+            runDeviceTests(PKG, CLASS, "testDataRead");
+            runDeviceTests(PKG, CLASS, "testNative");
+
+            // Un-adopt volume and app should still be fine
+            getDevice().executeShellCommand("sm partition " + diskId + " public");
+            runDeviceTests(PKG, CLASS, "testDataInternal");
+            runDeviceTests(PKG, CLASS, "testDataRead");
+            runDeviceTests(PKG, CLASS, "testNative");
+
+        } finally {
+            cleanUp(diskId);
+        }
+    }
+
+    public void testPrimaryStorage() throws Exception {
+        if (!hasAdoptable()) return;
+        final String diskId = getAdoptionDisk();
+        try {
+            final String originalVol = getDevice()
+                    .executeShellCommand("sm get-primary-storage-uuid").trim();
+
+            if ("null".equals(originalVol)) {
+                verifyPrimaryInternal(diskId);
+            } else if ("primary_physical".equals(originalVol)) {
+                verifyPrimaryPhysical(diskId);
+            }
+        } finally {
+            cleanUp(diskId);
+        }
+    }
+
+    private void verifyPrimaryInternal(String diskId) throws Exception {
+        // Write some data to shared storage
+        new InstallMultiple().addApk(APK).run();
+        runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
+        runDeviceTests(PKG, CLASS, "testPrimaryInternal");
+        runDeviceTests(PKG, CLASS, "testPrimaryDataWrite");
+        runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+        // Adopt that disk!
+        assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
+        final LocalVolumeInfo vol = getAdoptionVolume();
+
+        // Move storage there and verify that data went along for ride
+        assertSuccess(getDevice().executeShellCommand("pm move-primary-storage " + vol.uuid));
+        runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
+        runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+        // Unmount and verify
+        getDevice().executeShellCommand("sm unmount " + vol.volId);
+        runDeviceTests(PKG, CLASS, "testPrimaryUnmounted");
+        getDevice().executeShellCommand("sm mount " + vol.volId);
+        runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
+        runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+        // Move app and verify backing storage volume is same
+        assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " " + vol.uuid));
+        runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
+        runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+        // And move back to internal
+        assertSuccess(getDevice().executeShellCommand("pm move-primary-storage internal"));
+        runDeviceTests(PKG, CLASS, "testPrimaryInternal");
+        runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+        assertSuccess(getDevice().executeShellCommand("pm move-package " + PKG + " internal"));
+        runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
+        runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+    }
+
+    private void verifyPrimaryPhysical(String diskId) throws Exception {
+        // Write some data to shared storage
+        new InstallMultiple().addApk(APK).run();
+        runDeviceTests(PKG, CLASS, "testPrimaryPhysical");
+        runDeviceTests(PKG, CLASS, "testPrimaryDataWrite");
+        runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+        // Adopt that disk!
+        assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
+        final LocalVolumeInfo vol = getAdoptionVolume();
+
+        // Move primary storage there, but since we just nuked primary physical
+        // the storage device will be empty
+        assertSuccess(getDevice().executeShellCommand("pm move-primary-storage " + vol.uuid));
+        runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
+        runDeviceTests(PKG, CLASS, "testPrimaryDataWrite");
+        runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+        // Unmount and verify
+        getDevice().executeShellCommand("sm unmount " + vol.volId);
+        runDeviceTests(PKG, CLASS, "testPrimaryUnmounted");
+        getDevice().executeShellCommand("sm mount " + vol.volId);
+        runDeviceTests(PKG, CLASS, "testPrimaryAdopted");
+        runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+
+        // And move to internal
+        assertSuccess(getDevice().executeShellCommand("pm move-primary-storage internal"));
+        runDeviceTests(PKG, CLASS, "testPrimaryOnSameVolume");
+        runDeviceTests(PKG, CLASS, "testPrimaryInternal");
+        runDeviceTests(PKG, CLASS, "testPrimaryDataRead");
+    }
+
+    /**
+     * Verify that we can install both new and inherited packages directly on
+     * adopted volumes.
+     */
+    public void testPackageInstaller() throws Exception {
+        if (!hasAdoptable()) return;
+        final String diskId = getAdoptionDisk();
+        try {
+            assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
+            final LocalVolumeInfo vol = getAdoptionVolume();
+
+            // Install directly onto adopted volume
+            new InstallMultiple().locationAuto().forceUuid(vol.uuid)
+                    .addApk(APK).addApk(APK_mdpi).run();
+            runDeviceTests(PKG, CLASS, "testDataNotInternal");
+            runDeviceTests(PKG, CLASS, "testDensityBest1");
+
+            // Now splice in an additional split which offers better resources
+            new InstallMultiple().locationAuto().inheritFrom(PKG)
+                    .addApk(APK_xxhdpi).run();
+            runDeviceTests(PKG, CLASS, "testDataNotInternal");
+            runDeviceTests(PKG, CLASS, "testDensityBest2");
+
+        } finally {
+            cleanUp(diskId);
+        }
+    }
+
+    /**
+     * Verify behavior when changes occur while adopted device is ejected and
+     * returned at a later time.
+     */
+    public void testEjected() throws Exception {
+        if (!hasAdoptable()) return;
+        final String diskId = getAdoptionDisk();
+        try {
+            assertEmpty(getDevice().executeShellCommand("sm partition " + diskId + " private"));
+            final LocalVolumeInfo vol = getAdoptionVolume();
+
+            // Install directly onto adopted volume, and write data there
+            new InstallMultiple().locationAuto().forceUuid(vol.uuid).addApk(APK).run();
+            runDeviceTests(PKG, CLASS, "testDataNotInternal");
+            runDeviceTests(PKG, CLASS, "testDataWrite");
+            runDeviceTests(PKG, CLASS, "testDataRead");
+
+            // Now unmount and uninstall; leaving stale package on adopted volume
+            getDevice().executeShellCommand("sm unmount " + vol.volId);
+            getDevice().uninstallPackage(PKG);
+
+            // Install second copy on internal, but don't write anything
+            new InstallMultiple().locationInternalOnly().addApk(APK).run();
+            runDeviceTests(PKG, CLASS, "testDataInternal");
+
+            // Kick through a remount cycle, which should purge the adopted app
+            getDevice().executeShellCommand("sm mount " + vol.volId);
+            runDeviceTests(PKG, CLASS, "testDataInternal");
+            try {
+                runDeviceTests(PKG, CLASS, "testDataRead");
+                fail("Unexpected data from adopted volume picked up");
+            } catch (AssertionError expected) {
+            }
+            getDevice().executeShellCommand("sm unmount " + vol.volId);
+
+            // Uninstall the internal copy and remount; we should have no record of app
+            getDevice().uninstallPackage(PKG);
+            getDevice().executeShellCommand("sm mount " + vol.volId);
+
+            try {
+                runDeviceTests(PKG, CLASS, "testNothing");
+                fail("Unexpected app not purged from adopted volume");
+            } catch (AssertionError expected) {
+            }
+        } finally {
+            cleanUp(diskId);
+        }
+    }
+
+    private boolean hasAdoptable() throws Exception {
+        return Boolean.parseBoolean(getDevice().executeShellCommand("sm has-adoptable").trim());
+    }
+
+    private String getAdoptionDisk() throws Exception {
+        final String disks = getDevice().executeShellCommand("sm list-disks adoptable");
+        if (disks == null || disks.length() == 0) {
+            throw new AssertionError("Devices that claim to support adoptable storage must have "
+                    + "adoptable media inserted during CTS to verify correct behavior");
+        }
+        return disks.split("\n")[0].trim();
+    }
+
+    private LocalVolumeInfo getAdoptionVolume() throws Exception {
+        final String[] lines = getDevice().executeShellCommand("sm list-volumes private")
+                .split("\n");
+        for (String line : lines) {
+            final LocalVolumeInfo info = new LocalVolumeInfo(line.trim());
+            if (!"private".equals(info.volId)) {
+                return info;
+            }
+        }
+        throw new AssertionError("Expected private volume; found " + Arrays.toString(lines));
+    }
+
+    private void cleanUp(String diskId) throws Exception {
+        getDevice().executeShellCommand("sm partition " + diskId + " public");
+        getDevice().executeShellCommand("sm forget all");
+    }
+
+    private void runDeviceTests(String packageName, String testClassName, String testMethodName)
+            throws DeviceNotAvailableException {
+        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
+    }
+
+    private static void assertSuccess(String str) {
+        if (str == null || !str.startsWith("Success")) {
+            throw new AssertionError("Expected success string but found " + str);
+        }
+    }
+
+    private static void assertEmpty(String str) {
+        if (str != null && str.trim().length() > 0) {
+            throw new AssertionError("Expected empty string but found " + str);
+        }
+    }
+
+    private static class LocalVolumeInfo {
+        public String volId;
+        public String state;
+        public String uuid;
+
+        public LocalVolumeInfo(String line) {
+            final String[] split = line.split(" ");
+            volId = split[0];
+            state = split[1];
+            uuid = split[2];
+        }
+    }
+
+    private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
+        public InstallMultiple() {
+            super(getDevice(), mCtsBuild, mAbi);
+        }
+    }
+}
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
index 206bdbe..bf9e81c 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AppSecurityTests.java
@@ -19,15 +19,8 @@
 import com.android.cts.tradefed.build.CtsBuildHelper;
 import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.Log;
-import com.android.ddmlib.testrunner.InstrumentationResultParser;
-import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.ddmlib.testrunner.TestResult;
-import com.android.ddmlib.testrunner.TestResult.TestStatus;
-import com.android.ddmlib.testrunner.TestRunResult;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
-import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.result.CollectingTestListener;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IAbi;
 import com.android.tradefed.testtype.IAbiReceiver;
@@ -35,15 +28,13 @@
 
 import java.io.File;
 import java.io.FileNotFoundException;
-import java.util.Map;
 
 /**
- * Set of tests that verify various security checks involving multiple apps are properly enforced.
+ * Set of tests that verify various security checks involving multiple apps are
+ * properly enforced.
  */
 public class AppSecurityTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
 
-    private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
-
     // testSharedUidDifferentCerts constants
     private static final String SHARED_UI_APK = "CtsSharedUidInstall.apk";
     private static final String SHARED_UI_PKG = "com.android.cts.shareuidinstall";
@@ -68,18 +59,6 @@
     private static final String APP_ACCESS_DATA_APK = "CtsAppAccessData.apk";
     private static final String APP_ACCESS_DATA_PKG = "com.android.cts.appaccessdata";
 
-    // External storage constants
-    private static final String COMMON_EXTERNAL_STORAGE_APP_CLASS = "com.android.cts.externalstorageapp.CommonExternalStorageTest";
-    private static final String EXTERNAL_STORAGE_APP_APK = "CtsExternalStorageApp.apk";
-    private static final String EXTERNAL_STORAGE_APP_PKG = "com.android.cts.externalstorageapp";
-    private static final String EXTERNAL_STORAGE_APP_CLASS = EXTERNAL_STORAGE_APP_PKG + ".ExternalStorageTest";
-    private static final String READ_EXTERNAL_STORAGE_APP_APK = "CtsReadExternalStorageApp.apk";
-    private static final String READ_EXTERNAL_STORAGE_APP_PKG = "com.android.cts.readexternalstorageapp";
-    private static final String READ_EXTERNAL_STORAGE_APP_CLASS = READ_EXTERNAL_STORAGE_APP_PKG + ".ReadExternalStorageTest";
-    private static final String WRITE_EXTERNAL_STORAGE_APP_APK = "CtsWriteExternalStorageApp.apk";
-    private static final String WRITE_EXTERNAL_STORAGE_APP_PKG = "com.android.cts.writeexternalstorageapp";
-    private static final String WRITE_EXTERNAL_STORAGE_APP_CLASS = WRITE_EXTERNAL_STORAGE_APP_PKG + ".WriteExternalStorageTest";
-
     // testInstrumentationDiffCert constants
     private static final String TARGET_INSTRUMENT_APK = "CtsTargetInstrumentationApp.apk";
     private static final String TARGET_INSTRUMENT_PKG = "com.android.cts.targetinstrumentationapp";
@@ -97,17 +76,8 @@
     private static final String PERMISSION_DIFF_CERT_PKG =
         "com.android.cts.usespermissiondiffcertapp";
 
-    private static final String READ_EXTERNAL_STORAGE = "android.permission.READ_EXTERNAL_STORAGE";
-
-    private static final String MULTIUSER_STORAGE_APK = "CtsMultiUserStorageApp.apk";
-    private static final String MULTIUSER_STORAGE_PKG = "com.android.cts.multiuserstorageapp";
-    private static final String MULTIUSER_STORAGE_CLASS = MULTIUSER_STORAGE_PKG
-            + ".MultiUserStorageTest";
-
     private static final String LOG_TAG = "AppSecurityTests";
 
-    private static final int USER_OWNER = 0;
-
     private IAbi mAbi;
     private CtsBuildHelper mCtsBuild;
 
@@ -156,8 +126,7 @@
             assertNotNull("shared uid app with different cert than existing app installed " +
                     "successfully", installResult);
             assertEquals("INSTALL_FAILED_SHARED_USER_INCOMPATIBLE", installResult);
-        }
-        finally {
+        } finally {
             getDevice().uninstallPackage(SHARED_UI_PKG);
             getDevice().uninstallPackage(SHARED_UI_DIFF_CERT_PKG);
         }
@@ -183,8 +152,7 @@
             assertNotNull("app upgrade with different cert than existing app installed " +
                     "successfully", installResult);
             assertEquals("INSTALL_FAILED_UPDATE_INCOMPATIBLE", installResult);
-        }
-        finally {
+        } finally {
             getDevice().uninstallPackage(SIMPLE_APP_PKG);
         }
     }
@@ -205,132 +173,21 @@
             assertNull(String.format("failed to install app with data. Reason: %s", installResult),
                     installResult);
             // run appwithdata's tests to create private data
-            assertTrue("failed to create app's private data", runDeviceTests(APP_WITH_DATA_PKG,
-                    APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD));
+            runDeviceTests(APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD);
 
             installResult = getDevice().installPackage(getTestAppFile(APP_ACCESS_DATA_APK),
                     false, options);
             assertNull(String.format("failed to install app access data. Reason: %s",
                     installResult), installResult);
             // run appaccessdata's tests which attempt to access appwithdata's private data
-            assertTrue("could access app's private data", runDeviceTests(APP_ACCESS_DATA_PKG));
-        }
-        finally {
+            runDeviceTests(APP_ACCESS_DATA_PKG);
+        } finally {
             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
             getDevice().uninstallPackage(APP_ACCESS_DATA_PKG);
         }
     }
 
     /**
-     * Verify that app with no external storage permissions works correctly.
-     */
-    public void testExternalStorageNone() throws Exception {
-        final int[] users = createUsersForTest();
-        try {
-            wipePrimaryExternalStorage(getDevice());
-
-            getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
-            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
-            assertNull(getDevice()
-                    .installPackage(getTestAppFile(EXTERNAL_STORAGE_APP_APK), false, options));
-
-            for (int user : users) {
-                assertTrue("Failed external storage with no permissions",
-                        runDeviceTests(EXTERNAL_STORAGE_APP_PKG, user));
-            }
-        } finally {
-            getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
-            removeUsersForTest(users);
-        }
-    }
-
-    /**
-     * Verify that app with
-     * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} works
-     * correctly.
-     */
-    public void testExternalStorageRead() throws Exception {
-        final int[] users = createUsersForTest();
-        try {
-            wipePrimaryExternalStorage(getDevice());
-
-            getDevice().uninstallPackage(READ_EXTERNAL_STORAGE_APP_PKG);
-            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
-            assertNull(getDevice()
-                    .installPackage(getTestAppFile(READ_EXTERNAL_STORAGE_APP_APK), false, options));
-
-            for (int user : users) {
-                assertTrue("Failed external storage with read permissions",
-                        runDeviceTests(READ_EXTERNAL_STORAGE_APP_PKG, user));
-            }
-        } finally {
-            getDevice().uninstallPackage(READ_EXTERNAL_STORAGE_APP_PKG);
-            removeUsersForTest(users);
-        }
-    }
-
-    /**
-     * Verify that app with
-     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} works
-     * correctly.
-     */
-    public void testExternalStorageWrite() throws Exception {
-        final int[] users = createUsersForTest();
-        try {
-            wipePrimaryExternalStorage(getDevice());
-
-            getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
-            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
-            assertNull(getDevice()
-                    .installPackage(getTestAppFile(WRITE_EXTERNAL_STORAGE_APP_APK), false, options));
-
-            for (int user : users) {
-                assertTrue("Failed external storage with write permissions",
-                        runDeviceTests(WRITE_EXTERNAL_STORAGE_APP_PKG, user));
-            }
-        } finally {
-            getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
-            removeUsersForTest(users);
-        }
-    }
-
-    /**
-     * Verify that app with WRITE_EXTERNAL can leave gifts in external storage
-     * directories belonging to other apps, and those apps can read.
-     */
-    public void testExternalStorageGifts() throws Exception {
-        final int[] users = createUsersForTest();
-        try {
-            wipePrimaryExternalStorage(getDevice());
-
-            getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
-            getDevice().uninstallPackage(READ_EXTERNAL_STORAGE_APP_PKG);
-            getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
-            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
-            assertNull(getDevice()
-                    .installPackage(getTestAppFile(EXTERNAL_STORAGE_APP_APK), false, options));
-            assertNull(getDevice()
-                    .installPackage(getTestAppFile(READ_EXTERNAL_STORAGE_APP_APK), false, options));
-            assertNull(getDevice()
-                    .installPackage(getTestAppFile(WRITE_EXTERNAL_STORAGE_APP_APK), false, options));
-
-            for (int user : users) {
-                assertTrue("Failed to write gifts", runDeviceTests(WRITE_EXTERNAL_STORAGE_APP_PKG,
-                        WRITE_EXTERNAL_STORAGE_APP_CLASS, "doWriteGifts", user));
-                assertTrue("Read failed to verify gifts", runDeviceTests(READ_EXTERNAL_STORAGE_APP_PKG,
-                        READ_EXTERNAL_STORAGE_APP_CLASS, "doVerifyGifts", user));
-                assertTrue("None failed to verify gifts", runDeviceTests(EXTERNAL_STORAGE_APP_PKG,
-                        EXTERNAL_STORAGE_APP_CLASS, "doVerifyGifts", user));
-            }
-        } finally {
-            getDevice().uninstallPackage(EXTERNAL_STORAGE_APP_PKG);
-            getDevice().uninstallPackage(READ_EXTERNAL_STORAGE_APP_PKG);
-            getDevice().uninstallPackage(WRITE_EXTERNAL_STORAGE_APP_PKG);
-            removeUsersForTest(users);
-        }
-    }
-
-    /**
      * Test that uninstall of an app removes its private data.
      */
     public void testUninstallRemovesData() throws Exception {
@@ -345,8 +202,7 @@
             assertNull(String.format("failed to install app with data. Reason: %s", installResult),
                     installResult);
             // run appwithdata's tests to create private data
-            assertTrue("failed to create app's private data", runDeviceTests(APP_WITH_DATA_PKG,
-                    APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD));
+            runDeviceTests(APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CREATE_METHOD);
 
             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
 
@@ -355,11 +211,9 @@
             assertNull(String.format("failed to install app with data second time. Reason: %s",
                     installResult), installResult);
             // run appwithdata's 'check if file exists' test
-            assertTrue("app's private data still exists after install", runDeviceTests(
-                    APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS, APP_WITH_DATA_CHECK_NOEXIST_METHOD));
-
-        }
-        finally {
+            runDeviceTests(APP_WITH_DATA_PKG, APP_WITH_DATA_CLASS,
+                    APP_WITH_DATA_CHECK_NOEXIST_METHOD);
+        } finally {
             getDevice().uninstallPackage(APP_WITH_DATA_PKG);
         }
     }
@@ -389,10 +243,8 @@
             // run INSTRUMENT_DIFF_CERT_PKG tests
             // this test will attempt to call startInstrumentation directly and verify
             // SecurityException is thrown
-            assertTrue("running instrumentation with diff cert unexpectedly succeeded",
-                    runDeviceTests(INSTRUMENT_DIFF_CERT_PKG));
-        }
-        finally {
+            runDeviceTests(INSTRUMENT_DIFF_CERT_PKG);
+        } finally {
             getDevice().uninstallPackage(TARGET_INSTRUMENT_PKG);
             getDevice().uninstallPackage(INSTRUMENT_DIFF_CERT_PKG);
         }
@@ -427,209 +279,20 @@
             assertNull(String.format("failed to install permission app with diff cert. Reason: %s",
                     installResult), installResult);
             // run PERMISSION_DIFF_CERT_PKG tests which try to access the permission
-            TestRunResult result = doRunTests(PERMISSION_DIFF_CERT_PKG, null, null, USER_OWNER);
-            assertDeviceTestsPass(result);
-        }
-        finally {
+            runDeviceTests(PERMISSION_DIFF_CERT_PKG);
+        } finally {
             getDevice().uninstallPackage(DECLARE_PERMISSION_PKG);
             getDevice().uninstallPackage(DECLARE_PERMISSION_COMPAT_PKG);
             getDevice().uninstallPackage(PERMISSION_DIFF_CERT_PKG);
         }
     }
 
-    /**
-     * Test multi-user emulated storage environment, ensuring that each user has
-     * isolated storage.
-     */
-    public void testMultiUserStorageIsolated() throws Exception {
-        final String PACKAGE = MULTIUSER_STORAGE_PKG;
-        final String CLAZZ = MULTIUSER_STORAGE_CLASS;
-
-        final int[] users = createUsersForTest();
-        try {
-            if (users.length == 1) {
-                Log.d(LOG_TAG, "Single user device; skipping isolated storage tests");
-                return;
-            }
-
-            final int owner = users[0];
-            final int secondary = users[1];
-
-            // Install our test app
-            getDevice().uninstallPackage(MULTIUSER_STORAGE_PKG);
-            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
-            final String installResult = getDevice()
-                    .installPackage(getTestAppFile(MULTIUSER_STORAGE_APK), false, options);
-            assertNull("Failed to install: " + installResult, installResult);
-
-            // Clear data from previous tests
-            assertDeviceTestsPass(
-                    doRunTests(PACKAGE, CLAZZ, "cleanIsolatedStorage", owner));
-            assertDeviceTestsPass(
-                    doRunTests(PACKAGE, CLAZZ, "cleanIsolatedStorage", secondary));
-
-            // Have both users try writing into isolated storage
-            assertDeviceTestsPass(
-                    doRunTests(PACKAGE, CLAZZ, "writeIsolatedStorage", owner));
-            assertDeviceTestsPass(
-                    doRunTests(PACKAGE, CLAZZ, "writeIsolatedStorage", secondary));
-
-            // Verify they both have isolated view of storage
-            assertDeviceTestsPass(
-                    doRunTests(PACKAGE, CLAZZ, "readIsolatedStorage", owner));
-            assertDeviceTestsPass(
-                    doRunTests(PACKAGE, CLAZZ, "readIsolatedStorage", secondary));
-        } finally {
-            getDevice().uninstallPackage(MULTIUSER_STORAGE_PKG);
-            removeUsersForTest(users);
-        }
+    private void runDeviceTests(String packageName) throws DeviceNotAvailableException {
+        Utils.runDeviceTests(getDevice(), packageName);
     }
 
-    /**
-     * Helper method that checks that all tests in given result passed, and attempts to generate
-     * a meaningful error message if they failed.
-     *
-     * @param result
-     */
-    private void assertDeviceTestsPass(TestRunResult result) {
-        // TODO: consider rerunning if this occurred
-        assertFalse(String.format("Failed to successfully run device tests for %s. Reason: %s",
-                result.getName(), result.getRunFailureMessage()), result.isRunFailure());
-
-        if (result.hasFailedTests()) {
-            // build a meaningful error message
-            StringBuilder errorBuilder = new StringBuilder("on-device tests failed:\n");
-            for (Map.Entry<TestIdentifier, TestResult> resultEntry :
-                result.getTestResults().entrySet()) {
-                if (!resultEntry.getValue().getStatus().equals(TestStatus.PASSED)) {
-                    errorBuilder.append(resultEntry.getKey().toString());
-                    errorBuilder.append(":\n");
-                    errorBuilder.append(resultEntry.getValue().getStackTrace());
-                }
-            }
-            fail(errorBuilder.toString());
-        }
-    }
-
-    /**
-     * Helper method that will the specified packages tests on device.
-     *
-     * @param pkgName Android application package for tests
-     * @return <code>true</code> if all tests passed.
-     * @throws DeviceNotAvailableException if connection to device was lost.
-     */
-    private boolean runDeviceTests(String pkgName) throws DeviceNotAvailableException {
-        return runDeviceTests(pkgName, null, null, USER_OWNER);
-    }
-
-    private boolean runDeviceTests(String pkgName, int userId) throws DeviceNotAvailableException {
-        return runDeviceTests(pkgName, null, null, userId);
-    }
-
-    /**
-     * Helper method that will the specified packages tests on device.
-     *
-     * @param pkgName Android application package for tests
-     * @return <code>true</code> if all tests passed.
-     * @throws DeviceNotAvailableException if connection to device was lost.
-     */
-    private boolean runDeviceTests(String pkgName, String testClassName, String testMethodName)
+    private void runDeviceTests(String packageName, String testClassName, String testMethodName)
             throws DeviceNotAvailableException {
-        return runDeviceTests(pkgName, testClassName, testMethodName, USER_OWNER);
-    }
-
-    private boolean runDeviceTests(String pkgName, String testClassName, String testMethodName,
-            int userId) throws DeviceNotAvailableException {
-        TestRunResult runResult = doRunTests(pkgName, testClassName, testMethodName,
-                userId);
-        return !runResult.hasFailedTests();
-    }
-
-    private static boolean isMultiUserSupportedOnDevice(ITestDevice device)
-            throws DeviceNotAvailableException {
-        // TODO: move this to ITestDevice once it supports users
-        final String output = device.executeShellCommand("pm get-max-users");
-        try {
-            return Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim()) > 1;
-        } catch (NumberFormatException e) {
-            fail("Failed to parse result: " + output);
-        }
-        return false;
-    }
-
-    /**
-     * Return set of users that test should be run for, creating a secondary
-     * user if the device supports it. Always call
-     * {@link #removeUsersForTest(int[])} when finished.
-     */
-    private int[] createUsersForTest() throws DeviceNotAvailableException {
-        if (isMultiUserSupportedOnDevice(getDevice())) {
-            return new int[] { USER_OWNER, createUserOnDevice(getDevice()) };
-        } else {
-            Log.d(LOG_TAG, "Single user device; skipping isolated storage tests");
-            return new int[] { USER_OWNER };
-        }
-    }
-
-    private void removeUsersForTest(int[] users) throws DeviceNotAvailableException {
-        for (int user : users) {
-            if (user != USER_OWNER) {
-                removeUserOnDevice(getDevice(), user);
-            }
-        }
-   }
-
-    private static int createUserOnDevice(ITestDevice device) throws DeviceNotAvailableException {
-        // TODO: move this to ITestDevice once it supports users
-        final String name = "CTS_" + System.currentTimeMillis();
-        final String output = device.executeShellCommand("pm create-user " + name);
-        if (output.startsWith("Success")) {
-            try {
-                final int userId = Integer.parseInt(
-                        output.substring(output.lastIndexOf(" ")).trim());
-                device.executeShellCommand("am start-user " + userId);
-                return userId;
-            } catch (NumberFormatException e) {
-                fail("Failed to parse result: " + output);
-            }
-        } else {
-            fail("Failed to create user: " + output);
-        }
-        throw new IllegalStateException();
-    }
-
-    private static void removeUserOnDevice(ITestDevice device, int userId)
-            throws DeviceNotAvailableException {
-        // TODO: move this to ITestDevice once it supports users
-        final String output = device.executeShellCommand("pm remove-user " + userId);
-        if (output.startsWith("Error")) {
-            fail("Failed to remove user: " + output);
-        }
-    }
-
-    private TestRunResult doRunTests(String pkgName, String testClassName, String testMethodName,
-            int userId) throws DeviceNotAvailableException {
-        // TODO: move this to RemoteAndroidTestRunner once it supports users
-        final StringBuilder cmd = new StringBuilder("am instrument --user " + userId + " -w -r");
-        if (testClassName != null) {
-            cmd.append(" -e class " + testClassName);
-            if (testMethodName != null) {
-                cmd.append("#" + testMethodName);
-            }
-        }
-        cmd.append(" " + pkgName + "/" + RUNNER);
-
-        Log.i(LOG_TAG, "Running " + cmd + " on " + getDevice().getSerialNumber());
-
-        CollectingTestListener listener = new CollectingTestListener();
-        InstrumentationResultParser parser = new InstrumentationResultParser(pkgName, listener);
-
-        getDevice().executeShellCommand(cmd.toString(), parser);
-        return listener.getCurrentRunResults();
-    }
-
-    private static void wipePrimaryExternalStorage(ITestDevice device)
-            throws DeviceNotAvailableException {
-        device.executeShellCommand("rm -rf /sdcard/*");
+        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName);
     }
 }
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/DocumentsTest.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/DocumentsTest.java
index fbde558..a4ec65a 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/DocumentsTest.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/DocumentsTest.java
@@ -24,6 +24,10 @@
 import com.android.tradefed.testtype.IAbiReceiver;
 import com.android.tradefed.testtype.IBuildReceiver;
 
+/**
+ * Set of tests that verify behavior of
+ * {@link android.provider.DocumentsContract} and related intents.
+ */
 public class DocumentsTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
     private static final String PROVIDER_PKG = "com.android.cts.documentprovider";
     private static final String PROVIDER_APK = "CtsDocumentProvider.apk";
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/ExternalStorageHostTest.java
new file mode 100644
index 0000000..d74ec52
--- /dev/null
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/ExternalStorageHostTest.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.appsecurity;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.cts.util.AbiUtils;
+import com.android.ddmlib.Log;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IAbi;
+import com.android.tradefed.testtype.IAbiReceiver;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+
+/**
+ * Set of tests that verify behavior of external storage devices.
+ */
+public class ExternalStorageHostTest extends DeviceTestCase
+        implements IAbiReceiver, IBuildReceiver {
+    private static final String TAG = "ExternalStorageHostTest";
+
+    private static final String COMMON_CLASS =
+            "com.android.cts.externalstorageapp.CommonExternalStorageTest";
+
+    private static final String NONE_APK = "CtsExternalStorageApp.apk";
+    private static final String NONE_PKG = "com.android.cts.externalstorageapp";
+    private static final String NONE_CLASS = ".ExternalStorageTest";
+    private static final String READ_APK = "CtsReadExternalStorageApp.apk";
+    private static final String READ_PKG = "com.android.cts.readexternalstorageapp";
+    private static final String READ_CLASS = ".ReadExternalStorageTest";
+    private static final String WRITE_APK = "CtsWriteExternalStorageApp.apk";
+    private static final String WRITE_PKG = "com.android.cts.writeexternalstorageapp";
+    private static final String WRITE_CLASS = ".WriteExternalStorageTest";
+    private static final String MULTIUSER_APK = "CtsMultiUserStorageApp.apk";
+    private static final String MULTIUSER_PKG = "com.android.cts.multiuserstorageapp";
+    private static final String MULTIUSER_CLASS = ".MultiUserStorageTest";
+
+    private IAbi mAbi;
+    private CtsBuildHelper mCtsBuild;
+
+    @Override
+    public void setAbi(IAbi abi) {
+        mAbi = abi;
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = CtsBuildHelper.createBuildHelper(buildInfo);
+    }
+
+    private File getTestAppFile(String fileName) throws FileNotFoundException {
+        return mCtsBuild.getTestApp(fileName);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        assertNotNull(mAbi);
+        assertNotNull(mCtsBuild);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    /**
+     * Verify that app with no external storage permissions works correctly.
+     */
+    public void testExternalStorageNone() throws Exception {
+        final int[] users = createUsersForTest();
+        try {
+            wipePrimaryExternalStorage();
+
+            getDevice().uninstallPackage(NONE_PKG);
+            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            assertNull(getDevice().installPackage(getTestAppFile(NONE_APK), false, options));
+
+            for (int user : users) {
+                runDeviceTests(NONE_PKG, COMMON_CLASS, user);
+                runDeviceTests(NONE_PKG, NONE_CLASS, user);
+            }
+        } finally {
+            getDevice().uninstallPackage(NONE_PKG);
+            removeUsersForTest(users);
+        }
+    }
+
+    /**
+     * Verify that app with
+     * {@link android.Manifest.permission#READ_EXTERNAL_STORAGE} works
+     * correctly.
+     */
+    public void testExternalStorageRead() throws Exception {
+        final int[] users = createUsersForTest();
+        try {
+            wipePrimaryExternalStorage();
+
+            getDevice().uninstallPackage(READ_PKG);
+            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            assertNull(getDevice().installPackage(getTestAppFile(READ_APK), false, options));
+
+            for (int user : users) {
+                runDeviceTests(READ_PKG, COMMON_CLASS, user);
+                runDeviceTests(READ_PKG, READ_CLASS, user);
+            }
+        } finally {
+            getDevice().uninstallPackage(READ_PKG);
+            removeUsersForTest(users);
+        }
+    }
+
+    /**
+     * Verify that app with
+     * {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} works
+     * correctly.
+     */
+    public void testExternalStorageWrite() throws Exception {
+        final int[] users = createUsersForTest();
+        try {
+            wipePrimaryExternalStorage();
+
+            getDevice().uninstallPackage(WRITE_PKG);
+            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            assertNull(getDevice().installPackage(getTestAppFile(WRITE_APK), false, options));
+
+            for (int user : users) {
+                runDeviceTests(WRITE_PKG, COMMON_CLASS, user);
+                runDeviceTests(WRITE_PKG, WRITE_CLASS, user);
+            }
+        } finally {
+            getDevice().uninstallPackage(WRITE_PKG);
+            removeUsersForTest(users);
+        }
+    }
+
+    /**
+     * Verify that app with WRITE_EXTERNAL can leave gifts in external storage
+     * directories belonging to other apps, and those apps can read.
+     */
+    public void testExternalStorageGifts() throws Exception {
+        final int[] users = createUsersForTest();
+        try {
+            wipePrimaryExternalStorage();
+
+            getDevice().uninstallPackage(NONE_PKG);
+            getDevice().uninstallPackage(READ_PKG);
+            getDevice().uninstallPackage(WRITE_PKG);
+            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            assertNull(getDevice().installPackage(getTestAppFile(NONE_APK), false, options));
+            assertNull(getDevice().installPackage(getTestAppFile(READ_APK), false, options));
+            assertNull(getDevice().installPackage(getTestAppFile(WRITE_APK), false, options));
+
+            for (int user : users) {
+                runDeviceTests(WRITE_PKG, "WriteGiftTest", user);
+                runDeviceTests(READ_PKG, "ReadGiftTest", user);
+                runDeviceTests(NONE_PKG, "GiftTest", user);
+            }
+        } finally {
+            getDevice().uninstallPackage(NONE_PKG);
+            getDevice().uninstallPackage(READ_PKG);
+            getDevice().uninstallPackage(WRITE_PKG);
+            removeUsersForTest(users);
+        }
+    }
+
+    /**
+     * Test multi-user emulated storage environment, ensuring that each user has
+     * isolated storage.
+     */
+    public void testMultiUserStorageIsolated() throws Exception {
+        final int[] users = createUsersForTest();
+        try {
+            if (users.length == 1) {
+                Log.d(TAG, "Single user device; skipping isolated storage tests");
+                return;
+            }
+
+            final int owner = users[0];
+            final int secondary = users[1];
+
+            // Install our test app
+            getDevice().uninstallPackage(MULTIUSER_PKG);
+            String[] options = {AbiUtils.createAbiFlag(mAbi.getName())};
+            final String installResult = getDevice()
+                    .installPackage(getTestAppFile(MULTIUSER_APK), false, options);
+            assertNull("Failed to install: " + installResult, installResult);
+
+            // Clear data from previous tests
+            runDeviceTests(MULTIUSER_PKG, MULTIUSER_CLASS, "testCleanIsolatedStorage", owner);
+            runDeviceTests(MULTIUSER_PKG, MULTIUSER_CLASS, "testCleanIsolatedStorage", secondary);
+
+            // Have both users try writing into isolated storage
+            runDeviceTests(MULTIUSER_PKG, MULTIUSER_CLASS, "testWriteIsolatedStorage", owner);
+            runDeviceTests(MULTIUSER_PKG, MULTIUSER_CLASS, "testWriteIsolatedStorage", secondary);
+
+            // Verify they both have isolated view of storage
+            runDeviceTests(MULTIUSER_PKG, MULTIUSER_CLASS, "testReadIsolatedStorage", owner);
+            runDeviceTests(MULTIUSER_PKG, MULTIUSER_CLASS, "testReadIsolatedStorage", secondary);
+        } finally {
+            getDevice().uninstallPackage(MULTIUSER_PKG);
+            removeUsersForTest(users);
+        }
+    }
+
+    private void wipePrimaryExternalStorage() throws DeviceNotAvailableException {
+        getDevice().executeShellCommand("rm -rf /sdcard/*");
+    }
+
+    private int[] createUsersForTest() throws DeviceNotAvailableException {
+        return Utils.createUsersForTest(getDevice());
+    }
+
+    private void removeUsersForTest(int[] users) throws DeviceNotAvailableException {
+        Utils.removeUsersForTest(getDevice(), users);
+    }
+
+    private void runDeviceTests(String packageName, String testClassName, int userId)
+            throws DeviceNotAvailableException {
+        Utils.runDeviceTests(getDevice(), packageName, testClassName, userId);
+    }
+
+    private void runDeviceTests(String packageName, String testClassName, String testMethodName,
+            int userId) throws DeviceNotAvailableException {
+        Utils.runDeviceTests(getDevice(), packageName, testClassName, testMethodName, userId);
+    }
+}
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/PermissionsHostTest.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/PermissionsHostTest.java
index 2eafcfc..4b5259d 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/PermissionsHostTest.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/PermissionsHostTest.java
@@ -24,6 +24,10 @@
 import com.android.tradefed.testtype.IAbiReceiver;
 import com.android.tradefed.testtype.IBuildReceiver;
 
+/**
+ * Set of tests that verify behavior of runtime permissions, including both
+ * dynamic granting and behavior of legacy apps.
+ */
 public class PermissionsHostTest extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
     private static final String PKG = "com.android.cts.usepermission";
 
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java
index 9de7d84..ef3af8d 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/SplitTests.java
@@ -36,14 +36,15 @@
  * Tests that verify installing of various split APKs from host side.
  */
 public class SplitTests extends DeviceTestCase implements IAbiReceiver, IBuildReceiver {
-    private static final String PKG = "com.android.cts.splitapp";
+    static final String PKG = "com.android.cts.splitapp";
+    static final String CLASS = ".SplitAppTest";
 
-    private static final String APK = "CtsSplitApp.apk";
+    static final String APK = "CtsSplitApp.apk";
 
-    private static final String APK_mdpi = "CtsSplitApp_mdpi-v4.apk";
-    private static final String APK_hdpi = "CtsSplitApp_hdpi-v4.apk";
-    private static final String APK_xhdpi = "CtsSplitApp_xhdpi-v4.apk";
-    private static final String APK_xxhdpi = "CtsSplitApp_xxhdpi-v4.apk";
+    static final String APK_mdpi = "CtsSplitApp_mdpi-v4.apk";
+    static final String APK_hdpi = "CtsSplitApp_hdpi-v4.apk";
+    static final String APK_xhdpi = "CtsSplitApp_xhdpi-v4.apk";
+    static final String APK_xxhdpi = "CtsSplitApp_xxhdpi-v4.apk";
 
     private static final String APK_v7 = "CtsSplitApp_v7.apk";
     private static final String APK_fr = "CtsSplitApp_fr.apk";
@@ -69,7 +70,7 @@
     private static final String APK_FEATURE = "CtsSplitAppFeature.apk";
     private static final String APK_FEATURE_v7 = "CtsSplitAppFeature_v7.apk";
 
-    private static final HashMap<String, String> ABI_TO_APK = new HashMap<>();
+    static final HashMap<String, String> ABI_TO_APK = new HashMap<>();
 
     static {
         ABI_TO_APK.put("x86", APK_x86);
@@ -113,18 +114,18 @@
 
     public void testSingleBase() throws Exception {
         new InstallMultiple().addApk(APK).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testSingleBase");
+        runDeviceTests(PKG, CLASS, "testSingleBase");
     }
 
     public void testDensitySingle() throws Exception {
         new InstallMultiple().addApk(APK).addApk(APK_mdpi).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testDensitySingle");
+        runDeviceTests(PKG, CLASS, "testDensitySingle");
     }
 
     public void testDensityAll() throws Exception {
         new InstallMultiple().addApk(APK).addApk(APK_mdpi).addApk(APK_hdpi).addApk(APK_xhdpi)
                 .addApk(APK_xxhdpi).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testDensityAll");
+        runDeviceTests(PKG, CLASS, "testDensityAll");
     }
 
     /**
@@ -133,11 +134,11 @@
      */
     public void testDensityBest() throws Exception {
         new InstallMultiple().addApk(APK).addApk(APK_mdpi).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testDensityBest1");
+        runDeviceTests(PKG, CLASS, "testDensityBest1");
 
         // Now splice in an additional split which offers better resources
         new InstallMultiple().inheritFrom(PKG).addApk(APK_xxhdpi).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testDensityBest2");
+        runDeviceTests(PKG, CLASS, "testDensityBest2");
     }
 
     /**
@@ -146,12 +147,12 @@
      */
     public void testApi() throws Exception {
         new InstallMultiple().addApk(APK).addApk(APK_v7).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testApi");
+        runDeviceTests(PKG, CLASS, "testApi");
     }
 
     public void testLocale() throws Exception {
         new InstallMultiple().addApk(APK).addApk(APK_de).addApk(APK_fr).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testLocale");
+        runDeviceTests(PKG, CLASS, "testLocale");
     }
 
     /**
@@ -164,7 +165,7 @@
         assertNotNull("Failed to find APK for ABI " + abi, apk);
 
         new InstallMultiple().addApk(APK).addApk(apk).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testNative");
+        runDeviceTests(PKG, CLASS, "testNative");
     }
 
     /**
@@ -179,7 +180,7 @@
         assertNotNull("Failed to find APK for ABI " + abi, apk);
 
         new InstallMultiple().useNaturalAbi().addApk(APK).addApk(apk).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testNative");
+        runDeviceTests(PKG, CLASS, "testNative");
     }
 
     /**
@@ -192,7 +193,7 @@
             inst.addApk(apk);
         }
         inst.run();
-        runDeviceTests(PKG, ".SplitAppTest", "testNative");
+        runDeviceTests(PKG, CLASS, "testNative");
     }
 
     /**
@@ -207,7 +208,7 @@
             inst.addApk(apk);
         }
         inst.run();
-        runDeviceTests(PKG, ".SplitAppTest", "testNative");
+        runDeviceTests(PKG, CLASS, "testNative");
     }
 
     public void testDuplicateBase() throws Exception {
@@ -238,21 +239,21 @@
 
     public void testDiffRevision() throws Exception {
         new InstallMultiple().addApk(APK).addApk(APK_DIFF_REVISION_v7).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testRevision0_12");
+        runDeviceTests(PKG, CLASS, "testRevision0_12");
     }
 
     public void testDiffRevisionInheritBase() throws Exception {
         new InstallMultiple().addApk(APK).addApk(APK_v7).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testRevision0_0");
+        runDeviceTests(PKG, CLASS, "testRevision0_0");
         new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_REVISION_v7).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testRevision0_12");
+        runDeviceTests(PKG, CLASS, "testRevision0_12");
     }
 
     public void testDiffRevisionInheritSplit() throws Exception {
         new InstallMultiple().addApk(APK).addApk(APK_v7).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testRevision0_0");
+        runDeviceTests(PKG, CLASS, "testRevision0_0");
         new InstallMultiple().inheritFrom(PKG).addApk(APK_DIFF_REVISION).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testRevision12_0");
+        runDeviceTests(PKG, CLASS, "testRevision12_0");
     }
 
     public void testDiffRevisionDowngrade() throws Exception {
@@ -262,12 +263,12 @@
 
     public void testFeatureBase() throws Exception {
         new InstallMultiple().addApk(APK).addApk(APK_FEATURE).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testFeatureBase");
+        runDeviceTests(PKG, CLASS, "testFeatureBase");
     }
 
     public void testFeatureApi() throws Exception {
         new InstallMultiple().addApk(APK).addApk(APK_FEATURE).addApk(APK_FEATURE_v7).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testFeatureApi");
+        runDeviceTests(PKG, CLASS, "testFeatureApi");
     }
 
     public void testInheritUpdatedBase() throws Exception {
@@ -283,39 +284,72 @@
      */
     public void testClearCodeCache() throws Exception {
         new InstallMultiple().addApk(APK).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testCodeCacheWrite");
+        runDeviceTests(PKG, CLASS, "testCodeCacheWrite");
         new InstallMultiple().addArg("-r").addApk(APK_DIFF_VERSION).run();
-        runDeviceTests(PKG, ".SplitAppTest", "testCodeCacheRead");
+        runDeviceTests(PKG, CLASS, "testCodeCacheRead");
     }
 
-    class InstallMultiple {
-        private List<String> mArgs = new ArrayList<>();
-        private List<File> mApks = new ArrayList<>();
+    private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
+        public InstallMultiple() {
+            super(getDevice(), mCtsBuild, mAbi);
+        }
+    }
+
+    public static class BaseInstallMultiple<T extends BaseInstallMultiple<?>> {
+        private final ITestDevice mDevice;
+        private final CtsBuildHelper mBuild;
+        private final IAbi mAbi;
+
+        private final List<String> mArgs = new ArrayList<>();
+        private final List<File> mApks = new ArrayList<>();
         private boolean mUseNaturalAbi;
 
-        public InstallMultiple() {
+        public BaseInstallMultiple(ITestDevice device, CtsBuildHelper build, IAbi abi) {
+            mDevice = device;
+            mBuild = build;
+            mAbi = abi;
             addArg("-g");
         }
 
-        InstallMultiple addArg(String arg) {
+        T addArg(String arg) {
             mArgs.add(arg);
-            return this;
+            return (T) this;
         }
 
-        InstallMultiple addApk(String apk) throws FileNotFoundException {
-            mApks.add(mCtsBuild.getTestApp(apk));
-            return this;
+        T addApk(String apk) throws FileNotFoundException {
+            mApks.add(mBuild.getTestApp(apk));
+            return (T) this;
         }
 
-        InstallMultiple inheritFrom(String packageName) {
+        T inheritFrom(String packageName) {
             addArg("-r");
             addArg("-p " + packageName);
-            return this;
+            return (T) this;
         }
 
-        InstallMultiple useNaturalAbi() {
+        T useNaturalAbi() {
             mUseNaturalAbi = true;
-            return this;
+            return (T) this;
+        }
+
+        T locationAuto() {
+            addArg("--install-location 0");
+            return (T) this;
+        }
+
+        T locationInternalOnly() {
+            addArg("--install-location 1");
+            return (T) this;
+        }
+
+        T locationPreferExternal() {
+            addArg("--install-location 2");
+            return (T) this;
+        }
+
+        T forceUuid(String uuid) {
+            addArg("--force-uuid " + uuid);
+            return (T) this;
         }
 
         void run() throws DeviceNotAvailableException {
@@ -327,7 +361,7 @@
         }
 
         private void run(boolean expectingSuccess) throws DeviceNotAvailableException {
-            final ITestDevice device = getDevice();
+            final ITestDevice device = mDevice;
 
             // Create an install session
             final StringBuilder cmd = new StringBuilder();
diff --git a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/Utils.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/Utils.java
index c58d6bf..fdf84d3 100644
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/Utils.java
+++ b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/Utils.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.appsecurity;
 
+import com.android.ddmlib.Log;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.ddmlib.testrunner.TestResult;
@@ -28,13 +29,37 @@
 import java.util.Map;
 
 public class Utils {
+    private static final String TAG = "AppSecurity";
+
+    public static final int USER_OWNER = 0;
+
     public static void runDeviceTests(ITestDevice device, String packageName)
             throws DeviceNotAvailableException {
-        runDeviceTests(device, packageName, null, null);
+        runDeviceTests(device, packageName, null, null, USER_OWNER);
+    }
+
+    public static void runDeviceTests(ITestDevice device, String packageName, int userId)
+            throws DeviceNotAvailableException {
+        runDeviceTests(device, packageName, null, null, userId);
+    }
+
+    public static void runDeviceTests(ITestDevice device, String packageName, String testClassName)
+            throws DeviceNotAvailableException {
+        runDeviceTests(device, packageName, testClassName, null, USER_OWNER);
+    }
+
+    public static void runDeviceTests(ITestDevice device, String packageName, String testClassName,
+            int userId) throws DeviceNotAvailableException {
+        runDeviceTests(device, packageName, testClassName, null, userId);
     }
 
     public static void runDeviceTests(ITestDevice device, String packageName, String testClassName,
             String testMethodName) throws DeviceNotAvailableException {
+        runDeviceTests(device, packageName, testClassName, testMethodName, USER_OWNER);
+    }
+
+    public static void runDeviceTests(ITestDevice device, String packageName, String testClassName,
+            String testMethodName, int userId) throws DeviceNotAvailableException {
         if (testClassName != null && testClassName.startsWith(".")) {
             testClassName = packageName + testClassName;
         }
@@ -43,6 +68,13 @@
                 "android.support.test.runner.AndroidJUnitRunner", device.getIDevice());
         if (testClassName != null && testMethodName != null) {
             testRunner.setMethodName(testClassName, testMethodName);
+        } else if (testClassName != null) {
+            testRunner.setClassName(testClassName);
+        }
+
+        if (userId != USER_OWNER) {
+            // TODO: move this to RemoteAndroidTestRunner once it supports users
+            testRunner.addInstrumentationArg("hack_key", "hack_value --user " + userId);
         }
 
         final CollectingTestListener listener = new CollectingTestListener();
@@ -68,4 +100,66 @@
             throw new AssertionError(errorBuilder.toString());
         }
     }
+
+    private static boolean isMultiUserSupportedOnDevice(ITestDevice device)
+            throws DeviceNotAvailableException {
+        // TODO: move this to ITestDevice once it supports users
+        final String output = device.executeShellCommand("pm get-max-users");
+        try {
+            return Integer.parseInt(output.substring(output.lastIndexOf(" ")).trim()) > 1;
+        } catch (NumberFormatException e) {
+            throw new AssertionError("Failed to parse result: " + output);
+        }
+    }
+
+    /**
+     * Return set of users that test should be run for, creating a secondary
+     * user if the device supports it. Always call
+     * {@link #removeUsersForTest(ITestDevice, int[])} when finished.
+     */
+    public static int[] createUsersForTest(ITestDevice device) throws DeviceNotAvailableException {
+        if (isMultiUserSupportedOnDevice(device)) {
+            return new int[] { USER_OWNER, createUserOnDevice(device) };
+        } else {
+            Log.d(TAG, "Single user device; skipping isolated storage tests");
+            return new int[] { USER_OWNER };
+        }
+    }
+
+    public static void removeUsersForTest(ITestDevice device, int[] users)
+            throws DeviceNotAvailableException {
+        for (int user : users) {
+            if (user != USER_OWNER) {
+                removeUserOnDevice(device, user);
+            }
+        }
+    }
+
+    private static int createUserOnDevice(ITestDevice device) throws DeviceNotAvailableException {
+        // TODO: move this to ITestDevice once it supports users
+        final String name = "CTS_" + System.currentTimeMillis();
+        final String output = device.executeShellCommand("pm create-user " + name);
+        if (output.startsWith("Success")) {
+            try {
+                final int userId = Integer.parseInt(
+                        output.substring(output.lastIndexOf(" ")).trim());
+                device.executeShellCommand("am start-user " + userId);
+                return userId;
+            } catch (NumberFormatException e) {
+                throw new AssertionError("Failed to parse result: " + output);
+            }
+        } else {
+            throw new AssertionError("Failed to create user: " + output);
+        }
+    }
+
+    private static void removeUserOnDevice(ITestDevice device, int userId)
+            throws DeviceNotAvailableException {
+        // TODO: move this to ITestDevice once it supports users
+        final String output = device.executeShellCommand("pm remove-user " + userId);
+        if (output.startsWith("Error")) {
+            throw new AssertionError("Failed to remove user: " + output);
+        }
+    }
+
 }
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
index 379de5f..f01db4f 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/CommonExternalStorageTest.java
@@ -16,8 +16,13 @@
 
 package com.android.cts.externalstorageapp;
 
+import android.content.ContentResolver;
+import android.content.ContentValues;
 import android.content.Context;
+import android.net.Uri;
 import android.os.Environment;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
@@ -100,7 +105,7 @@
         for (File path : paths) {
             assertNotNull("Valid media must be inserted during CTS", path);
             assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
-                    Environment.getStorageState(path));
+                    Environment.getExternalStorageState(path));
 
             assertDirReadWriteAccess(path);
 
@@ -286,6 +291,35 @@
         }
     }
 
+    public static void assertMediaNoAccess(ContentResolver resolver) throws Exception {
+        final ContentValues values = new ContentValues();
+        values.put(Images.Media.MIME_TYPE, "image/jpeg");
+        values.put(Images.Media.DATA,
+                buildProbeFile(Environment.getExternalStorageDirectory()).getAbsolutePath());
+
+        try {
+            resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+            fail("Expected access to be blocked");
+        } catch (Exception expected) {
+        }
+    }
+
+    public static void assertMediaReadWriteAccess(ContentResolver resolver) throws Exception {
+        final ContentValues values = new ContentValues();
+        values.put(Images.Media.MIME_TYPE, "image/jpeg");
+        values.put(Images.Media.DATA,
+                buildProbeFile(Environment.getExternalStorageDirectory()).getAbsolutePath());
+
+        final Uri uri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
+        try {
+            resolver.openFileDescriptor(uri, "rw").close();
+            resolver.openFileDescriptor(uri, "w").close();
+            resolver.openFileDescriptor(uri, "r").close();
+        } finally {
+            resolver.delete(uri, null, null);
+        }
+    }
+
     private static boolean isWhiteList(File file) {
         final String[] whiteLists = {
                 "autorun.inf", ".android_secure", "android_secure"
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java
index 5d492b2..7dc462a 100644
--- a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/ExternalStorageTest.java
@@ -16,16 +16,9 @@
 
 package com.android.cts.externalstorageapp;
 
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileNoAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPaths;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
 
 import android.app.DownloadManager;
 import android.app.DownloadManager.Query;
@@ -75,7 +68,7 @@
             }
 
             // Keep walking up until we leave device
-            while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+            while (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
                 assertDirNoAccess(path);
                 path = path.getParentFile();
             }
@@ -120,21 +113,6 @@
     }
 
     /**
-     * Verify we can read only our gifts.
-     */
-    public void doVerifyGifts() throws Exception {
-        final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
-        assertFileReadWriteAccess(none);
-        assertEquals(100, readInt(none));
-
-        final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
-        assertFileNoAccess(read);
-
-        final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
-        assertFileNoAccess(write);
-    }
-
-    /**
      * Shamelessly borrowed from DownloadManagerTest.java
      */
     private static class DownloadCompleteReceiver extends BroadcastReceiver {
diff --git a/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/GiftTest.java b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/GiftTest.java
new file mode 100644
index 0000000..e482b2f
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ExternalStorageApp/src/com/android/cts/externalstorageapp/GiftTest.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.externalstorageapp;
+
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileNoAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
+
+import android.test.AndroidTestCase;
+
+import java.io.File;
+
+public class GiftTest extends AndroidTestCase {
+    /**
+     * Verify we can read only our gifts.
+     */
+    public void testGifts() throws Exception {
+        final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
+        assertFileReadWriteAccess(none);
+        assertEquals(100, readInt(none));
+
+        final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
+        assertFileNoAccess(read);
+
+        final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
+        assertFileNoAccess(write);
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/src/com/android/cts/multiuserstorageapp/MultiUserStorageTest.java b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/src/com/android/cts/multiuserstorageapp/MultiUserStorageTest.java
index 2a80c75..ed84a66 100644
--- a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/src/com/android/cts/multiuserstorageapp/MultiUserStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/src/com/android/cts/multiuserstorageapp/MultiUserStorageTest.java
@@ -45,7 +45,9 @@
 
     private void wipeTestFiles(File dir) {
         dir.mkdirs();
-        for (File file : dir.listFiles()) {
+        final File[] files = dir.listFiles();
+        if (files == null) return;
+        for (File file : files) {
             if (file.getName().startsWith(FILE_PREFIX)) {
                 Log.d(TAG, "Wiping " + file);
                 file.delete();
@@ -53,11 +55,11 @@
         }
     }
 
-    public void cleanIsolatedStorage() throws Exception {
+    public void testCleanIsolatedStorage() throws Exception {
         wipeTestFiles(Environment.getExternalStorageDirectory());
     }
 
-    public void writeIsolatedStorage() throws Exception {
+    public void testWriteIsolatedStorage() throws Exception {
         final int uid = android.os.Process.myUid();
 
         writeInt(buildApiPath(FILE_SINGLETON), uid);
@@ -67,13 +69,13 @@
         for (File path : getAllPackageSpecificPathsExceptObb(getContext())) {
             assertNotNull("Valid media must be inserted during CTS", path);
             assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
-                    Environment.getStorageState(path));
+                    Environment.getExternalStorageState(path));
 
             writeInt(new File(path, FILE_SINGLETON), uid);
         }
     }
 
-    public void readIsolatedStorage() throws Exception {
+    public void testReadIsolatedStorage() throws Exception {
         final int uid = android.os.Process.myUid();
 
         // Expect that the value we wrote earlier is still valid and wasn't
@@ -91,23 +93,23 @@
         for (File path : getAllPackageSpecificPathsExceptObb(getContext())) {
             assertNotNull("Valid media must be inserted during CTS", path);
             assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
-                    Environment.getStorageState(path));
+                    Environment.getExternalStorageState(path));
 
             assertEquals("Unexpected value in singleton file at " + path, uid,
                     readInt(new File(path, FILE_SINGLETON)));
         }
     }
 
-    public void cleanObbStorage() throws Exception {
+    public void testCleanObbStorage() throws Exception {
         wipeTestFiles(getContext().getObbDir());
     }
 
-    public void writeObbStorage() throws Exception {
+    public void testWriteObbStorage() throws Exception {
         writeInt(buildApiObbPath(FILE_OBB_API_SINGLETON), OBB_API_VALUE);
         writeInt(buildEnvObbPath(FILE_OBB_SINGLETON), OBB_VALUE);
     }
 
-    public void readObbStorage() throws Exception {
+    public void testReadObbStorage() throws Exception {
         assertEquals("Failed to read OBB file from API path", OBB_API_VALUE,
                 readInt(buildApiObbPath(FILE_OBB_API_SINGLETON)));
 
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java
index bbd1e7a..71faab2 100644
--- a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadExternalStorageTest.java
@@ -16,16 +16,9 @@
 
 package com.android.cts.readexternalstorageapp;
 
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadOnlyAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadOnlyAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPaths;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
 
 import android.os.Environment;
 import android.test.AndroidTestCase;
@@ -54,7 +47,7 @@
         for (File path : paths) {
             assertNotNull("Valid media must be inserted during CTS", path);
             assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
-                    Environment.getStorageState(path));
+                    Environment.getExternalStorageState(path));
 
             assertTrue(path.getAbsolutePath().contains(packageName));
 
@@ -65,27 +58,10 @@
             }
 
             // Keep walking up until we leave device
-            while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+            while (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
                 assertDirReadOnlyAccess(path);
                 path = path.getParentFile();
             }
         }
     }
-
-    /**
-     * Verify we can read all gifts.
-     */
-    public void doVerifyGifts() throws Exception {
-        final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
-        assertFileReadOnlyAccess(none);
-        assertEquals(100, readInt(none));
-
-        final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
-        assertFileReadWriteAccess(read);
-        assertEquals(101, readInt(read));
-
-        final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
-        assertFileReadOnlyAccess(write);
-        assertEquals(102, readInt(write));
-    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadGiftTest.java b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadGiftTest.java
new file mode 100644
index 0000000..e72be77
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/ReadExternalStorageApp/src/com/android/cts/readexternalstorageapp/ReadGiftTest.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.readexternalstorageapp;
+
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadOnlyAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
+
+import android.test.AndroidTestCase;
+
+import java.io.File;
+
+public class ReadGiftTest extends AndroidTestCase {
+    /**
+     * Verify we can read all gifts.
+     */
+    public void testGifts() throws Exception {
+        final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
+        assertFileReadOnlyAccess(none);
+        assertEquals(100, readInt(none));
+
+        final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
+        assertFileReadWriteAccess(read);
+        assertEquals(101, readInt(read));
+
+        final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
+        assertFileReadOnlyAccess(write);
+        assertEquals(102, readInt(write));
+    }
+}
diff --git a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
index 3d6cee7..c5f0fd0 100644
--- a/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/SplitApp/src/com/android/cts/splitapp/SplitAppTest.java
@@ -19,6 +19,7 @@
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
@@ -27,9 +28,17 @@
 import android.content.pm.ResolveInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.os.StatFs;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructStat;
 import android.test.AndroidTestCase;
 import android.test.MoreAsserts;
 import android.util.DisplayMetrics;
@@ -39,7 +48,12 @@
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.BufferedReader;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
 import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.lang.reflect.Field;
@@ -51,9 +65,14 @@
     private static final String TAG = "SplitAppTest";
     private static final String PKG = "com.android.cts.splitapp";
 
+    private static final long MB_IN_BYTES = 1 * 1024 * 1024;
+
     public static boolean sFeatureTouched = false;
     public static String sFeatureValue = null;
 
+    public void testNothing() throws Exception {
+    }
+
     public void testSingleBase() throws Exception {
         final Resources r = getContext().getResources();
         final PackageManager pm = getContext().getPackageManager();
@@ -311,6 +330,131 @@
         assertEquals(0, result.size());
     }
 
+    /**
+     * Write app data in a number of locations that expect to remain intact over
+     * long periods of time, such as across app moves.
+     */
+    public void testDataWrite() throws Exception {
+        final String token = String.valueOf(android.os.Process.myUid());
+        writeString(getContext().getFileStreamPath("my_int"), token);
+
+        final SQLiteDatabase db = getContext().openOrCreateDatabase("my_db",
+                Context.MODE_PRIVATE, null);
+        try {
+            db.execSQL("DROP TABLE IF EXISTS my_table");
+            db.execSQL("CREATE TABLE my_table(value INTEGER)");
+            db.execSQL("INSERT INTO my_table VALUES (101), (102), (103)");
+        } finally {
+            db.close();
+        }
+    }
+
+    /**
+     * Verify that data written by {@link #testDataWrite()} is still intact.
+     */
+    public void testDataRead() throws Exception {
+        final String token = String.valueOf(android.os.Process.myUid());
+        assertEquals(token, readString(getContext().getFileStreamPath("my_int")));
+
+        final SQLiteDatabase db = getContext().openOrCreateDatabase("my_db",
+                Context.MODE_PRIVATE, null);
+        try {
+            final Cursor cursor = db.query("my_table", null, null, null, null, null, "value ASC");
+            try {
+                assertEquals(3, cursor.getCount());
+                assertTrue(cursor.moveToPosition(0));
+                assertEquals(101, cursor.getInt(0));
+                assertTrue(cursor.moveToPosition(1));
+                assertEquals(102, cursor.getInt(0));
+                assertTrue(cursor.moveToPosition(2));
+                assertEquals(103, cursor.getInt(0));
+            } finally {
+                cursor.close();
+            }
+        } finally {
+            db.close();
+        }
+    }
+
+    /**
+     * Verify that app is installed on internal storage.
+     */
+    public void testDataInternal() throws Exception {
+        final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath());
+        final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath());
+        assertEquals(internal.st_dev, actual.st_dev);
+    }
+
+    /**
+     * Verify that app is not installed on internal storage.
+     */
+    public void testDataNotInternal() throws Exception {
+        final StructStat internal = Os.stat(Environment.getDataDirectory().getAbsolutePath());
+        final StructStat actual = Os.stat(getContext().getFilesDir().getAbsolutePath());
+        MoreAsserts.assertNotEqual(internal.st_dev, actual.st_dev);
+    }
+
+    public void testPrimaryDataWrite() throws Exception {
+        final String token = String.valueOf(android.os.Process.myUid());
+        writeString(new File(getContext().getExternalFilesDir(null), "my_ext"), token);
+    }
+
+    public void testPrimaryDataRead() throws Exception {
+        final String token = String.valueOf(android.os.Process.myUid());
+        assertEquals(token, readString(new File(getContext().getExternalFilesDir(null), "my_ext")));
+    }
+
+    /**
+     * Verify shared storage behavior when on internal storage.
+     */
+    public void testPrimaryInternal() throws Exception {
+        assertTrue("emulated", Environment.isExternalStorageEmulated());
+        assertFalse("removable", Environment.isExternalStorageRemovable());
+        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+    }
+
+    /**
+     * Verify shared storage behavior when on physical storage.
+     */
+    public void testPrimaryPhysical() throws Exception {
+        assertFalse("emulated", Environment.isExternalStorageEmulated());
+        assertTrue("removable", Environment.isExternalStorageRemovable());
+        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+    }
+
+    /**
+     * Verify shared storage behavior when on adopted storage.
+     */
+    public void testPrimaryAdopted() throws Exception {
+        assertTrue("emulated", Environment.isExternalStorageEmulated());
+        assertTrue("removable", Environment.isExternalStorageRemovable());
+        assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
+    }
+
+    /**
+     * Verify that shared storage is unmounted.
+     */
+    public void testPrimaryUnmounted() throws Exception {
+        MoreAsserts.assertNotEqual(Environment.MEDIA_MOUNTED,
+                Environment.getExternalStorageState());
+    }
+
+    /**
+     * Verify that shared storage lives on same volume as app.
+     */
+    public void testPrimaryOnSameVolume() throws Exception {
+        final File current = getContext().getFilesDir();
+        final File primary = Environment.getExternalStorageDirectory();
+
+        // Shared storage may jump through another filesystem for permission
+        // enforcement, so we verify that total/free space are identical.
+        final long totalDelta = Math.abs(current.getTotalSpace() - primary.getTotalSpace());
+        final long freeDelta = Math.abs(current.getFreeSpace() - primary.getFreeSpace());
+        if (totalDelta > MB_IN_BYTES || freeDelta > MB_IN_BYTES) {
+            fail("Expected primary storage to be on same volume as app");
+        }
+    }
+
     public void testCodeCacheWrite() throws Exception {
         assertTrue(new File(getContext().getFilesDir(), "normal.raw").createNewFile());
         assertTrue(new File(getContext().getCodeCacheDir(), "cache.raw").createNewFile());
@@ -390,4 +534,22 @@
             if (in != null) in.close();
         }
     }
+
+    private static void writeString(File file, String value) throws IOException {
+        final DataOutputStream os = new DataOutputStream(new FileOutputStream(file));
+        try {
+            os.writeUTF(value);
+        } finally {
+            os.close();
+        }
+    }
+
+    private static String readString(File file) throws IOException {
+        final DataInputStream is = new DataInputStream(new FileInputStream(file));
+        try {
+            return is.readUTF();
+        } finally {
+            is.close();
+        }
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionApp/src/com/android/cts/usepermission/UsePermissionTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionApp/src/com/android/cts/usepermission/UsePermissionTest.java
index 5137ee8..d6e97e0 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionApp/src/com/android/cts/usepermission/UsePermissionTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionApp/src/com/android/cts/usepermission/UsePermissionTest.java
@@ -18,6 +18,8 @@
 
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertMediaNoAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertMediaReadWriteAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.logCommand;
 
 import android.content.pm.PackageManager;
@@ -52,6 +54,7 @@
         assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
         assertDirNoAccess(Environment.getExternalStorageDirectory());
         assertDirReadWriteAccess(getInstrumentation().getContext().getExternalCacheDir());
+        assertMediaNoAccess(getInstrumentation().getContext().getContentResolver());
     }
 
     public void testGranted() throws Exception {
@@ -64,6 +67,7 @@
         assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
         assertDirReadWriteAccess(Environment.getExternalStorageDirectory());
         assertDirReadWriteAccess(getInstrumentation().getContext().getExternalCacheDir());
+        assertMediaReadWriteAccess(getInstrumentation().getContext().getContentResolver());
     }
 
     public void testInteractiveGrant() throws Exception {
@@ -77,6 +81,7 @@
         assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
         assertDirNoAccess(Environment.getExternalStorageDirectory());
         assertDirReadWriteAccess(getInstrumentation().getContext().getExternalCacheDir());
+        assertMediaNoAccess(getInstrumentation().getContext().getContentResolver());
 
         // Go through normal grant flow
         mDevice = UiDevice.getInstance(getInstrumentation());
@@ -110,6 +115,7 @@
         assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState());
         assertDirReadWriteAccess(Environment.getExternalStorageDirectory());
         assertDirReadWriteAccess(getInstrumentation().getContext().getExternalCacheDir());
+        assertMediaReadWriteAccess(getInstrumentation().getContext().getContentResolver());
 
         mActivity.finish();
     }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionAppCompat/src/com/android/cts/usepermission/UsePermissionCompatTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionAppCompat/src/com/android/cts/usepermission/UsePermissionCompatTest.java
index fb87e81..7f21d3400 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionAppCompat/src/com/android/cts/usepermission/UsePermissionCompatTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionAppCompat/src/com/android/cts/usepermission/UsePermissionCompatTest.java
@@ -18,6 +18,8 @@
 
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertMediaNoAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertMediaReadWriteAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPaths;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.logCommand;
 
@@ -50,6 +52,7 @@
                 assertDirReadWriteAccess(path);
             }
         }
+        assertMediaReadWriteAccess(getInstrumentation().getContext().getContentResolver());
     }
 
     public void testCompatRevoked() throws Exception {
@@ -71,6 +74,7 @@
                 assertDirNoAccess(dir);
             }
         }
+        assertMediaNoAccess(getInstrumentation().getContext().getContentResolver());
 
         // Just to be sure, poke explicit path
         assertDirNoAccess(new File(Environment.getExternalStorageDirectory(),
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
index afee8541..badc852 100644
--- a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteExternalStorageTest.java
@@ -17,14 +17,10 @@
 package com.android.cts.writeexternalstorageapp;
 
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.TAG;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirNoWriteAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadOnlyAccess;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertDirReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
-import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildProbeFile;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.deleteContents;
 import static com.android.cts.externalstorageapp.CommonExternalStorageTest.getAllPackageSpecificPaths;
@@ -142,12 +138,12 @@
         for (File path : paths) {
             assertNotNull("Valid media must be inserted during CTS", path);
             assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
-                    Environment.getStorageState(path));
+                    Environment.getExternalStorageState(path));
 
             assertTrue(path.getAbsolutePath().contains(packageName));
 
             // Walk until we leave device, writing the whole way
-            while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+            while (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
                 assertDirReadWriteAccess(path);
                 path = path.getParentFile();
             }
@@ -179,7 +175,7 @@
         int depth = 0;
         while (depth++ < 32) {
             assertDirReadWriteAccess(path);
-            assertEquals(Environment.MEDIA_MOUNTED, Environment.getStorageState(path));
+            assertEquals(Environment.MEDIA_MOUNTED, Environment.getExternalStorageState(path));
 
             if (path.getAbsolutePath().equals(top.getAbsolutePath())) {
                 break;
@@ -194,7 +190,7 @@
         // And going one step further should be outside our reach
         path = path.getParentFile();
         assertDirNoWriteAccess(path);
-        assertEquals(Environment.MEDIA_UNKNOWN, Environment.getStorageState(path));
+        assertEquals(Environment.MEDIA_UNKNOWN, Environment.getExternalStorageState(path));
     }
 
     /**
@@ -202,11 +198,11 @@
      */
     public void testMountStatus() {
         assertEquals(Environment.MEDIA_UNKNOWN,
-                Environment.getStorageState(new File("/meow-should-never-exist")));
+                Environment.getExternalStorageState(new File("/meow-should-never-exist")));
 
         // Internal data isn't a mount point
         assertEquals(Environment.MEDIA_UNKNOWN,
-                Environment.getStorageState(getContext().getCacheDir()));
+                Environment.getExternalStorageState(getContext().getCacheDir()));
     }
 
     /**
@@ -220,7 +216,7 @@
         for (File path : paths) {
             assertNotNull("Valid media must be inserted during CTS", path);
             assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
-                    Environment.getStorageState(path));
+                    Environment.getExternalStorageState(path));
 
             assertTrue(path.getAbsolutePath().contains(packageName));
 
@@ -231,7 +227,7 @@
             }
 
             // Keep walking up until we leave device
-            while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+            while (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
                 assertDirReadOnlyAccess(path);
                 path = path.getParentFile();
             }
@@ -250,12 +246,12 @@
         for (File path : paths) {
             assertNotNull("Valid media must be inserted during CTS", path);
             assertEquals("Valid media must be inserted during CTS", Environment.MEDIA_MOUNTED,
-                    Environment.getStorageState(path));
+                    Environment.getExternalStorageState(path));
 
             final File start = path;
 
             boolean found = false;
-            while (Environment.MEDIA_MOUNTED.equals(Environment.getStorageState(path))) {
+            while (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState(path))) {
                 final File test = new File(path, ".nomedia");
                 if (test.exists()) {
                     found = true;
@@ -301,33 +297,4 @@
            probe.delete();
        }
     }
-
-    /**
-     * Leave gifts for other packages in their primary external cache dirs.
-     */
-    public void doWriteGifts() throws Exception {
-        final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
-        none.getParentFile().mkdirs();
-        none.createNewFile();
-        assertFileReadWriteAccess(none);
-
-        writeInt(none, 100);
-        assertEquals(100, readInt(none));
-
-        final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
-        read.getParentFile().mkdirs();
-        read.createNewFile();
-        assertFileReadWriteAccess(read);
-
-        writeInt(read, 101);
-        assertEquals(101, readInt(read));
-
-        final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
-        write.getParentFile().mkdirs();
-        write.createNewFile();
-        assertFileReadWriteAccess(write);
-
-        writeInt(write, 102);
-        assertEquals(102, readInt(write));
-    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteGiftTest.java b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteGiftTest.java
new file mode 100644
index 0000000..5da42da
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/WriteExternalStorageApp/src/com/android/cts/writeexternalstorageapp/WriteGiftTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.writeexternalstorageapp;
+
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_NONE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_READ;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.PACKAGE_WRITE;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.assertFileReadWriteAccess;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.buildGiftForPackage;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.readInt;
+import static com.android.cts.externalstorageapp.CommonExternalStorageTest.writeInt;
+
+import android.test.AndroidTestCase;
+
+import java.io.File;
+
+public class WriteGiftTest extends AndroidTestCase {
+    /**
+     * Leave gifts for other packages in their primary external cache dirs.
+     */
+    public void testGifts() throws Exception {
+        final File none = buildGiftForPackage(getContext(), PACKAGE_NONE);
+        none.getParentFile().mkdirs();
+        none.createNewFile();
+        assertFileReadWriteAccess(none);
+
+        writeInt(none, 100);
+        assertEquals(100, readInt(none));
+
+        final File read = buildGiftForPackage(getContext(), PACKAGE_READ);
+        read.getParentFile().mkdirs();
+        read.createNewFile();
+        assertFileReadWriteAccess(read);
+
+        writeInt(read, 101);
+        assertEquals(101, readInt(read));
+
+        final File write = buildGiftForPackage(getContext(), PACKAGE_WRITE);
+        write.getParentFile().mkdirs();
+        write.createNewFile();
+        assertFileReadWriteAccess(write);
+
+        writeInt(write, 102);
+        assertEquals(102, readInt(write));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.mk b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.mk
new file mode 100644
index 0000000..0548af6
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/Android.mk
@@ -0,0 +1,33 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_PACKAGE_NAME := CtsDeviceAndProfileOwnerApp
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
+
+LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 ctstestrunner ub-uiautomator
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_PACKAGE)
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/AndroidManifest.xml
new file mode 100644
index 0000000..484dd6e
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.deviceandprofileowner">
+
+    <uses-sdk android:minSdkVersion="23"/>
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <receiver
+            android:name="com.android.cts.deviceandprofileowner.BaseDeviceAdminTest$BasicAdminReceiver"
+            android:permission="android.permission.BIND_DEVICE_ADMIN">
+            <meta-data android:name="android.app.device_admin"
+                       android:resource="@xml/device_admin" />
+            <intent-filter>
+                <action android:name="android.app.action.DEVICE_ADMIN_ENABLED" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.deviceandprofileowner"
+                     android:label="Profile and Device Owner CTS Tests"/>
+</manifest>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/device_admin.xml b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/device_admin.xml
new file mode 100644
index 0000000..69a661c
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/res/xml/device_admin.xml
@@ -0,0 +1,18 @@
+<!-- Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<device-admin xmlns:android="http://schemas.android.com/apk/res/android" android:visible="false">
+    <uses-policies>
+    </uses-policies>
+</device-admin>
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java
new file mode 100644
index 0000000..148d8a8
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/BaseDeviceAdminTest.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.deviceandprofileowner;
+
+import android.app.admin.DeviceAdminReceiver;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.test.InstrumentationTestCase;
+
+/**
+ * Base class for profile and device based tests.
+ *
+ * This class handles making sure that the test is the profile or device owner and that it has an
+ * active admin registered, so that all tests may assume these are done.
+ */
+public class BaseDeviceAdminTest extends InstrumentationTestCase {
+
+    public static class BasicAdminReceiver extends DeviceAdminReceiver {
+    }
+
+    public static final String PACKAGE_NAME = BasicAdminReceiver.class.getPackage().getName();
+    public static final ComponentName ADMIN_RECEIVER_COMPONENT = new ComponentName(
+            PACKAGE_NAME, BasicAdminReceiver.class.getName());
+
+    protected DevicePolicyManager mDevicePolicyManager;
+    protected Context mContext;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getContext();
+
+        mDevicePolicyManager = (DevicePolicyManager)
+            mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        assertNotNull(mDevicePolicyManager);
+
+        assertTrue(mDevicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT));
+        assertTrue("App is neither device nor profile owner",
+                mDevicePolicyManager.isProfileOwnerApp(PACKAGE_NAME) ||
+                mDevicePolicyManager.isDeviceOwnerApp(PACKAGE_NAME));
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearDeviceOwnerTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearDeviceOwnerTest.java
new file mode 100644
index 0000000..eefe781
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/ClearDeviceOwnerTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.deviceandprofileowner;
+
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.test.AndroidTestCase;
+
+public class ClearDeviceOwnerTest extends BaseDeviceAdminTest {
+
+    private DevicePolicyManager mDevicePolicyManager;
+
+    @Override
+    protected void tearDown() throws Exception {
+        mDevicePolicyManager = (DevicePolicyManager)
+                mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        if (mDevicePolicyManager != null) {
+            removeActiveAdmin(ADMIN_RECEIVER_COMPONENT);
+            if (mDevicePolicyManager.isDeviceOwnerApp(PACKAGE_NAME)) {
+                mDevicePolicyManager.clearDeviceOwnerApp(PACKAGE_NAME);
+            }
+            assertFalse(mDevicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT));
+            assertFalse(mDevicePolicyManager.isDeviceOwnerApp(PACKAGE_NAME));
+        }
+
+        super.tearDown();
+    }
+
+    // This test clears the device owner and active admin on tearDown(). To be called from the host
+    // side test once a test case is finished.
+    public void testClearDeviceOwner() {
+    }
+
+    private void removeActiveAdmin(ComponentName cn) throws InterruptedException {
+        if (mDevicePolicyManager.isAdminActive(cn)) {
+            mDevicePolicyManager.removeActiveAdmin(cn);
+            for (int i = 0; i < 1000 && mDevicePolicyManager.isAdminActive(cn); i++) {
+                Thread.sleep(100);
+            }
+        }
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PermissionsTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
similarity index 81%
rename from hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PermissionsTest.java
rename to hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
index 727b47f..8b3a975 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/PermissionsTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/PermissionsTest.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.cts.managedprofile;
+package com.android.cts.deviceandprofileowner;
 
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
@@ -23,6 +23,12 @@
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.os.UserManager;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.UiWatcher;
+import android.support.test.uiautomator.Until;
 import android.util.Log;
 
 import java.util.concurrent.ArrayBlockingQueue;
@@ -32,7 +38,7 @@
 /**
  * Test Runtime Permissions APIs in DevicePolicyManager.
  */
-public class PermissionsTest extends BaseManagedProfileTest {
+public class PermissionsTest extends BaseDeviceAdminTest {
     private static final String TAG = "PermissionsTest";
 
     private static final String PERMISSION_APP_PACKAGE_NAME
@@ -54,26 +60,32 @@
     private static final String EXTRA_GRANT_STATE
             = "com.android.cts.permission.extra.GRANT_STATE";
     private static final int PERMISSION_ERROR = -2;
+    private static final BySelector CRASH_POPUP_BUTTON_SELECTOR = By
+            .clazz(android.widget.Button.class.getName())
+            .text("OK")
+            .pkg("android");
+    private static final BySelector CRASH_POPUP_TEXT_SELECTOR = By
+            .clazz(android.widget.TextView.class.getName())
+            .pkg("android");
+    private static final String CRASH_WATCHER_ID = "CRASH";
 
     private PermissionBroadcastReceiver mReceiver;
     private PackageManager mPackageManager;
+    private UiDevice mDevice;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        // Make sure we are running in a managed profile, otherwise risk wiping the primary user's
-        // data.
-        assertTrue(mDevicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT));
-        assertTrue(mDevicePolicyManager.isProfileOwnerApp(
-                ADMIN_RECEIVER_COMPONENT.getPackageName()));
         mReceiver = new PermissionBroadcastReceiver();
         mContext.registerReceiver(mReceiver, new IntentFilter(ACTION_PERMISSION_RESULT));
         mPackageManager = mContext.getPackageManager();
+        mDevice = UiDevice.getInstance(getInstrumentation());
     }
 
     @Override
     protected void tearDown() throws Exception {
         mContext.unregisterReceiver(mReceiver);
+        mDevice.removeWatcher(CRASH_WATCHER_ID);
         super.tearDown();
     }
 
@@ -144,6 +156,28 @@
         assertPermissionRequest(PackageManager.PERMISSION_GRANTED);
     }
 
+    public void testPermissionPrompts() throws Exception {
+        // register a crash watcher
+        mDevice.registerWatcher(CRASH_WATCHER_ID, new UiWatcher() {
+            @Override
+            public boolean checkForCondition() {
+                UiObject2 button = mDevice.findObject(CRASH_POPUP_BUTTON_SELECTOR);
+                if (button != null) {
+                    UiObject2 text = mDevice.findObject(CRASH_POPUP_TEXT_SELECTOR);
+                    Log.d(TAG, "Removing an error dialog: " + text != null ? text.getText() : null);
+                    button.click();
+                    return true;
+                }
+                return false;
+            }
+        });
+        mDevice.runWatchers();
+
+        assertSetPermissionPolicy(DevicePolicyManager.PERMISSION_POLICY_PROMPT);
+        assertPermissionRequest(PackageManager.PERMISSION_DENIED, "permission_deny_button");
+        assertPermissionRequest(PackageManager.PERMISSION_GRANTED, "permission_allow_button");
+    }
+
     public void testPermissionUpdate_setDeniedState() throws Exception {
         assertEquals(mDevicePolicyManager.getPermissionGrantState(ADMIN_RECEIVER_COMPONENT,
                 PERMISSION_APP_PACKAGE_NAME, PERMISSION_NAME),
@@ -192,13 +226,18 @@
     }
 
     private void assertPermissionRequest(int expected) throws Exception {
+        assertPermissionRequest(expected, null);
+    }
+
+    private void assertPermissionRequest(int expected, String buttonResource) throws Exception {
         Intent launchIntent = new Intent();
         launchIntent.setComponent(new ComponentName(PERMISSION_APP_PACKAGE_NAME,
                 PERMISSIONS_ACTIVITY_NAME));
         launchIntent.putExtra(EXTRA_PERMISSION, PERMISSION_NAME);
         launchIntent.setAction(ACTION_REQUEST_PERMISSION);
-        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
         mContext.startActivity(launchIntent);
+        pressPermissionPromptButton(buttonResource);
         assertEquals(expected, mReceiver.waitForBroadcast());
         assertEquals(expected, mPackageManager.checkPermission(PERMISSION_NAME,
                 PERMISSION_APP_PACKAGE_NAME));
@@ -212,7 +251,7 @@
                 PERMISSIONS_ACTIVITY_NAME));
         launchIntent.putExtra(EXTRA_PERMISSION, PERMISSION_NAME);
         launchIntent.setAction(ACTION_CHECK_HAS_PERMISSION);
-        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
         mContext.startActivity(launchIntent);
         assertEquals(expected, mReceiver.waitForBroadcast());
     }
@@ -246,6 +285,20 @@
                 PackageManager.PERMISSION_GRANTED);
     }
 
+    private void pressPermissionPromptButton(String resName) throws Exception {
+        if (resName == null) {
+            return;
+        }
+
+        BySelector selector = By
+                .clazz(android.widget.Button.class.getName())
+                .res("com.android.packageinstaller", resName);
+        mDevice.wait(Until.hasObject(selector), 5000);
+        UiObject2 button = mDevice.findObject(selector);
+        assertNotNull("Couldn't find button with resource id: " + resName, button);
+        button.click();
+    }
+
     private class PermissionBroadcastReceiver extends BroadcastReceiver {
         private BlockingQueue<Integer> mQueue = new ArrayBlockingQueue<Integer> (1);
 
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
index 36ac7a7..2a24f4d 100644
--- a/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/AndroidManifest.xml
@@ -32,6 +32,29 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
         </activity>
+
+        <activity android:name=".SimpleIntentReceiverActivity" android:exported="true"/>
+
+        <activity-alias android:name=".BrowserActivity"
+            android:targetActivity=".SimpleIntentReceiverActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="http"/>
+            </intent-filter>
+        </activity-alias>
+
+        <activity-alias android:name=".AppLinkActivity"
+            android:targetActivity=".SimpleIntentReceiverActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <data android:scheme="http" android:host="com.android.cts.intent.receiver"/>
+            </intent-filter>
+        </activity-alias>
+
     </application>
 
 </manifest>
diff --git a/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.java
new file mode 100644
index 0000000..23755df
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/IntentReceiver/src/com/android/cts/intent/receiver/SimpleIntentReceiverActivity.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 com.android.cts.intent.receiver;
+
+import android.app.admin.DevicePolicyManager;
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.util.Log;
+
+import android.os.Bundle;
+
+/**
+ * An activity that receives an intent and returns immediately, indicating its own name and if it is
+ * running in a managed profile.
+ */
+public class SimpleIntentReceiverActivity extends Activity {
+    private static final String TAG = "SimpleIntentReceiverActivity";
+
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        String className = getIntent().getComponent().getClassName();
+
+        // We try to check if we are in a managed profile or not.
+        // To do this, check if com.android.cts.managedprofile is the profile owner.
+        DevicePolicyManager dpm =
+                (DevicePolicyManager) getSystemService(Context.DEVICE_POLICY_SERVICE);
+        boolean inManagedProfile = dpm.isProfileOwnerApp("com.android.cts.managedprofile");
+
+        Log.i(TAG, "activity " + className + " started, is in managed profile: "
+                + inManagedProfile);
+        Intent result = new Intent();
+        result.putExtra("extra_receiver_class", className);
+        result.putExtra("extra_in_managed_profile", inManagedProfile);
+        setResult(Activity.RESULT_OK, result);
+        finish();
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
new file mode 100644
index 0000000..51ff362
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/AppLinkTest.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.intent.sender;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.test.InstrumentationTestCase;
+import android.util.Log;
+
+public class AppLinkTest extends InstrumentationTestCase {
+
+    private static final String TAG = "AppLinkTest";
+
+    private Context mContext;
+    private IntentSenderActivity mActivity;
+
+    private static final String EXTRA_IN_MANAGED_PROFILE = "extra_in_managed_profile";
+    private static final String EXTRA_RECEIVER_CLASS = "extra_receiver_class";
+    private static final String APP_LINK_ACTIVITY
+            = "com.android.cts.intent.receiver.AppLinkActivity";
+    private static final String BROWSER_ACTIVITY
+            = "com.android.cts.intent.receiver.BrowserActivity";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getTargetContext();
+        mActivity = launchActivity(mContext.getPackageName(), IntentSenderActivity.class, null);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mActivity.finish();
+        super.tearDown();
+    }
+
+    public void testReceivedByAppLinkActivityInPrimary() throws Exception {
+        checkHttpIntentResult(APP_LINK_ACTIVITY, false);
+    }
+
+    public void testReceivedByAppLinkActivityInManaged() throws Exception {
+        checkHttpIntentResult(APP_LINK_ACTIVITY, true);
+    }
+
+    public void testReceivedByBrowserActivityInManaged() throws Exception {
+        checkHttpIntentResult(BROWSER_ACTIVITY, true);
+    }
+
+    public void testTwoReceivers() {
+        assertNumberOfReceivers(2);
+    }
+
+    public void testThreeReceivers() {
+        assertNumberOfReceivers(3);
+    }
+
+    // Should not be called if there are several possible receivers to the intent
+    // (see getHttpIntent)
+    private void checkHttpIntentResult(String receiverClassName, boolean inManagedProfile)
+            throws Exception {
+        PackageManager pm = mContext.getPackageManager();
+
+        Intent result = mActivity.getResult(getHttpIntent());
+        // If it is received in the other profile, we cannot check the class from the ResolveInfo
+        // returned by queryIntentActivities. So we rely on the receiver telling us its class.
+        assertEquals(receiverClassName, result.getStringExtra(EXTRA_RECEIVER_CLASS));
+        assertTrue(result.hasExtra(EXTRA_IN_MANAGED_PROFILE));
+        assertEquals(inManagedProfile, result.getBooleanExtra(EXTRA_IN_MANAGED_PROFILE, false));
+    }
+
+    private void assertNumberOfReceivers(int n) {
+        PackageManager pm = mContext.getPackageManager();
+        assertEquals(n, pm.queryIntentActivities(getHttpIntent(), /* flags = */ 0).size());
+    }
+
+    private Intent getHttpIntent() {
+        Intent i = new Intent(Intent.ACTION_VIEW);
+        i.addCategory(Intent.CATEGORY_BROWSABLE);
+        i.setData(Uri.parse("http://com.android.cts.intent.receiver"));
+        return i;
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
index fd421ac..eb64d47 100644
--- a/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
+++ b/hostsidetests/devicepolicy/app/IntentSender/src/com/android/cts/intent/sender/IntentSenderActivity.java
@@ -66,8 +66,12 @@
     }
 
     public Intent getResult(Intent intent) throws Exception {
+        Log.d(TAG, "Sending intent " + intent);
         startActivityForResult(intent, 42);
         final Result result = mResult.poll(30, TimeUnit.SECONDS);
+        if (result != null) {
+            Log.d(TAG, "Result intent: " + result.data);
+        }
         return (result != null) ? result.data : null;
     }
 
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
index 7b3fba3..b31e74b 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/Android.mk
@@ -26,7 +26,8 @@
 
 LOCAL_JAVA_LIBRARIES := android.test.runner cts-junit
 
-LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 ctstestrunner compatibility-device-util_v2
+LOCAL_STATIC_JAVA_LIBRARIES = android-support-v4 ctstestrunner compatibility-device-util_v2 \
+	ub-uiautomator
 
 LOCAL_SDK_VERSION := current
 
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
index 9bf7046..e03ebdc 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/AndroidManifest.xml
@@ -52,12 +52,7 @@
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
         </activity>
-        <activity android:name=".ComponentDisablingActivity" >
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
-                <category android:name="android.intent.category.DEFAULT"/>
-                <category android:name="android.intent.category.LAUNCHER" />
-            </intent-filter>
+        <activity android:name=".ComponentDisablingActivity" android:exported="true">
         </activity>
         <activity android:name=".ManagedProfileActivity">
             <intent-filter>
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
index 2a54d97..49754d0 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/BaseManagedProfileTest.java
@@ -19,7 +19,8 @@
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
 import android.content.Context;
-import android.test.AndroidTestCase;
+import android.support.test.uiautomator.UiDevice;
+import android.test.InstrumentationTestCase;
 
 /**
  * Base class for profile-owner based tests.
@@ -27,7 +28,7 @@
  * This class handles making sure that the test is the profile owner and that it has an active admin
  * registered, so that all tests may assume these are done.
  */
-public class BaseManagedProfileTest extends AndroidTestCase {
+public class BaseManagedProfileTest extends InstrumentationTestCase {
 
     public static class BasicAdminReceiver extends DeviceAdminReceiver {
     }
@@ -36,21 +37,23 @@
             BasicAdminReceiver.class.getPackage().getName(), BasicAdminReceiver.class.getName());
 
     protected DevicePolicyManager mDevicePolicyManager;
+    protected Context mContext;
 
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        mContext = getInstrumentation().getContext();
 
-       mDevicePolicyManager = (DevicePolicyManager)
-               mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
-       assertNotNull(mDevicePolicyManager);
+        mDevicePolicyManager = (DevicePolicyManager)
+                mContext.getSystemService(Context.DEVICE_POLICY_SERVICE);
+        assertNotNull(mDevicePolicyManager);
 
-       // TODO: Only check the below if we are running as the profile user. If running under the
-       // user owner, can we check that there is a profile and that the below holds for it? If we
-       // don't want to do these checks every time we could get rid of this class altogether and
-       // just have a single test case running under the profile user that do them.
-       assertTrue(mDevicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT));
-       assertTrue(mDevicePolicyManager.isProfileOwnerApp(
-               ADMIN_RECEIVER_COMPONENT.getPackageName()));
+        // TODO: Only check the below if we are running as the profile user. If running under the
+        // user owner, can we check that there is a profile and that the below holds for it? If we
+        // don't want to do these checks every time we could get rid of this class altogether and
+        // just have a single test case running under the profile user that do them.
+        assertTrue(mDevicePolicyManager.isAdminActive(ADMIN_RECEIVER_COMPONENT));
+        assertTrue(mDevicePolicyManager.isProfileOwnerApp(
+                ADMIN_RECEIVER_COMPONENT.getPackageName()));
     }
 }
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
index 21b2d36..6a63cea 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/CrossProfileUtils.java
@@ -20,9 +20,16 @@
 
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
 import android.os.UserManager;
 import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.util.List;
 
 /**
  * The methods in this class are not really tests.
@@ -31,6 +38,8 @@
  * device-side methods from the host.
  */
 public class CrossProfileUtils extends AndroidTestCase {
+    private static final String TAG = "CrossProfileUtils";
+
     private static final String ACTION_READ_FROM_URI = "com.android.cts.action.READ_FROM_URI";
 
     private static final String ACTION_WRITE_TO_URI = "com.android.cts.action.WRITE_TO_URI";
@@ -86,4 +95,18 @@
         dpm.clearUserRestriction(ADMIN_RECEIVER_COMPONENT,
                 UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE);
     }
+
+    // Disables all browsers in current user
+    public void testDisableAllBrowsers() {
+        PackageManager pm = (PackageManager) getContext().getPackageManager();
+        DevicePolicyManager dpm = (DevicePolicyManager)
+               getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
+        Intent webIntent = new Intent(Intent.ACTION_VIEW);
+        webIntent.setData(Uri.parse("http://com.android.cts.intent.receiver"));
+        List<ResolveInfo> ris = pm.queryIntentActivities(webIntent, 0 /* no flags*/);
+        for (ResolveInfo ri : ris) {
+            Log.d(TAG, "Hiding " + ri.activityInfo.packageName);
+            dpm.setApplicationHidden(ADMIN_RECEIVER_COMPONENT, ri.activityInfo.packageName, true);
+        }
+    }
 }
diff --git a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
index 76a9e44..9646e61 100644
--- a/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
+++ b/hostsidetests/devicepolicy/app/ManagedProfile/src/com/android/cts/managedprofile/WipeDataTest.java
@@ -64,11 +64,4 @@
         // Verify the profile is deleted
         assertFalse(mUserManager.getUserProfiles().contains(currentUser));
     }
-
-    // Override this test inherited from base class, as it will trigger another round of setUp()
-    // which would fail because the managed profile has been removed by this test.
-    @Override
-    @Ignore
-    public void testAndroidTestCaseSetupProperly() {
-    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
index 374f2f9..e47ec9e 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/BaseDevicePolicyTest.java
@@ -49,13 +49,9 @@
 
     private static final String RUNNER = "android.support.test.runner.AndroidJUnitRunner";
 
-    protected static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
-    protected static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
-    protected static final String ADMIN_RECEIVER_TEST_CLASS =
-            MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
-
     protected CtsBuildHelper mCtsBuild;
 
+    private String mPackageVerifier;
     private HashSet<String> mAvailableFeatures;
     protected boolean mHasFeature;
 
@@ -70,6 +66,18 @@
         assertNotNull(mCtsBuild);  // ensure build has been set before test is run.
         mHasFeature = getDevice().getApiLevel() >= 21 /* Build.VERSION_CODES.L */
                 && hasDeviceFeature("android.software.device_admin");
+        // disable the package verifier to avoid the dialog when installing an app
+        mPackageVerifier = getDevice().executeShellCommand(
+                "settings get global package_verifier_enable");
+        getDevice().executeShellCommand("settings put global package_verifier_enable 0");
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // reset the package verifier setting to its original value
+        getDevice().executeShellCommand("settings put global package_verifier_enable "
+                + mPackageVerifier);
+        super.tearDown();
     }
 
     protected void installApp(String fileName)
@@ -103,8 +111,6 @@
         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
         assertTrue(commandOutput + " expected to start with \"Success:\"",
                 commandOutput.startsWith("Success:"));
-        // Wait 60 seconds for intents generated to be handled.
-        Thread.sleep(60 * 1000);
     }
 
     protected int getMaxNumberOfUsersSupported() throws DeviceNotAvailableException {
@@ -144,12 +150,14 @@
     }
 
     protected void removeUser(int userId) throws Exception  {
+        String stopUserCommand = "am stop-user -w " + userId;
+        CLog.logAndDisplay(LogLevel.INFO, "starting command \"" + stopUserCommand + "\" and waiting.");
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + stopUserCommand + ": "
+                + getDevice().executeShellCommand(stopUserCommand));
         String removeUserCommand = "pm remove-user " + userId;
         CLog.logAndDisplay(LogLevel.INFO, "starting command " + removeUserCommand);
         CLog.logAndDisplay(LogLevel.INFO, "Output for command " + removeUserCommand + ": "
                 + getDevice().executeShellCommand(removeUserCommand));
-        // Wait 60 seconds for user to finish being removed.
-        Thread.sleep(60 * 1000);
     }
 
     protected void removeTestUsers() throws Exception {
@@ -288,8 +296,6 @@
         String[] tokens = commandOutput.split("\\s+");
         assertTrue(tokens.length > 0);
         assertEquals("Success:", tokens[0]);
-        // Wait 60 seconds for intents generated to be handled.
-        Thread.sleep(60 * 1000);
         return Integer.parseInt(tokens[tokens.length-1]);
     }
 
@@ -343,4 +349,12 @@
         assertTrue(commandOutput + " expected to start with \"Success:\"",
                 commandOutput.startsWith("Success:"));
     }
+
+    protected void setDeviceOwner(String componentName) throws DeviceNotAvailableException {
+        String command = "dpm set-device-owner '" + componentName + "'";
+        String commandOutput = getDevice().executeShellCommand(command);
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
+        assertTrue(commandOutput + " expected to start with \"Success:\"",
+                commandOutput.startsWith("Success:"));
+    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
new file mode 100644
index 0000000..b0918bc
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import junit.framework.AssertionFailedError;
+
+/**
+ * Set of tests for usecases that apply to profile and device owner.
+ * This class is the base class of MixedProfileOwnerTest and MixedDeviceOwnerTest and is abstract
+ * to avoid running spurious tests.
+ */
+public abstract class DeviceAndProfileOwnerTest extends BaseDevicePolicyTest {
+
+    protected static final String DEVICE_ADMIN_PKG = "com.android.cts.deviceandprofileowner";
+    protected static final String DEVICE_ADMIN_APK = "CtsDeviceAndProfileOwnerApp.apk";
+    protected static final String ADMIN_RECEIVER_TEST_CLASS
+            = ".BaseDeviceAdminTest$BasicAdminReceiver";
+
+    private static final String PERMISSIONS_APP_PKG = "com.android.cts.permission.permissionapp";
+    private static final String PERMISSIONS_APP_APK = "CtsPermissionApp.apk";
+
+    private static final String SIMPLE_PRE_M_APP_PKG = "com.android.cts.launcherapps.simplepremapp";
+    private static final String SIMPLE_PRE_M_APP_APK = "CtsSimplePreMApp.apk";
+
+    // ID of the user all tests are run as. For device owner this will be 0, for profile owner it
+    // is the user id of the created profile.
+    protected int mUserId;
+
+    protected void tearDown() throws Exception {
+        if (mHasFeature) {
+            getDevice().uninstallPackage(PERMISSIONS_APP_PKG);
+            getDevice().uninstallPackage(SIMPLE_PRE_M_APP_PKG);
+        }
+        super.tearDown();
+    }
+
+    public void testPermissionGrant() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionGrantState", mUserId));
+    }
+
+    public void testPermissionPolicy() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionPolicy", mUserId));
+    }
+
+    public void testPermissionMixedPolicies() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionMixedPolicies", mUserId));
+    }
+
+    public void testPermissionPrompts() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        try {
+            // unlock device and ensure that the screen stays on
+            getDevice().executeShellCommand("input keyevent 82");
+            getDevice().executeShellCommand("settings put global stay_on_while_plugged_in 2");
+            installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+            assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                    "testPermissionPrompts", mUserId));
+        } finally {
+            getDevice().executeShellCommand("settings put global stay_on_while_plugged_in 0");
+        }
+    }
+
+    public void testPermissionAppUpdate() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionUpdate_setDeniedState", mUserId));
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionUpdate_checkDenied", mUserId));
+        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionUpdate_checkDenied", mUserId));
+
+        assertNull(getDevice().uninstallPackage(PERMISSIONS_APP_PKG));
+        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionUpdate_setGrantedState", mUserId));
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionUpdate_checkGranted", mUserId));
+        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionUpdate_checkGranted", mUserId));
+
+        assertNull(getDevice().uninstallPackage(PERMISSIONS_APP_PKG));
+        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionUpdate_setAutoDeniedPolicy", mUserId));
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionUpdate_checkDenied", mUserId));
+        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionUpdate_checkDenied", mUserId));
+
+        assertNull(getDevice().uninstallPackage(PERMISSIONS_APP_PKG));
+        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionUpdate_setAutoGrantedPolicy", mUserId));
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionUpdate_checkGranted", mUserId));
+        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionUpdate_checkGranted", mUserId));
+    }
+
+    public void testPermissionGrantPreMApp() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        installAppAsUser(SIMPLE_PRE_M_APP_APK, mUserId);
+        assertTrue(runDeviceTestsAsUser(DEVICE_ADMIN_PKG, ".PermissionsTest",
+                "testPermissionGrantStatePreMApp", mUserId));
+    }
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 66a123a1..26d0d233 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -135,13 +135,4 @@
         String testClass = DEVICE_OWNER_PKG + "." + testClassName;
         assertTrue(testClass + " failed.", runDeviceTests(DEVICE_OWNER_PKG, testClass));
     }
-
-    private void setDeviceOwner(String componentName) throws DeviceNotAvailableException {
-        String command = "dpm set-device-owner '" + componentName + "'";
-        String commandOutput = getDevice().executeShellCommand(command);
-        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": " + commandOutput);
-        assertTrue(commandOutput + " expected to start with \"Success:\"",
-                commandOutput.startsWith("Success:"));
-    }
-
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
index 43f1f5a..c65d443 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/LauncherAppsProfileTest.java
@@ -25,6 +25,11 @@
  */
 public class LauncherAppsProfileTest extends BaseLauncherAppsTest {
 
+    private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
+    private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
+    private static final String ADMIN_RECEIVER_TEST_CLASS =
+            MANAGED_PROFILE_PKG + ".BaseManagedProfileTest$BasicAdminReceiver";
+
     private int mProfileUserId;
     private int mProfileSerialNumber;
     private int mMainUserSerialNumber;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
index 50987ef..ade4807 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfileTest.java
@@ -30,12 +30,6 @@
     private static final String MANAGED_PROFILE_PKG = "com.android.cts.managedprofile";
     private static final String MANAGED_PROFILE_APK = "CtsManagedProfileApp.apk";
 
-    private static final String SIMPLE_PRE_M_APP_PKG = "com.android.cts.launcherapps.simplepremapp";
-    private static final String SIMPLE_PRE_M_APP_APK = "CtsSimplePreMApp.apk";
-
-    private static final String PERMISSIONS_APP_PKG = "com.android.cts.permission.permissionapp";
-    private static final String PERMISSIONS_APP_APK = "CtsPermissionApp.apk";
-
     private static final String INTENT_SENDER_PKG = "com.android.cts.intent.sender";
     private static final String INTENT_SENDER_APK = "CtsIntentSenderApp.apk";
 
@@ -55,6 +49,8 @@
     private static final String FEATURE_CAMERA = "android.hardware.camera";
     private static final String FEATURE_WIFI = "android.hardware.wifi";
 
+    private static final String ADD_RESTRICTION_COMMAND = "add-restriction";
+
     private static final int USER_OWNER = 0;
 
     // ID of the profile we'll create. This will always be a profile of USER_OWNER.
@@ -73,11 +69,6 @@
             removeTestUsers();
             mUserId = createManagedProfile();
 
-            // disable the package verifier to avoid the dialog when installing an app
-            mPackageVerifier = getDevice().executeShellCommand(
-                    "settings get global package_verifier_enable");
-            getDevice().executeShellCommand("settings put global package_verifier_enable 0");
-
             installApp(MANAGED_PROFILE_APK);
             setProfileOwner(MANAGED_PROFILE_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
             startUser(mUserId);
@@ -92,9 +83,6 @@
             getDevice().uninstallPackage(INTENT_SENDER_PKG);
             getDevice().uninstallPackage(INTENT_RECEIVER_PKG);
             getDevice().uninstallPackage(CERT_INSTALLER_PKG);
-            // reset the package verifier setting to its original value
-            getDevice().executeShellCommand("settings put global package_verifier_enable "
-                    + mPackageVerifier);
         }
         super.tearDown();
     }
@@ -179,6 +167,49 @@
         // TODO: Test with startActivity
     }
 
+    public void testAppLinks() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+        // Disable all pre-existing browsers in the managed profile so they don't interfere with
+        // intents resolution.
+        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".CrossProfileUtils",
+                "testDisableAllBrowsers", mUserId));
+        installApp(INTENT_RECEIVER_APK);
+        installApp(INTENT_SENDER_APK);
+
+        changeVerificationStatus(USER_OWNER, INTENT_RECEIVER_PKG, "ask");
+        changeVerificationStatus(mUserId, INTENT_RECEIVER_PKG, "ask");
+        // We should have two receivers: IntentReceiverActivity and BrowserActivity in the
+        // managed profile
+        assertAppLinkResult("testTwoReceivers");
+
+        changeUserRestrictionForUser("allow_parent_profile_app_linking", ADD_RESTRICTION_COMMAND,
+                mUserId);
+        // Now we should also have one receiver in the primary user, so three receivers in total.
+        assertAppLinkResult("testThreeReceivers");
+
+        changeVerificationStatus(USER_OWNER, INTENT_RECEIVER_PKG, "never");
+        // The primary user one has been set to never: we should only have the managed profile ones.
+        assertAppLinkResult("testTwoReceivers");
+
+        changeVerificationStatus(mUserId, INTENT_RECEIVER_PKG, "never");
+        // Now there's only the browser in the managed profile left
+        assertAppLinkResult("testReceivedByBrowserActivityInManaged");
+
+        changeVerificationStatus(USER_OWNER, INTENT_RECEIVER_PKG, "always");
+        changeVerificationStatus(mUserId, INTENT_RECEIVER_PKG, "ask");
+        // We've set the receiver in the primary user to always: only this one should receive the
+        // intent.
+        assertAppLinkResult("testReceivedByAppLinkActivityInPrimary");
+
+        changeVerificationStatus(mUserId, INTENT_RECEIVER_PKG, "always");
+        // We have one always in the primary user and one always in the managed profile: the managed
+        // profile one should have precedence.
+        assertAppLinkResult("testReceivedByAppLinkActivityInManaged");
+    }
+
+
     public void testSettingsIntents() throws Exception {
         if (!mHasFeature) {
             return;
@@ -269,17 +300,16 @@
             return;
         }
         String restriction = "no_debugging_features";  // UserManager.DISALLOW_DEBUGGING_FEATURES
-        String command = "add-restriction";
 
         String addRestrictionCommandOutput =
-                changeUserRestrictionForUser(restriction, command, mUserId);
+                changeUserRestrictionForUser(restriction, ADD_RESTRICTION_COMMAND, mUserId);
         assertTrue("Command was expected to succeed " + addRestrictionCommandOutput,
                 addRestrictionCommandOutput.contains("Status: ok"));
 
         // This should now fail, as the shell is not available to start activities under a different
         // user once the restriction is in place.
         addRestrictionCommandOutput =
-                changeUserRestrictionForUser(restriction, command, mUserId);
+                changeUserRestrictionForUser(restriction, ADD_RESTRICTION_COMMAND, mUserId);
         assertTrue(
                 "Expected SecurityException when starting the activity "
                         + addRestrictionCommandOutput,
@@ -505,86 +535,6 @@
         }
     }
 
-    public void testPermissionGrant() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionGrantState", mUserId));
-    }
-
-    public void testPermissionPolicy() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionPolicy", mUserId));
-    }
-
-    public void testPermissionMixedPolicies() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionMixedPolicies", mUserId));
-    }
-
-    public void testPermissionAppUpdate() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionUpdate_setDeniedState", mUserId));
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionUpdate_checkDenied", mUserId));
-        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionUpdate_checkDenied", mUserId));
-
-        assertNull(getDevice().uninstallPackage(PERMISSIONS_APP_PKG));
-        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionUpdate_setGrantedState", mUserId));
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionUpdate_checkGranted", mUserId));
-        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionUpdate_checkGranted", mUserId));
-
-        assertNull(getDevice().uninstallPackage(PERMISSIONS_APP_PKG));
-        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionUpdate_setAutoDeniedPolicy", mUserId));
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionUpdate_checkDenied", mUserId));
-        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionUpdate_checkDenied", mUserId));
-
-        assertNull(getDevice().uninstallPackage(PERMISSIONS_APP_PKG));
-        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionUpdate_setAutoGrantedPolicy", mUserId));
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionUpdate_checkGranted", mUserId));
-        installAppAsUser(PERMISSIONS_APP_APK, mUserId);
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionUpdate_checkGranted", mUserId));
-    }
-
-    public void testPermissionGrantPreMApp() throws Exception {
-        if (!mHasFeature) {
-            return;
-        }
-        installAppAsUser(SIMPLE_PRE_M_APP_APK, mUserId);
-        assertTrue(runDeviceTestsAsUser(MANAGED_PROFILE_PKG, ".PermissionsTest",
-                "testPermissionGrantStatePreMApp", mUserId));
-    }
-
     private void disableActivityForUser(String activityName, int userId)
             throws DeviceNotAvailableException {
         String command = "am start -W --user " + userId
@@ -607,4 +557,16 @@
                 "Output for command " + adbCommand + ": " + commandOutput);
         return commandOutput;
     }
+
+    // status should be one of never, undefined, ask, always
+    private void changeVerificationStatus(int userId, String packageName, String status)
+            throws DeviceNotAvailableException {
+        String command = "pm set-app-link --user " + userId + " " + packageName + " " + status;
+        CLog.logAndDisplay(LogLevel.INFO, "Output for command " + command + ": "
+                + getDevice().executeShellCommand(command));
+    }
+
+    private void assertAppLinkResult(String methodName) throws DeviceNotAvailableException {
+        assertTrue(runDeviceTestsAsUser(INTENT_SENDER_PKG, ".AppLinkTest", methodName, mUserId));
+    }
 }
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
new file mode 100644
index 0000000..97851d4
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedDeviceOwnerTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import junit.framework.AssertionFailedError;
+
+/**
+ * Set of tests for device owner use cases that also apply to profile owners.
+ * Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTest.
+ */
+public class MixedDeviceOwnerTest extends DeviceAndProfileOwnerTest {
+
+    private static final String CLEAR_DEVICE_OWNER_TEST_CLASS =
+            DEVICE_ADMIN_PKG + ".ClearDeviceOwnerTest";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        if (mHasFeature) {
+            mUserId = 0;
+
+            installApp(DEVICE_ADMIN_APK);
+            setDeviceOwner(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS);
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mHasFeature) {
+            assertTrue("Failed to remove device owner.",
+                    runDeviceTests(DEVICE_ADMIN_PKG, CLEAR_DEVICE_OWNER_TEST_CLASS));
+            getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+        }
+        super.tearDown();
+    }
+
+    // All tests for this class are defined in DeviceAndProfileOwnerTest
+}
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
new file mode 100644
index 0000000..7f47227
--- /dev/null
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedProfileOwnerTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.devicepolicy;
+
+import com.android.ddmlib.Log.LogLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
+
+import junit.framework.AssertionFailedError;
+
+/**
+ * Set of tests for profile owner use cases that also apply to device owners.
+ * Tests that should be run identically in both cases are added in DeviceAndProfileOwnerTest.
+ */
+public class MixedProfileOwnerTest extends DeviceAndProfileOwnerTest {
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        // We need managed users to be supported in order to create a profile of the user owner.
+        mHasFeature &= hasDeviceFeature("android.software.managed_users");
+
+        if (mHasFeature) {
+            removeTestUsers();
+            mUserId = createManagedProfile();
+
+            installApp(DEVICE_ADMIN_APK);
+            setProfileOwner(DEVICE_ADMIN_PKG + "/" + ADMIN_RECEIVER_TEST_CLASS, mUserId);
+            startUser(mUserId);
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        if (mHasFeature) {
+            removeUser(mUserId);
+            getDevice().uninstallPackage(DEVICE_ADMIN_PKG);
+        }
+        super.tearDown();
+    }
+
+    // All tests for this class are defined in DeviceAndProfileOwnerTest
+}
diff --git a/hostsidetests/monkey/src/com/android/cts/monkey/SeedTest.java b/hostsidetests/monkey/src/com/android/cts/monkey/SeedTest.java
index d070972..a0016e2 100644
--- a/hostsidetests/monkey/src/com/android/cts/monkey/SeedTest.java
+++ b/hostsidetests/monkey/src/com/android/cts/monkey/SeedTest.java
@@ -16,8 +16,6 @@
 
 package com.android.cts.monkey;
 
-import com.android.tradefed.log.LogUtil.CLog;
-
 import java.util.Scanner;
 
 public class SeedTest extends AbstractMonkeyTest {
@@ -26,15 +24,11 @@
         String cmd1 = MONKEY_CMD + " -s 1337 -v -p " + PKGS[0] + " 500";
         String out1 = mDevice.executeShellCommand(cmd1);
         String out2 = mDevice.executeShellCommand(cmd1);
-        CLog.d("monkey output1: %s", out1);
-        CLog.d("monkey output2: %s", out2);
         assertOutputs(out1, out2);
 
         String cmd2 = MONKEY_CMD + " -s 3007 -v -p " + PKGS[0] + " 125";
         String out3 = mDevice.executeShellCommand(cmd2);
         String out4 = mDevice.executeShellCommand(cmd2);
-        CLog.d("monkey output3: %s", out3);
-        CLog.d("monkey output4: %s", out4);
         assertOutputs(out3, out4);
     }
 
diff --git a/hostsidetests/usage/Android.mk b/hostsidetests/usage/Android.mk
new file mode 100644
index 0000000..917528b
--- /dev/null
+++ b/hostsidetests/usage/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE_TAGS := optional
+
+# Must match the package name in CtsTestCaseList.mk
+LOCAL_MODULE := CtsUsageHostTestCases
+
+LOCAL_JAVA_LIBRARIES := cts-tradefed tradefed-prebuilt
+
+LOCAL_CTS_TEST_PACKAGE := android.host.app.usage
+
+include $(BUILD_CTS_HOST_JAVA_LIBRARY)
+
+include $(call all-subdir-makefiles)
diff --git a/hostsidetests/usage/app/Android.mk b/hostsidetests/usage/app/Android.mk
new file mode 100644
index 0000000..b23efbc
--- /dev/null
+++ b/hostsidetests/usage/app/Android.mk
@@ -0,0 +1,28 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# Don't include this package in any target.
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_SDK_VERSION := current
+
+LOCAL_PACKAGE_NAME := CtsDeviceAppUsageTestApp
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/hostsidetests/usage/app/AndroidManifest.xml b/hostsidetests/usage/app/AndroidManifest.xml
new file mode 100755
index 0000000..bad453f
--- /dev/null
+++ b/hostsidetests/usage/app/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.cts.app.usage.test">
+
+    <application>
+        <activity android:name=".TestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
+
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/InputCallback.java b/hostsidetests/usage/app/src/com/android/cts/app/usage/test/TestActivity.java
similarity index 67%
rename from tests/tests/hardware/src/android/hardware/input/cts/InputCallback.java
rename to hostsidetests/usage/app/src/com/android/cts/app/usage/test/TestActivity.java
index accdcaf..9432477 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/InputCallback.java
+++ b/hostsidetests/usage/app/src/com/android/cts/app/usage/test/TestActivity.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2015 The Android Open Source Project
+ * 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.
@@ -14,12 +14,9 @@
  * limitations under the License.
  */
 
-package android.hardware.input.cts;
+package com.android.cts.app.usage.test;
 
-import android.view.KeyEvent;
-import android.view.MotionEvent;
+import android.app.Activity;
 
-public interface InputCallback {
-    public void onKeyEvent(KeyEvent ev);
-    public void onMotionEvent(MotionEvent ev);
-}
+public class TestActivity extends Activity {
+}
\ No newline at end of file
diff --git a/hostsidetests/usage/src/com/android/cts/app/usage/AppIdleHostTest.java b/hostsidetests/usage/src/com/android/cts/app/usage/AppIdleHostTest.java
new file mode 100644
index 0000000..f24be0e
--- /dev/null
+++ b/hostsidetests/usage/src/com/android/cts/app/usage/AppIdleHostTest.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.app.usage;
+
+import com.android.cts.tradefed.build.CtsBuildHelper;
+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.IBuildReceiver;
+
+public class AppIdleHostTest extends DeviceTestCase implements IBuildReceiver {
+    private static final String SETTINGS_APP_IDLE_CONSTANTS = "app_idle_constants";
+
+    private static final String TEST_APP_PACKAGE = "com.android.cts.app.usage.test";
+    private static final String TEST_APP_APK = "CtsDeviceAppUsageTestApp.apk";
+    private static final String TEST_APP_CLASS = "TestActivity";
+
+    private static final long ACTIVITY_LAUNCH_WAIT_MILLIS = 500;
+
+    /**
+     * A reference to the build.
+     */
+    private CtsBuildHelper mBuild;
+
+    /**
+     * A reference to the device under test.
+     */
+    private ITestDevice mDevice;
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        // Get the build, this is used to access the APK.
+        mBuild = CtsBuildHelper.createBuildHelper(buildInfo);
+    }
+
+    @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(TEST_APP_PACKAGE);
+
+        // Install the APK on the device.
+        mDevice.installPackage(mBuild.getTestApp(TEST_APP_APK), false);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        // Remove the package once complete.
+        mDevice.uninstallPackage(TEST_APP_PACKAGE);
+        super.tearDown();
+    }
+
+    /**
+     * Checks whether an package is idle.
+     * @param appPackage The package to check for idleness.
+     * @return true if the package is idle
+     * @throws DeviceNotAvailableException
+     */
+    private boolean isAppIdle(String appPackage) throws DeviceNotAvailableException {
+        String result = mDevice.executeShellCommand(String.format("am get-inactive %s", appPackage));
+        return result.contains("Idle=true");
+    }
+
+    /**
+     * Set the app idle settings.
+     * @param settingsStr The settings string, a comma separated key=value list.
+     * @throws DeviceNotAvailableException
+     */
+    private void setAppIdleSettings(String settingsStr) throws DeviceNotAvailableException {
+        mDevice.executeShellCommand(String.format("settings put global %s \"%s\"",
+                SETTINGS_APP_IDLE_CONSTANTS, settingsStr));
+    }
+
+    /**
+     * Get the current app idle settings.
+     * @throws DeviceNotAvailableException
+     */
+    private String getAppIdleSettings() throws DeviceNotAvailableException {
+        String result = mDevice.executeShellCommand(String.format("settings get global %s",
+                SETTINGS_APP_IDLE_CONSTANTS));
+        return result.trim();
+    }
+
+    /**
+     * Launch the test app for a few hundred milliseconds then launch home.
+     * @throws DeviceNotAvailableException
+     */
+    private void startAndStopTestApp() throws DeviceNotAvailableException {
+        // Launch the app.
+        mDevice.executeShellCommand(
+                String.format("am start -W -a android.intent.action.MAIN -n %s/%s.%s",
+                        TEST_APP_PACKAGE, TEST_APP_PACKAGE, TEST_APP_CLASS));
+
+        // Wait for some time.
+        sleepUninterrupted(ACTIVITY_LAUNCH_WAIT_MILLIS);
+
+        // Launch home.
+        mDevice.executeShellCommand(
+                "am start -W -a android.intent.action.MAIN -c android.intent.category.HOME");
+    }
+
+    /**
+     * Tests that the app is idle when the idle threshold is passed, and that the app is not-idle
+     * before the threshold is passed.
+     *
+     * @throws Exception
+     */
+    public void testAppIsIdle() throws Exception {
+        final String previousState = getAppIdleSettings();
+        try {
+            // Set the app idle time to be super low.
+            setAppIdleSettings("idle_duration=5,wallclock_threshold=5");
+            startAndStopTestApp();
+            assertTrue(isAppIdle(TEST_APP_PACKAGE));
+
+            // Set the app idle time to something larger.
+            setAppIdleSettings("idle_duration=10000,wallclock_threshold=10000");
+            startAndStopTestApp();
+            assertFalse(isAppIdle(TEST_APP_PACKAGE));
+        } finally {
+            setAppIdleSettings(previousState);
+        }
+    }
+
+    private static void sleepUninterrupted(long timeMillis) {
+        boolean interrupted;
+        do {
+            try {
+                Thread.sleep(timeMillis);
+                interrupted = false;
+            } catch (InterruptedException e) {
+                interrupted = true;
+            }
+        } while (interrupted);
+    }
+}
diff --git a/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java b/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
index a2fd20d9..0c2afb0 100644
--- a/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
+++ b/suite/cts/deviceTests/videoperf/src/com/android/cts/videoperf/VideoEncoderDecoderTest.java
@@ -16,6 +16,7 @@
 
 package com.android.cts.videoperf;
 
+import android.cts.util.DeviceReportLog;
 import android.graphics.ImageFormat;
 import android.graphics.Point;
 import android.media.cts.CodecImage;
@@ -107,6 +108,8 @@
 
     private TestConfig mTestConfig;
 
+    private DeviceReportLog mReportLog;
+
     @Override
     protected void setUp() throws Exception {
         mEncodedOutputBuffer = new LinkedList<Pair<ByteBuffer, BufferInfo>>();
@@ -114,6 +117,7 @@
         long now = System.currentTimeMillis();
         mRandom = new Random(now);
         mTestConfig = new TestConfig();
+        mReportLog = new DeviceReportLog();
         super.setUp();
     }
 
@@ -127,6 +131,7 @@
         mUVDirectBuffer = null;
         mRandom = null;
         mTestConfig = null;
+        mReportLog.deliverReportToHost(getInstrumentation());
         super.tearDown();
     }
 
@@ -362,7 +367,6 @@
 
     private void doTestGoog(String mimeType, int w, int h) throws Exception {
         mTestConfig.mTestPixels = false;
-        mTestConfig.mReportFrameTime = true;
         mTestConfig.mTotalFrames = 3000;
         mTestConfig.mNumberOfRepeat = 2;
         doTest(true /* isGoog */, mimeType, w, h);
@@ -371,7 +375,6 @@
     private void doTestOther(String mimeType, int w, int h) throws Exception {
         mTestConfig.mTestPixels = false;
         mTestConfig.mTestResult = true;
-        mTestConfig.mReportFrameTime = true;
         mTestConfig.mTotalFrames = 3000;
         mTestConfig.mNumberOfRepeat = 2;
         doTest(false /* isGoog */, mimeType, w, h);
@@ -486,19 +489,19 @@
             // it will be good to clean everything to make every run the same.
             System.gc();
         }
-        getReportLog().printArray("encoder", encoderFpsResults, ResultType.HIGHER_BETTER,
+        mReportLog.printArray("encoder", encoderFpsResults, ResultType.HIGHER_BETTER,
                 ResultUnit.FPS);
-        getReportLog().printArray("rms error", decoderRmsErrorResults, ResultType.LOWER_BETTER,
+        mReportLog.printArray("rms error", decoderRmsErrorResults, ResultType.LOWER_BETTER,
                 ResultUnit.NONE);
-        getReportLog().printArray("decoder", decoderFpsResults, ResultType.HIGHER_BETTER,
+        mReportLog.printArray("decoder", decoderFpsResults, ResultType.HIGHER_BETTER,
                 ResultUnit.FPS);
-        getReportLog().printArray("encoder decoder", totalFpsResults, ResultType.HIGHER_BETTER,
+        mReportLog.printArray("encoder decoder", totalFpsResults, ResultType.HIGHER_BETTER,
                 ResultUnit.FPS);
-        getReportLog().printValue(mimeType + " encoder average fps for " + w + "x" + h,
+        mReportLog.printValue(mimeType + " encoder average fps for " + w + "x" + h,
                 Stat.getAverage(encoderFpsResults), ResultType.HIGHER_BETTER, ResultUnit.FPS);
-        getReportLog().printValue(mimeType + " decoder average fps for " + w + "x" + h,
+        mReportLog.printValue(mimeType + " decoder average fps for " + w + "x" + h,
                 Stat.getAverage(decoderFpsResults), ResultType.HIGHER_BETTER, ResultUnit.FPS);
-        getReportLog().printSummary("encoder decoder", Stat.getAverage(totalFpsResults),
+        mReportLog.printSummary("encoder decoder", Stat.getAverage(totalFpsResults),
                 ResultType.HIGHER_BETTER, ResultUnit.FPS);
         for (int i = 0; i < mTestConfig.mNumberOfRepeat; i++) {
             // make sure that rms error is not too big.
@@ -508,10 +511,10 @@
             }
 
             if (mTestConfig.mReportFrameTime) {
-                getReportLog().printValue(
+                mReportLog.printValue(
                         "encodertest#" + i + ": " + Arrays.toString(mEncoderFrameTimeDiff[i]),
                         0, ResultType.NEUTRAL, ResultUnit.NONE);
-                getReportLog().printValue(
+                mReportLog.printValue(
                         "decodertest#" + i + ": " + Arrays.toString(mDecoderFrameTimeDiff[i]),
                         0, ResultType.NEUTRAL, ResultUnit.NONE);
             }
diff --git a/tests/leanbackjank/Android.mk b/tests/leanbackjank/Android.mk
new file mode 100644
index 0000000..f7e8bff
--- /dev/null
+++ b/tests/leanbackjank/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsLeanbackJankTestCases
+
+LOCAL_STATIC_JAVA_LIBRARIES := ctsdeviceutil ctstestrunner ub-uiautomator ub-janktesthelper android-support-v17-leanback
+
+include $(BUILD_CTS_PACKAGE)
+
+include $(call all-makefiles-under, $(LOCAL_PATH))
+
diff --git a/tests/leanbackjank/AndroidManifest.xml b/tests/leanbackjank/AndroidManifest.xml
new file mode 100644
index 0000000..1cd552d
--- /dev/null
+++ b/tests/leanbackjank/AndroidManifest.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="android.cts.leanbackjank">
+
+  <application>
+      <uses-library android:name="android.test.runner"/>
+  </application>
+
+  <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
+                   android:targetPackage="android.cts.leanbackjank"
+                   android:label="Jank tests">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
diff --git a/tests/leanbackjank/AndroidTest.xml b/tests/leanbackjank/AndroidTest.xml
new file mode 100644
index 0000000..e61c58d
--- /dev/null
+++ b/tests/leanbackjank/AndroidTest.xml
@@ -0,0 +1,19 @@
+<?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="CTS Jank test config">
+    <include name="common-config" />
+    <option name="cts-apk-installer:test-file-name" value="CtsLeanbackJank.apk" />
+</configuration>
diff --git a/tests/leanbackjank/app/Android.mk b/tests/leanbackjank/app/Android.mk
new file mode 100644
index 0000000..5abe1a7
--- /dev/null
+++ b/tests/leanbackjank/app/Android.mk
@@ -0,0 +1,47 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsLeanbackJank
+
+LOCAL_RESOURCE_DIR := \
+    $(TOP)/frameworks/support/v17/leanback/res \
+    $(TOP)/frameworks/support/v7/recyclerview/res \
+    $(LOCAL_PATH)/res
+
+LOCAL_STATIC_JAVA_LIBRARIES := \
+    ctsdeviceutil \
+    ctstestrunner \
+    ub-uiautomator \
+    ub-janktesthelper \
+    android-support-v4 \
+    android-support-v7-recyclerview \
+    android-support-v17-leanback \
+    glide
+
+LOCAL_AAPT_FLAGS := \
+        --auto-add-overlay \
+        --extra-packages android.support.v17.leanback \
+        --extra-packages android.support.v7.recyclerview
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/leanbackjank/app/AndroidManifest.xml b/tests/leanbackjank/app/AndroidManifest.xml
new file mode 100644
index 0000000..333399e
--- /dev/null
+++ b/tests/leanbackjank/app/AndroidManifest.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.cts.jank.leanback"
+    android:versionCode="1"
+    android:versionName="1.1" >
+
+    <uses-sdk
+        android:minSdkVersion="21"
+        android:targetSdkVersion="23" />
+
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+
+    <uses-feature
+        android:name="android.hardware.touchscreen"
+        android:required="false" />
+
+    <uses-feature android:name="android.software.leanback"
+        android:required="true" />
+
+    <application
+        android:allowBackup="false"
+        android:icon="@drawable/videos_by_google_banner"
+        android:label="@string/app_name"
+        android:logo="@drawable/videos_by_google_banner"
+        android:theme="@style/Theme.Example.Leanback" >
+        <activity
+            android:name=".ui.MainActivity"
+            android:icon="@drawable/videos_by_google_banner"
+            android:label="@string/app_name"
+            android:logo="@drawable/videos_by_google_banner"
+            android:screenOrientation="landscape" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/leanbackjank/app/res/drawable-hdpi/app_icon_quantum.png b/tests/leanbackjank/app/res/drawable-hdpi/app_icon_quantum.png
new file mode 100644
index 0000000..fda9a74
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-hdpi/app_icon_quantum.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-hdpi/app_icon_quantum_card.png b/tests/leanbackjank/app/res/drawable-hdpi/app_icon_quantum_card.png
new file mode 100644
index 0000000..498cf66
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-hdpi/app_icon_quantum_card.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-hdpi/ic_launcher.png b/tests/leanbackjank/app/res/drawable-hdpi/ic_launcher.png
new file mode 100644
index 0000000..96a442e
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-hdpi/ic_launcher.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-hdpi/ic_main_icon.png b/tests/leanbackjank/app/res/drawable-hdpi/ic_main_icon.png
new file mode 100644
index 0000000..6f0c962
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-hdpi/ic_main_icon.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-hdpi/videos_by_google_banner.png b/tests/leanbackjank/app/res/drawable-hdpi/videos_by_google_banner.png
new file mode 100644
index 0000000..4cedb52
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-hdpi/videos_by_google_banner.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-hdpi/videos_by_google_icon.png b/tests/leanbackjank/app/res/drawable-hdpi/videos_by_google_icon.png
new file mode 100644
index 0000000..20fd898
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-hdpi/videos_by_google_icon.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-ldpi/ic_launcher.png b/tests/leanbackjank/app/res/drawable-ldpi/ic_launcher.png
new file mode 100644
index 0000000..9923872
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-ldpi/ic_launcher.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-mdpi/app_icon_quantum.png b/tests/leanbackjank/app/res/drawable-mdpi/app_icon_quantum.png
new file mode 100644
index 0000000..6b62138
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-mdpi/app_icon_quantum.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-mdpi/app_icon_quantum_card.png b/tests/leanbackjank/app/res/drawable-mdpi/app_icon_quantum_card.png
new file mode 100644
index 0000000..ac9cc30
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-mdpi/app_icon_quantum_card.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-mdpi/ic_launcher.png b/tests/leanbackjank/app/res/drawable-mdpi/ic_launcher.png
new file mode 100644
index 0000000..359047d
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-mdpi/ic_launcher.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-mdpi/ic_main_icon.png b/tests/leanbackjank/app/res/drawable-mdpi/ic_main_icon.png
new file mode 100644
index 0000000..e9effc8
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-mdpi/ic_main_icon.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-mdpi/videos_by_google_banner.png b/tests/leanbackjank/app/res/drawable-mdpi/videos_by_google_banner.png
new file mode 100644
index 0000000..b191626
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-mdpi/videos_by_google_banner.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-mdpi/videos_by_google_icon.png b/tests/leanbackjank/app/res/drawable-mdpi/videos_by_google_icon.png
new file mode 100644
index 0000000..8a7c6dc
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-mdpi/videos_by_google_icon.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-xhdpi/app_icon_quantum.png b/tests/leanbackjank/app/res/drawable-xhdpi/app_icon_quantum.png
new file mode 100644
index 0000000..825ef63
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xhdpi/app_icon_quantum.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-xhdpi/app_icon_quantum_card.png b/tests/leanbackjank/app/res/drawable-xhdpi/app_icon_quantum_card.png
new file mode 100644
index 0000000..9b1703d
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xhdpi/app_icon_quantum_card.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-xhdpi/bg.png b/tests/leanbackjank/app/res/drawable-xhdpi/bg.png
new file mode 100644
index 0000000..476c698
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xhdpi/bg.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-xhdpi/card_background_default.9.png b/tests/leanbackjank/app/res/drawable-xhdpi/card_background_default.9.png
new file mode 100644
index 0000000..29f4e01
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xhdpi/card_background_default.9.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-xhdpi/default_background.xml b/tests/leanbackjank/app/res/drawable-xhdpi/default_background.xml
new file mode 100644
index 0000000..07b0589
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xhdpi/default_background.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+       android:shape="rectangle">
+    <gradient
+            android:startColor="@color/background_gradient_start"
+            android:endColor="@color/background_gradient_end"
+            android:angle="-270" />
+</shape>
\ No newline at end of file
diff --git a/tests/leanbackjank/app/res/drawable-xhdpi/grid_bg.png b/tests/leanbackjank/app/res/drawable-xhdpi/grid_bg.png
new file mode 100644
index 0000000..476c698
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xhdpi/grid_bg.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-xhdpi/ic_launcher.png b/tests/leanbackjank/app/res/drawable-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..71c6d76
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xhdpi/ic_launcher.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-xhdpi/ic_main_icon.png b/tests/leanbackjank/app/res/drawable-xhdpi/ic_main_icon.png
new file mode 100644
index 0000000..2e56516
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xhdpi/ic_main_icon.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-xhdpi/shadow7.9.png b/tests/leanbackjank/app/res/drawable-xhdpi/shadow7.9.png
new file mode 100644
index 0000000..6d00d09
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xhdpi/shadow7.9.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-xhdpi/videos_by_google_banner.png b/tests/leanbackjank/app/res/drawable-xhdpi/videos_by_google_banner.png
new file mode 100644
index 0000000..bdcf41e
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xhdpi/videos_by_google_banner.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-xhdpi/videos_by_google_icon.png b/tests/leanbackjank/app/res/drawable-xhdpi/videos_by_google_icon.png
new file mode 100644
index 0000000..9bc4836
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xhdpi/videos_by_google_icon.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-xxhdpi/app_icon_quantum.png b/tests/leanbackjank/app/res/drawable-xxhdpi/app_icon_quantum.png
new file mode 100644
index 0000000..c82f94c
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xxhdpi/app_icon_quantum.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-xxhdpi/app_icon_quantum_card.png b/tests/leanbackjank/app/res/drawable-xxhdpi/app_icon_quantum_card.png
new file mode 100644
index 0000000..6c50e8f
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xxhdpi/app_icon_quantum_card.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-xxhdpi/videos_by_google_banner.png b/tests/leanbackjank/app/res/drawable-xxhdpi/videos_by_google_banner.png
new file mode 100644
index 0000000..6c121e61
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xxhdpi/videos_by_google_banner.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable-xxhdpi/videos_by_google_icon.png b/tests/leanbackjank/app/res/drawable-xxhdpi/videos_by_google_icon.png
new file mode 100644
index 0000000..4258160e
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable-xxhdpi/videos_by_google_icon.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable/android_header.png b/tests/leanbackjank/app/res/drawable/android_header.png
new file mode 100644
index 0000000..56206ec
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable/android_header.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable/app_icon_quantum.png b/tests/leanbackjank/app/res/drawable/app_icon_quantum.png
new file mode 100644
index 0000000..fda9a74
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable/app_icon_quantum.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable/app_icon_quantum_card.png b/tests/leanbackjank/app/res/drawable/app_icon_quantum_card.png
new file mode 100644
index 0000000..498cf66
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable/app_icon_quantum_card.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable/gradation.png b/tests/leanbackjank/app/res/drawable/gradation.png
new file mode 100644
index 0000000..ba43a90
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable/gradation.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable/ic_action_a.png b/tests/leanbackjank/app/res/drawable/ic_action_a.png
new file mode 100644
index 0000000..3d555ef
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable/ic_action_a.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable/ic_title.png b/tests/leanbackjank/app/res/drawable/ic_title.png
new file mode 100644
index 0000000..1c62b2e
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable/ic_title.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable/movie.png b/tests/leanbackjank/app/res/drawable/movie.png
new file mode 100644
index 0000000..cb5cb6d
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable/movie.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable/player_bg_gradient_dark.xml b/tests/leanbackjank/app/res/drawable/player_bg_gradient_dark.xml
new file mode 100644
index 0000000..4450cb6
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable/player_bg_gradient_dark.xml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android" >
+
+    <gradient
+        android:angle="90"
+        android:centerColor="#00000000"
+        android:endColor="#B2000000"
+        android:startColor="#B2000000"
+        android:type="linear" />
+
+</shape>
\ No newline at end of file
diff --git a/tests/leanbackjank/app/res/drawable/shadow7.9.png b/tests/leanbackjank/app/res/drawable/shadow7.9.png
new file mode 100644
index 0000000..6d00d09
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable/shadow7.9.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable/text_bg.xml b/tests/leanbackjank/app/res/drawable/text_bg.xml
new file mode 100644
index 0000000..43fbeaf
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable/text_bg.xml
@@ -0,0 +1,32 @@
+<?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.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+    android:shape="rectangle">
+    <gradient
+        android:startColor="#FFFF0000"
+        android:endColor="#FFFF00FF"
+        android:angle="45" />
+    <padding
+        android:left="7dp"
+        android:top="7dp"
+        android:right="7dp"
+        android:bottom="7dp" />
+    <size
+        android:height="160dp"
+        android:width="100dp" />
+</shape>
diff --git a/tests/leanbackjank/app/res/drawable/videos_by_google_banner.png b/tests/leanbackjank/app/res/drawable/videos_by_google_banner.png
new file mode 100644
index 0000000..4cedb52
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable/videos_by_google_banner.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/drawable/videos_by_google_icon.png b/tests/leanbackjank/app/res/drawable/videos_by_google_icon.png
new file mode 100644
index 0000000..20fd898
--- /dev/null
+++ b/tests/leanbackjank/app/res/drawable/videos_by_google_icon.png
Binary files differ
diff --git a/tests/leanbackjank/app/res/layout/icon_header_item.xml b/tests/leanbackjank/app/res/layout/icon_header_item.xml
new file mode 100644
index 0000000..586881d
--- /dev/null
+++ b/tests/leanbackjank/app/res/layout/icon_header_item.xml
@@ -0,0 +1,33 @@
+<?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.
+-->
+<android.support.v17.leanback.widget.NonOverlappingLinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:orientation="horizontal"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <ImageView
+        android:id="@+id/header_icon"
+        android:layout_width="32dp"
+        android:layout_height="32dp" />
+
+    <TextView
+        android:id="@+id/header_label"
+        android:layout_marginTop="6dp"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content" />
+</android.support.v17.leanback.widget.NonOverlappingLinearLayout>
diff --git a/tests/leanbackjank/app/res/layout/main.xml b/tests/leanbackjank/app/res/layout/main.xml
new file mode 100644
index 0000000..7a83b69
--- /dev/null
+++ b/tests/leanbackjank/app/res/layout/main.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:id="@+id/main_frame"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent">
+
+    <fragment
+        android:name="android.cts.jank.leanback.ui.MainFragment"
+        android:id="@+id/main_browse_fragment"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent" />
+
+</FrameLayout>
diff --git a/tests/leanbackjank/app/res/layout/movie_card.xml b/tests/leanbackjank/app/res/layout/movie_card.xml
new file mode 100644
index 0000000..c63841d
--- /dev/null
+++ b/tests/leanbackjank/app/res/layout/movie_card.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<android.support.v17.leanback.widget.NonOverlappingLinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_height="wrap_content"
+              android:layout_width="wrap_content"
+              android:scaleType="centerCrop"
+              android:focusable="true"
+              android:focusableInTouchMode="true">
+    <ImageView
+            android:layout_width="wrap_content"
+            android:layout_height="match_parent"
+            android:id="@+id/poster" android:layout_gravity="center"/>
+    <TextView
+       android:id="@+id/poster_text"
+       android:layout_width="wrap_content"
+       android:layout_height="wrap_content"
+       android:paddingTop="10dp"
+       android:paddingRight="10dp"
+       android:paddingBottom="10dp"
+       android:paddingLeft="10dp" />
+</android.support.v17.leanback.widget.NonOverlappingLinearLayout>
\ No newline at end of file
diff --git a/tests/leanbackjank/app/res/values/colors.xml b/tests/leanbackjank/app/res/values/colors.xml
new file mode 100644
index 0000000..7e246ed
--- /dev/null
+++ b/tests/leanbackjank/app/res/values/colors.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <color name="background_gradient_start">#000000</color>
+    <color name="background_gradient_end">#DDDDDD</color>
+    <color name="fastlane_background">#0096a6</color>
+    <color name="search_opaque">#ffaa3f</color>
+    <color name="selected_background">#ffaa3f</color>
+    <color name="soft_opaque">#30000000</color>
+    <color name="img_soft_opaque">#30FF0000</color>
+    <color name="img_full_opaque">#00000000</color>
+    <color name="black_opaque">#AA000000</color>
+    <color name="black">#59000000</color>
+    <color name="white">#FFFFFF</color>
+    <color name="orange_transparent">#AAFADCA7</color>
+    <color name="orange">#FADCA7</color>
+    <color name="yellow">#EEFF41</color>
+    <color name="default_background">#0096a6</color>
+    <color name="icon_background">#4A4F51</color>
+    <color name="icon_alt_background">#2A2F51</color>
+</resources>
\ No newline at end of file
diff --git a/tests/leanbackjank/app/res/values/strings.xml b/tests/leanbackjank/app/res/values/strings.xml
new file mode 100644
index 0000000..17edf95
--- /dev/null
+++ b/tests/leanbackjank/app/res/values/strings.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+     Copyright (C) 2015 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<resources>
+    <string name="app_name"><![CDATA[Leanback launcher jank test application]]></string>
+    <string name="browse_title"><![CDATA[Leanback launcher jank test application]]></string>
+    <string name="error">Error</string>
+    <string name="ok">OK</string>
+    <string name="grid_item_template">Item %1$d</string>
+    <string name="settings">Settings</string>
+
+    <!-- Error messages -->
+    <string name="error_fragment_message">An error occurred</string>
+    <string name="dismiss_error">Dismiss</string>
+</resources>
diff --git a/tests/leanbackjank/app/res/values/themes.xml b/tests/leanbackjank/app/res/values/themes.xml
new file mode 100644
index 0000000..4a1e06d
--- /dev/null
+++ b/tests/leanbackjank/app/res/values/themes.xml
@@ -0,0 +1,15 @@
+<resources>
+    <style name="Theme.Example.Leanback" parent="Theme.Leanback">
+        <item name="android:colorPrimary">@color/search_opaque</item>
+        <item name="android:windowEnterTransition">@android:transition/fade</item>
+        <item name="android:windowExitTransition">@android:transition/fade</item>
+        <item name="android:windowSharedElementExitTransition">@android:transition/move</item>
+        <item name="android:windowSharedElementEnterTransition">@android:transition/move</item>
+        <!-- Set to display colorPrimary when apps launches -->
+        <item name="android:windowAllowReturnTransitionOverlap">true</item>
+        <item name="android:windowAllowEnterTransitionOverlap">false</item>
+        <item name="android:windowContentTransitions">true</item>
+    </style>
+    <style name="Widget.Example.Leanback.Title.Text" parent="Widget.Leanback.Title.Text" >
+    </style>
+</resources>
diff --git a/tests/leanbackjank/app/src/android/cts/jank/leanback/Utils.java b/tests/leanbackjank/app/src/android/cts/jank/leanback/Utils.java
new file mode 100644
index 0000000..de6d6af
--- /dev/null
+++ b/tests/leanbackjank/app/src/android/cts/jank/leanback/Utils.java
@@ -0,0 +1,112 @@
+/*
+ * 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.cts.jank.leanback;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Point;
+import android.media.MediaMetadataRetriever;
+import android.os.Build;
+import android.util.DisplayMetrics;
+import android.view.Display;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+import android.widget.Toast;
+import android.widget.VideoView;
+
+import java.util.HashMap;
+
+/**
+ * A collection of utility methods, all static.
+ */
+public class Utils {
+
+    public interface MediaDimensions {
+        double MEDIA_HEIGHT = 0.95;
+        double MEDIA_WIDTH = 0.95;
+        double MEDIA_TOP_MARGIN = 0.025;
+        double MEDIA_RIGHT_MARGIN = 0.025;
+        double MEDIA_BOTTOM_MARGIN = 0.025;
+        double MEDIA_LEFT_MARGIN = 0.025;
+    }
+
+    /*
+     * Making sure public utility methods remain static
+     */
+    private Utils() {
+    }
+
+    /**
+     * Returns the screen/display size
+     */
+    public static Point getDisplaySize(Context context) {
+        WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+        Display display = wm.getDefaultDisplay();
+        Point size = new Point();
+        display.getSize(size);
+        return size;
+    }
+
+    /**
+     * Shows a (long) toast
+     */
+    public static void showToast(Context context, String msg) {
+        Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
+    }
+
+    /**
+     * Shows a (long) toast.
+     */
+    public static void showToast(Context context, int resourceId) {
+        Toast.makeText(context, context.getString(resourceId), Toast.LENGTH_LONG).show();
+    }
+
+    public static int convertDpToPixel(Context ctx, int dp) {
+        float density = ctx.getResources().getDisplayMetrics().density;
+        return Math.round((float) dp * density);
+    }
+
+
+    /**
+     * Example for handling resizing content for overscan.  Typically you won't need to resize
+     * when using the Leanback support library.
+     */
+    public void overScan(Activity activity, VideoView videoView) {
+        DisplayMetrics metrics = new DisplayMetrics();
+        activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
+        int w = (int) (metrics.widthPixels * MediaDimensions.MEDIA_WIDTH);
+        int h = (int) (metrics.heightPixels * MediaDimensions.MEDIA_HEIGHT);
+        int marginLeft = (int) (metrics.widthPixels * MediaDimensions.MEDIA_LEFT_MARGIN);
+        int marginTop = (int) (metrics.heightPixels * MediaDimensions.MEDIA_TOP_MARGIN);
+        int marginRight = (int) (metrics.widthPixels * MediaDimensions.MEDIA_RIGHT_MARGIN);
+        int marginBottom = (int) (metrics.heightPixels * MediaDimensions.MEDIA_BOTTOM_MARGIN);
+        FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(w, h);
+        lp.setMargins(marginLeft, marginTop, marginRight, marginBottom);
+        videoView.setLayoutParams(lp);
+    }
+
+
+    public static long getDuration(String videoUrl) {
+        MediaMetadataRetriever mmr = new MediaMetadataRetriever();
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+            mmr.setDataSource(videoUrl, new HashMap<String, String>());
+        } else {
+            mmr.setDataSource(videoUrl);
+        }
+        return Long.parseLong(mmr.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION));
+    }
+}
diff --git a/tests/leanbackjank/app/src/android/cts/jank/leanback/data/VideoProvider.java b/tests/leanbackjank/app/src/android/cts/jank/leanback/data/VideoProvider.java
new file mode 100644
index 0000000..7f78438
--- /dev/null
+++ b/tests/leanbackjank/app/src/android/cts/jank/leanback/data/VideoProvider.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.cts.jank.leanback.data;
+
+import android.cts.jank.leanback.model.Movie;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * Provides synthesized movie data.
+ */
+public class VideoProvider {
+    private static HashMap<String, List<Movie>> sMovieList;
+    private static HashMap<String, Movie> sMovieListById;
+
+    public static Movie getMovieById(String mediaId) {
+        return sMovieListById.get(mediaId);
+    }
+
+    public static HashMap<String, List<Movie>> getMovieList() {
+        return sMovieList;
+    }
+
+    public static HashMap<String, List<Movie>> buildMedia() {
+        if (null != sMovieList) {
+            return sMovieList;
+        }
+        sMovieList = new HashMap<>();
+        sMovieListById = new HashMap<>();
+
+        String title = new String();
+        String studio = new String();
+        int nCategories = 10;
+        for (int i = 0; i < nCategories; i++) {
+            String category_name = String.format("Category %d",  i);
+            List<Movie> categoryList = new ArrayList<Movie>();
+            for (int j = 0; j < 5 + i * 2; j++) {
+                String description = "This is description of a movie.";
+                title = String.format("Video %d-%d", i, j);
+                studio = String.format("Studio %d", (i + j) % 7);
+                Movie movie = buildMovieInfo(category_name, title, description, studio);
+                sMovieListById.put(movie.getId(), movie);
+                categoryList.add(movie);
+            }
+            sMovieList.put(category_name, categoryList);
+        }
+        return sMovieList;
+    }
+
+    private static Movie buildMovieInfo(String category,
+                                        String title,
+                                        String description,
+                                        String studio) {
+        Movie movie = new Movie();
+        movie.setId(Movie.getCount());
+        Movie.incrementCount();
+        movie.setTitle(title);
+        movie.setDescription(description);
+        movie.setStudio(studio);
+        movie.setCategory(category);
+
+        return movie;
+    }
+}
diff --git a/tests/leanbackjank/app/src/android/cts/jank/leanback/model/Movie.java b/tests/leanbackjank/app/src/android/cts/jank/leanback/model/Movie.java
new file mode 100644
index 0000000..874bb00
--- /dev/null
+++ b/tests/leanbackjank/app/src/android/cts/jank/leanback/model/Movie.java
@@ -0,0 +1,129 @@
+/*
+ * 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.cts.jank.leanback.model;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Movie class represents video entity with title, description, image thumbs and video url.
+ */
+public class Movie implements Parcelable {
+    static final long serialVersionUID = 727566175075960653L;
+    private static int sCount = 0;
+    private String mId;
+    private String mTitle;
+    private String mDescription;
+    private String mStudio;
+    private String mCategory;
+
+    public Movie() {
+    }
+
+    public Movie(Parcel in){
+        String[] data = new String[5];
+
+        in.readStringArray(data);
+        mId = data[0];
+        mTitle = data[1];
+        mDescription = data[2];
+        mStudio = data[3];
+        mCategory = data[4];
+    }
+
+    public static String getCount() {
+        return Integer.toString(sCount);
+    }
+
+    public static void incrementCount() {
+        sCount++;
+    }
+
+    public String getId() {
+        return mId;
+    }
+
+    public void setId(String id) {
+        mId = id;
+    }
+
+    public String getTitle() {
+        return mTitle;
+    }
+
+    public void setTitle(String title) {
+        mTitle = title;
+    }
+
+    public String getDescription() {
+        return mDescription;
+    }
+
+    public void setDescription(String description) {
+        mDescription = description;
+    }
+
+    public String getStudio() {
+        return mStudio;
+    }
+
+    public void setStudio(String studio) {
+        mStudio = studio;
+    }
+
+    public String getCategory() {
+        return mCategory;
+    }
+
+    public void setCategory(String category) {
+        mCategory = category;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStringArray(new String[] {mId,
+                mTitle,
+                mDescription,
+                mStudio,
+                mCategory});
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(200);
+        sb.append("Movie{");
+        sb.append("mId=" + mId);
+        sb.append(", mTitle='" + mTitle + '\'');
+        sb.append('}');
+        return sb.toString();
+    }
+
+    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+        public Movie createFromParcel(Parcel in) {
+            return new Movie(in);
+        }
+
+        public Movie[] newArray(int size) {
+            return new Movie[size];
+        }
+    };
+}
diff --git a/tests/leanbackjank/app/src/android/cts/jank/leanback/presenter/CardPresenter.java b/tests/leanbackjank/app/src/android/cts/jank/leanback/presenter/CardPresenter.java
new file mode 100644
index 0000000..9f425ca
--- /dev/null
+++ b/tests/leanbackjank/app/src/android/cts/jank/leanback/presenter/CardPresenter.java
@@ -0,0 +1,90 @@
+/*
+ * 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.cts.jank.leanback.presenter;
+
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.widget.ImageCardView;
+import android.support.v17.leanback.widget.Presenter;
+import android.view.ViewGroup;
+
+import com.bumptech.glide.Glide;
+import android.cts.jank.leanback.R;
+import android.cts.jank.leanback.model.Movie;
+
+/**
+ * A CardPresenter is used to generate Views and bind Objects to them on demand.
+ * It contains an Image CardView
+ */
+public class CardPresenter extends Presenter {
+    private static int CARD_WIDTH = 313;
+    private static int CARD_HEIGHT = 176;
+    private static int sSelectedBackgroundColor;
+    private static int sDefaultBackgroundColor;
+    private Drawable mDefaultCardImage;
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent) {
+        sDefaultBackgroundColor = parent.getResources().getColor(R.color.default_background, null);
+        sSelectedBackgroundColor =
+                parent.getResources().getColor(R.color.selected_background, null);
+        mDefaultCardImage = parent.getResources().getDrawable(R.drawable.movie, null);
+
+        ImageCardView cardView = new ImageCardView(parent.getContext()) {
+            @Override
+            public void setSelected(boolean selected) {
+                updateCardBackgroundColor(this, selected);
+                super.setSelected(selected);
+            }
+        };
+
+        cardView.setFocusable(true);
+        cardView.setFocusableInTouchMode(true);
+        updateCardBackgroundColor(cardView, false);
+        return new ViewHolder(cardView);
+    }
+
+    private static void updateCardBackgroundColor(ImageCardView view, boolean selected) {
+        int color = selected ? sSelectedBackgroundColor : sDefaultBackgroundColor;
+        // Both background colors should be set because the view's background is temporarily visible
+        // during animations.
+        view.setBackgroundColor(color);
+        view.findViewById(R.id.info_field).setBackgroundColor(color);
+    }
+
+    @Override
+    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+        Movie movie = (Movie) item;
+        ImageCardView cardView = (ImageCardView) viewHolder.view;
+
+        cardView.setTitleText(movie.getTitle());
+        cardView.setContentText(movie.getStudio());
+        cardView.setMainImageDimensions(CARD_WIDTH, CARD_HEIGHT);
+        Glide.with(viewHolder.view.getContext())
+                .load(R.drawable.gradation)
+                .centerCrop()
+                .error(mDefaultCardImage)
+                .into(cardView.getMainImageView());
+    }
+
+    @Override
+    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+        ImageCardView cardView = (ImageCardView) viewHolder.view;
+        // Remove references to images so that the garbage collector can free up memory
+        cardView.setBadgeImage(null);
+        cardView.setMainImage(null);
+    }
+}
diff --git a/tests/leanbackjank/app/src/android/cts/jank/leanback/presenter/GridItemPresenter.java b/tests/leanbackjank/app/src/android/cts/jank/leanback/presenter/GridItemPresenter.java
new file mode 100644
index 0000000..4084383
--- /dev/null
+++ b/tests/leanbackjank/app/src/android/cts/jank/leanback/presenter/GridItemPresenter.java
@@ -0,0 +1,59 @@
+/*
+ * 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.cts.jank.leanback.presenter;
+
+import android.graphics.Color;
+import android.support.v17.leanback.widget.Presenter;
+import android.view.Gravity;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import android.cts.jank.leanback.R;
+import android.cts.jank.leanback.ui.MainFragment;
+
+public class GridItemPresenter extends Presenter {
+    private static int GRID_ITEM_WIDTH = 200;
+    private static int GRID_ITEM_HEIGHT = 200;
+
+    private MainFragment mainFragment;
+
+    public GridItemPresenter(MainFragment mainFragment) {
+        this.mainFragment = mainFragment;
+    }
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup parent) {
+        TextView view = new TextView(parent.getContext());
+        view.setLayoutParams(new ViewGroup.LayoutParams(GRID_ITEM_WIDTH, GRID_ITEM_HEIGHT));
+        view.setFocusable(true);
+        view.setFocusableInTouchMode(true);
+        view.setBackgroundColor(
+            mainFragment.getResources().getColor(R.color.default_background, null));
+        view.setTextColor(Color.WHITE);
+        view.setGravity(Gravity.CENTER);
+        return new ViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(ViewHolder viewHolder, Object item) {
+        ((TextView) viewHolder.view).setText((String) item);
+    }
+
+    @Override
+    public void onUnbindViewHolder(ViewHolder viewHolder) {
+    }
+}
diff --git a/tests/leanbackjank/app/src/android/cts/jank/leanback/presenter/IconHeaderItemPresenter.java b/tests/leanbackjank/app/src/android/cts/jank/leanback/presenter/IconHeaderItemPresenter.java
new file mode 100644
index 0000000..42e4c0c
--- /dev/null
+++ b/tests/leanbackjank/app/src/android/cts/jank/leanback/presenter/IconHeaderItemPresenter.java
@@ -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.
+ */
+
+package android.cts.jank.leanback.presenter;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.RowHeaderPresenter;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import android.cts.jank.leanback.R;
+
+public class IconHeaderItemPresenter extends RowHeaderPresenter {
+    private float mUnselectedAlpha;
+
+    @Override
+    public ViewHolder onCreateViewHolder(ViewGroup viewGroup) {
+        mUnselectedAlpha = viewGroup.getResources()
+                .getFraction(R.fraction.lb_browse_header_unselect_alpha, 1, 1);
+        LayoutInflater inflater = (LayoutInflater) viewGroup.getContext()
+                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        View view = inflater.inflate(R.layout.icon_header_item, null);
+
+        return new ViewHolder(view);
+    }
+
+    @Override
+    public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object o) {
+        HeaderItem headerItem = ((ListRow) o).getHeaderItem();
+        View rootView = viewHolder.view;
+
+        ImageView iconView = (ImageView) rootView.findViewById(R.id.header_icon);
+        Drawable icon = rootView.getResources().getDrawable(R.drawable.android_header, null);
+        iconView.setImageDrawable(icon);
+
+        TextView label = (TextView) rootView.findViewById(R.id.header_label);
+        label.setText(headerItem.getName());
+    }
+
+    @Override
+    public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+    }
+
+    // TODO: TEMP - remove me when leanback onCreateViewHolder no longer sets the mUnselectAlpha,AND
+    // also assumes the xml inflation will return a RowHeaderView
+    @Override
+    protected void onSelectLevelChanged(RowHeaderPresenter.ViewHolder holder) {
+        // this is a temporary fix
+        holder.view.setAlpha(mUnselectedAlpha + holder.getSelectLevel() *
+                (1.0f - mUnselectedAlpha));
+    }
+}
\ No newline at end of file
diff --git a/tests/leanbackjank/app/src/android/cts/jank/leanback/ui/MainActivity.java b/tests/leanbackjank/app/src/android/cts/jank/leanback/ui/MainActivity.java
new file mode 100644
index 0000000..fb27fa1
--- /dev/null
+++ b/tests/leanbackjank/app/src/android/cts/jank/leanback/ui/MainActivity.java
@@ -0,0 +1,35 @@
+/*
+ * 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.cts.jank.leanback.ui;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.cts.jank.leanback.R;
+
+/**
+ * MainActivity class that loads MainFragment
+ */
+public class MainActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.main);
+    }
+
+    @Override
+    public boolean onSearchRequested() {
+        return false;
+    }
+}
diff --git a/tests/leanbackjank/app/src/android/cts/jank/leanback/ui/MainFragment.java b/tests/leanbackjank/app/src/android/cts/jank/leanback/ui/MainFragment.java
new file mode 100644
index 0000000..c552a16
--- /dev/null
+++ b/tests/leanbackjank/app/src/android/cts/jank/leanback/ui/MainFragment.java
@@ -0,0 +1,137 @@
+/*
+ * 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.cts.jank.leanback.ui;
+
+import android.content.res.Resources.Theme;
+import android.cts.jank.leanback.R;
+import android.cts.jank.leanback.data.VideoProvider;
+import android.cts.jank.leanback.model.Movie;
+import android.cts.jank.leanback.presenter.CardPresenter;
+import android.cts.jank.leanback.presenter.GridItemPresenter;
+import android.cts.jank.leanback.presenter.IconHeaderItemPresenter;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.v17.leanback.app.BackgroundManager;
+import android.support.v17.leanback.app.BrowseFragment;
+import android.support.v17.leanback.widget.ArrayObjectAdapter;
+import android.support.v17.leanback.widget.HeaderItem;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.util.DisplayMetrics;
+import android.view.View;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Main class to show BrowseFragment with header and rows of videos
+ */
+public class MainFragment extends BrowseFragment {
+    private ArrayObjectAdapter mRowsAdapter;
+    private DisplayMetrics mMetrics;
+    private BackgroundManager mBackgroundManager;
+
+    @Override
+    public void onActivityCreated(Bundle savedInstanceState) {
+        super.onActivityCreated(savedInstanceState);
+
+        buildRowAdapterItems(VideoProvider.buildMedia());
+        prepareBackgroundManager();
+        setupUIElements();
+        setupEventListeners();
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+    }
+
+    @Override
+    public void onStop() {
+        mBackgroundManager.release();
+        super.onStop();
+    }
+
+    private void prepareBackgroundManager() {
+        mBackgroundManager = BackgroundManager.getInstance(getActivity());
+        mBackgroundManager.attach(getActivity().getWindow());
+        mBackgroundManager.setDrawable(getActivity().getResources().getDrawable(
+                R.drawable.default_background, getContext().getTheme()));
+        mMetrics = new DisplayMetrics();
+        getActivity().getWindowManager().getDefaultDisplay().getMetrics(mMetrics);
+    }
+
+    private void setupUIElements() {
+        setBadgeDrawable(getActivity().getResources().getDrawable(
+                R.drawable.videos_by_google_banner, getContext().getTheme()));
+        setTitle(getString(R.string.browse_title));
+        setHeadersState(HEADERS_ENABLED);
+        setHeadersTransitionOnBackEnabled(true);
+
+        Theme theme = getContext().getTheme();
+        setBrandColor(getResources().getColor(R.color.fastlane_background, theme));
+
+        setSearchAffordanceColor(getResources().getColor(R.color.search_opaque, theme));
+
+        setHeaderPresenterSelector(new PresenterSelector() {
+            @Override
+            public Presenter getPresenter(Object o) {
+                return new IconHeaderItemPresenter();
+            }
+        });
+    }
+
+    private void setupEventListeners() {
+        // Add lister to show the search button.
+        setOnSearchClickedListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+            }
+        });
+    }
+
+    public void buildRowAdapterItems(HashMap<String, List<Movie>> data) {
+        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
+        CardPresenter cardPresenter = new CardPresenter();
+
+        int i = 0;
+
+        for (Map.Entry<String, List<Movie>> entry : data.entrySet()) {
+            ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(cardPresenter);
+            List<Movie> list = entry.getValue();
+
+            for (int j = 0; j < list.size(); j++) {
+                listRowAdapter.add(list.get(j));
+            }
+            HeaderItem header = new HeaderItem(i, entry.getKey());
+            i++;
+            mRowsAdapter.add(new ListRow(header, listRowAdapter));
+        }
+
+        HeaderItem gridHeader = new HeaderItem(i, getString(R.string.settings));
+
+        GridItemPresenter gridPresenter = new GridItemPresenter(this);
+        ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(gridPresenter);
+        for (int j = 0; j < 10; j++) {
+            gridRowAdapter.add(getString(R.string.grid_item_template, j));
+        }
+        mRowsAdapter.add(new ListRow(gridHeader, gridRowAdapter));
+
+        setAdapter(mRowsAdapter);
+    }
+}
diff --git a/tests/leanbackjank/src/android/cts/leanbackjank/CtsDeviceLeanback.java b/tests/leanbackjank/src/android/cts/leanbackjank/CtsDeviceLeanback.java
new file mode 100644
index 0000000..8c95b3c
--- /dev/null
+++ b/tests/leanbackjank/src/android/cts/leanbackjank/CtsDeviceLeanback.java
@@ -0,0 +1,67 @@
+/*
+ * 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.cts.leanbackjank;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.SystemClock;
+import android.support.test.jank.GfxMonitor;
+import android.support.test.jank.JankTest;
+import android.support.test.jank.WindowContentFrameStatsMonitor;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+public class CtsDeviceLeanback extends CtsJankTestBase {
+    private static final String TAG = "CtsDeviceLeanback";
+    private static final long WAIT_TIMEOUT = 5 * 1000;
+    private static final long POST_SCROLL_IDLE_TIME = 3 * 1000;
+    private final static String APP_PACKAGE = "android.cts.jank.leanback";
+    private final static String JAVA_PACKAGE = "android.cts.jank.leanback.ui";
+    private final static String CLASS = JAVA_PACKAGE + ".MainActivity";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setComponent(new ComponentName(APP_PACKAGE, CLASS));
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getInstrumentation().getTargetContext().startActivity(intent);
+        if (!getUiDevice().wait(Until.hasObject(By.pkg(APP_PACKAGE)), WAIT_TIMEOUT)) {
+            fail("Test helper app package not found on device");
+        }
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        getUiDevice().pressHome();
+        super.tearDown();
+    }
+
+    @JankTest(expectedFrames = 10, defaultIterationCount = 2)
+    @GfxMonitor(processName = APP_PACKAGE)
+    @WindowContentFrameStatsMonitor
+    public void testScrollingByDpad() {
+        Log.i(TAG, "testScrolling");
+        getUiDevice().pressDPadDown();
+        getUiDevice().pressDPadDown();
+        getUiDevice().pressDPadDown();
+        getUiDevice().pressDPadUp();
+        getUiDevice().pressDPadUp();
+        getUiDevice().pressDPadUp();
+        SystemClock.sleep(POST_SCROLL_IDLE_TIME);
+        Log.i(TAG, "testScrolling end");
+    }
+}
diff --git a/tests/leanbackjank/src/android/cts/leanbackjank/CtsJankTestBase.java b/tests/leanbackjank/src/android/cts/leanbackjank/CtsJankTestBase.java
new file mode 100644
index 0000000..fdd832b
--- /dev/null
+++ b/tests/leanbackjank/src/android/cts/leanbackjank/CtsJankTestBase.java
@@ -0,0 +1,70 @@
+/*
+ * 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.cts.leanbackjank;
+
+import android.cts.util.DeviceReportLog;
+import android.os.Bundle;
+import android.support.test.jank.JankTestBase;
+import android.support.test.jank.WindowContentFrameStatsMonitor;
+import android.support.test.uiautomator.UiDevice;
+
+import com.android.cts.util.ResultType;
+import com.android.cts.util.ResultUnit;
+
+public abstract class CtsJankTestBase extends JankTestBase {
+
+    private UiDevice mDevice;
+    private DeviceReportLog mLog;
+
+    @Override
+    public void afterTest(Bundle metrics) {
+        String source = String.format("%s#%s", getClass().getCanonicalName(), getName());
+        mLog.printValue(source, WindowContentFrameStatsMonitor.KEY_AVG_FPS,
+                metrics.getDouble(WindowContentFrameStatsMonitor.KEY_AVG_FPS),
+                ResultType.HIGHER_BETTER, ResultUnit.FPS);
+        mLog.printValue(source, WindowContentFrameStatsMonitor.KEY_AVG_LONGEST_FRAME,
+                metrics.getDouble(WindowContentFrameStatsMonitor.KEY_AVG_LONGEST_FRAME),
+                ResultType.LOWER_BETTER, ResultUnit.MS);
+        mLog.printValue(source, WindowContentFrameStatsMonitor.KEY_MAX_NUM_JANKY,
+                metrics.getInt(WindowContentFrameStatsMonitor.KEY_MAX_NUM_JANKY),
+                ResultType.LOWER_BETTER, ResultUnit.COUNT);
+        mLog.printSummary(WindowContentFrameStatsMonitor.KEY_AVG_NUM_JANKY,
+                metrics.getDouble(WindowContentFrameStatsMonitor.KEY_AVG_NUM_JANKY),
+                ResultType.LOWER_BETTER, ResultUnit.COUNT);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mLog = new DeviceReportLog();
+        // fix device orientation
+        mDevice = UiDevice.getInstance(getInstrumentation());
+        mDevice.setOrientationNatural();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mLog.deliverReportToHost(getInstrumentation());
+        // restore device orientation
+        mDevice.unfreezeRotation();
+        super.tearDown();
+    }
+
+    protected UiDevice getUiDevice() {
+        return mDevice;
+    }
+}
diff --git a/tests/tests/alarmclock/Android.mk b/tests/tests/alarmclock/Android.mk
index c99f953..6c30bc7 100644
--- a/tests/tests/alarmclock/Android.mk
+++ b/tests/tests/alarmclock/Android.mk
@@ -21,7 +21,7 @@
 # and when built explicitly put it in the data partition
 LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
 
-LOCAL_STATIC_JAVA_LIBRARIES := ctstestrunner ctsdeviceutil
+LOCAL_STATIC_JAVA_LIBRARIES := CtsAlarmClockCommon ctstestrunner ctsdeviceutil
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/alarmclock/AndroidManifest.xml b/tests/tests/alarmclock/AndroidManifest.xml
index 88bb91d..c0193dd 100644
--- a/tests/tests/alarmclock/AndroidManifest.xml
+++ b/tests/tests/alarmclock/AndroidManifest.xml
@@ -22,9 +22,15 @@
 
     <application>
         <uses-library android:name="android.test.runner" />
-        <activity android:name="TestActivity"
-            android:label="The Target Activity for alarmClock Intents Test">
+
+        <activity android:name="TestStartActivity"
+                  android:label="The Target Activity for AlarmClock CTS Test">
             <intent-filter>
+                <action android:name="android.intent.action.TEST_START_ACTIVITY_DISMISS_ALARM" />
+                <action android:name="android.intent.action.TEST_START_ACTIVITY_SET_ALARM" />
+                <action android:name=
+                        "android.intent.action.TEST_START_ACTIVITY_SET_ALARM_FOR_DISMISSAL" />
+                <action android:name="android.intent.action.TEST_START_ACTIVITY_SNOOZE_ALARM" />
                 <category android:name="android.intent.category.LAUNCHER" />
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
diff --git a/tests/tests/alarmclock/AndroidTest.xml b/tests/tests/alarmclock/AndroidTest.xml
new file mode 100644
index 0000000..1cdd7f4
--- /dev/null
+++ b/tests/tests/alarmclock/AndroidTest.xml
@@ -0,0 +1,23 @@
+<!-- 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="Test module config for AlarmClock">
+    <include name="common-config" />
+    <option name="cts-apk-installer:test-file-name" value="CtsAlarmClockService.apk" />
+    <option name="run-command:run-command"
+         value="settings put secure voice_interaction_service android.alarmclock.service/.MainInteractionService" />
+    <option name="run-command:teardown-command"
+         value="settings put secure voice_interaction_service com.google.android.googlequicksearchbox/com.google.android.voiceinteraction.GsaVoiceInteractionService" />
+    <option name="cts-apk-installer:test-file-name" value="CtsAlarmClockTestCases.apk" />
+</configuration>
diff --git a/tests/tests/alarmclock/common/Android.mk b/tests/tests/alarmclock/common/Android.mk
new file mode 100644
index 0000000..d460ade
--- /dev/null
+++ b/tests/tests/alarmclock/common/Android.mk
@@ -0,0 +1,30 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_MODULE := CtsAlarmClockCommon
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/tests/alarmclock/common/src/android/alarmclock/common/Utils.java b/tests/tests/alarmclock/common/src/android/alarmclock/common/Utils.java
new file mode 100644
index 0000000..d469592
--- /dev/null
+++ b/tests/tests/alarmclock/common/src/android/alarmclock/common/Utils.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.alarmclock.common;
+
+import android.os.Bundle;
+
+public class Utils {
+    // private constructor - so it can't be instantiated.
+    private Utils() {
+    }
+
+    public enum TestcaseType {
+        DISMISS_ALARM,
+        SET_ALARM,
+        SET_ALARM_FOR_DISMISSAL,
+        SNOOZE_ALARM,
+    }
+    public static final String TESTCASE_TYPE = "Testcase_type";
+    public static final String BROADCAST_INTENT =
+            "android.intent.action.FROM_ALARMCLOCK_CTS_TEST_";
+    public static final String TEST_RESULT = "test_result";
+    public static final String COMPLETION_RESULT = "completion";
+    public static final String ABORT_RESULT = "abort";
+
+    public static final String toBundleString(Bundle bundle) {
+        if (bundle == null) {
+            return "*** Bundle is null ****";
+        }
+        StringBuilder buf = new StringBuilder();
+        if (bundle != null) {
+            buf.append("extras: ");
+            for (String s : bundle.keySet()) {
+                buf.append("(" + s + " = " + bundle.get(s) + "), ");
+            }
+        }
+        return buf.toString();
+    }
+}
diff --git a/tests/tests/alarmclock/res/xml/interaction_service.xml b/tests/tests/alarmclock/res/xml/interaction_service.xml
new file mode 100644
index 0000000..e37017c
--- /dev/null
+++ b/tests/tests/alarmclock/res/xml/interaction_service.xml
@@ -0,0 +1,20 @@
+<!-- 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.
+-->
+
+<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:sessionService="android.alarmclock.service.MainInteractionSessionService"
+    android:recognitionService="android.alarmclock.service.MainRecognitionService"
+    android:settingsActivity="android.alarmclock.service.SettingsActivity"
+    android:supportsAssist="false" />
diff --git a/tests/tests/alarmclock/service/Android.mk b/tests/tests/alarmclock/service/Android.mk
new file mode 100644
index 0000000..53dc564
--- /dev/null
+++ b/tests/tests/alarmclock/service/Android.mk
@@ -0,0 +1,32 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+# don't include this package in any target
+LOCAL_MODULE_TAGS := optional
+# and when built explicitly put it in the data partition
+LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
+
+LOCAL_STATIC_JAVA_LIBRARIES := CtsAlarmClockCommon ctstestrunner ctsdeviceutil
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_PACKAGE_NAME := CtsAlarmClockService
+
+LOCAL_SDK_VERSION := current
+
+include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/tests/alarmclock/service/AndroidManifest.xml b/tests/tests/alarmclock/service/AndroidManifest.xml
new file mode 100644
index 0000000..074df26
--- /dev/null
+++ b/tests/tests/alarmclock/service/AndroidManifest.xml
@@ -0,0 +1,64 @@
+<!--
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.alarmclock.service">
+    <uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <service android:name=".MainInteractionService"
+                android:label="CTS test voice interaction service"
+                android:permission="android.permission.BIND_VOICE_INTERACTION"
+                android:process=":interactor"
+                android:exported="true">
+            <meta-data android:name="android.voice_interaction"
+                       android:resource="@xml/interaction_service" />
+            <intent-filter>
+                <action android:name="android.service.voice.VoiceInteractionService" />
+            </intent-filter>
+        </service>
+        <activity android:name=".VoiceInteractionMain" >
+            <intent-filter>
+                <action android:name="android.intent.action.VIMAIN_DISMISS_ALARM" />
+                <action android:name="android.intent.action.VIMAIN_SET_ALARM" />
+                <action android:name="android.intent.action.VIMAIN_SET_ALARM_FOR_DISMISSAL" />
+                <action android:name="android.intent.action.VIMAIN_SNOOZE_ALARM" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".SettingsActivity"
+                  android:label="Voice Interaction Settings">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+        <service android:name=".MainInteractionSessionService"
+                android:permission="android.permission.BIND_VOICE_INTERACTION"
+                android:process=":session">
+        </service>
+        <service android:name=".MainRecognitionService"
+                android:label="CTS Voice Recognition Service">
+            <intent-filter>
+                <action android:name="android.speech.RecognitionService" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+            <meta-data android:name="android.speech" android:resource="@xml/recognition_service" />
+        </service>
+    </application>
+</manifest>
+
diff --git a/tests/tests/alarmclock/service/res/xml/interaction_service.xml b/tests/tests/alarmclock/service/res/xml/interaction_service.xml
new file mode 100644
index 0000000..e37017c
--- /dev/null
+++ b/tests/tests/alarmclock/service/res/xml/interaction_service.xml
@@ -0,0 +1,20 @@
+<!-- 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.
+-->
+
+<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:sessionService="android.alarmclock.service.MainInteractionSessionService"
+    android:recognitionService="android.alarmclock.service.MainRecognitionService"
+    android:settingsActivity="android.alarmclock.service.SettingsActivity"
+    android:supportsAssist="false" />
diff --git a/tests/tests/alarmclock/service/res/xml/recognition_service.xml b/tests/tests/alarmclock/service/res/xml/recognition_service.xml
new file mode 100644
index 0000000..132c987
--- /dev/null
+++ b/tests/tests/alarmclock/service/res/xml/recognition_service.xml
@@ -0,0 +1,17 @@
+<!-- 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.
+-->
+
+<recognition-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:settingsActivity="android.alarmclock.service.SettingsActivity" />
diff --git a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionService.java b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionService.java
new file mode 100644
index 0000000..f96140e
--- /dev/null
+++ b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionService.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.alarmclock.service;
+
+import android.alarmclock.common.Utils;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionService;
+import android.util.Log;
+
+public class MainInteractionService extends VoiceInteractionService {
+    static final String TAG = "MainInteractionService";
+    private Intent mIntent;
+    private boolean mReady = false;
+
+    @Override
+    public void onReady() {
+        super.onReady();
+        mReady = true;
+    }
+
+    @Override
+    public int onStartCommand(Intent intent, int flags, int startId) {
+        Log.i(TAG, "onStartCommand received");
+        mIntent = intent;
+        Log.i(TAG, "received_testcasetype = " + mIntent.getStringExtra(Utils.TESTCASE_TYPE));
+        maybeStart();
+        return START_NOT_STICKY;
+    }
+
+    private void maybeStart() {
+       if (mIntent == null || !mReady) {
+            Log.wtf(TAG, "Can't start session because either intent is null or onReady() "
+                    + "is not called yet. mIntent = " + mIntent + ", mReady = " + mReady);
+        } else {
+            Log.i(TAG,
+                    "Yay! about to start MainInteractionSession as the voice_interaction_service");
+            if (isActiveService(this, new ComponentName(this, getClass()))) {
+                Bundle args = new Bundle();
+                args.putString(Utils.TESTCASE_TYPE, mIntent.getStringExtra(Utils.TESTCASE_TYPE));
+                Log.i(TAG, "xferring_testcasetype = " + args.getString(Utils.TESTCASE_TYPE));
+                showSession(args, 0);
+            } else {
+                Log.wtf(TAG, "**** Not starting MainInteractionService because" +
+                    " it is not set as the current voice_interaction_service");
+            }
+        }
+    }
+}
diff --git a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSession.java b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSession.java
new file mode 100644
index 0000000..ede762a
--- /dev/null
+++ b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSession.java
@@ -0,0 +1,174 @@
+/*
+ * 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.alarmclock.service;
+
+import android.alarmclock.common.Utils;
+import android.alarmclock.common.Utils.TestcaseType;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.provider.AlarmClock;
+import android.service.voice.VoiceInteractionSession;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MainInteractionSession extends VoiceInteractionSession {
+    static final String TAG = "MainInteractionSession";
+
+    private List<MyTask> mUsedTasks = new ArrayList<MyTask>();
+    private Context mContext;
+    private TestcaseType mTestType;
+    private String mTestResult = "";
+
+    MainInteractionSession(Context context) {
+        super(context);
+        mContext = context;
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+    }
+
+    @Override
+    public void onDestroy() {
+        Log.i(TAG, "Canceling the Asynctasks in onDestroy()");
+        for (MyTask t : mUsedTasks) {
+            t.cancel(true);
+        }
+        super.onDestroy();
+    }
+
+    @Override
+    public void onShow(Bundle args, int showFlags) {
+        super.onShow(args, showFlags);
+        String testCaseType = args.getString(Utils.TESTCASE_TYPE);
+        Log.i(TAG, "received_testcasetype = " + testCaseType);
+        try {
+            mTestType = TestcaseType.valueOf(testCaseType);
+        } catch (IllegalArgumentException e) {
+            Log.wtf(TAG, e);
+            return;
+        } catch (NullPointerException e) {
+            Log.wtf(TAG, e);
+            return;
+        }
+        Intent intent;
+        switch(mTestType) {
+            case DISMISS_ALARM:
+                intent = new Intent(AlarmClock.ACTION_DISMISS_ALARM);
+                intent.putExtra(AlarmClock.EXTRA_ALARM_SEARCH_MODE,
+                        AlarmClock.ALARM_SEARCH_MODE_NEXT);
+                break;
+
+            case SET_ALARM_FOR_DISMISSAL:
+            case SET_ALARM:
+                intent = new Intent(AlarmClock.ACTION_SET_ALARM);
+                intent.putExtra(AlarmClock.EXTRA_HOUR, 14);
+                break;
+
+            case SNOOZE_ALARM:
+                intent = new Intent(AlarmClock.ACTION_SNOOZE_ALARM);
+                break;
+
+            default:
+                Log.e(TAG, "Unexpected value");
+                return;
+        }
+        Log.i(TAG, "starting_voiceactivity: " + intent.toString());
+        startVoiceActivity(intent);
+    }
+
+    @Override
+    public void onTaskFinished(Intent intent, int taskId) {
+        // extras contain the info on what the activity started above did.
+        // we probably could verify this also.
+        Bundle extras = intent.getExtras();
+        Log.i(TAG, "in onTaskFinished: testcasetype = " + mTestType + ", intent: " +
+                intent.toString() + Utils.toBundleString(extras));
+        Intent broadcastIntent = new Intent(Utils.BROADCAST_INTENT + mTestType.toString());
+        broadcastIntent.putExtra(Utils.TEST_RESULT, mTestResult);
+        broadcastIntent.putExtra(Utils.TESTCASE_TYPE, mTestType.toString());
+        Log.i(TAG, "sending_broadcast for testcase+type = " +
+                broadcastIntent.getStringExtra(Utils.TESTCASE_TYPE) +
+                ", test_result = " + broadcastIntent.getStringExtra(Utils.TEST_RESULT));
+        mContext.sendBroadcast(broadcastIntent);
+    }
+
+    synchronized MyTask newTask() {
+        MyTask t = new MyTask();
+        mUsedTasks.add(t);
+        return t;
+    }
+
+    @Override
+    public void onRequestCompleteVoice(CompleteVoiceRequest request) {
+        mTestResult = Utils.COMPLETION_RESULT;
+        CharSequence prompt = request.getVoicePrompt().getVoicePromptAt(0);
+        Log.i(TAG, "in Session testcasetype = " + mTestType +
+                ", onRequestCompleteVoice recvd. message = " + prompt);
+        AsyncTaskArg asyncTaskArg = new AsyncTaskArg().setRequest(request).setCompleteReq(true);
+        newTask().execute(asyncTaskArg);
+    }
+
+    @Override
+    public void onRequestAbortVoice(AbortVoiceRequest request) {
+        mTestResult = Utils.ABORT_RESULT;
+        AsyncTaskArg asyncTaskArg = new AsyncTaskArg().setRequest(request).setCompleteReq(false);
+        Log.i(TAG, "in Session sending sendAbortResult for testcasetype = " + mTestType);
+        newTask().execute(asyncTaskArg);
+    }
+
+    private class AsyncTaskArg {
+        CompleteVoiceRequest mCompReq;
+        AbortVoiceRequest mAbortReq;
+        boolean isCompleteRequest = true;
+
+        AsyncTaskArg setRequest(CompleteVoiceRequest r) {
+            mCompReq = r;
+            return this;
+        }
+
+        AsyncTaskArg setRequest(AbortVoiceRequest r) {
+            mAbortReq = r;
+            return this;
+        }
+
+        AsyncTaskArg setCompleteReq(boolean flag) {
+            isCompleteRequest = flag;
+            return this;
+        }
+    }
+
+    private class MyTask extends AsyncTask<AsyncTaskArg, Void, Void> {
+        @Override
+        protected Void doInBackground(AsyncTaskArg... params) {
+            AsyncTaskArg arg = params[0];
+            Log.i(TAG, "in MyTask - doInBackground: testType = " +
+                    MainInteractionSession.this.mTestType);
+            if (arg.isCompleteRequest) {
+                arg.mCompReq.sendCompleteResult(new Bundle());
+            } else {
+                arg.mAbortReq.sendAbortResult(new Bundle());
+            }
+            return null;
+        }
+    }
+}
diff --git a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSessionService.java b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSessionService.java
new file mode 100644
index 0000000..a83c115
--- /dev/null
+++ b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSessionService.java
@@ -0,0 +1,28 @@
+/*
+ * 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.alarmclock.service;
+
+import android.os.Bundle;
+import android.service.voice.VoiceInteractionSession;
+import android.service.voice.VoiceInteractionSessionService;
+
+public class MainInteractionSessionService extends VoiceInteractionSessionService {
+    @Override
+    public VoiceInteractionSession onNewSession(Bundle args) {
+        return new MainInteractionSession(this);
+    }
+}
diff --git a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainRecognitionService.java b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainRecognitionService.java
new file mode 100644
index 0000000..15a32d4
--- /dev/null
+++ b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainRecognitionService.java
@@ -0,0 +1,55 @@
+/*
+ * 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.alarmclock.service;
+
+import android.content.Intent;
+import android.speech.RecognitionService;
+import android.util.Log;
+
+/**
+ * Stub recognition service needed to be a complete voice interactor.
+ */
+public class MainRecognitionService extends RecognitionService {
+    private static final String TAG = "MainRecognitionService";
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        Log.i(TAG, "onCreate");
+    }
+
+    @Override
+    protected void onStartListening(Intent recognizerIntent, Callback listener) {
+        Log.i(TAG, "onStartListening");
+    }
+
+    @Override
+    protected void onCancel(Callback listener) {
+        Log.i(TAG, "onCancel");
+    }
+
+    @Override
+    protected void onStopListening(Callback listener) {
+        Log.i(TAG, "onStopListening");
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        Log.i(TAG, "onDestroy");
+    }
+}
diff --git a/tests/tests/alarmclock/service/src/android/alarmclock/service/SettingsActivity.java b/tests/tests/alarmclock/service/src/android/alarmclock/service/SettingsActivity.java
new file mode 100644
index 0000000..0c4d289
--- /dev/null
+++ b/tests/tests/alarmclock/service/src/android/alarmclock/service/SettingsActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.alarmclock.service;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Stub activity to test out settings selection for voice interactor.
+ */
+public class SettingsActivity extends Activity {
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+}
diff --git a/tests/tests/alarmclock/service/src/android/alarmclock/service/VoiceInteractionMain.java b/tests/tests/alarmclock/service/src/android/alarmclock/service/VoiceInteractionMain.java
new file mode 100644
index 0000000..c1f23a0
--- /dev/null
+++ b/tests/tests/alarmclock/service/src/android/alarmclock/service/VoiceInteractionMain.java
@@ -0,0 +1,40 @@
+/*
+ * 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.alarmclock.service;
+
+import android.alarmclock.common.Utils;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.util.Log;
+
+public class VoiceInteractionMain extends Activity {
+    static final String TAG = "VoiceInteractionMain";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intent = new Intent();
+        String testCaseType = getIntent().getStringExtra(Utils.TESTCASE_TYPE);
+        Log.i(TAG, "received_testcasetype = " + testCaseType);
+        intent.putExtra(Utils.TESTCASE_TYPE, testCaseType);
+        intent.setComponent(new ComponentName(this, MainInteractionService.class));
+        ComponentName serviceName = startService(intent);
+        Log.i(TAG, "Started service: " + serviceName);
+    }
+}
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockIntentsTest.java b/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockIntentsTest.java
deleted file mode 100644
index 33ac546..0000000
--- a/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockIntentsTest.java
+++ /dev/null
@@ -1,151 +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.alarmclock.cts;
-
-import android.app.AlarmManager;
-import android.app.AlarmManager.AlarmClockInfo;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.provider.AlarmClock;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-
-import java.util.Calendar;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class AlarmClockIntentsTest extends ActivityInstrumentationTestCase2<TestActivity> {
-    static final String TAG = "AlarmClockIntentsTest";
-    static final int ALARM_TEST_WINDOW_MINS = 2;
-    private static final int TIMEOUT_MS = 20 * 1000;
-
-    private TestActivity mActivity;
-    private AlarmManager mAlarmManager;
-    private NextAlarmReceiver mReceiver = new NextAlarmReceiver();
-    private final CountDownLatch mLatch = new CountDownLatch(1);
-    private Context mContext;
-
-    public AlarmClockIntentsTest() {
-      super(TestActivity.class);
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mContext = getInstrumentation().getTargetContext();
-        mActivity = getActivity();
-        mAlarmManager = (AlarmManager) mActivity.getSystemService(Context.ALARM_SERVICE);
-        IntentFilter filter = new IntentFilter(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED);
-        filter.addAction(AlarmClock.ACTION_SET_ALARM);
-        mContext.registerReceiver(mReceiver, filter);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        mContext.unregisterReceiver(mReceiver);
-        super.tearDown();
-    }
-
-    public void testSetAlarm() throws Exception {
-        // set an alarm for ALARM_TEST_WINDOW millisec from now.
-        // Assume the next alarm is NOT within the next ALARM_TEST_WINDOW millisec
-        // TODO: fix this assumption
-        AlarmTime expected = getAlarmTime();
-
-        // set the alarm
-        assertNotNull(mActivity);
-        assertTrue(mActivity.setAlarm(expected));
-
-        // wait for the alarm to be set
-        if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
-            fail("Failed to receive broadcast in " + (TIMEOUT_MS / 1000) + "sec");
-        }
-
-        // verify the next alarm
-        assertTrue(isNextAlarmSameAs(expected));
-    }
-
-    public void testDismissAlarm() throws Exception {
-        assertTrue(mActivity.cancelAlarm(getAlarmTime()));
-    }
-
-    public void testSnoozeAlarm() throws Exception {
-        assertTrue(mActivity.snoozeAlarm());
-    }
-
-    private AlarmTime getAlarmTime() {
-        Calendar now = Calendar.getInstance();
-        now.add(Calendar.MINUTE, ALARM_TEST_WINDOW_MINS);
-        long nextAlarmTime = now.getTimeInMillis();
-        return new AlarmTime(nextAlarmTime);
-    }
-
-    private boolean isNextAlarmSameAs(AlarmTime expected) {
-        AlarmClockInfo alarmInfo = mAlarmManager.getNextAlarmClock();
-        if (alarmInfo == null) {
-            return false;
-        }
-        AlarmTime next = new AlarmTime(alarmInfo.getTriggerTime());
-        if (expected.mIsPm != next.mIsPm ||
-            expected.mHour != next.mHour ||
-            expected.mMinute != next.mMinute) {
-          Log.i(TAG, "Next Alarm time is not same expected time: " +
-              "next = " + next + ", expected = " + expected);
-          return false;
-        }
-        return true;
-    }
-
-    class NextAlarmReceiver extends BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(AlarmManager.ACTION_NEXT_ALARM_CLOCK_CHANGED)) {
-                Log.i(TAG, "received broadcast");
-                mLatch.countDown();
-            }
-        }
-    }
-
-    static class AlarmTime {
-      int mHour;
-      int mMinute;
-      boolean mIsPm;
-
-      AlarmTime(long l) {
-          Calendar cal = Calendar.getInstance();
-          cal.setTimeInMillis(l);
-          mHour = cal.get(Calendar.HOUR);
-          mMinute = cal.get(Calendar.MINUTE);
-          mIsPm = cal.get(Calendar.AM_PM) == 1;
-          Log.i(TAG, "Calendar converted is: " + cal);
-      }
-
-      AlarmTime(int hour, int minute, boolean isPM) {
-          mHour = hour;
-          mMinute = minute;
-          mIsPm = isPM;
-      }
-
-      @Override
-      public String toString() {
-          String isPmString = (mIsPm) ? "pm" : "am";
-          return  mHour + ":" + mMinute + isPmString;
-      }
-    }
-}
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockTestBase.java b/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockTestBase.java
new file mode 100644
index 0000000..0089e69
--- /dev/null
+++ b/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockTestBase.java
@@ -0,0 +1,111 @@
+/*
+ * 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.alarmclock.cts;
+
+import android.alarmclock.common.Utils;
+import android.alarmclock.common.Utils.TestcaseType;
+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 java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class AlarmClockTestBase extends ActivityInstrumentationTestCase2<TestStartActivity> {
+    static final String TAG = "AlarmClockTestBase";
+    protected static final int TIMEOUT_MS = 20 * 1000;
+
+    private Context mContext;
+    private String mTestResult;
+    private CountDownLatch mLatch;
+    private ActivityDoneReceiver mActivityDoneReceiver = null;
+    private TestStartActivity mActivity;
+    private TestcaseType mTestCaseType;
+
+    public AlarmClockTestBase() {
+        super(TestStartActivity.class);
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getTargetContext();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        mContext.unregisterReceiver(mActivityDoneReceiver);
+        super.tearDown();
+    }
+
+    private void registerBroadcastReceiver(TestcaseType testCaseType) throws Exception {
+        mTestCaseType = testCaseType;
+        mLatch = new CountDownLatch(1);
+        if (mActivityDoneReceiver != null) {
+            mContext.unregisterReceiver(mActivityDoneReceiver);
+        }
+        mActivityDoneReceiver = new ActivityDoneReceiver();
+        mContext.registerReceiver(mActivityDoneReceiver,
+                new IntentFilter(Utils.BROADCAST_INTENT + testCaseType.toString()));
+    }
+
+    protected String runTest(TestcaseType testCaseType) throws Exception {
+        Log.i(TAG, "Begin Testing: " + testCaseType);
+        if (!startTestActivity(testCaseType)) {
+            fail("test activity start failed for testcase = " + testCaseType);
+            return "";
+        }
+
+        registerBroadcastReceiver(testCaseType);
+        mActivity.startTest(testCaseType.toString());
+        if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+            fail("Failed to receive broadcast in " + TIMEOUT_MS + "msec");
+            return "";
+        }
+        return mTestResult;
+    }
+
+    private boolean startTestActivity(TestcaseType testCaseType) {
+        Log.i(TAG, "Starting test activity for test: " + testCaseType);
+        Intent intent = new Intent();
+        intent.setAction("android.intent.action.TEST_START_ACTIVITY_" + testCaseType.toString());
+        intent.setComponent(new ComponentName(getInstrumentation().getContext(),
+                TestStartActivity.class));
+        setActivityIntent(intent);
+        mActivity = getActivity();
+        return mActivity != null;
+    }
+
+    class ActivityDoneReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (intent.getAction().equals(
+                    Utils.BROADCAST_INTENT + AlarmClockTestBase.this.mTestCaseType.toString())) {
+                AlarmClockTestBase.this.mTestResult = intent.getStringExtra(Utils.TEST_RESULT);
+                Log.i(TAG, "received_broadcast for testcase_type = " +
+                        Utils.BROADCAST_INTENT + AlarmClockTestBase.this.mTestCaseType +
+                        ", test_result = " + AlarmClockTestBase.this.mTestResult);
+                mLatch.countDown();
+            }
+        }
+    }
+}
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/DismissAlarmTest.java b/tests/tests/alarmclock/src/android/alarmclock/cts/DismissAlarmTest.java
new file mode 100644
index 0000000..64ecf86
--- /dev/null
+++ b/tests/tests/alarmclock/src/android/alarmclock/cts/DismissAlarmTest.java
@@ -0,0 +1,31 @@
+/*
+ * 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.alarmclock.cts;
+
+import android.alarmclock.common.Utils;
+import android.alarmclock.common.Utils.TestcaseType;
+
+public class DismissAlarmTest extends AlarmClockTestBase {
+    public DismissAlarmTest() {
+        super();
+    }
+
+    public void testAll() throws Exception {
+        assertEquals(Utils.COMPLETION_RESULT, runTest(TestcaseType.SET_ALARM_FOR_DISMISSAL));
+        assertEquals(Utils.COMPLETION_RESULT, runTest(TestcaseType.DISMISS_ALARM));
+    }
+}
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/SetAlarmTest.java b/tests/tests/alarmclock/src/android/alarmclock/cts/SetAlarmTest.java
new file mode 100644
index 0000000..a95a1dd
--- /dev/null
+++ b/tests/tests/alarmclock/src/android/alarmclock/cts/SetAlarmTest.java
@@ -0,0 +1,30 @@
+/*
+ * 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.alarmclock.cts;
+
+import android.alarmclock.common.Utils;
+import android.alarmclock.common.Utils.TestcaseType;
+
+public class SetAlarmTest extends AlarmClockTestBase {
+    public SetAlarmTest() {
+        super();
+    }
+
+    public void testAll() throws Exception {
+        assertEquals(Utils.COMPLETION_RESULT, runTest(TestcaseType.SET_ALARM));
+    }
+}
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/SnoozeAlarmTest.java b/tests/tests/alarmclock/src/android/alarmclock/cts/SnoozeAlarmTest.java
new file mode 100644
index 0000000..b67a7cf
--- /dev/null
+++ b/tests/tests/alarmclock/src/android/alarmclock/cts/SnoozeAlarmTest.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.alarmclock.cts;
+
+import android.alarmclock.common.Utils;
+import android.alarmclock.common.Utils.TestcaseType;
+
+public class SnoozeAlarmTest extends AlarmClockTestBase {
+    public SnoozeAlarmTest() {
+        super();
+    }
+
+    public void testAll() throws Exception {
+        String result = runTest(TestcaseType.SNOOZE_ALARM);
+        // Since there is no way to figure out if there is a currently-firing alarm,
+        // we should expect either of the 2 results:
+        //      Utils.COMPLETION_RESULT
+        //      Utils.ABORT_RESULT
+        // For CTS test purposes, all we can do is to know that snooze-alarm intent
+        // was picked up and processed by the voice_interaction_service and the associated app.
+        // The above call to runTest() would have failed the test otherwise.
+        assertTrue(result.equals(Utils.ABORT_RESULT) || result.equals(Utils.COMPLETION_RESULT));
+    }
+}
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/TestActivity.java b/tests/tests/alarmclock/src/android/alarmclock/cts/TestActivity.java
deleted file mode 100644
index 9ec33ae..0000000
--- a/tests/tests/alarmclock/src/android/alarmclock/cts/TestActivity.java
+++ /dev/null
@@ -1,77 +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.alarmclock.cts;
-
-import android.app.Activity;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.os.Bundle;
-import android.provider.AlarmClock;
-import android.util.Log;
-
-import java.util.Calendar;
-
-public class TestActivity extends Activity {
-    static final String TAG = "TestActivity";
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        Log.i(TAG, " in onCreate");
-    }
-
-    private void setParams(int hour, int minute, boolean isPM, Intent intent) {
-        intent.putExtra(AlarmClock.EXTRA_ALARM_SEARCH_MODE, AlarmClock.ALARM_SEARCH_MODE_TIME);
-        intent.putExtra(AlarmClock.EXTRA_IS_PM, isPM);
-        intent.putExtra(AlarmClock.EXTRA_HOUR, hour);
-        intent.putExtra(AlarmClock.EXTRA_MINUTES, minute);
-    }
-
-    private boolean start(Intent intent) {
-        try {
-            startActivity(intent);
-        } catch (ActivityNotFoundException e) {
-            Log.wtf(TAG, e);
-            return false;
-        }
-        return true;
-    }
-
-    public boolean cancelAlarm(AlarmClockIntentsTest.AlarmTime time) {
-        Intent intent = new Intent(AlarmClock.ACTION_DISMISS_ALARM);
-        setParams(time.mHour, time.mMinute, time.mIsPm, intent);
-        Log.i(TAG, "sending DISMISS_ALARM intent for: " + time + ", Intent = " + intent);
-        return start(intent);
-    }
-
-    public boolean setAlarm(AlarmClockIntentsTest.AlarmTime time) {
-        Intent intent = new Intent(AlarmClock.ACTION_SET_ALARM);
-        int hour = time.mHour;
-        if (time.mIsPm) {
-          hour += 12;
-        }
-        setParams(hour, time.mMinute, time.mIsPm, intent);
-        Log.i(TAG, "Setting alarm: " + hour + ":" + time.mMinute + ", Intent = " + intent);
-        return start(intent);
-    }
-
-    public boolean snoozeAlarm() {
-        Intent intent = new Intent(AlarmClock.ACTION_SNOOZE_ALARM);
-        Log.i(TAG, "sending SNOOZE_ALARM intent." + intent);
-        return start(intent);
-  }
-}
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/TestStartActivity.java b/tests/tests/alarmclock/src/android/alarmclock/cts/TestStartActivity.java
new file mode 100644
index 0000000..1a3c4eb
--- /dev/null
+++ b/tests/tests/alarmclock/src/android/alarmclock/cts/TestStartActivity.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.alarmclock.cts;
+
+import android.alarmclock.common.Utils;
+import android.app.Activity;
+import android.content.Intent;
+import android.content.ComponentName;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TestStartActivity extends Activity {
+    static final String TAG = "TestStartActivity";
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Log.i(TAG, " in onCreate");
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        Log.i(TAG, " in onResume");
+    }
+
+    void startTest(String testCaseType) {
+        Intent intent = new Intent();
+        Log.i(TAG, "received_testcasetype = " + testCaseType);
+        intent.putExtra(Utils.TESTCASE_TYPE, testCaseType);
+        intent.setAction("android.intent.action.VIMAIN_" + testCaseType);
+        intent.setComponent(new ComponentName("android.alarmclock.service",
+                "android.alarmclock.service.VoiceInteractionMain"));
+        startActivity(intent);
+    }
+
+    @Override
+    protected void onPause() {
+        Log.i(TAG, " in onPause");
+        super.onPause();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        Log.i(TAG, " in onStart");
+    }
+
+    @Override
+    protected void onRestart() {
+        super.onRestart();
+        Log.i(TAG, " in onRestart");
+    }
+
+    @Override
+    protected void onStop() {
+        Log.i(TAG, " in onStop");
+        super.onStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.i(TAG, " in onDestroy");
+        super.onDestroy();
+    }
+}
diff --git a/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java b/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
index 35466be..3024896 100644
--- a/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
+++ b/tests/tests/content/src/android/content/res/cts/PrivateAttributeTest.java
@@ -26,7 +26,7 @@
  */
 public class PrivateAttributeTest extends AndroidTestCase {
 
-    private static final int sLastPublicAttr = 0x010104d5;
+    private static final int sLastPublicAttr = 0x010104f1;
 
     public void testNoAttributesAfterLastPublicAttribute() throws Exception {
         final Resources res = getContext().getResources();
diff --git a/tests/tests/display/src/android/display/cts/DisplayTest.java b/tests/tests/display/src/android/display/cts/DisplayTest.java
index 112710e..1f9f8d1 100644
--- a/tests/tests/display/src/android/display/cts/DisplayTest.java
+++ b/tests/tests/display/src/android/display/cts/DisplayTest.java
@@ -17,6 +17,7 @@
 package android.display.cts;
 
 import android.app.Presentation;
+import android.app.UiAutomation;
 import android.content.Context;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
@@ -26,17 +27,21 @@
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
-import android.test.AndroidTestCase;
+import android.os.ParcelFileDescriptor;
+import android.test.InstrumentationTestCase;
 import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.Scanner;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-public class DisplayTest extends AndroidTestCase {
+public class DisplayTest extends InstrumentationTestCase {
     // The CTS package brings up an overlay display on the target device (see AndroidTest.xml).
     // The overlay display parameters must match the ones defined there which are
     // 181x161/214 (wxh/dpi).  It only matters that these values are different from any real
@@ -54,6 +59,7 @@
 
     private DisplayManager mDisplayManager;
     private WindowManager mWindowManager;
+    private Context mContext;
 
     // To test display mode switches.
     private TestPresentation mPresentation;
@@ -61,10 +67,39 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        mContext = getInstrumentation().getContext();
         mDisplayManager = (DisplayManager)mContext.getSystemService(Context.DISPLAY_SERVICE);
         mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
     }
 
+    private void enableAppOps() {
+        StringBuilder cmd = new StringBuilder();
+        cmd.append("appops set ");
+        cmd.append(getInstrumentation().getContext().getPackageName());
+        cmd.append(" android:system_alert_window allow");
+        getInstrumentation().getUiAutomation().executeShellCommand(cmd.toString());
+
+        StringBuilder query = new StringBuilder();
+        query.append("appops get ");
+        query.append(getInstrumentation().getContext().getPackageName());
+        query.append(" android:system_alert_window");
+        String queryStr = query.toString();
+
+        String result = "No operations.";
+        while (result.contains("No operations")) {
+            ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation().executeShellCommand(
+                    queryStr);
+            InputStream inputStream = new FileInputStream(pfd.getFileDescriptor());
+            result = convertStreamToString(inputStream);
+        }
+    }
+
+    private String convertStreamToString(InputStream is) {
+        try (java.util.Scanner s = new Scanner(is).useDelimiter("\\A")) {
+            return s.hasNext() ? s.next() : "";
+        }
+    }
+
     private boolean isSecondarySize(Display display) {
         final Point p = new Point();
         display.getSize(p);
@@ -209,6 +244,7 @@
      * Tests that mode switch requests are correctly executed.
      */
     public void testModeSwitch() throws Exception {
+        enableAppOps();
         final Display display = getSecondaryDisplay(mDisplayManager.getDisplays());
         Display.Mode[] modes = display.getSupportedModes();
         assertEquals(2, modes.length);
@@ -241,7 +277,7 @@
             @Override
             public void run() {
                 mPresentation = new TestPresentation(
-                        getContext(), display, newMode.getModeId());
+                        getInstrumentation().getContext(), display, newMode.getModeId());
                 mPresentation.show();
                 presentationSignal.countDown();
             }
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
index f58b871..3f8b6ee 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
@@ -446,9 +446,22 @@
         }catch(IllegalArgumentException e){
         }
 
-        // normal case
+        // normal case 565
         mBitmap.setPixel(10, 16, 0xFF << 24);
         assertEquals(0xFF << 24, mBitmap.getPixel(10, 16));
+
+        // normal case A_8
+        mBitmap = Bitmap.createBitmap(10, 10, Config.ALPHA_8);
+        mBitmap.setPixel(5, 5, 0xFFFFFFFF);
+        assertEquals(0xFFFFFFFF, mBitmap.getPixel(5, 5));
+        mBitmap.setPixel(5, 5, 0xA8A8A8A8);
+        assertEquals(0xA8A8A8A8, mBitmap.getPixel(5, 5));
+        mBitmap.setPixel(5, 5, 0x00000000);
+        assertEquals(0x00000000, mBitmap.getPixel(5, 5));
+
+        // test reconstructing color channels
+        mBitmap.setPixel(5, 5, 0x1F000000);
+        assertEquals(0x1F1F1F1F, mBitmap.getPixel(5, 5));
     }
 
     public void testGetRowBytes(){
diff --git a/tests/tests/hardware/AndroidManifest.xml b/tests/tests/hardware/AndroidManifest.xml
index 1d1e3a8..031b19e 100644
--- a/tests/tests/hardware/AndroidManifest.xml
+++ b/tests/tests/hardware/AndroidManifest.xml
@@ -26,6 +26,7 @@
     <uses-permission android:name="android.permission.BODY_SENSORS" />
     <uses-permission android:name="android.permission.TRANSMIT_IR" />
     <uses-permission android:name="android.permission.REORDER_TASKS" />
+    <uses-permission android:name="android.permission.USE_FINGERPRINT" />
 
     <application>
         <uses-library android:name="android.test.runner" />
@@ -71,8 +72,10 @@
             android:process=":camera2ActivityProcess">
         </activity>
 
-        <activity android:name="android.hardware.input.cts.InputCtsActivity"
-            android:label="InputCtsActivity" />
+        <activity android:name="android.hardware.cts.FingerprintTestActivity"
+            android:label="FingerprintTestActivity">
+        </activity>
+
     </application>
 
     <instrumentation android:name="android.support.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/hardware/res/raw/gamepad_press_a.json b/tests/tests/hardware/res/raw/gamepad_press_a.json
deleted file mode 100644
index ff3ca4f..0000000
--- a/tests/tests/hardware/res/raw/gamepad_press_a.json
+++ /dev/null
@@ -1,39 +0,0 @@
-{
-    "id": 1,
-    "command": "register",
-    "name": "Odie (Test)",
-    "vid": 0x18d1,
-    "pid": 0x2c40,
-    "descriptor": [0x05, 0x01, 0x09, 0x05, 0xa1, 0x01, 0x85, 0x01, 0x05, 0x09, 0x0a, 0x01, 0x00,
-        0x0a, 0x02, 0x00, 0x0a, 0x04, 0x00, 0x0a, 0x05, 0x00, 0x0a, 0x07, 0x00, 0x0a, 0x08, 0x00,
-        0x0a, 0x0e, 0x00, 0x0a, 0x0f, 0x00, 0x0a, 0x0d, 0x00, 0x05, 0x0c, 0x0a, 0x24, 0x02, 0x0a,
-        0x23, 0x02, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0b, 0x81, 0x02, 0x75, 0x01, 0x95,
-        0x01, 0x81, 0x03, 0x05, 0x01, 0x75, 0x04, 0x95, 0x01, 0x25, 0x07, 0x46, 0x3b, 0x01, 0x66,
-        0x14, 0x00, 0x09, 0x39, 0x81, 0x42, 0x66, 0x00, 0x00, 0x09, 0x01, 0xa1, 0x00, 0x09, 0x30,
-        0x09, 0x31, 0x09, 0x32, 0x09, 0x35, 0x05, 0x02, 0x09, 0xc5, 0x09, 0xc4, 0x15, 0x00, 0x26,
-        0xff, 0x00, 0x35, 0x00, 0x46, 0xff, 0x00, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0x85,
-        0x02, 0x05, 0x08, 0x0a, 0x01, 0x00, 0x0a, 0x02, 0x00, 0x0a, 0x03, 0x00, 0x0a, 0x04, 0x00,
-        0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x04, 0x91, 0x02, 0x75, 0x04, 0x95, 0x01, 0x91,
-        0x03, 0xc0, 0x05, 0x0c, 0x09, 0x01, 0xa1, 0x01, 0x85, 0x03, 0x05, 0x01, 0x09, 0x06, 0xa1,
-        0x02, 0x05, 0x06, 0x09, 0x20, 0x15, 0x00, 0x26, 0xff, 0x00, 0x75, 0x08, 0x95, 0x01, 0x81,
-        0x02, 0x06, 0xbc, 0xff, 0x0a, 0xad, 0xbd, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0xc0, 0xc0],
-    "report": [0x01, 0x00, 0x80, 0x90, 0x80, 0x7f, 0x73, 0x00, 0x00]
-}
-
-{
-    "id": 1,
-    "command": "report",
-    "report": [0x01, 0x01, 0x80, 0x90, 0x80, 0x7f, 0x73, 0x00, 0x00]
-}
-
-{
-    "id": 1,
-    "command": "delay",
-    "duration": 10
-}
-
-{
-    "id": 1,
-    "command": "report",
-    "report": [0x01, 0x00, 0x80, 0x90, 0x80, 0x7f, 0x73, 0x00, 0x00]
-}
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
index 74db8cf..a823512 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CameraTestUtils.java
@@ -50,7 +50,9 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.Size;
+import android.view.Display;
 import android.view.Surface;
+import android.view.WindowManager;
 
 import com.android.ex.camera2.blocking.BlockingCameraManager;
 import com.android.ex.camera2.blocking.BlockingCameraManager.BlockingOpenException;
@@ -2058,4 +2060,22 @@
             thumbnailQuality = thumbQuality;
         }
     }
+
+    public static Size getPreviewSizeBound(WindowManager windowManager, Size bound) {
+        Display display = windowManager.getDefaultDisplay();
+
+        int width = display.getWidth();
+        int height = display.getHeight();
+
+        if (height > width) {
+            height = width;
+            width = display.getHeight();
+        }
+
+        if (bound.getWidth() <= width &&
+            bound.getHeight() <= height)
+            return bound;
+        else
+            return new Size(width, height);
+    }
 }
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 3aa1c1f..4b966367 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -147,7 +147,8 @@
                 changeExposure(requestBuilder, DEFAULT_EXP_TIME_NS, DEFAULT_SENSITIVITY);
 
                 Size previewSz =
-                        getMaxPreviewSize(mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND);
+                        getMaxPreviewSize(mCamera.getId(), mCameraManager,
+                        getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
 
                 startPreview(requestBuilder, previewSz, listener);
                 waitForSettingsApplied(listener, NUM_FRAMES_WAITED_FOR_UNKNOWN_LATENCY);
@@ -207,7 +208,8 @@
                         STATISTICS_LENS_SHADING_MAP_MODE_ON);
 
                 Size previewSz =
-                        getMaxPreviewSize(mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND);
+                        getMaxPreviewSize(mCamera.getId(), mCameraManager,
+                        getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
                 List<Integer> lensShadingModes = Arrays.asList(CameraTestUtils.toObject(
                         mStaticInfo.getAvailableLensShadingModesChecked()));
 
@@ -272,7 +274,8 @@
                 int[] modes = mStaticInfo.getAeAvailableAntiBandingModesChecked();
 
                 Size previewSz =
-                        getMaxPreviewSize(mCamera.getId(), mCameraManager, PREVIEW_SIZE_BOUND);
+                        getMaxPreviewSize(mCamera.getId(), mCameraManager,
+                        getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
 
                 for (int mode : modes) {
                     antiBandingTestByMode(previewSz, mode);
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
index da083a6..f8ee615 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/ExtendedCameraCharacteristicsTest.java
@@ -64,7 +64,11 @@
     private String[] mIds;
     private CameraErrorCollector mCollector;
 
+    private static final Size FULLHD = new Size(1920, 1080);
+    private static final Size FULLHD_ALT = new Size(1920, 1088);
+    private static final Size HD = new Size(1280, 720);
     private static final Size VGA = new Size(640, 480);
+    private static final Size QVGA = new Size(320, 240);
 
     /*
      * HW Levels short hand
@@ -149,14 +153,102 @@
             assertArrayContains(String.format("No JPEG image format for: ID %s",
                     mIds[counter]), outputFormats, ImageFormat.JPEG);
 
-            Size[] sizes = config.getOutputSizes(ImageFormat.YUV_420_888);
-            CameraTestUtils.assertArrayNotEmpty(sizes,
+            Size[] yuvSizes = config.getOutputSizes(ImageFormat.YUV_420_888);
+            Size[] jpegSizes = config.getOutputSizes(ImageFormat.JPEG);
+            Size[] privateSizes = config.getOutputSizes(ImageFormat.PRIVATE);
+
+            CameraTestUtils.assertArrayNotEmpty(yuvSizes,
                     String.format("No sizes for preview format %x for: ID %s",
                             ImageFormat.YUV_420_888, mIds[counter]));
 
-            assertArrayContains(String.format(
-                            "Required VGA size not found for format %x for: ID %s",
-                            ImageFormat.YUV_420_888, mIds[counter]), sizes, VGA);
+            Rect activeRect = CameraTestUtils.getValueNotNull(
+                    c, CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
+            Size activeArraySize = new Size(activeRect.width(), activeRect.height());
+            Integer hwLevel = c.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL);
+
+            if (activeArraySize.getWidth() >= FULLHD.getWidth() &&
+                    activeArraySize.getHeight() >= FULLHD.getHeight()) {
+                assertArrayContains(String.format(
+                        "Required FULLHD size not found for format %x for: ID %s",
+                        ImageFormat.JPEG, mIds[counter]), jpegSizes, FULLHD);
+            }
+
+            if (activeArraySize.getWidth() >= HD.getWidth() &&
+                    activeArraySize.getHeight() >= HD.getHeight()) {
+                assertArrayContains(String.format(
+                        "Required HD size not found for format %x for: ID %s",
+                        ImageFormat.JPEG, mIds[counter]), jpegSizes, HD);
+            }
+
+            if (activeArraySize.getWidth() >= VGA.getWidth() &&
+                    activeArraySize.getHeight() >= VGA.getHeight()) {
+                assertArrayContains(String.format(
+                        "Required VGA size not found for format %x for: ID %s",
+                        ImageFormat.JPEG, mIds[counter]), jpegSizes, VGA);
+            }
+
+            if (activeArraySize.getWidth() >= QVGA.getWidth() &&
+                    activeArraySize.getHeight() >= QVGA.getHeight()) {
+                assertArrayContains(String.format(
+                        "Required QVGA size not found for format %x for: ID %s",
+                        ImageFormat.JPEG, mIds[counter]), jpegSizes, QVGA);
+            }
+
+            ArrayList<Size> jpegSizesList = new ArrayList<>(Arrays.asList(jpegSizes));
+            ArrayList<Size> yuvSizesList = new ArrayList<>(Arrays.asList(yuvSizes));
+            ArrayList<Size> privateSizesList = new ArrayList<>(Arrays.asList(privateSizes));
+
+            CamcorderProfile maxVideoProfile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
+            Size maxVideoSize = new Size(
+                    maxVideoProfile.videoFrameWidth, maxVideoProfile.videoFrameHeight);
+
+            // Handle FullHD special case first
+            if (jpegSizesList.contains(FULLHD)) {
+                if (hwLevel == FULL || (hwLevel == LIMITED &&
+                        maxVideoSize.getWidth() >= FULLHD.getWidth() &&
+                        maxVideoSize.getHeight() >= FULLHD.getHeight())) {
+                    boolean yuvSupportFullHD = yuvSizesList.contains(FULLHD) ||
+                            yuvSizesList.contains(FULLHD_ALT);
+                    boolean privateSupportFullHD = privateSizesList.contains(FULLHD) ||
+                            privateSizesList.contains(FULLHD_ALT);
+                    assertTrue("Full device FullHD YUV size not found", yuvSupportFullHD);
+                    assertTrue("Full device FullHD PRIVATE size not found", privateSupportFullHD);
+                }
+                // remove all FullHD or FullHD_Alt sizes for the remaining of the test
+                jpegSizesList.remove(FULLHD);
+                jpegSizesList.remove(FULLHD_ALT);
+            }
+
+            // Check all sizes other than FullHD
+            if (hwLevel == LIMITED) {
+                // Remove all jpeg sizes larger than max video size
+                ArrayList<Size> toBeRemoved = new ArrayList<>();
+                for (Size size : jpegSizesList) {
+                    if (size.getWidth() >= maxVideoSize.getWidth() &&
+                            size.getHeight() >= maxVideoSize.getHeight()) {
+                        toBeRemoved.add(size);
+                    }
+                }
+                jpegSizesList.removeAll(toBeRemoved);
+            }
+
+            if (hwLevel == FULL || hwLevel == LIMITED) {
+                if (!yuvSizesList.containsAll(jpegSizesList)) {
+                    for (Size s : jpegSizesList) {
+                        if (!yuvSizesList.contains(s)) {
+                            fail("Size " + s + " not found in YUV format");
+                        }
+                    }
+                }
+            }
+
+            if (!privateSizesList.containsAll(yuvSizesList)) {
+                for (Size s : yuvSizesList) {
+                    if (!privateSizesList.contains(s)) {
+                        fail("Size " + s + " not found in PRIVATE format");
+                    }
+                }
+            }
 
             counter++;
         }
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/PerformanceTest.java b/tests/tests/hardware/src/android/hardware/camera2/cts/PerformanceTest.java
index 8dbceae..908e6a5 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/PerformanceTest.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/PerformanceTest.java
@@ -684,7 +684,9 @@
      */
     private void initializeImageReader(String cameraId, int format) throws Exception {
         mOrderedPreviewSizes = CameraTestUtils.getSupportedPreviewSizes(
-                cameraId, mCameraManager, CameraTestUtils.PREVIEW_SIZE_BOUND);
+                cameraId, mCameraManager,
+                CameraTestUtils.getPreviewSizeBound(mWindowManager,
+                    CameraTestUtils.PREVIEW_SIZE_BOUND));
         Size maxPreviewSize = mOrderedPreviewSizes.get(0);
         createImageReader(maxPreviewSize, format, NUM_MAX_IMAGES, /*listener*/null);
         updatePreviewSurface(maxPreviewSize);
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
index 78370b3..86dbb5b 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
@@ -41,6 +41,7 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 import android.view.Surface;
+import android.view.WindowManager;
 
 import com.android.ex.camera2.blocking.BlockingSessionCallback;
 import com.android.ex.camera2.blocking.BlockingStateCallback;
@@ -75,11 +76,14 @@
     protected List<Size> mOrderedVideoSizes; // In descending order.
     protected List<Size> mOrderedStillSizes; // In descending order.
 
+    protected WindowManager mWindowManager;
+
     @Override
     public void setContext(Context context) {
         super.setContext(context);
         mCameraManager = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
         assertNotNull("Can't connect to camera manager!", mCameraManager);
+        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
     }
 
     /**
@@ -189,7 +193,8 @@
         mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId),
                 CheckLevel.ASSERT, /*collector*/null);
         mOrderedPreviewSizes = getSupportedPreviewSizes(
-                cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+                cameraId, mCameraManager,
+                getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
         mOrderedVideoSizes = getSupportedVideoSizes(cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
         mOrderedStillSizes = getSupportedStillSizes(cameraId, mCameraManager, null);
 
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
index 03e9647..5d832d6 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2MultiViewTestCase.java
@@ -43,6 +43,7 @@
 import android.util.Size;
 import android.view.Surface;
 import android.view.TextureView;
+import android.view.WindowManager;
 
 import com.android.ex.camera2.blocking.BlockingCameraManager;
 import com.android.ex.camera2.blocking.BlockingSessionCallback;
@@ -76,6 +77,8 @@
     private CameraHolder[] mCameraHolders;
     private HashMap<String, Integer> mCameraIdMap;
 
+    protected WindowManager mWindowManager;
+
     public Camera2MultiViewTestCase() {
         super(Camera2MultiViewCtsActivity.class);
     }
@@ -104,6 +107,7 @@
             mCameraHolders[i] = new CameraHolder(mCameraIds[i]);
             mCameraIdMap.put(mCameraIds[i], i);
         }
+        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
     }
 
     @Override
@@ -359,7 +363,8 @@
             mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(mCameraId),
                     CheckLevel.ASSERT, /*collector*/null);
             mOrderedPreviewSizes = getSupportedPreviewSizes(
-                    mCameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+                    mCameraId, mCameraManager,
+                    getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
             assertNotNull(String.format("Failed to open camera device ID: %s", mCameraId), mCamera);
         }
 
diff --git a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
index 3ca696b..d25f1f5 100644
--- a/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/camera2/cts/testcases/Camera2SurfaceViewTestCase.java
@@ -29,6 +29,7 @@
 import android.util.Log;
 import android.view.Surface;
 import android.view.SurfaceHolder;
+import android.view.WindowManager;
 import android.content.Context;
 import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraAccessException;
@@ -102,6 +103,7 @@
     protected List<Size> mOrderedStillSizes; // In descending order.
     protected HashMap<Size, Long> mMinPreviewFrameDurationMap;
 
+    protected WindowManager mWindowManager;
 
     public Camera2SurfaceViewTestCase() {
         super(Camera2SurfaceViewCtsActivity.class);
@@ -131,6 +133,8 @@
         mHandler = new Handler(mHandlerThread.getLooper());
         mCameraListener = new BlockingStateCallback();
         mCollector = new CameraErrorCollector();
+
+        mWindowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
     }
 
     @Override
@@ -559,7 +563,8 @@
         mCollector.setCameraId(cameraId);
         mStaticInfo = new StaticMetadata(mCameraManager.getCameraCharacteristics(cameraId),
                 CheckLevel.ASSERT, /*collector*/null);
-        mOrderedPreviewSizes = getSupportedPreviewSizes(cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
+        mOrderedPreviewSizes = getSupportedPreviewSizes(cameraId, mCameraManager,
+                getPreviewSizeBound(mWindowManager, PREVIEW_SIZE_BOUND));
         mOrderedVideoSizes = getSupportedVideoSizes(cameraId, mCameraManager, PREVIEW_SIZE_BOUND);
         mOrderedStillSizes = getSupportedStillSizes(cameraId, mCameraManager, null);
         // Use ImageFormat.YUV_420_888 for now. TODO: need figure out what's format for preview
diff --git a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java
index b9848fa..f1dc229c8 100644
--- a/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java
+++ b/tests/tests/hardware/src/android/hardware/cts/helpers/sensorverification/EventOrderingVerificationTest.java
@@ -41,16 +41,6 @@
     }
 
     /**
-     * Test that the verification passes when the timestamps are the same.
-     */
-    public void testSameTimestamp() {
-        SensorStats stats = new SensorStats();
-        EventOrderingVerification verification = getVerification(0, 0, 0, 0, 0);
-        verification.verify(stats);
-        verifyStats(stats, true, 0);
-    }
-
-    /**
      * Test that the verification passes when the timestamps are increasing.
      */
     public void testSequentialTimestamp() {
diff --git a/tests/tests/hardware/src/android/hardware/fingerprint/cts/FingerprintManagerTest.java b/tests/tests/hardware/src/android/hardware/fingerprint/cts/FingerprintManagerTest.java
new file mode 100644
index 0000000..95704b9
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/fingerprint/cts/FingerprintManagerTest.java
@@ -0,0 +1,117 @@
+/*
+ * 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.hardware.fingerprint.cts;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintManager.AuthenticationCallback;
+import android.os.CancellationSignal;
+import android.test.AndroidTestCase;
+
+/**
+ * Basic test cases for FingerprintManager
+ */
+public class FingerprintManagerTest extends AndroidTestCase {
+    private enum AuthState {
+        AUTH_UNKNOWN, AUTH_ERROR, AUTH_FAILED, AUTH_SUCCEEDED
+    }
+    private AuthState mAuthState = AuthState.AUTH_UNKNOWN;
+    private FingerprintManager mFingerprintManager;
+
+    boolean mHasFingerprintManager;
+    private AuthenticationCallback mAuthCallback = new AuthenticationCallback() {
+
+        @Override
+        public void onAuthenticationError(int errorCode, CharSequence errString) {
+        }
+
+        @Override
+        public void onAuthenticationFailed() {
+            mAuthState = AuthState.AUTH_SUCCEEDED;
+        }
+
+        @Override
+        public void onAuthenticationSucceeded(
+                android.hardware.fingerprint.FingerprintManager.AuthenticationResult result) {
+            mAuthState = AuthState.AUTH_SUCCEEDED;
+        }
+    };
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mAuthState = AuthState.AUTH_UNKNOWN;
+
+        PackageManager pm = getContext().getPackageManager();
+        if (pm.hasSystemFeature(PackageManager.FEATURE_FINGERPRINT)) {
+            mHasFingerprintManager = true;
+            mFingerprintManager = (FingerprintManager)
+                    getContext().getSystemService(Context.FINGERPRINT_SERVICE);
+        }
+    }
+
+    public void test_hasFingerprintHardware() {
+        if (!mHasFingerprintManager) {
+            return; // skip test if no fingerprint feature
+        }
+        assertTrue(mFingerprintManager.isHardwareDetected());
+    }
+
+    public void test_hasEnrolledFingerprints() {
+        if (!mHasFingerprintManager) {
+            return; // skip test if no fingerprint feature
+        }
+        boolean hasEnrolledFingerprints = mFingerprintManager.hasEnrolledFingerprints();
+        assertTrue(!hasEnrolledFingerprints);
+    }
+
+    public void test_authenticateNullCallback() {
+        if (!mHasFingerprintManager) {
+            return; // skip test if no fingerprint feature
+        }
+        boolean exceptionTaken = false;
+        CancellationSignal cancelAuth = new CancellationSignal();
+        try {
+            mFingerprintManager.authenticate(null, cancelAuth, 0, null, null);
+        } catch (IllegalArgumentException e) {
+            exceptionTaken = true;
+        } finally {
+            assertTrue(mAuthState == AuthState.AUTH_UNKNOWN);
+            assertTrue(exceptionTaken);
+            cancelAuth.cancel();
+        }
+    }
+
+    public void test_authenticate() {
+        if (!mHasFingerprintManager) {
+            return; // skip test if no fingerprint feature
+        }
+        boolean exceptionTaken = false;
+        CancellationSignal cancelAuth = new CancellationSignal();
+        try {
+            mFingerprintManager.authenticate(null, cancelAuth, 0, mAuthCallback, null);
+        } catch (IllegalArgumentException e) {
+            exceptionTaken = true;
+        } finally {
+            assertFalse(exceptionTaken);
+            // We should never get out of this state without user interaction
+            assertTrue(mAuthState == AuthState.AUTH_UNKNOWN);
+            cancelAuth.cancel();
+        }
+    }
+}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java b/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
deleted file mode 100644
index b16cadb..0000000
--- a/tests/tests/hardware/src/android/hardware/input/cts/InputCtsActivity.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.input.cts;
-
-import android.app.Activity;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class InputCtsActivity extends Activity {
-    private InputCallback mInputCallback;
-
-    @Override
-    public boolean dispatchGenericMotionEvent(MotionEvent ev) {
-        if (mInputCallback != null) {
-            mInputCallback.onMotionEvent(ev);
-        }
-        return true;
-    }
-
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent ev) {
-        if (mInputCallback != null) {
-            mInputCallback.onMotionEvent(ev);
-        }
-        return true;
-    }
-
-    @Override
-    public boolean dispatchTrackballEvent(MotionEvent ev) {
-        if (mInputCallback != null) {
-            mInputCallback.onMotionEvent(ev);
-        }
-        return true;
-    }
-
-    @Override
-    public boolean dispatchKeyEvent(KeyEvent ev) {
-        if (mInputCallback != null) {
-            mInputCallback.onKeyEvent(ev);
-        }
-        return true;
-    }
-
-    public void setInputCallback(InputCallback callback) {
-        mInputCallback = callback;
-    }
-}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/GamepadTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/GamepadTestCase.java
deleted file mode 100644
index 92fba12..0000000
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/GamepadTestCase.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.input.cts.tests;
-
-import android.util.Log;
-import android.view.KeyEvent;
-
-import java.io.Writer;
-import java.util.List;
-
-import com.android.cts.hardware.R;
-
-public class GamepadTestCase extends InputTestCase {
-    private static final String TAG = "GamepadTests";
-
-    public void testButtonA() throws Exception {
-        sendHidCommands(R.raw.gamepad_press_a);
-        assertReceivedKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BUTTON_A);
-        assertReceivedKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BUTTON_A);
-        assertNoMoreEvents();
-    }
-}
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
deleted file mode 100644
index fba5f51..0000000
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Copyright 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.input.cts.tests;
-
-import android.app.UiAutomation;
-import android.hardware.input.cts.InputCtsActivity;
-import android.hardware.input.cts.InputCallback;
-import android.system.ErrnoException;
-import android.system.Os;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.MotionEvent;
-
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileWriter;
-import java.io.InputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.Writer;
-import java.util.ArrayList;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.List;
-import java.util.UUID;
-
-public class InputTestCase extends ActivityInstrumentationTestCase2<InputCtsActivity> {
-    private static final String TAG = "InputTestCase";
-    private static final String HID_EXECUTABLE = "hid";
-    private static final int SHELL_UID = 2000;
-    private static final String[] KEY_ACTIONS = {"DOWN", "UP", "MULTIPLE"};
-
-    private File mFifo;
-    private Writer mWriter;
-
-    private BlockingQueue<KeyEvent> mKeys;
-    private BlockingQueue<MotionEvent> mMotions;
-    private InputListener mInputListener;
-
-    public InputTestCase() {
-        super(InputCtsActivity.class);
-        mKeys = new LinkedBlockingQueue<KeyEvent>();
-        mMotions = new LinkedBlockingQueue<MotionEvent>();
-        mInputListener = new InputListener();
-    }
-
-    @Override
-    protected void setUp() throws Exception {
-        super.setUp();
-        mFifo = setupFifo();
-        clearKeys();
-        clearMotions();
-        getActivity().setInputCallback(mInputListener);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        if (mFifo != null) {
-            mFifo.delete();
-            mFifo = null;
-        }
-        closeQuietly(mWriter);
-        mWriter = null;
-        super.tearDown();
-    }
-
-    /**
-     * Sends the HID commands designated by the given resource id.
-     * The commands must be in the format expected by the `hid` shell command.
-     *
-     * @param id The resource id from which to load the HID commands. This must be a "raw"
-     * resource.
-     */
-    public void sendHidCommands(int id) {
-        try {
-            Writer w = getWriter();
-            w.write(getEvents(id));
-            w.flush();
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        }
-    }
-
-    /**
-     * Asserts that the application received a {@link android.view.KeyEvent} with the given action
-     * and keycode.
-     *
-     * If other KeyEvents are received by the application prior to the expected KeyEvent, or no
-     * KeyEvents are received within a reasonable amount of time, then this will throw an
-     * AssertionFailedError.
-     *
-     * @param action The action to expect on the next KeyEvent
-     * (e.g. {@link android.view.KeyEvent#ACTION_DOWN}).
-     * @param keyCode The expected key code of the next KeyEvent.
-     */
-    public void assertReceivedKeyEvent(int action, int keyCode) {
-        KeyEvent k = waitForKey();
-        if (k == null) {
-            fail("Timed out waiting for " + KeyEvent.keyCodeToString(keyCode)
-                    + " with action " + KEY_ACTIONS[action]);
-            return;
-        }
-        assertEquals(action, k.getAction());
-        assertEquals(keyCode, k.getKeyCode());
-    }
-
-    /**
-     * Asserts that no more events have been received by the application.
-     *
-     * If any more events have been received by the application, this throws an
-     * AssertionFailedError.
-     */
-    public void assertNoMoreEvents() {
-        KeyEvent key;
-        MotionEvent motion;
-        if ((key = mKeys.poll()) != null) {
-            fail("Extraneous key events generated: " + key);
-        }
-        if ((motion = mMotions.poll()) != null) {
-            fail("Extraneous motion events generated: " + motion);
-        }
-    }
-
-    private KeyEvent waitForKey() {
-        try {
-            return mKeys.poll(1, TimeUnit.SECONDS);
-        } catch (InterruptedException e) {
-            return null;
-        }
-    }
-
-    private void clearKeys() {
-        mKeys.clear();
-    }
-
-    private void clearMotions() {
-        mMotions.clear();
-    }
-
-    private File setupFifo() throws ErrnoException {
-        File dir = getActivity().getCacheDir();
-        String filename = dir.getAbsolutePath() + File.separator +  UUID.randomUUID().toString();
-        Os.mkfifo(filename, 0666);
-        File f = new File(filename);
-        return f;
-    }
-
-    private Writer getWriter() throws IOException {
-        if (mWriter == null) {
-            UiAutomation ui = getInstrumentation().getUiAutomation();
-            ui.executeShellCommand("hid " + mFifo.getAbsolutePath());
-            mWriter = new FileWriter(mFifo);
-        }
-        return mWriter;
-    }
-
-    private String getEvents(int id) throws IOException {
-        InputStream is =
-            getInstrumentation().getTargetContext().getResources().openRawResource(id);
-        return readFully(is);
-    }
-
-
-    private static void closeQuietly(AutoCloseable closeable) {
-        if (closeable != null) {
-            try {
-                closeable.close();
-            } catch (RuntimeException rethrown) {
-                throw rethrown;
-            } catch (Exception ignored) { }
-        }
-    }
-
-    private static String readFully(InputStream is) throws IOException {
-        ByteArrayOutputStream baos = new ByteArrayOutputStream();
-        int read = 0;
-        byte[] buffer = new byte[1024];
-        while ((read = is.read(buffer)) >= 0) {
-            baos.write(buffer, 0, read);
-        }
-        return baos.toString();
-    }
-
-    private class InputListener implements InputCallback {
-        public void onKeyEvent(KeyEvent ev) {
-            boolean done = false;
-            do {
-                try {
-                    mKeys.put(new KeyEvent(ev));
-                    done = true;
-                } catch (InterruptedException ignore) { }
-            } while (!done);
-        }
-
-        public void onMotionEvent(MotionEvent ev) {
-            boolean done = false;
-            do {
-                try {
-                    mMotions.put(MotionEvent.obtain(ev));
-                    done = true;
-                } catch (InterruptedException ignore) { }
-            } while (!done);
-        }
-    }
-}
diff --git a/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java b/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java
index 6cd64b8..5fe3505 100644
--- a/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/AndroidKeyStoreTest.java
@@ -62,7 +62,6 @@
 import javax.crypto.Cipher;
 import javax.crypto.Mac;
 import javax.crypto.SecretKey;
-import javax.crypto.spec.SecretKeySpec;
 import javax.security.auth.x500.X500Principal;
 
 public class AndroidKeyStoreTest extends AndroidTestCase {
@@ -1888,7 +1887,7 @@
                 0x00, 0x05, (byte) 0xAA, (byte) 0x0A5, (byte) 0xFF, 0x55, 0x0A
         };
 
-        SecretKey expectedSecret = new SecretKeySpec(expectedKey, "AES");
+        SecretKey expectedSecret = new TransparentSecretKey(expectedKey, "AES");
 
         byte[] wrappedExpected = c.wrap(expectedSecret);
 
@@ -2231,15 +2230,15 @@
 
         long testStartTimeMillis = System.currentTimeMillis();
 
-        SecretKey key1 =
-                new SecretKeySpec(HexEncoding.decode("010203040506070809fafbfcfdfeffcc"), "AES");
+        SecretKey key1 = new TransparentSecretKey(
+                HexEncoding.decode("010203040506070809fafbfcfdfeffcc"), "AES");
         String entryName1 = "test0";
 
-        SecretKey key2 =
-                new SecretKeySpec(HexEncoding.decode("808182838485868788897a7b7c7d7e7f"), "AES");
+        SecretKey key2 = new TransparentSecretKey(
+                HexEncoding.decode("808182838485868788897a7b7c7d7e7f"), "AES");
 
-        SecretKey key3 =
-                new SecretKeySpec(HexEncoding.decode("33333333333333333333777777777777"), "AES");
+        SecretKey key3 = new TransparentSecretKey(
+                HexEncoding.decode("33333333333333333333777777777777"), "AES");
 
         mKeyStore.load(null);
         int latestImportedEntryNumber = 0;
@@ -2322,14 +2321,14 @@
 
         long testStartTimeMillis = System.currentTimeMillis();
 
-        SecretKey key1 = new SecretKeySpec(
+        SecretKey key1 = new TransparentSecretKey(
                 HexEncoding.decode("010203040506070809fafbfcfdfeffcc"), "HmacSHA256");
         String entryName1 = "test0";
 
-        SecretKey key2 = new SecretKeySpec(
+        SecretKey key2 = new TransparentSecretKey(
                 HexEncoding.decode("808182838485868788897a7b7c7d7e7f"), "HmacSHA256");
 
-        SecretKey key3 = new SecretKeySpec(
+        SecretKey key3 = new TransparentSecretKey(
                 HexEncoding.decode("33333333333333333333777777777777"), "HmacSHA256");
 
         mKeyStore.load(null);
@@ -2406,7 +2405,7 @@
             try {
                 String digest = TestUtils.getHmacAlgorithmDigest(algorithm);
                 assertNotNull(digest);
-                SecretKeySpec keyBeingImported = new SecretKeySpec(new byte[16], algorithm);
+                SecretKey keyBeingImported = new TransparentSecretKey(new byte[16], algorithm);
 
                 KeyProtection.Builder goodSpec =
                         new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN);
@@ -2457,4 +2456,121 @@
             }
         }
     }
+
+    public void testKeyStore_ImportSupportedSizes_AES() throws Exception {
+        mKeyStore.load(null);
+
+        KeyProtection params = new KeyProtection.Builder(
+                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
+                .build();
+        String alias = "test1";
+        mKeyStore.deleteEntry(alias);
+        assertFalse(mKeyStore.containsAlias(alias));
+        for (int keySizeBytes = 0; keySizeBytes <= 512 / 8; keySizeBytes++) {
+            int keySizeBits = keySizeBytes * 8;
+            try {
+                KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(
+                        new TransparentSecretKey(new byte[keySizeBytes], "AES"));
+                if (TestUtils.contains(KeyGeneratorTest.AES_SUPPORTED_KEY_SIZES, keySizeBits)) {
+                    mKeyStore.setEntry(alias, entry, params);
+                    SecretKey key = (SecretKey) mKeyStore.getKey(alias, null);
+                    assertEquals("AES", key.getAlgorithm());
+                    assertEquals(keySizeBits, TestUtils.getKeyInfo(key).getKeySize());
+                } else {
+                    mKeyStore.deleteEntry(alias);
+                    assertFalse(mKeyStore.containsAlias(alias));
+                    try {
+                        mKeyStore.setEntry(alias, entry, params);
+                        fail();
+                    } catch (KeyStoreException expected) {}
+                    assertFalse(mKeyStore.containsAlias(alias));
+                }
+            } catch (Throwable e) {
+                throw new RuntimeException("Failed for key size " + keySizeBits, e);
+            }
+        }
+    }
+
+    public void testKeyStore_ImportSupportedSizes_HMAC() throws Exception {
+        mKeyStore.load(null);
+
+        KeyProtection params = new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN).build();
+        String alias = "test1";
+        mKeyStore.deleteEntry(alias);
+        assertFalse(mKeyStore.containsAlias(alias));
+        for (String algorithm : KeyGeneratorTest.EXPECTED_ALGORITHMS) {
+            if (!TestUtils.isHmacAlgorithm(algorithm)) {
+                continue;
+            }
+            for (int keySizeBytes = 0; keySizeBytes <= 1024 / 8; keySizeBytes++) {
+                try {
+                    KeyStore.SecretKeyEntry entry = new KeyStore.SecretKeyEntry(
+                            new TransparentSecretKey(new byte[keySizeBytes], algorithm));
+                    if (keySizeBytes > 0) {
+                        mKeyStore.setEntry(alias, entry, params);
+                        SecretKey key = (SecretKey) mKeyStore.getKey(alias, null);
+                        assertEquals(algorithm, key.getAlgorithm());
+                        assertEquals(keySizeBytes * 8, TestUtils.getKeyInfo(key).getKeySize());
+                    } else {
+                        mKeyStore.deleteEntry(alias);
+                        assertFalse(mKeyStore.containsAlias(alias));
+                        try {
+                            mKeyStore.setEntry(alias, entry, params);
+                            fail();
+                        } catch (KeyStoreException expected) {}
+                    }
+                } catch (Throwable e) {
+                    throw new RuntimeException(
+                            "Failed for " + algorithm + " with key size " + (keySizeBytes * 8), e);
+                }
+            }
+        }
+    }
+
+    public void testKeyStore_ImportSupportedSizes_EC() throws Exception {
+        mKeyStore.load(null);
+        KeyProtection params =
+                TestUtils.getMinimalWorkingImportParametersForSigningingWith("SHA256withECDSA");
+        checkKeyPairImportSucceeds(
+                "secp224r1", R.raw.ec_key3_secp224r1_pkcs8, R.raw.ec_key3_secp224r1_cert, params);
+        checkKeyPairImportSucceeds(
+                "secp256r1", R.raw.ec_key4_secp256r1_pkcs8, R.raw.ec_key4_secp256r1_cert, params);
+        checkKeyPairImportSucceeds(
+                "secp384r1", R.raw.ec_key5_secp384r1_pkcs8, R.raw.ec_key5_secp384r1_cert, params);
+        checkKeyPairImportSucceeds(
+                "secp512r1", R.raw.ec_key6_secp521r1_pkcs8, R.raw.ec_key6_secp521r1_cert, params);
+    }
+
+    public void testKeyStore_ImportSupportedSizes_RSA() throws Exception {
+        mKeyStore.load(null);
+        KeyProtection params =
+                TestUtils.getMinimalWorkingImportParametersForSigningingWith("SHA256withRSA");
+        checkKeyPairImportSucceeds(
+                "512", R.raw.rsa_key5_512_pkcs8, R.raw.rsa_key5_512_cert, params);
+        checkKeyPairImportSucceeds(
+                "768", R.raw.rsa_key6_768_pkcs8, R.raw.rsa_key6_768_cert, params);
+        checkKeyPairImportSucceeds(
+                "1024", R.raw.rsa_key3_1024_pkcs8, R.raw.rsa_key3_1024_cert, params);
+        checkKeyPairImportSucceeds(
+                "2048", R.raw.rsa_key8_2048_pkcs8, R.raw.rsa_key8_2048_cert, params);
+        checkKeyPairImportSucceeds(
+                "3072", R.raw.rsa_key7_3072_pksc8, R.raw.rsa_key7_3072_cert, params);
+        checkKeyPairImportSucceeds(
+                "4096", R.raw.rsa_key4_4096_pkcs8, R.raw.rsa_key4_4096_cert, params);
+    }
+
+    private void checkKeyPairImportSucceeds(
+            String alias, int privateResId, int certResId, KeyProtection params) throws Exception {
+        try {
+            mKeyStore.deleteEntry(alias);
+            TestUtils.importIntoAndroidKeyStore(
+                    alias, getContext(), privateResId, certResId, params);
+        } catch (Throwable e) {
+            throw new RuntimeException("Failed for " + alias, e);
+        } finally {
+            try {
+                mKeyStore.deleteEntry(alias);
+            } catch (Exception ignored) {}
+        }
+    }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
index bce2539..d9d4f43 100644
--- a/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/CipherTest.java
@@ -26,16 +26,15 @@
 import java.security.AlgorithmParameters;
 import java.security.InvalidKeyException;
 import java.security.Key;
-import java.security.KeyPair;
 import java.security.Provider;
 import java.security.Security;
-import java.security.interfaces.RSAKey;
+import java.security.Signature;
+import java.security.SignatureException;
 import java.security.spec.AlgorithmParameterSpec;
 import java.security.spec.MGF1ParameterSpec;
 import java.security.Provider.Service;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.Locale;
@@ -43,8 +42,9 @@
 import java.util.Set;
 import java.util.TreeMap;
 
+import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
+import javax.crypto.IllegalBlockSizeException;
 import javax.crypto.spec.GCMParameterSpec;
 import javax.crypto.spec.IvParameterSpec;
 import javax.crypto.spec.OAEPParameterSpec;
@@ -212,9 +212,15 @@
 
     private static final long DAY_IN_MILLIS = TestUtils.DAY_IN_MILLIS;
 
-    private static final byte[] AES_KAT_KEY_BYTES =
+    private static final byte[] AES128_KAT_KEY_BYTES =
             HexEncoding.decode("7d9f11a0da111e9d8bdd14f04648ed91");
 
+    private static final byte[] AES192_KAT_KEY_BYTES =
+            HexEncoding.decode("69ef2c44a48d3dc4d5744a281f7ebb5ca976c2202f91e10c");
+
+    private static final byte[] AES256_KAT_KEY_BYTES =
+            HexEncoding.decode("cf601cc10aaf434d1f01747136aff222af7fb426d101901712214c3fea18125f");
+
     private static final KeyProtection.Builder GOOD_IMPORT_PARAMS_BUILDER =
             new KeyProtection.Builder(
                     KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
@@ -252,43 +258,43 @@
 
     public void testAndroidKeyStoreKeysHandledByAndroidKeyStoreProviderWhenDecrypting()
             throws Exception {
-        Collection<SecretKey> secretKeys = importDefaultKatSecretKeys();
-        Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
-
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
+                ImportedKey key = importDefaultKatKey(
+                        algorithm,
+                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+                        false);
+
                 // Decryption may need additional parameters. Initializing a Cipher for encryption
                 // forces it to generate any such parameters.
                 Cipher cipher = Cipher.getInstance(algorithm, provider);
-                cipher.init(Cipher.ENCRYPT_MODE, getEncryptionKey(algorithm, secretKeys, keyPairs));
+                cipher.init(Cipher.ENCRYPT_MODE, key.getKeystoreBackedEncryptionKey());
                 AlgorithmParameters params = cipher.getParameters();
 
                 // Test DECRYPT_MODE
-                Key key = getDecryptionKey(algorithm, secretKeys, keyPairs);
                 cipher = Cipher.getInstance(algorithm);
-                cipher.init(Cipher.DECRYPT_MODE, key, params);
+                Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+                cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
                 assertSame(provider, cipher.getProvider());
 
                 // Test UNWRAP_MODE
                 cipher = Cipher.getInstance(algorithm);
                 if (params != null) {
-                    cipher.init(Cipher.UNWRAP_MODE, key, params);
+                    cipher.init(Cipher.UNWRAP_MODE, decryptionKey, params);
                 } else {
-                    cipher.init(Cipher.UNWRAP_MODE, key);
+                    cipher.init(Cipher.UNWRAP_MODE, decryptionKey);
                 }
                 assertSame(provider, cipher.getProvider());
             } catch (Throwable e) {
-                throw new RuntimeException(algorithm + " failed", e);
+                throw new RuntimeException("Failed for " + algorithm, e);
             }
         }
     }
 
     public void testAndroidKeyStorePublicKeysAcceptedByHighestPriorityProviderWhenEncrypting()
             throws Exception {
-        Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
-
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
         for (String algorithm : EXPECTED_ALGORITHMS) {
@@ -296,7 +302,10 @@
                 continue;
             }
             try {
-                Key key = getEncryptionKey(algorithm, Collections.<SecretKey>emptyList(), keyPairs);
+                Key key = importDefaultKatKey(
+                        algorithm,
+                        KeyProperties.PURPOSE_ENCRYPT,
+                        false).getKeystoreBackedEncryptionKey();
 
                 Cipher cipher = Cipher.getInstance(algorithm);
                 cipher.init(Cipher.ENCRYPT_MODE, key);
@@ -304,192 +313,457 @@
                 cipher = Cipher.getInstance(algorithm);
                 cipher.init(Cipher.WRAP_MODE, key);
             } catch (Throwable e) {
-                throw new RuntimeException(algorithm + " failed", e);
+                throw new RuntimeException("Failed for" + algorithm, e);
+            }
+        }
+    }
+
+    public void testEmptyPlaintextEncryptsAndDecrypts()
+            throws Exception {
+        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(provider);
+        final byte[] originalPlaintext = EmptyArray.BYTE;
+        for (String algorithm : EXPECTED_ALGORITHMS) {
+            for (ImportedKey key : importKatKeys(
+                    algorithm,
+                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+                    false)) {
+                try {
+                    Key encryptionKey = key.getKeystoreBackedEncryptionKey();
+                    byte[] plaintext = truncatePlaintextIfNecessary(
+                            algorithm, encryptionKey, originalPlaintext);
+                    if (plaintext == null) {
+                        // Key is too short to encrypt anything using this transformation
+                        continue;
+                    }
+                    Cipher cipher = Cipher.getInstance(algorithm, provider);
+                    cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+                    AlgorithmParameters params = cipher.getParameters();
+                    byte[] ciphertext = cipher.doFinal(plaintext);
+                    byte[] expectedPlaintext = plaintext;
+                    if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
+                        // RSA decryption without padding left-pads resulting plaintext with NUL
+                        // bytes to the length of RSA modulus.
+                        int modulusLengthBytes = (TestUtils.getKeySizeBits(encryptionKey) + 7) / 8;
+                        expectedPlaintext = TestUtils.leftPadWithZeroBytes(
+                                expectedPlaintext, modulusLengthBytes);
+                    }
+
+                    cipher = Cipher.getInstance(algorithm, provider);
+                    Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+                    cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+                    byte[] actualPlaintext = cipher.doFinal(ciphertext);
+                    MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+                } catch (Throwable e) {
+                    throw new RuntimeException(
+                            "Failed for " + algorithm + " with key " + key.getAlias(),
+                            e);
+                }
             }
         }
     }
 
     public void testCiphertextGeneratedByAndroidKeyStoreDecryptsByAndroidKeyStore()
             throws Exception {
-        Collection<SecretKey> secretKeys = importDefaultKatSecretKeys();
-        Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
+        final byte[] originalPlaintext = "Very secret message goes here...".getBytes("US-ASCII");
         for (String algorithm : EXPECTED_ALGORITHMS) {
-            try {
-                Key encryptionKey = getEncryptionKey(algorithm, secretKeys, keyPairs);
-                Cipher cipher = Cipher.getInstance(algorithm, provider);
-                cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
-                AlgorithmParameters params = cipher.getParameters();
-                byte[] expectedPlaintext = "Very secret message goes here...".getBytes("UTF-8");
-                byte[] ciphertext = cipher.doFinal(expectedPlaintext);
-                if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
-                    // RSA decryption without padding left-pads resulting plaintext with NUL bytes
-                    // to the length of RSA modulus.
-                    int modulusLengthBytes =
-                            (((RSAKey) encryptionKey).getModulus().bitLength() + 7) / 8;
-                    expectedPlaintext = TestUtils.leftPadWithZeroBytes(
-                            expectedPlaintext, modulusLengthBytes);
-                }
+            for (ImportedKey key : importKatKeys(
+                    algorithm,
+                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+                    false)) {
+                try {
+                    Key encryptionKey = key.getKeystoreBackedEncryptionKey();
+                    byte[] plaintext = truncatePlaintextIfNecessary(
+                            algorithm, encryptionKey, originalPlaintext);
+                    if (plaintext == null) {
+                        // Key is too short to encrypt anything using this transformation
+                        continue;
+                    }
+                    Cipher cipher = Cipher.getInstance(algorithm, provider);
+                    cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+                    AlgorithmParameters params = cipher.getParameters();
+                    byte[] ciphertext = cipher.doFinal(plaintext);
+                    byte[] expectedPlaintext = plaintext;
+                    if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
+                        // RSA decryption without padding left-pads resulting plaintext with NUL
+                        // bytes to the length of RSA modulus.
+                        int modulusLengthBytes = (TestUtils.getKeySizeBits(encryptionKey) + 7) / 8;
+                        expectedPlaintext = TestUtils.leftPadWithZeroBytes(
+                                expectedPlaintext, modulusLengthBytes);
+                    }
 
-                cipher = Cipher.getInstance(algorithm, provider);
-                Key decryptionKey = getDecryptionKey(algorithm, secretKeys, keyPairs);
-                cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
-                byte[] actualPlaintext = cipher.doFinal(ciphertext);
-                MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
-            } catch (Throwable e) {
-                throw new RuntimeException(algorithm + " failed", e);
+                    cipher = Cipher.getInstance(algorithm, provider);
+                    Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+                    cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+                    byte[] actualPlaintext = cipher.doFinal(ciphertext);
+                    MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+                } catch (Throwable e) {
+                    throw new RuntimeException(
+                            "Failed for " + algorithm + " with key " + key.getAlias(),
+                            e);
+                }
             }
         }
     }
 
     public void testCiphertextGeneratedByHighestPriorityProviderDecryptsByAndroidKeyStore()
             throws Exception {
-        Collection<SecretKey> secretKeys = getDefaultKatSecretKeys();
-        Collection<SecretKey> keystoreSecretKeys = importDefaultKatSecretKeys();
-        Collection<KeyPair> keyPairs = getDefaultKatKeyPairs();
-        Collection<KeyPair> keystoreKeyPairs = importDefaultKatKeyPairs();
-        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
-        assertNotNull(provider);
+        Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(keystoreProvider);
+        byte[] originalPlaintext = "Very secret message goes here...".getBytes("UTF-8");
         for (String algorithm : EXPECTED_ALGORITHMS) {
-            try {
-                Key encryptionKey = getEncryptionKey(algorithm, secretKeys, keyPairs);
-                Cipher cipher;
+            for (ImportedKey key : importKatKeys(
+                    algorithm,
+                    KeyProperties.PURPOSE_DECRYPT,
+                    false)) {
+                Provider encryptionProvider = null;
                 try {
-                    cipher = Cipher.getInstance(algorithm);
-                    cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
-                } catch (InvalidKeyException e) {
-                    // No providers support encrypting using this algorithm and key.
-                    continue;
-                }
-                if (provider == cipher.getProvider()) {
-                    // This is covered by another test.
-                    continue;
-                }
-                AlgorithmParameters params = cipher.getParameters();
-
-                // TODO: Remove this workaround for Bug 22405492 once the issue is fixed. The issue
-                // is that Bouncy Castle incorrectly defaults the MGF1 digest to the digest
-                // specified in the transformation. RI and Android Keystore keep the MGF1 digest
-                // defaulted at SHA-1.
-                if ((params != null) && ("OAEP".equalsIgnoreCase(params.getAlgorithm()))) {
-                    OAEPParameterSpec spec = params.getParameterSpec(OAEPParameterSpec.class);
-                    if (!"SHA-1".equalsIgnoreCase(
-                            ((MGF1ParameterSpec) spec.getMGFParameters()).getDigestAlgorithm())) {
-                        cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, new OAEPParameterSpec(
-                                spec.getDigestAlgorithm(),
-                                "MGF1",
-                                MGF1ParameterSpec.SHA1,
-                                PSource.PSpecified.DEFAULT));
-                        params = cipher.getParameters();
+                    Key encryptionKey = key.getOriginalEncryptionKey();
+                    byte[] plaintext = truncatePlaintextIfNecessary(
+                            algorithm, encryptionKey, originalPlaintext);
+                    if (plaintext == null) {
+                        // Key is too short to encrypt anything using this transformation
+                        continue;
                     }
-                }
 
-                byte[] expectedPlaintext = "Very secret message goes here...".getBytes("UTF-8");
-                byte[] ciphertext = cipher.doFinal(expectedPlaintext);
-                if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
-                    // RSA decryption without padding left-pads resulting plaintext with NUL bytes
-                    // to the length of RSA modulus.
-                    int modulusLengthBytes =
-                            (((RSAKey) encryptionKey).getModulus().bitLength() + 7) / 8;
-                    expectedPlaintext = TestUtils.leftPadWithZeroBytes(
-                            expectedPlaintext, modulusLengthBytes);
-                }
+                    Cipher cipher;
+                    try {
+                        cipher = Cipher.getInstance(algorithm);
+                        cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+                    } catch (InvalidKeyException e) {
+                        // No providers support encrypting using this algorithm and key.
+                        continue;
+                    }
+                    encryptionProvider = cipher.getProvider();
+                    if (keystoreProvider == encryptionProvider) {
+                        // This is covered by another test.
+                        continue;
+                    }
+                    AlgorithmParameters params = cipher.getParameters();
 
-                // TODO: Remove this workaround for Bug 22319986 once the issue is fixed. The issue
-                // is that Conscrypt and Bouncy Castle's AES/GCM/NoPadding implementations return
-                // AlgorithmParameters of algorithm "AES" from which it's impossible to obtain a
-                // GCMParameterSpec. They should be returning AlgorithmParameters of algorithm
-                // "GCM".
-                if (("AES/GCM/NoPadding".equalsIgnoreCase(algorithm))
-                        && (!"GCM".equalsIgnoreCase(params.getAlgorithm()))) {
-                    params = AlgorithmParameters.getInstance("GCM");
-                    params.init(new GCMParameterSpec(128, cipher.getIV()));
-                }
+                    // TODO: Remove this workaround for Bug 22405492 once the issue is fixed. The
+                    // issue is that Bouncy Castle incorrectly defaults the MGF1 digest to the
+                    // digest specified in the transformation. RI and Android Keystore keep the MGF1
+                    // digest defaulted at SHA-1.
+                    if ((params != null) && ("OAEP".equalsIgnoreCase(params.getAlgorithm()))) {
+                        OAEPParameterSpec spec = params.getParameterSpec(OAEPParameterSpec.class);
+                        if (!"SHA-1".equalsIgnoreCase(
+                                ((MGF1ParameterSpec) spec.getMGFParameters())
+                                        .getDigestAlgorithm())) {
+                            cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, new OAEPParameterSpec(
+                                    spec.getDigestAlgorithm(),
+                                    "MGF1",
+                                    MGF1ParameterSpec.SHA1,
+                                    PSource.PSpecified.DEFAULT));
+                            params = cipher.getParameters();
+                        }
+                    }
 
-                cipher = Cipher.getInstance(algorithm, provider);
-                Key decryptionKey = getDecryptionKey(
-                        algorithm, keystoreSecretKeys, keystoreKeyPairs);
-                cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
-                byte[] actualPlaintext = cipher.doFinal(ciphertext);
-                MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
-            } catch (Throwable e) {
-                throw new RuntimeException(algorithm + " failed", e);
+                    byte[] ciphertext = cipher.doFinal(plaintext);
+                    byte[] expectedPlaintext = plaintext;
+                    if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
+                        // RSA decryption without padding left-pads resulting plaintext with NUL
+                        // bytes to the length of RSA modulus.
+                        int modulusLengthBytes = (TestUtils.getKeySizeBits(encryptionKey) + 7) / 8;
+                        expectedPlaintext = TestUtils.leftPadWithZeroBytes(
+                                expectedPlaintext, modulusLengthBytes);
+                    }
+
+                    // TODO: Remove this workaround for Bug 22319986 once the issue is fixed. The issue
+                    // is that Conscrypt and Bouncy Castle's AES/GCM/NoPadding implementations return
+                    // AlgorithmParameters of algorithm "AES" from which it's impossible to obtain a
+                    // GCMParameterSpec. They should be returning AlgorithmParameters of algorithm
+                    // "GCM".
+                    if (("AES/GCM/NoPadding".equalsIgnoreCase(algorithm))
+                            && (!"GCM".equalsIgnoreCase(params.getAlgorithm()))) {
+                        params = AlgorithmParameters.getInstance("GCM");
+                        params.init(new GCMParameterSpec(128, cipher.getIV()));
+                    }
+
+                    cipher = Cipher.getInstance(algorithm, keystoreProvider);
+                    Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+                    cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+                    byte[] actualPlaintext = cipher.doFinal(ciphertext);
+                    MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+                } catch (Throwable e) {
+                    throw new RuntimeException(
+                            "Failed for " + algorithm + " with key " + key.getAlias()
+                                    + ", encryption provider: " + encryptionProvider,
+                            e);
+                }
             }
         }
     }
 
     public void testCiphertextGeneratedByAndroidKeyStoreDecryptsByHighestPriorityProvider()
             throws Exception {
-        Collection<SecretKey> secretKeys = getDefaultKatSecretKeys();
-        Collection<SecretKey> keystoreSecretKeys = importDefaultKatSecretKeys();
-        Collection<KeyPair> keyPairs = getDefaultKatKeyPairs();
-        Collection<KeyPair> keystoreKeyPairs = importDefaultKatKeyPairs();
-        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
-        assertNotNull(provider);
+        Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(keystoreProvider);
+        byte[] originalPlaintext = "Very secret message goes here...".getBytes("UTF-8");
         for (String algorithm : EXPECTED_ALGORITHMS) {
-            try {
-                Key encryptionKey =
-                        getEncryptionKey(algorithm, keystoreSecretKeys, keystoreKeyPairs);
-                Cipher cipher = Cipher.getInstance(algorithm, provider);
-                cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
-                AlgorithmParameters params = cipher.getParameters();
-
-                byte[] expectedPlaintext = "Very secret message goes here...".getBytes("UTF-8");
-                byte[] ciphertext = cipher.doFinal(expectedPlaintext);
-                if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
-                    // RSA decryption without padding left-pads resulting plaintext with NUL bytes
-                    // to the length of RSA modulus.
-                    int modulusLengthBytes =
-                            (((RSAKey) encryptionKey).getModulus().bitLength() + 7) / 8;
-                    expectedPlaintext = TestUtils.leftPadWithZeroBytes(
-                            expectedPlaintext, modulusLengthBytes);
-                }
-
-                Key decryptionKey = getDecryptionKey(algorithm, secretKeys, keyPairs);
+            for (ImportedKey key : importKatKeys(
+                    algorithm,
+                    KeyProperties.PURPOSE_ENCRYPT,
+                    false)) {
+                Provider decryptionProvider = null;
                 try {
-                    cipher = Cipher.getInstance(algorithm);
-                    if (params != null) {
-                        cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
-                    } else {
-                        cipher.init(Cipher.DECRYPT_MODE, decryptionKey);
+                    Key encryptionKey = key.getKeystoreBackedEncryptionKey();
+                    byte[] plaintext = truncatePlaintextIfNecessary(
+                            algorithm, encryptionKey, originalPlaintext);
+                    if (plaintext == null) {
+                        // Key is too short to encrypt anything using this transformation
+                        continue;
                     }
-                } catch (InvalidKeyException e) {
-                    // No providers support decrypting using this algorithm and key.
-                    continue;
+                    Cipher cipher = Cipher.getInstance(algorithm, keystoreProvider);
+                    cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+                    AlgorithmParameters params = cipher.getParameters();
+
+                    byte[] ciphertext = cipher.doFinal(plaintext);
+                    byte[] expectedPlaintext = plaintext;
+                    if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
+                        // RSA decryption without padding left-pads resulting plaintext with NUL
+                        // bytes to the length of RSA modulus.
+                        int modulusLengthBytes = (TestUtils.getKeySizeBits(encryptionKey) + 7) / 8;
+                        expectedPlaintext = TestUtils.leftPadWithZeroBytes(
+                                expectedPlaintext, modulusLengthBytes);
+                    }
+
+                    Key decryptionKey = key.getOriginalDecryptionKey();
+                    try {
+                        cipher = Cipher.getInstance(algorithm);
+                        if (params != null) {
+                            cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+                        } else {
+                            cipher.init(Cipher.DECRYPT_MODE, decryptionKey);
+                        }
+                    } catch (InvalidKeyException e) {
+                        // No providers support decrypting using this algorithm and key.
+                        continue;
+                    }
+                    decryptionProvider = cipher.getProvider();
+                    if (keystoreProvider == decryptionProvider) {
+                        // This is covered by another test.
+                        continue;
+                    }
+                    byte[] actualPlaintext = cipher.doFinal(ciphertext);
+                    MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+                } catch (Throwable e) {
+                    throw new RuntimeException(
+                            "Failed for " + algorithm + " with key " + key.getAlias()
+                                    + ", decryption provider: " + decryptionProvider,
+                            e);
                 }
-                if (provider == cipher.getProvider()) {
-                    // This is covered by another test.
-                    continue;
+            }
+        }
+    }
+
+    public void testMaxSizedPlaintextSupported() throws Exception {
+        Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(keystoreProvider);
+        for (String algorithm : EXPECTED_ALGORITHMS) {
+            if (isSymmetric(algorithm)) {
+                // No input length restrictions (except multiple of block size for some
+                // transformations).
+                continue;
+            }
+            for (ImportedKey key : importKatKeys(
+                    algorithm,
+                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+                    false)) {
+                int plaintextSizeBytes = -1;
+                Provider otherProvider = null;
+                try {
+                    Key encryptionKey = key.getKeystoreBackedEncryptionKey();
+                    int maxSupportedPlaintextSizeBytes =
+                            TestUtils.getMaxSupportedPlaintextInputSizeBytes(
+                                    algorithm, encryptionKey);
+                    if (maxSupportedPlaintextSizeBytes < 0) {
+                        // Key too short to encrypt anything using this transformation.
+                        continue;
+                    } else if (maxSupportedPlaintextSizeBytes == Integer.MAX_VALUE) {
+                        // No input length restrictions.
+                        continue;
+                    }
+                    byte[] plaintext = new byte[maxSupportedPlaintextSizeBytes];
+                    Arrays.fill(plaintext, (byte) 0xff);
+                    plaintextSizeBytes = plaintext.length;
+
+                    // Encrypt plaintext using Android Keystore Cipher
+                    Cipher cipher = Cipher.getInstance(algorithm, keystoreProvider);
+                    cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+                    AlgorithmParameters params = cipher.getParameters();
+                    byte[] ciphertext = cipher.doFinal(plaintext);
+                    byte[] expectedPlaintext = plaintext;
+                    if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
+                        // RSA decryption without padding left-pads resulting plaintext with NUL
+                        // bytes to the length of RSA modulus.
+                        int modulusLengthBytes = (TestUtils.getKeySizeBits(encryptionKey) + 7) / 8;
+                        expectedPlaintext = TestUtils.leftPadWithZeroBytes(
+                                expectedPlaintext, modulusLengthBytes);
+                    }
+
+                    // Check that ciphertext decrypts using Android Keystore Cipher
+                    cipher = Cipher.getInstance(algorithm, keystoreProvider);
+                    Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+                    cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+                    byte[] actualPlaintext = cipher.doFinal(ciphertext);
+                    MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+
+                    // Check that ciphertext decrypts using the highest-priority provider.
+                    cipher = Cipher.getInstance(algorithm);
+                    decryptionKey = key.getOriginalDecryptionKey();
+                    try {
+                        cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params);
+                    } catch (InvalidKeyException e) {
+                        // No other providers offer decryption using this transformation and key.
+                        continue;
+                    }
+                    otherProvider = cipher.getProvider();
+                    if (otherProvider == keystoreProvider) {
+                        // This has already been tested above.
+                        continue;
+                    }
+                    actualPlaintext = cipher.doFinal(ciphertext);
+                    MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
+                } catch (Throwable e) {
+                    throw new RuntimeException(
+                            "Failed for " + algorithm + " with key " + key.getAlias()
+                                    + " and " + plaintextSizeBytes + " long plaintext"
+                                    + ", other provider: " + otherProvider,
+                            e);
                 }
-                byte[] actualPlaintext = cipher.doFinal(ciphertext);
-                MoreAsserts.assertEquals(expectedPlaintext, actualPlaintext);
-            } catch (Throwable e) {
-                throw new RuntimeException(algorithm + " failed", e);
+            }
+        }
+    }
+
+    public void testLargerThanMaxSizedPlaintextRejected() throws Exception {
+        Provider keystoreProvider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(keystoreProvider);
+        for (String algorithm : EXPECTED_ALGORITHMS) {
+            if (isSymmetric(algorithm)) {
+                // No input length restrictions (except multiple of block size for some
+                // transformations).
+                continue;
+            }
+            for (ImportedKey key : importKatKeys(
+                    algorithm,
+                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+                    false)) {
+                int plaintextSizeBytes = -1;
+                Provider otherProvider = null;
+                try {
+                    Key encryptionKey = key.getKeystoreBackedEncryptionKey();
+                    int maxSupportedPlaintextSizeBytes =
+                            TestUtils.getMaxSupportedPlaintextInputSizeBytes(
+                                    algorithm, encryptionKey);
+                    if (maxSupportedPlaintextSizeBytes < 0) {
+                        // Key too short to encrypt anything using this transformation.
+                        continue;
+                    } else if (maxSupportedPlaintextSizeBytes == Integer.MAX_VALUE) {
+                        // No input length restrictions.
+                        continue;
+                    }
+                    // Create plaintext which is one byte longer than maximum supported one.
+                    byte[] plaintext = new byte[maxSupportedPlaintextSizeBytes + 1];
+                    Arrays.fill(plaintext, (byte) 0xff);
+                    plaintextSizeBytes = plaintext.length;
+
+                    // Encrypting this plaintext using Android Keystore Cipher should fail.
+                    Cipher cipher = Cipher.getInstance(algorithm, keystoreProvider);
+                    cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+                    if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
+                        // This transformation is special: it's supposed to throw a
+                        // BadPaddingException instead of an IllegalBlockSizeException.
+                        try {
+                            byte[] ciphertext = cipher.doFinal(plaintext);
+                            fail("Unexpectedly produced ciphertext (" + ciphertext.length
+                                    + " bytes): " + HexEncoding.encode(ciphertext) + " for "
+                                    + plaintext.length + " byte long plaintext");
+                        } catch (BadPaddingException expected) {}
+                    } else {
+                        try {
+                            byte[] ciphertext = cipher.doFinal(plaintext);
+                            fail("Unexpectedly produced ciphertext (" + ciphertext.length
+                                    + " bytes): " + HexEncoding.encode(ciphertext) + " for "
+                                    + plaintext.length + " byte long plaintext");
+                        } catch (IllegalBlockSizeException expected) {}
+                    }
+
+                    // Encrypting this plaintext using the highest-priority implementation should
+                    // fail.
+                    cipher = Cipher.getInstance(algorithm);
+                    encryptionKey = key.getOriginalEncryptionKey();
+                    try {
+                        cipher.init(Cipher.ENCRYPT_MODE, encryptionKey);
+                    } catch (InvalidKeyException e) {
+                        // No other providers support this transformation with this key.
+                        continue;
+                    }
+                    otherProvider = cipher.getProvider();
+                    if (otherProvider == keystoreProvider) {
+                        // This has already been tested above.
+                        continue;
+                    }
+                    if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
+                        // This transformation is special: it's supposed to throw a
+                        // BadPaddingException instead of an IllegalBlockSizeException.
+                        try {
+                            byte[] ciphertext = cipher.doFinal(plaintext);
+                            fail(otherProvider.getName() + " unexpectedly produced ciphertext ("
+                                    + ciphertext.length + " bytes): "
+                                    + HexEncoding.encode(ciphertext) + " for "
+                                    + plaintext.length + " byte long plaintext");
+                            // TODO: Remove this workaround once Conscrypt's RSA Cipher Bug 22567458
+                            // is fixed. Conscrypt's Cipher.doFinal throws a SignatureException.
+                            // This code is unreachable because of the fail() above. It's here only
+                            // so that the compiler does not complain about us catching
+                            // SignatureException.
+                            Signature sig = Signature.getInstance("SHA256withRSA");
+                            sig.sign();
+                        } catch (BadPaddingException | SignatureException expected) {}
+                    } else {
+                        try {
+                            byte[] ciphertext = cipher.doFinal(plaintext);
+                            fail(otherProvider.getName() + " unexpectedly produced ciphertext ("
+                                    + ciphertext.length + " bytes): "
+                                    + HexEncoding.encode(ciphertext) + " for "
+                                    + plaintext.length + " byte long plaintext");
+                            // TODO: Remove the catching of RuntimeException workaround once the
+                            // corresponding Bug 22567463 in Conscrypt is fixed.
+                        } catch (IllegalBlockSizeException | RuntimeException expected) {}
+                    }
+                } catch (Throwable e) {
+                    throw new RuntimeException(
+                            "Failed for " + algorithm + " with key " + key.getAlias()
+                            + " and " + plaintextSizeBytes + " byte long plaintext"
+                            + ", other provider: " + otherProvider,
+                            e);
+                }
             }
         }
     }
 
     public void testKat() throws Exception {
-        Collection<SecretKey> secretKeys = importDefaultKatSecretKeys();
-        Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
-
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
-                Key key = getDecryptionKey(algorithm, secretKeys, keyPairs);
+                ImportedKey key = importDefaultKatKey(algorithm,
+                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+                        true);
                 KatVector testVector = KAT_VECTORS.get(algorithm);
                 assertNotNull(testVector);
                 Cipher cipher = Cipher.getInstance(algorithm, provider);
-                cipher.init(Cipher.DECRYPT_MODE, key, testVector.params);
+                Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+                cipher.init(Cipher.DECRYPT_MODE, decryptionKey, testVector.params);
                 byte[] actualPlaintext = cipher.doFinal(testVector.ciphertext);
                 byte[] expectedPlaintext = testVector.plaintext;
                 if ("RSA/ECB/NoPadding".equalsIgnoreCase(algorithm)) {
                     // RSA decryption without padding left-pads resulting plaintext with NUL bytes
                     // to the length of RSA modulus.
-                    int modulusLengthBytes =
-                            (((RSAKey) key).getModulus().bitLength() + 7) / 8;
+                    int modulusLengthBytes = (TestUtils.getKeySizeBits(decryptionKey) + 7) / 8;
                     expectedPlaintext = TestUtils.leftPadWithZeroBytes(
                             expectedPlaintext, modulusLengthBytes);
                 }
@@ -498,9 +772,9 @@
                     // Deterministic encryption: ciphertext depends only on plaintext and input
                     // parameters. Assert that encrypting the plaintext results in the same
                     // ciphertext as in the test vector.
-                    key = getEncryptionKey(algorithm, secretKeys, keyPairs);
+                    Key encryptionKey = key.getKeystoreBackedEncryptionKey();
                     cipher = Cipher.getInstance(algorithm, provider);
-                    cipher.init(Cipher.ENCRYPT_MODE, key, testVector.params);
+                    cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, testVector.params);
                     byte[] actualCiphertext = cipher.doFinal(testVector.plaintext);
                     MoreAsserts.assertEquals(testVector.ciphertext, actualCiphertext);
                 }
@@ -921,149 +1195,152 @@
         // Assert that encryption consumes the correct amount of entropy from the provided
         // SecureRandom and that decryption consumes no entropy.
 
-        Collection<SecretKey> secretKeys = importDefaultKatSecretKeys();
-        Collection<KeyPair> keyPairs = importDefaultKatKeyPairs();
-
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
 
         CountingSecureRandom rng = new CountingSecureRandom();
         for (String transformation : EXPECTED_ALGORITHMS) {
-            try {
-                Cipher cipher = Cipher.getInstance(transformation, provider);
-                Key encryptionKey = getEncryptionKey(transformation, secretKeys, keyPairs);
-
-                // Cipher.init may only consume entropy for generating the IV.
-                rng.resetCounters();
-                cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, rng);
-                int expectedEntropyBytesConsumedDuringInit;
-                String transformationUpperCase = transformation.toUpperCase(Locale.US);
-                if (transformationUpperCase.startsWith("AES")) {
-                    String blockMode = transformationUpperCase.split("/")[1];
-                    // Entropy should consumed for IV generation only.
-                    switch (blockMode) {
-                        case "ECB":
-                            expectedEntropyBytesConsumedDuringInit = 0;
-                            break;
-                        case "CBC":
-                        case "CTR":
-                            expectedEntropyBytesConsumedDuringInit = 16;
-                            break;
-                        case "GCM":
-                            expectedEntropyBytesConsumedDuringInit = 12;
-                            break;
-                        default:
-                            throw new RuntimeException("Unsupported block mode " + blockMode);
+            for (ImportedKey key : importKatKeys(
+                    transformation,
+                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+                    true)) {
+                try {
+                    Cipher cipher = Cipher.getInstance(transformation, provider);
+                    Key encryptionKey = key.getKeystoreBackedEncryptionKey();
+                    byte[] plaintext = truncatePlaintextIfNecessary(
+                            transformation, encryptionKey, new byte[32]);
+                    if (plaintext == null) {
+                        // Key too short to encrypt anything using this transformation.
+                        continue;
                     }
-                } else if (transformationUpperCase.startsWith("RSA")) {
-                    expectedEntropyBytesConsumedDuringInit = 0;
-                } else {
-                    throw new RuntimeException("Unsupported transformation: " + transformation);
-                }
-                assertEquals(expectedEntropyBytesConsumedDuringInit, rng.getOutputSizeBytes());
-                AlgorithmParameters params = cipher.getParameters();
+                    Arrays.fill(plaintext, (byte) 0x1);
 
-                // Cipher.update should not consume entropy.
-                byte[] plaintext = new byte[16];
-                Arrays.fill(plaintext, (byte) 0x1);
-                rng.resetCounters();
-                byte[] ciphertext = cipher.update(plaintext);
-                assertEquals(0, rng.getOutputSizeBytes());
-
-                // Cipher.doFinal may consume entropy to pad the message (RSA only).
-                rng.resetCounters();
-                ciphertext = TestUtils.concat(ciphertext, cipher.doFinal());
-                int expectedEntropyBytesConsumedDuringDoFinal;
-                if (transformationUpperCase.startsWith("AES")) {
-                    expectedEntropyBytesConsumedDuringDoFinal = 0;
-                } else if (transformationUpperCase.startsWith("RSA")) {
-                    // Entropy should not be consumed during Cipher.init.
-                    if (transformationUpperCase.contains("/OAEP")) {
-                        if (transformationUpperCase.endsWith("/OAEPPADDING")) {
-                            expectedEntropyBytesConsumedDuringDoFinal = 160 / 8;
-                        } else if (transformationUpperCase.endsWith(
-                                "OAEPWITHSHA-1ANDMGF1PADDING")) {
-                            expectedEntropyBytesConsumedDuringDoFinal = 160 / 8;
-                        } else if (transformationUpperCase.endsWith(
-                                "OAEPWITHSHA-224ANDMGF1PADDING")) {
-                            expectedEntropyBytesConsumedDuringDoFinal = 224 / 8;
-                        } else if (transformationUpperCase.endsWith(
-                                "OAEPWITHSHA-256ANDMGF1PADDING")) {
-                            expectedEntropyBytesConsumedDuringDoFinal = 256 / 8;
-                        } else if (transformationUpperCase.endsWith(
-                                "OAEPWITHSHA-384ANDMGF1PADDING")) {
-                            expectedEntropyBytesConsumedDuringDoFinal = 384 / 8;
-                        } else if (transformationUpperCase.endsWith(
-                                "OAEPWITHSHA-512ANDMGF1PADDING")) {
-                            expectedEntropyBytesConsumedDuringDoFinal = 512 / 8;
-                        } else {
-                            throw new RuntimeException("Unsupported OAEP padding scheme: "
-                                    + transformation);
+                    // Cipher.init may only consume entropy for generating the IV.
+                    rng.resetCounters();
+                    cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, rng);
+                    int expectedEntropyBytesConsumedDuringInit;
+                    String keyAlgorithm = TestUtils.getCipherKeyAlgorithm(transformation);
+                    if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
+                        String blockMode =
+                                TestUtils.getCipherBlockMode(transformation).toUpperCase(Locale.US);
+                        // Entropy should consumed for IV generation only.
+                        switch (blockMode) {
+                            case "ECB":
+                                expectedEntropyBytesConsumedDuringInit = 0;
+                                break;
+                            case "CBC":
+                            case "CTR":
+                                expectedEntropyBytesConsumedDuringInit = 16;
+                                break;
+                            case "GCM":
+                                expectedEntropyBytesConsumedDuringInit = 12;
+                                break;
+                            default:
+                                throw new RuntimeException("Unsupported block mode " + blockMode);
                         }
-                    } else if (transformationUpperCase.endsWith("/PKCS1PADDING")) {
-                        expectedEntropyBytesConsumedDuringDoFinal =
-                                (((RSAKey) encryptionKey).getModulus().bitLength() + 7) / 8;
-                    } else if (transformationUpperCase.endsWith("/NOPADDING")) {
-                        expectedEntropyBytesConsumedDuringDoFinal = 0;
+                    } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+                        expectedEntropyBytesConsumedDuringInit = 0;
                     } else {
-                        throw new RuntimeException(
-                                "Unknown padding in transformation: " + transformation);
+                        throw new RuntimeException("Unsupported key algorithm: " + transformation);
                     }
-                } else {
-                    throw new RuntimeException("Unsupported transformation: " + transformation);
-                }
-                assertEquals(expectedEntropyBytesConsumedDuringDoFinal, rng.getOutputSizeBytes());
+                    assertEquals(expectedEntropyBytesConsumedDuringInit, rng.getOutputSizeBytes());
+                    AlgorithmParameters params = cipher.getParameters();
 
-                // Assert that when initialization parameters are provided when encrypting, no
-                // entropy is consumed by Cipher.init. This is because Cipher.init should only
-                // use entropy for generating an IV which in this case no longer needs to be
-                // generated because it's specified in the parameters.
-                cipher = Cipher.getInstance(transformation, provider);
-                rng.resetCounters();
-                cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, params, rng);
-                assertEquals(0, rng.getOutputSizeBytes());
-                Key key = getDecryptionKey(transformation, secretKeys, keyPairs);
-                rng.resetCounters();
-                cipher = Cipher.getInstance(transformation, provider);
-                cipher.init(Cipher.DECRYPT_MODE, key, params, rng);
-                assertEquals(0, rng.getOutputSizeBytes());
-                rng.resetCounters();
-                cipher.update(ciphertext);
-                assertEquals(0, rng.getOutputSizeBytes());
-                rng.resetCounters();
-                cipher.doFinal();
-                assertEquals(0, rng.getOutputSizeBytes());
-            } catch (Throwable e) {
-                throw new RuntimeException("Failed for " + transformation, e);
+                    // Cipher.update should not consume entropy.
+                    rng.resetCounters();
+                    byte[] ciphertext = cipher.update(plaintext);
+                    assertEquals(0, rng.getOutputSizeBytes());
+
+                    // Cipher.doFinal may consume entropy to pad the message (RSA only).
+                    rng.resetCounters();
+                    ciphertext = TestUtils.concat(ciphertext, cipher.doFinal());
+                    int expectedEntropyBytesConsumedDuringDoFinal;
+                    if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
+                        expectedEntropyBytesConsumedDuringDoFinal = 0;
+                    } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+                        // Entropy should not be consumed during Cipher.init.
+                        String encryptionPadding =
+                                TestUtils.getCipherEncryptionPadding(transformation);
+                        if (KeyProperties.ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(
+                                encryptionPadding)) {
+                            int digestOutputSizeBits =
+                                    TestUtils.getDigestOutputSizeBits(TestUtils.getCipherDigest(
+                                            transformation));
+                            expectedEntropyBytesConsumedDuringDoFinal =
+                                    (digestOutputSizeBits + 7) / 8;
+                        } else if (encryptionPadding.equalsIgnoreCase(
+                                KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)) {
+                            expectedEntropyBytesConsumedDuringDoFinal =
+                                    (TestUtils.getKeySizeBits(encryptionKey) + 7) / 8;
+                        } else if (encryptionPadding.equalsIgnoreCase(
+                                KeyProperties.ENCRYPTION_PADDING_NONE)) {
+                            expectedEntropyBytesConsumedDuringDoFinal = 0;
+                        } else {
+                            throw new RuntimeException(
+                                    "Unexpected encryption padding: " + encryptionPadding);
+                        }
+                    } else {
+                        throw new RuntimeException("Unsupported key algorithm: " + keyAlgorithm);
+                    }
+                    assertEquals(
+                            expectedEntropyBytesConsumedDuringDoFinal, rng.getOutputSizeBytes());
+
+                    // Assert that when initialization parameters are provided when encrypting, no
+                    // entropy is consumed by Cipher.init. This is because Cipher.init should only
+                    // use entropy for generating an IV which in this case no longer needs to be
+                    // generated because it's specified in the parameters.
+                    cipher = Cipher.getInstance(transformation, provider);
+                    rng.resetCounters();
+                    cipher.init(Cipher.ENCRYPT_MODE, encryptionKey, params, rng);
+                    assertEquals(0, rng.getOutputSizeBytes());
+                    Key decryptionKey = key.getKeystoreBackedDecryptionKey();
+                    rng.resetCounters();
+                    cipher = Cipher.getInstance(transformation, provider);
+                    cipher.init(Cipher.DECRYPT_MODE, decryptionKey, params, rng);
+                    assertEquals(0, rng.getOutputSizeBytes());
+                    rng.resetCounters();
+                    cipher.update(ciphertext);
+                    assertEquals(0, rng.getOutputSizeBytes());
+                    rng.resetCounters();
+                    cipher.doFinal();
+                    assertEquals(0, rng.getOutputSizeBytes());
+                } catch (Throwable e) {
+                    throw new RuntimeException(
+                            "Failed for " + transformation + " with key " + key.getAlias(), e);
+                }
             }
         }
     }
 
     private AlgorithmParameterSpec getWorkingDecryptionParameterSpec(String transformation) {
-        String transformationUpperCase = transformation.toUpperCase();
-        if (transformationUpperCase.startsWith("RSA/")) {
+        String keyAlgorithm = TestUtils.getCipherKeyAlgorithm(transformation);
+        if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
             return null;
-        } else if (transformationUpperCase.startsWith("AES/")) {
-            if (transformationUpperCase.startsWith("AES/ECB")) {
+        } else if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
+            String blockMode = TestUtils.getCipherBlockMode(transformation);
+            if (KeyProperties.BLOCK_MODE_ECB.equalsIgnoreCase(blockMode)) {
                 return null;
-            } else if ((transformationUpperCase.startsWith("AES/CBC"))
-                    || (transformationUpperCase.startsWith("AES/CTR"))) {
+            } else if ((KeyProperties.BLOCK_MODE_CBC.equalsIgnoreCase(blockMode))
+                    || (KeyProperties.BLOCK_MODE_CTR.equalsIgnoreCase(blockMode))) {
                 return new IvParameterSpec(
                         new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16});
-            } else if (transformationUpperCase.startsWith("AES/GCM")) {
+            } else if (KeyProperties.BLOCK_MODE_GCM.equalsIgnoreCase(blockMode)) {
                 return new GCMParameterSpec(
                         128,
                         new byte[] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12});
+            } else {
+                throw new IllegalArgumentException("Unsupported block mode: " + blockMode);
             }
+        } else {
+            throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
         }
-
-        throw new IllegalArgumentException("Unsupported transformation: " + transformation);
     }
 
     private void assertInitDecryptSucceeds(String transformation, KeyProtection importParams)
             throws Exception {
         Cipher cipher = Cipher.getInstance(transformation, EXPECTED_PROVIDER_NAME);
-        Key key = importDefaultKatDecryptionKey(transformation, importParams);
+        Key key =
+                importDefaultKatKey(transformation, importParams).getKeystoreBackedDecryptionKey();
         AlgorithmParameterSpec params = getWorkingDecryptionParameterSpec(transformation);
         cipher.init(Cipher.DECRYPT_MODE, key, params);
     }
@@ -1071,7 +1348,8 @@
     private void assertInitDecryptThrowsInvalidKeyException(
             String transformation, KeyProtection importParams) throws Exception {
         Cipher cipher = Cipher.getInstance(transformation, EXPECTED_PROVIDER_NAME);
-        Key key = importDefaultKatDecryptionKey(transformation, importParams);
+        Key key =
+                importDefaultKatKey(transformation, importParams).getKeystoreBackedDecryptionKey();
         AlgorithmParameterSpec params = getWorkingDecryptionParameterSpec(transformation);
         try {
             cipher.init(Cipher.DECRYPT_MODE, key, params);
@@ -1082,122 +1360,97 @@
     private void assertInitEncryptSucceeds(String transformation, KeyProtection importParams)
             throws Exception {
         Cipher cipher = Cipher.getInstance(transformation, EXPECTED_PROVIDER_NAME);
-        Key key = importDefaultKatEncryptionKey(transformation, importParams);
+        Key key =
+                importDefaultKatKey(transformation, importParams).getKeystoreBackedEncryptionKey();
         cipher.init(Cipher.ENCRYPT_MODE, key);
     }
 
     private void assertInitEncryptThrowsInvalidKeyException(
             String transformation, KeyProtection importParams) throws Exception {
         Cipher cipher = Cipher.getInstance(transformation, EXPECTED_PROVIDER_NAME);
-        Key key = importDefaultKatEncryptionKey(transformation, importParams);
+        Key key =
+                importDefaultKatKey(transformation, importParams).getKeystoreBackedEncryptionKey();
         try {
             cipher.init(Cipher.ENCRYPT_MODE, key);
             fail("InvalidKeyException should have been thrown");
         } catch (InvalidKeyException expected) {}
     }
 
-    private Key importDefaultKatEncryptionKey(String transformation,
-            KeyProtection importParams) throws Exception {
-        String transformationUpperCase = transformation.toUpperCase();
-        if (transformationUpperCase.startsWith("RSA/")) {
-            return TestUtils.importIntoAndroidKeyStore("testRsa",
-                    TestUtils.getRawResPrivateKey(getContext(), R.raw.rsa_key2_pkcs8),
-                    TestUtils.getRawResX509Certificate(getContext(), R.raw.rsa_key2_cert),
-                    importParams).getPublic();
-        } else if (transformationUpperCase.startsWith("AES/")) {
-            return TestUtils.importIntoAndroidKeyStore("testAes",
-                    new SecretKeySpec(AES_KAT_KEY_BYTES, "AES"),
+    private ImportedKey importDefaultKatKey(
+            String transformation, KeyProtection importParams)
+            throws Exception {
+        String keyAlgorithm = TestUtils.getCipherKeyAlgorithm(transformation);
+        if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
+            return TestUtils.importIntoAndroidKeyStore(
+                    "testAES",
+                    new SecretKeySpec(AES128_KAT_KEY_BYTES, "AES"),
+                    importParams);
+        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+            return TestUtils.importIntoAndroidKeyStore(
+                    "testRSA",
+                    getContext(),
+                    R.raw.rsa_key2_pkcs8,
+                    R.raw.rsa_key2_cert,
                     importParams);
         } else {
-            throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+            throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
         }
     }
 
-    private Key importDefaultKatDecryptionKey(String transformation,
-            KeyProtection importParams) throws Exception {
-        String transformationUpperCase = transformation.toUpperCase();
-        if (transformationUpperCase.startsWith("RSA/")) {
-            return TestUtils.importIntoAndroidKeyStore("testRsa",
-                    TestUtils.getRawResPrivateKey(getContext(), R.raw.rsa_key2_pkcs8),
-                    TestUtils.getRawResX509Certificate(getContext(), R.raw.rsa_key2_cert),
-                    importParams).getPrivate();
-        } else if (transformationUpperCase.startsWith("AES/")) {
-            return TestUtils.importIntoAndroidKeyStore("testAes",
-                    new SecretKeySpec(AES_KAT_KEY_BYTES, "AES"),
-                    importParams);
+    private ImportedKey importDefaultKatKey(
+            String transformation, int purposes, boolean ivProvidedWhenEncrypting)
+            throws Exception {
+        KeyProtection importParams = TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+                transformation, purposes, ivProvidedWhenEncrypting);
+        return importDefaultKatKey(transformation, importParams);
+    }
+
+    private Collection<ImportedKey> importKatKeys(
+            String transformation, int purposes, boolean ivProvidedWhenEncrypting)
+            throws Exception {
+        KeyProtection importParams = TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+                transformation, purposes, ivProvidedWhenEncrypting);
+        String keyAlgorithm = TestUtils.getCipherKeyAlgorithm(transformation);
+        if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
+            return Arrays.asList(
+                    TestUtils.importIntoAndroidKeyStore(
+                            "testAES128",
+                            new SecretKeySpec(AES128_KAT_KEY_BYTES, "AES"),
+                            importParams),
+                    TestUtils.importIntoAndroidKeyStore(
+                            "testAES192",
+                            new SecretKeySpec(AES192_KAT_KEY_BYTES, "AES"),
+                            importParams),
+                    TestUtils.importIntoAndroidKeyStore(
+                            "testAES256",
+                            new SecretKeySpec(AES256_KAT_KEY_BYTES, "AES"),
+                            importParams)
+                    );
+        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+            return RSASignatureTest.importKatKeyPairs(getContext(), importParams);
         } else {
-            throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+            throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
         }
     }
 
-    private static Key getEncryptionKey(String transformation,
-            Iterable<SecretKey> secretKeys,
-            Iterable<KeyPair> keyPairs) {
-        String transformationUpperCase = transformation.toUpperCase();
-        if (transformationUpperCase.startsWith("RSA/")) {
-            return TestUtils.getKeyPairForKeyAlgorithm("RSA", keyPairs).getPublic();
-        } else if (transformationUpperCase.startsWith("AES/")) {
-            return TestUtils.getKeyForKeyAlgorithm("AES", secretKeys);
-        } else {
-            throw new IllegalArgumentException("Unsupported transformation: " + transformation);
-        }
-    }
-
-    private static Key getDecryptionKey(String transformation,
-            Iterable<SecretKey> secretKeys,
-            Iterable<KeyPair> keyPairs) {
-        String transformationUpperCase = transformation.toUpperCase();
-        if (transformationUpperCase.startsWith("RSA/")) {
-            return TestUtils.getKeyPairForKeyAlgorithm("RSA", keyPairs).getPrivate();
-        } else if (transformationUpperCase.startsWith("AES/")) {
-            return TestUtils.getKeyForKeyAlgorithm("AES", secretKeys);
-        } else {
-            throw new IllegalArgumentException("Unsupported transformation: " + transformation);
-        }
-    }
-
-    private Collection<KeyPair> getDefaultKatKeyPairs() throws Exception {
-        return Arrays.asList(
-                new KeyPair(
-                        TestUtils.getRawResX509Certificate(
-                                getContext(), R.raw.rsa_key2_cert).getPublicKey(),
-                        TestUtils.getRawResPrivateKey(getContext(), R.raw.rsa_key2_pkcs8)));
-    }
-
-    private Collection<KeyPair> importDefaultKatKeyPairs() throws Exception {
-        return Arrays.asList(
-                TestUtils.importIntoAndroidKeyStore(
-                        "testRsa",
-                        TestUtils.getRawResPrivateKey(getContext(), R.raw.rsa_key2_pkcs8),
-                        TestUtils.getRawResX509Certificate(getContext(), R.raw.rsa_key2_cert),
-                        new KeyProtection.Builder(
-                                KeyProperties.PURPOSE_DECRYPT)
-                                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                                .setRandomizedEncryptionRequired(false) // due to PADDING_NONE
-                                .setDigests(KeyProperties.DIGEST_NONE) // due to OAEP
-                                .build()));
-    }
-
-    private Collection<SecretKey> getDefaultKatSecretKeys() throws Exception {
-        return Arrays.asList((SecretKey) new SecretKeySpec(AES_KAT_KEY_BYTES, "AES"));
-    }
-
-    private Collection<SecretKey> importDefaultKatSecretKeys() throws Exception {
-        return Arrays.asList(
-                TestUtils.importIntoAndroidKeyStore("testAes",
-                        new SecretKeySpec(AES_KAT_KEY_BYTES, "AES"),
-                        new KeyProtection.Builder(
-                                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
-                                .setBlockModes(KeyProperties.BLOCK_MODE_ECB,
-                                        KeyProperties.BLOCK_MODE_CBC,
-                                        KeyProperties.BLOCK_MODE_CTR,
-                                        KeyProperties.BLOCK_MODE_GCM)
-                                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
-                                .setRandomizedEncryptionRequired(false) // due to ECB
-                                .build()));
-    }
-
     private static boolean isSymmetric(String transformation) {
-        return transformation.toUpperCase(Locale.US).startsWith("AES/");
+        return TestUtils.isCipherSymmetric(transformation);
+    }
+
+    private static byte[] truncatePlaintextIfNecessary(
+            String transformation, Key encryptionKey, byte[] plaintext) {
+        int maxSupportedPlaintextSizeBytes =
+                TestUtils.getMaxSupportedPlaintextInputSizeBytes(
+                        transformation, encryptionKey);
+        if (plaintext.length <= maxSupportedPlaintextSizeBytes) {
+            // No need to truncate
+            return plaintext;
+        } else if (maxSupportedPlaintextSizeBytes < 0) {
+            // Key too short to encrypt anything at all using this transformation
+            return null;
+        } else {
+            // Truncate plaintext to exercise this transformation with this key
+            return Arrays.copyOf(plaintext, maxSupportedPlaintextSizeBytes);
+        }
     }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/EmptyArray.java b/tests/tests/keystore/src/android/keystore/cts/EmptyArray.java
index 90ac2c4..bf08b73 100644
--- a/tests/tests/keystore/src/android/keystore/cts/EmptyArray.java
+++ b/tests/tests/keystore/src/android/keystore/cts/EmptyArray.java
@@ -20,4 +20,5 @@
     private EmptyArray() {}
 
     public static final byte[] BYTE = new byte[0];
+    public static final String[] STRING = new String[0];
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/ImportedKey.java b/tests/tests/keystore/src/android/keystore/cts/ImportedKey.java
index 647f8f3..b6c868f 100644
--- a/tests/tests/keystore/src/android/keystore/cts/ImportedKey.java
+++ b/tests/tests/keystore/src/android/keystore/cts/ImportedKey.java
@@ -38,6 +38,15 @@
         mKeystoreBackedSecretKey = null;
     }
 
+    public ImportedKey(String alias, SecretKey original, SecretKey keystoreBacked) {
+        mAlias = alias;
+        mSymmetric = true;
+        mOriginalKeyPair = null;
+        mKeystoreBackedKeyPair = null;
+        mOriginalSecretKey = original;
+        mKeystoreBackedSecretKey = keystoreBacked;
+    }
+
     public String getAlias() {
         return mAlias;
     }
@@ -134,7 +143,7 @@
     }
 
     private void checkIsSecretKey() {
-        if (mSymmetric) {
+        if (!mSymmetric) {
             throw new IllegalStateException("Not a SecretKey");
         }
     }
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
index 88d5421..6deaed4 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyGeneratorTest.java
@@ -66,7 +66,7 @@
         DEFAULT_KEY_SIZES.put("HmacSHA512", 512);
     }
 
-    private static final int[] AES_SUPPORTED_KEY_SIZES = new int[] {128, 192, 256};
+    static final int[] AES_SUPPORTED_KEY_SIZES = new int[] {128, 192, 256};
 
     public void testAlgorithmList() {
         // Assert that Android Keystore Provider exposes exactly the expected KeyGenerator
@@ -244,7 +244,7 @@
                     assertEquals(0, rng.getOutputSizeBytes());
                 }
             } catch (Throwable e) {
-                throw new RuntimeException("Failed to key size " + i, e);
+                throw new RuntimeException("Failed for key size " + i, e);
             }
         }
     }
diff --git a/tests/tests/keystore/src/android/keystore/cts/MacTest.java b/tests/tests/keystore/src/android/keystore/cts/MacTest.java
index 289e31b..c41dcda 100644
--- a/tests/tests/keystore/src/android/keystore/cts/MacTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/MacTest.java
@@ -26,6 +26,7 @@
 import java.security.Provider.Service;
 import java.security.Security;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.Locale;
@@ -58,22 +59,103 @@
 
     private static final byte[] SHORT_MSG_KAT_MESSAGE = HexEncoding.decode("a16037e3c901c9a1ab");
 
-    private static final Map<String, byte[]> SHORT_MSG_KAT_MACS =
-            new TreeMap<String, byte[]>(String.CASE_INSENSITIVE_ORDER);
+    private static final Map<String, Collection<KatVector>> SHORT_MSG_KAT_MACS =
+            new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
     static {
         // From RI
-        SHORT_MSG_KAT_MACS.put("HmacSHA1", HexEncoding.decode(
-                "47d5677267f0efe1f7416bf504b210765674ef50"));
-        SHORT_MSG_KAT_MACS.put("HmacSHA224", HexEncoding.decode(
-                "03a8bbcd05e7166bff5b0b2368709a0c61c0b9d94f1b4d65c0e04948"));
-        SHORT_MSG_KAT_MACS.put("HmacSHA256", HexEncoding.decode(
-                "17feed3de0b2d53b69b228c2d9d26e9d57b314c50d36662596777f49445df729"));
-        SHORT_MSG_KAT_MACS.put("HmacSHA384", HexEncoding.decode(
-                "26e034c6696d28722ffc446ff0994f835e616cc704517d283a29648aee1eca5569c792ada8a176cdc7"
-                + "813a87437e4ea0"));
-        SHORT_MSG_KAT_MACS.put("HmacSHA512", HexEncoding.decode(
-                "15cff189d754d6612bd18d157c8e59ac2ecd9a4b97b2ef343b7778130f7741795d5d2dc2b7addb9a36"
-                + "7677ad57833b42bfa0733f49b57afd6bc32cddc0dcebec"));
+        SHORT_MSG_KAT_MACS.put("HmacSHA1", Arrays.asList(
+                new KatVector(HexEncoding.decode("4818c5466a1d05bc8f6ad4"),
+                        HexEncoding.decode("aa178e4d174dc90d1ac3d22386c32d1af82396e7")),
+                new KatVector(HexEncoding.decode("8d07f59f63fc493711a2217d8603cbc4d9874c58"),
+                        HexEncoding.decode("002215de36f8836e966ff459af80c4b31fe0ca27")),
+                new KatVector(HexEncoding.decode("f85f1c94023968729e3b4a7f495bf31c283b710662"),
+                        HexEncoding.decode("f518db3015203606ea15145ad16b3d981db32a77"))));
+        SHORT_MSG_KAT_MACS.put("HmacSHA224", Arrays.asList(
+                new KatVector(
+                        HexEncoding.decode(
+                                "ed959815cc03f01e19ce93c1a3318ae87a905b7c27351d571ee858"),
+                        HexEncoding.decode(
+                                "abece4641458461f8b6a46f7daa61fc6119344c5c4bb5e7967da0e3e")),
+                new KatVector(
+                        HexEncoding.decode(
+                                "9150e1ad6a9d12370a2423a5d95e9bc2dd73b4ee9bc96b03c9cc2fba"),
+                        HexEncoding.decode(
+                                "523aa06730d1abe7da2ba94a966cd20db56c771f1899e2850c31158c")),
+                new KatVector(
+                        HexEncoding.decode(
+                                "424d5e7375c2040543f76b97451b1c074ee93b81ad24cef23800ebfe529a74ee2b"
+                                ),
+                        HexEncoding.decode(
+                                "7627a86d829f45e3295a25813219ed5291f80029b972192d32a845c3"))));
+        SHORT_MSG_KAT_MACS.put("HmacSHA256", Arrays.asList(
+                new KatVector(
+                        HexEncoding.decode(
+                                "74a7ec4c79419d76fa5d3bdbedc17e5bebf0ee011c609b9f4c9126091613"),
+                        HexEncoding.decode(
+                                "c17b62519155b0d7f005f465becf9a1610635ae46a2c4d2b255851f201689ba5"
+                                )),
+                new KatVector(
+                        HexEncoding.decode(
+                                "42b44e6a1600bed10ca6c6dc24df2871790f948e73f9457fa4889c340cf69496"),
+                        HexEncoding.decode(
+                                "e9082a5db98c8086ad306ac23a1da9478eb5733757af6b1148d25fa1459290de")
+                                ),
+                new KatVector(
+                        HexEncoding.decode(
+                                "20bfc407c62022fea95f046f8ade6ee4b232665a9e97f75d3e35f1a9447991651a"
+                                        ),
+                        HexEncoding.decode(
+                                "dbf10ca8c362aa665562065e76e42beb19444f61ab0828438714c82779b71a0d"
+                                ))));
+        SHORT_MSG_KAT_MACS.put("HmacSHA384", Arrays.asList(
+                new KatVector(
+                        HexEncoding.decode(
+                                "7d277b2ec95fca68fe6ce0b665b28b48e128762714c66ca2c3405b432f6ab835e3"
+                                ),
+                        HexEncoding.decode(
+                                "bf8555816d8fa058e1d0ed4be23abda522adfae629b6a8819dcc2416d00507782a"
+                                        + "c714fdbfc7a340da4e6cf646a619f1")),
+                new KatVector(
+                        HexEncoding.decode(
+                                "7b30abe948ceab9d94965f274fd2a21c966aa9bdf06476f94a0bcc6c20fd5d2bdc"
+                                        + "e21af7c6fdf6017bce342a701f55c3"),
+                        HexEncoding.decode(
+                                "0fe51798528407119a5884f65ad76409983e978e25ab8f82aa412c08e76c8065d2"
+                                        + "6dfdb1935de49036fb24262a532a29")),
+                new KatVector(
+                        HexEncoding.decode(
+                                "26f232a40ada35850a14edf8f23c9ca97898ac0fa18640e5a7835230fa68f630b1"
+                                        + "c4579bc059c318bb2c5da609db13f1567fa175a6439e1d729713d1fa"
+                                        + "1039a3db"),
+                        HexEncoding.decode(
+                                "a086c610a382c24bb05e8bdd12cffc01055ec98a8f071239360cf8135205ffee33"
+                                        + "2da2134bed2ec3efde8bf145d1d257"))));
+        SHORT_MSG_KAT_MACS.put("HmacSHA512", Arrays.asList(
+                new KatVector(
+                        HexEncoding.decode(
+                                "46fb12ef48d4e8162f0828a66c9f7124de"),
+                        HexEncoding.decode(
+                                "036320b51376f5840b03fdababac53c189d4d6b35f26f562a909f8ecac4a02c244"
+                                        + "bfddc8f4eb89e0d0909fd2d8a46b796175e619cff215a675ce309540"
+                                        + "42b1c9")),
+                new KatVector(
+                        HexEncoding.decode(
+                                "45b3f16b1a247dd76f72ab2d5019f87b94efeb9a2fc01da3ca347050302dbda9c1"
+                                        + "19cf991aaa30b747c808ec6bc19be7b5ae5e66176e38f222347a1659"
+                                        + "15d007"),
+                        HexEncoding.decode(
+                                "92817ce36858ccad20a903e15952565d241ebaa87e07655754470090f1c6b9252a"
+                                        + "cff9b873f36840fa8fdaaf91c6f9de3b82f46de0b1fdfa584eaf27de"
+                                        + "f52c65")),
+                new KatVector(
+                        HexEncoding.decode(
+                                "e91630c69c8c294755e27e5ccf01fe09e06de6c4e423c1c4ef0ac9b67f9af3cc6b"
+                                        + "bc6292d18cf6e76738888a948b49f9509b44eb3af6974ca7e61f5208"
+                                        + "b9f7dca3"),
+                        HexEncoding.decode(
+                                "6ff5616e9c38cef3d20076841c65b8747193eb8033ea61e8693715109e0e448966"
+                                        + "3d8abcb2b7cf0911e461202112819fb8650ba02bdce08aa0d24b3873"
+                                        + "30f18f"))));
     }
 
     private static final byte[] LONG_MSG_KAT_SEED = SHORT_MSG_KAT_MESSAGE;
@@ -141,6 +223,27 @@
         }
     }
 
+    public void testMacGeneratedForEmptyMessage() throws Exception {
+        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(provider);
+        for (String algorithm : EXPECTED_ALGORITHMS) {
+            try {
+                SecretKey key = importDefaultKatKey(algorithm);
+
+                // Generate a MAC
+                Mac mac = Mac.getInstance(algorithm, provider);
+                mac.init(key);
+                byte[] macBytes = mac.doFinal();
+                assertNotNull(macBytes);
+                if (macBytes.length == 0) {
+                    fail("Empty MAC");
+                }
+            } catch (Throwable e) {
+                throw new RuntimeException(algorithm + " failed", e);
+            }
+        }
+    }
+
     public void testMacGeneratedByAndroidKeyStoreVerifiesByAndroidKeyStore() throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
         assertNotNull(provider);
@@ -213,24 +316,33 @@
         byte[] message = SHORT_MSG_KAT_MESSAGE;
 
         for (String algorithm : EXPECTED_ALGORITHMS) {
-            try {
-                SecretKey key = importDefaultKatKey(algorithm);
+            for (KatVector testVector : SHORT_MSG_KAT_MACS.get(algorithm)) {
+                byte[] keyBytes = testVector.key;
+                try {
+                    SecretKey key = TestUtils.importIntoAndroidKeyStore(
+                            "test",
+                            new SecretKeySpec(keyBytes, algorithm),
+                            getWorkingImportParams(algorithm)).getKeystoreBackedSecretKey();
 
-                byte[] goodMacBytes = SHORT_MSG_KAT_MACS.get(algorithm);
-                assertNotNull(goodMacBytes);
-                assertMacVerifiesOneShot(algorithm, key, message, goodMacBytes);
-                assertMacVerifiesFedOneByteAtATime(algorithm, key, message, goodMacBytes);
-                assertMacVerifiesFedUsingFixedSizeChunks(algorithm, key, message, goodMacBytes, 3);
+                    byte[] goodMacBytes = testVector.mac.clone();
+                    assertNotNull(goodMacBytes);
+                    assertMacVerifiesOneShot(algorithm, key, message, goodMacBytes);
+                    assertMacVerifiesFedOneByteAtATime(algorithm, key, message, goodMacBytes);
+                    assertMacVerifiesFedUsingFixedSizeChunks(
+                            algorithm, key, message, goodMacBytes, 3);
 
-                byte[] messageWithBitFlip = message.clone();
-                messageWithBitFlip[messageWithBitFlip.length / 2] ^= 1;
-                assertMacDoesNotVerifyOneShot(algorithm, key, messageWithBitFlip, goodMacBytes);
+                    byte[] messageWithBitFlip = message.clone();
+                    messageWithBitFlip[messageWithBitFlip.length / 2] ^= 1;
+                    assertMacDoesNotVerifyOneShot(algorithm, key, messageWithBitFlip, goodMacBytes);
 
-                byte[] goodMacWithBitFlip = goodMacBytes.clone();
-                goodMacWithBitFlip[goodMacWithBitFlip.length / 2] ^= 1;
-                assertMacDoesNotVerifyOneShot(algorithm, key, message, goodMacWithBitFlip);
-            } catch (Throwable e) {
-                throw new RuntimeException("Failed for " + algorithm, e);
+                    byte[] goodMacWithBitFlip = goodMacBytes.clone();
+                    goodMacWithBitFlip[goodMacWithBitFlip.length / 2] ^= 1;
+                    assertMacDoesNotVerifyOneShot(algorithm, key, message, goodMacWithBitFlip);
+                } catch (Throwable e) {
+                    throw new RuntimeException(
+                            "Failed for " + algorithm + " with key " + HexEncoding.encode(keyBytes),
+                            e);
+                }
             }
         }
     }
@@ -261,8 +373,8 @@
 
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
-                KeyProtection.Builder good = getWorkingImportParams(algorithm);
-                assertInitSucceeds(algorithm, good.build());
+                KeyProtection good = getWorkingImportParams(algorithm);
+                assertInitSucceeds(algorithm, good);
                 assertInitThrowsInvalidKeyException(algorithm,
                         TestUtils.buildUpon(good, badPurposes).build());
             } catch (Throwable e) {
@@ -274,12 +386,12 @@
     public void testInitFailsWhenDigestNotAuthorized() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
-                KeyProtection.Builder good = getWorkingImportParams(algorithm);
-                assertInitSucceeds(algorithm, good.build());
+                KeyProtection good = getWorkingImportParams(algorithm);
+                assertInitSucceeds(algorithm, good);
 
                 String badKeyAlgorithm = ("HmacSHA256".equalsIgnoreCase(algorithm))
                         ? "HmacSHA384" : "HmacSHA256";
-                assertInitThrowsInvalidKeyException(algorithm, badKeyAlgorithm, good.build());
+                assertInitThrowsInvalidKeyException(algorithm, badKeyAlgorithm, good);
             } catch (Throwable e) {
                 throw new RuntimeException("Failed for " + algorithm, e);
             }
@@ -289,9 +401,10 @@
     public void testInitFailsWhenKeyNotYetValid() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
-                KeyProtection.Builder good = getWorkingImportParams(algorithm)
-                        .setKeyValidityStart(new Date(System.currentTimeMillis() - DAY_IN_MILLIS));
-                assertInitSucceeds(algorithm, good.build());
+                KeyProtection good = TestUtils.buildUpon(getWorkingImportParams(algorithm))
+                        .setKeyValidityStart(new Date(System.currentTimeMillis() - DAY_IN_MILLIS))
+                        .build();
+                assertInitSucceeds(algorithm, good);
 
                 Date badStartDate = new Date(System.currentTimeMillis() + DAY_IN_MILLIS);
                 assertInitThrowsInvalidKeyException(algorithm,
@@ -305,10 +418,11 @@
     public void testInitFailsWhenKeyNoLongerValidForOrigination() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
-                KeyProtection.Builder good = getWorkingImportParams(algorithm)
+                KeyProtection good = TestUtils.buildUpon(getWorkingImportParams(algorithm))
                         .setKeyValidityForOriginationEnd(
-                                new Date(System.currentTimeMillis() + DAY_IN_MILLIS));
-                assertInitSucceeds(algorithm, good.build());
+                                new Date(System.currentTimeMillis() + DAY_IN_MILLIS))
+                        .build();
+                assertInitSucceeds(algorithm, good);
 
                 Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
                 assertInitThrowsInvalidKeyException(algorithm,
@@ -324,10 +438,11 @@
     public void testInitIgnoresThatKeyNoLongerValidForConsumption() throws Exception {
         for (String algorithm : EXPECTED_ALGORITHMS) {
             try {
-                KeyProtection.Builder good = getWorkingImportParams(algorithm)
+                KeyProtection good = TestUtils.buildUpon(getWorkingImportParams(algorithm))
                         .setKeyValidityForConsumptionEnd(
-                                new Date(System.currentTimeMillis() + DAY_IN_MILLIS));
-                assertInitSucceeds(algorithm, good.build());
+                                new Date(System.currentTimeMillis() + DAY_IN_MILLIS))
+                        .build();
+                assertInitSucceeds(algorithm, good);
 
                 Date badEndDate = new Date(System.currentTimeMillis() - DAY_IN_MILLIS);
                 assertInitSucceeds(algorithm,
@@ -470,11 +585,21 @@
         return TestUtils.importIntoAndroidKeyStore(
                 "test1",
                 getDefaultKatKey(keyAlgorithm),
-                keyProtection);
+                keyProtection).getKeystoreBackedSecretKey();
     }
 
-    private static KeyProtection.Builder getWorkingImportParams(
+    private static KeyProtection getWorkingImportParams(
             @SuppressWarnings("unused") String algorithm) {
-        return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN);
+        return new KeyProtection.Builder(KeyProperties.PURPOSE_SIGN).build();
+    }
+
+    private static class KatVector {
+        public byte[] key;
+        public byte[] mac;
+
+        public KatVector(byte[] key, byte[] mac) {
+            this.key = key;
+            this.mac = mac;
+        }
     }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/RSACipherTest.java b/tests/tests/keystore/src/android/keystore/cts/RSACipherTest.java
new file mode 100644
index 0000000..3403df3
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/RSACipherTest.java
@@ -0,0 +1,251 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.keystore.cts;
+
+import java.math.BigInteger;
+import java.security.PrivateKey;
+import java.security.Provider;
+import java.security.PublicKey;
+import java.security.Security;
+import java.security.interfaces.RSAKey;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+
+import android.security.keystore.KeyProperties;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+
+public class RSACipherTest extends AndroidTestCase {
+
+    private static final String EXPECTED_PROVIDER_NAME = TestUtils.EXPECTED_CRYPTO_OP_PROVIDER_NAME;
+
+    public void testNoPaddingEncryptionAndDecryptionSucceedsWithInputShorterThanModulus()
+            throws Exception {
+        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(provider);
+
+        for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+                TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+                        "RSA/ECB/NoPadding",
+                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+                        false))) {
+            try {
+                PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+                PrivateKey privateKey = key.getKeystoreBackedKeyPair().getPrivate();
+                BigInteger modulus = ((RSAKey) publicKey).getModulus();
+                int modulusSizeBytes = (modulus.bitLength() + 7) / 8;
+
+                // 1-byte long input for which we know the output
+                byte[] input = new byte[] {1};
+                // Because of how RSA works, the output is 1 (left-padded with zero bytes).
+                byte[] expectedOutput = TestUtils.leftPadWithZeroBytes(input, modulusSizeBytes);
+
+                Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+                cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+                MoreAsserts.assertEquals(expectedOutput, cipher.doFinal(input));
+
+                cipher.init(Cipher.DECRYPT_MODE, privateKey);
+                MoreAsserts.assertEquals(expectedOutput, cipher.doFinal(input));
+            } catch (Throwable e) {
+                throw new RuntimeException("Failed for key " + key.getAlias(), e);
+            }
+        }
+    }
+
+    public void testNoPaddingEncryptionSucceedsWithPlaintextOneSmallerThanModulus()
+            throws Exception {
+        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(provider);
+
+        for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+                TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+                        "RSA/ECB/NoPadding",
+                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+                        false))) {
+            try {
+                PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+                PrivateKey privateKey = key.getKeystoreBackedKeyPair().getPrivate();
+                BigInteger modulus = ((RSAKey) publicKey).getModulus();
+
+                // Plaintext is one smaller than the modulus
+                byte[] plaintext =
+                        TestUtils.getBigIntegerMagnitudeBytes(modulus.subtract(BigInteger.ONE));
+                Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+                cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+                byte[] ciphertext = cipher.doFinal(plaintext);
+                cipher.init(Cipher.DECRYPT_MODE, privateKey);
+                MoreAsserts.assertEquals(plaintext, cipher.doFinal(ciphertext));
+            } catch (Throwable e) {
+                throw new RuntimeException("Failed for key " + key.getAlias(), e);
+            }
+        }
+    }
+
+    public void testNoPaddingEncryptionFailsWithPlaintextEqualToModulus() throws Exception {
+        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(provider);
+
+        for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+                TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+                        "RSA/ECB/NoPadding",
+                        KeyProperties.PURPOSE_ENCRYPT ,
+                        false))) {
+            try {
+                PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+                BigInteger modulus = ((RSAKey) publicKey).getModulus();
+
+                // Plaintext is exactly the modulus
+                byte[] plaintext = TestUtils.getBigIntegerMagnitudeBytes(modulus);
+                Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+                cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+                try {
+                    byte[] ciphertext = cipher.doFinal(plaintext);
+                    fail("Unexpectedly produced ciphertext (" + ciphertext.length + " bytes): "
+                            + HexEncoding.encode(ciphertext));
+                } catch (BadPaddingException expected) {}
+            } catch (Throwable e) {
+                throw new RuntimeException("Failed for key " + key.getAlias(), e);
+            }
+        }
+    }
+
+    public void testNoPaddingEncryptionFailsWithPlaintextOneLargerThanModulus() throws Exception {
+        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(provider);
+
+        for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+                TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+                        "RSA/ECB/NoPadding",
+                        KeyProperties.PURPOSE_ENCRYPT,
+                        false))) {
+            try {
+                PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+                BigInteger modulus = ((RSAKey) publicKey).getModulus();
+
+                // Plaintext is one larger than the modulus
+                byte[] plaintext =
+                        TestUtils.getBigIntegerMagnitudeBytes(modulus.add(BigInteger.ONE));
+                Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+                cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+                try {
+                    byte[] ciphertext = cipher.doFinal(plaintext);
+                    fail("Unexpectedly produced ciphertext (" + ciphertext.length + " bytes): "
+                            + HexEncoding.encode(ciphertext));
+                } catch (BadPaddingException expected) {}
+            } catch (Throwable e) {
+                throw new RuntimeException("Failed for key " + key.getAlias(), e);
+            }
+        }
+    }
+
+    public void testNoPaddingEncryptionFailsWithPlaintextOneByteLongerThanModulus()
+            throws Exception {
+        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(provider);
+
+        for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+                TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+                        "RSA/ECB/NoPadding",
+                        KeyProperties.PURPOSE_ENCRYPT,
+                        false))) {
+            try {
+                PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+                BigInteger modulus = ((RSAKey) publicKey).getModulus();
+
+                // Plaintext is one byte longer than the modulus. The message is filled with zeros
+                // (thus being 0 if treated as a BigInteger). This is on purpose, to check that the
+                // Cipher implementation rejects such long message without comparing it to the value
+                // of the modulus.
+                byte[] plaintext = new byte[((modulus.bitLength() + 7) / 8) + 1];
+                Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", provider);
+                cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+                try {
+                    byte[] ciphertext = cipher.doFinal(plaintext);
+                    fail("Unexpectedly produced ciphertext (" + ciphertext.length + " bytes): "
+                            + HexEncoding.encode(ciphertext));
+                } catch (IllegalBlockSizeException expected) {}
+            } catch (Throwable e) {
+                throw new RuntimeException("Failed for key " + key.getAlias(), e);
+            }
+        }
+    }
+
+    public void testNoPaddingDecryptionFailsWithCiphertextOneByteLongerThanModulus()
+            throws Exception {
+        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(provider);
+
+        for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+                TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+                        "RSA/ECB/NoPadding",
+                        KeyProperties.PURPOSE_DECRYPT,
+                        false))) {
+            try {
+                PrivateKey privateKey = key.getKeystoreBackedKeyPair().getPrivate();
+                BigInteger modulus = ((RSAKey) privateKey).getModulus();
+
+                // Ciphertext is one byte longer than the modulus. The message is filled with zeros
+                // (thus being 0 if treated as a BigInteger). This is on purpose, to check that the
+                // Cipher implementation rejects such long message without comparing it to the value
+                // of the modulus.
+                byte[] ciphertext = new byte[((modulus.bitLength() + 7) / 8) + 1];
+                Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", EXPECTED_PROVIDER_NAME);
+                cipher.init(Cipher.DECRYPT_MODE, privateKey);
+                try {
+                    byte[] plaintext = cipher.doFinal(ciphertext);
+                    fail("Unexpectedly produced plaintext (" + ciphertext.length + " bytes): "
+                            + HexEncoding.encode(plaintext));
+                } catch (IllegalBlockSizeException expected) {}
+            } catch (Throwable e) {
+                throw new RuntimeException("Failed for key " + key.getAlias(), e);
+            }
+        }
+    }
+
+    public void testNoPaddingWithZeroMessage() throws Exception {
+        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(provider);
+
+        for (ImportedKey key : RSASignatureTest.importKatKeyPairs(getContext(),
+                TestUtils.getMinimalWorkingImportParametersForCipheringWith(
+                        "RSA/ECB/NoPadding",
+                        KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT,
+                        false))) {
+            try {
+                PublicKey publicKey = key.getKeystoreBackedKeyPair().getPublic();
+                PrivateKey privateKey = key.getKeystoreBackedKeyPair().getPrivate();
+
+                byte[] plaintext = EmptyArray.BYTE;
+                Cipher cipher = Cipher.getInstance("RSA/ECB/NoPadding", EXPECTED_PROVIDER_NAME);
+                cipher.init(Cipher.ENCRYPT_MODE, publicKey);
+                byte[] ciphertext = cipher.doFinal(plaintext);
+                // Ciphertext should be all zero bytes
+                byte[] expectedCiphertext = new byte[(TestUtils.getKeySizeBits(publicKey) + 7) / 8];
+                MoreAsserts.assertEquals(expectedCiphertext, ciphertext);
+
+                cipher.init(Cipher.DECRYPT_MODE, privateKey);
+                // Decrypted plaintext should also be all zero bytes
+                byte[] expectedPlaintext = new byte[expectedCiphertext.length];
+                MoreAsserts.assertEquals(expectedPlaintext, cipher.doFinal(ciphertext));
+            } catch (Throwable e) {
+                throw new RuntimeException("Failed for key " + key.getAlias(), e);
+            }
+        }
+    }
+}
diff --git a/tests/tests/keystore/src/android/keystore/cts/RSASignatureTest.java b/tests/tests/keystore/src/android/keystore/cts/RSASignatureTest.java
index 15d9116..9ae3043 100644
--- a/tests/tests/keystore/src/android/keystore/cts/RSASignatureTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/RSASignatureTest.java
@@ -167,7 +167,7 @@
                         R.raw.rsa_key8_2048_pkcs8, R.raw.rsa_key8_2048_cert, importParams),
                 TestUtils.importIntoAndroidKeyStore("testRSA3072", context,
                         R.raw.rsa_key7_3072_pksc8, R.raw.rsa_key7_3072_cert, importParams),
-                TestUtils.importIntoAndroidKeyStore("testRsa4096", context,
+                TestUtils.importIntoAndroidKeyStore("testRSA4096", context,
                         R.raw.rsa_key4_4096_pkcs8, R.raw.rsa_key4_4096_cert, importParams),
                 });
     }
diff --git a/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java b/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
index 30d1dee..ee6472f 100644
--- a/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/SignatureTest.java
@@ -392,6 +392,58 @@
         }
     }
 
+    public void testValidSignatureGeneratedForEmptyMessage()
+            throws Exception {
+        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(provider);
+        for (String sigAlgorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+            for (ImportedKey key : importKatKeyPairsForSigning(getContext(), sigAlgorithm)) {
+                if (!TestUtils.isKeyLongEnoughForSignatureAlgorithm(
+                        sigAlgorithm, key.getOriginalSigningKey())) {
+                    continue;
+                }
+                try {
+                    KeyPair keyPair = key.getKeystoreBackedKeyPair();
+
+                    // Generate a signature
+                    Signature signature = Signature.getInstance(sigAlgorithm, provider);
+                    signature.initSign(keyPair.getPrivate());
+                    byte[] sigBytes = signature.sign();
+
+                    // Assert that it verifies using our own Provider
+                    signature.initVerify(keyPair.getPublic());
+                    assertTrue(signature.verify(sigBytes));
+                } catch (Throwable e) {
+                    throw new RuntimeException(
+                            "Failed for " + sigAlgorithm + " with key " + key.getAlias(), e);
+                }
+            }
+        }
+    }
+
+    public void testEmptySignatureDoesNotVerify()
+            throws Exception {
+        Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
+        assertNotNull(provider);
+        for (String sigAlgorithm : EXPECTED_SIGNATURE_ALGORITHMS) {
+            for (ImportedKey key : importKatKeyPairsForSigning(getContext(), sigAlgorithm)) {
+                if (!TestUtils.isKeyLongEnoughForSignatureAlgorithm(
+                        sigAlgorithm, key.getOriginalSigningKey())) {
+                    continue;
+                }
+                try {
+                    KeyPair keyPair = key.getKeystoreBackedKeyPair();
+                    Signature signature = Signature.getInstance(sigAlgorithm, provider);
+                    signature.initVerify(keyPair.getPublic());
+                    assertFalse(signature.verify(EmptyArray.BYTE));
+                } catch (Throwable e) {
+                    throw new RuntimeException(
+                            "Failed for " + sigAlgorithm + " with key " + key.getAlias(), e);
+                }
+            }
+        }
+    }
+
     public void testSignatureGeneratedByAndroidKeyStoreVerifiesByAndroidKeyStore()
             throws Exception {
         Provider provider = Security.getProvider(EXPECTED_PROVIDER_NAME);
diff --git a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
index 423c32b..b1ba453 100644
--- a/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
+++ b/tests/tests/keystore/src/android/keystore/cts/TestUtils.java
@@ -414,7 +414,7 @@
                 (PrivateKey) keyStore.getKey(alias, null));
     }
 
-    static SecretKey importIntoAndroidKeyStore(
+    static ImportedKey importIntoAndroidKeyStore(
             String alias,
             SecretKey key,
             KeyProtection keyProtection) throws Exception {
@@ -423,7 +423,7 @@
         keyStore.setEntry(alias,
                 new KeyStore.SecretKeyEntry(key),
                 keyProtection);
-        return (SecretKey) keyStore.getKey(alias, null);
+        return new ImportedKey(alias, key, (SecretKey) keyStore.getKey(alias, null));
     }
 
     static ImportedKey importIntoAndroidKeyStore(
@@ -441,29 +441,13 @@
                     + " Public: " + originalPublicKey.getAlgorithm()
                     + ", private: " + originalPrivateKey.getAlgorithm());
         }
-        String keyAlgorithm = originalPublicKey.getAlgorithm();
-        if (KeyProperties.KEY_ALGORITHM_EC.equalsIgnoreCase(keyAlgorithm)) {
-            ECParameterSpec publicKeyParams = ((ECKey) originalPublicKey).getParams();
-            ECParameterSpec privateKeyParams = ((ECKey) originalPrivateKey).getParams();
-            assertECParameterSpecEqualsIgnoreSeedIfNotPresent(
-                    publicKeyParams, privateKeyParams);
-        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
-            BigInteger publicKeyModulus = ((RSAKey) originalPublicKey).getModulus();
-            BigInteger privateKeyModulus = ((RSAKey) originalPrivateKey).getModulus();
-            if (!publicKeyModulus.equals(privateKeyModulus)) {
-                throw new IllegalArgumentException("RSA key pair modulus mismatch."
-                        + " Public (" + publicKeyModulus.bitLength() + " bit): "
-                        + publicKeyModulus.toString(16)
-                        + ", private (" + privateKeyModulus.bitLength() + " bit): "
-                        + privateKeyModulus.toString(16));
-            }
-        } else {
-            throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
-        }
+        assertKeyPairSelfConsistent(originalPublicKey, originalPrivateKey);
 
         KeyPair keystoreBacked = TestUtils.importIntoAndroidKeyStore(
                 alias, originalPrivateKey, originalCert,
                 params);
+        assertKeyPairSelfConsistent(keystoreBacked);
+        assertKeyPairSelfConsistent(keystoreBacked.getPublic(), originalPrivateKey);
         return new ImportedKey(
                 alias,
                 new KeyPair(originalCert.getPublicKey(), originalPrivateKey),
@@ -634,6 +618,76 @@
         return result;
     }
 
+    static String getCipherKeyAlgorithm(String transformation) {
+        String transformationUpperCase = transformation.toUpperCase(Locale.US);
+        if (transformationUpperCase.startsWith("AES/")) {
+            return KeyProperties.KEY_ALGORITHM_AES;
+        } else if (transformationUpperCase.startsWith("RSA/")) {
+            return KeyProperties.KEY_ALGORITHM_RSA;
+        } else {
+            throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+        }
+    }
+
+    static boolean isCipherSymmetric(String transformation) {
+        String transformationUpperCase = transformation.toUpperCase(Locale.US);
+        if (transformationUpperCase.startsWith("AES/")) {
+            return true;
+        } else if (transformationUpperCase.startsWith("RSA/")) {
+            return false;
+        } else {
+            throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+        }
+    }
+
+    static String getCipherDigest(String transformation) {
+        String transformationUpperCase = transformation.toUpperCase(Locale.US);
+        if (transformationUpperCase.contains("/OAEP")) {
+            if (transformationUpperCase.endsWith("/OAEPPADDING")) {
+                return KeyProperties.DIGEST_SHA1;
+            } else if (transformationUpperCase.endsWith(
+                    "/OAEPWITHSHA-1ANDMGF1PADDING")) {
+                return KeyProperties.DIGEST_SHA1;
+            } else if (transformationUpperCase.endsWith(
+                    "/OAEPWITHSHA-224ANDMGF1PADDING")) {
+                return KeyProperties.DIGEST_SHA224;
+            } else if (transformationUpperCase.endsWith(
+                    "/OAEPWITHSHA-256ANDMGF1PADDING")) {
+                return KeyProperties.DIGEST_SHA256;
+            } else if (transformationUpperCase.endsWith(
+                    "/OAEPWITHSHA-384ANDMGF1PADDING")) {
+                return KeyProperties.DIGEST_SHA384;
+            } else if (transformationUpperCase.endsWith(
+                    "/OAEPWITHSHA-512ANDMGF1PADDING")) {
+                return KeyProperties.DIGEST_SHA512;
+            } else {
+                throw new RuntimeException("Unsupported OAEP padding scheme: "
+                        + transformation);
+            }
+        } else {
+            return null;
+        }
+    }
+
+    static String getCipherEncryptionPadding(String transformation) {
+        String transformationUpperCase = transformation.toUpperCase(Locale.US);
+        if (transformationUpperCase.endsWith("/NOPADDING")) {
+            return KeyProperties.ENCRYPTION_PADDING_NONE;
+        } else if (transformationUpperCase.endsWith("/PKCS7PADDING")) {
+            return KeyProperties.ENCRYPTION_PADDING_PKCS7;
+        } else if (transformationUpperCase.endsWith("/PKCS1PADDING")) {
+            return KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1;
+        } else if (transformationUpperCase.split("/")[2].startsWith("OAEP")) {
+            return KeyProperties.ENCRYPTION_PADDING_RSA_OAEP;
+        } else {
+            throw new IllegalArgumentException("Unsupported transformation: " + transformation);
+        }
+    }
+
+    static String getCipherBlockMode(String transformation) {
+        return transformation.split("/")[1].toUpperCase(Locale.US);
+    }
+
     static String getSignatureAlgorithmDigest(String algorithm) {
         String algorithmUpperCase = algorithm.toUpperCase(Locale.US);
         int withIndex = algorithmUpperCase.indexOf("WITH");
@@ -703,6 +757,32 @@
         }
     }
 
+    static int getMaxSupportedPlaintextInputSizeBytes(String transformation, Key key) {
+        String keyAlgorithm = getCipherKeyAlgorithm(transformation);
+        if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
+            return Integer.MAX_VALUE;
+        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+            String encryptionPadding = getCipherEncryptionPadding(transformation);
+            int modulusSizeBytes = (getKeySizeBits(key) + 7) / 8;
+            if (KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(encryptionPadding)) {
+                return modulusSizeBytes - 1;
+            } else if (KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1.equalsIgnoreCase(
+                    encryptionPadding)) {
+                return modulusSizeBytes - 11;
+            } else if (KeyProperties.ENCRYPTION_PADDING_RSA_OAEP.equalsIgnoreCase(
+                    encryptionPadding)) {
+                String digest = getCipherDigest(transformation);
+                int digestOutputSizeBytes = (getDigestOutputSizeBits(digest) + 7) / 8;
+                return modulusSizeBytes - 2 * digestOutputSizeBytes - 2;
+            } else {
+                throw new IllegalArgumentException(
+                        "Unsupported encryption padding scheme: " + encryptionPadding);
+            }
+        } else {
+            throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
+        }
+    }
+
     static int getDigestOutputSizeBits(String digest) {
         if (KeyProperties.DIGEST_NONE.equals(digest)) {
             return -1;
@@ -776,4 +856,50 @@
                     "Unsupported signature algorithm: " + signatureAlgorithm);
         }
     }
+
+    static KeyProtection getMinimalWorkingImportParametersForCipheringWith(
+            String transformation, int purposes, boolean ivProvidedWhenEncrypting) {
+        String keyAlgorithm = TestUtils.getCipherKeyAlgorithm(transformation);
+        if (KeyProperties.KEY_ALGORITHM_AES.equalsIgnoreCase(keyAlgorithm)) {
+            String encryptionPadding = TestUtils.getCipherEncryptionPadding(transformation);
+            String blockMode = TestUtils.getCipherBlockMode(transformation);
+            boolean randomizedEncryptionRequired = true;
+            if (KeyProperties.BLOCK_MODE_ECB.equalsIgnoreCase(blockMode)) {
+                randomizedEncryptionRequired = false;
+            } else if ((ivProvidedWhenEncrypting)
+                    && ((purposes & KeyProperties.PURPOSE_ENCRYPT) != 0)) {
+                randomizedEncryptionRequired = false;
+            }
+            return new KeyProtection.Builder(
+                    purposes)
+                    .setBlockModes(blockMode)
+                    .setEncryptionPaddings(encryptionPadding)
+                    .setRandomizedEncryptionRequired(randomizedEncryptionRequired)
+                    .build();
+        } else if (KeyProperties.KEY_ALGORITHM_RSA.equalsIgnoreCase(keyAlgorithm)) {
+            String digest = TestUtils.getCipherDigest(transformation);
+            String encryptionPadding = TestUtils.getCipherEncryptionPadding(transformation);
+            boolean randomizedEncryptionRequired =
+                    !KeyProperties.ENCRYPTION_PADDING_NONE.equalsIgnoreCase(encryptionPadding);
+            return new KeyProtection.Builder(
+                    purposes)
+                    .setDigests((digest != null) ? new String[] {digest} : EmptyArray.STRING)
+                    .setEncryptionPaddings(encryptionPadding)
+                    .setRandomizedEncryptionRequired(randomizedEncryptionRequired)
+                    .build();
+        } else {
+            throw new IllegalArgumentException("Unsupported key algorithm: " + keyAlgorithm);
+        }
+    }
+
+    static byte[] getBigIntegerMagnitudeBytes(BigInteger value) {
+        return removeLeadingZeroByteIfPresent(value.toByteArray());
+    }
+
+    private static byte[] removeLeadingZeroByteIfPresent(byte[] value) {
+        if ((value.length < 1) || (value[0] != 0)) {
+            return value;
+        }
+        return TestUtils.subarray(value, 1, value.length - 1);
+    }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/TransparentSecretKey.java b/tests/tests/keystore/src/android/keystore/cts/TransparentSecretKey.java
new file mode 100644
index 0000000..6e74dc0
--- /dev/null
+++ b/tests/tests/keystore/src/android/keystore/cts/TransparentSecretKey.java
@@ -0,0 +1,50 @@
+/*
+ * 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.keystore.cts;
+
+import javax.crypto.SecretKey;
+
+/**
+ * {@link SecretKey} which exposes its key material. The two reasons for the existence of this class
+ * are: (1) to help test that classes under test don't assume that all transparent secret keys are
+ * instances of {@link SecretKeySpec}, and (2) because {@code SecretKeySpec} rejects zero-length
+ * key material which is needed in some tests.
+ */
+public class TransparentSecretKey implements SecretKey {
+    private final String mAlgorithm;
+    private final byte[] mKeyMaterial;
+
+    public TransparentSecretKey(byte[] keyMaterial, String algorithm) {
+        mAlgorithm = algorithm;
+        mKeyMaterial = keyMaterial.clone();
+    }
+
+    @Override
+    public String getAlgorithm() {
+        return mAlgorithm;
+    }
+
+    @Override
+    public byte[] getEncoded() {
+        return mKeyMaterial;
+    }
+
+    @Override
+    public String getFormat() {
+        return "RAW";
+    }
+}
diff --git a/tests/tests/media/Android.mk b/tests/tests/media/Android.mk
index 43e3e89..13daca6 100644
--- a/tests/tests/media/Android.mk
+++ b/tests/tests/media/Android.mk
@@ -41,7 +41,7 @@
 LOCAL_STATIC_JAVA_LIBRARIES := \
     ctsmediautil ctsdeviceutil ctstestserver ctstestrunner
 
-LOCAL_JNI_SHARED_LIBRARIES := libctsmediacodec_jni
+LOCAL_JNI_SHARED_LIBRARIES := libctsmediacodec_jni libaudio_jni
 
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
diff --git a/tests/tests/media/libaudiojni/Android.mk b/tests/tests/media/libaudiojni/Android.mk
new file mode 100644
index 0000000..a6c1bfc
--- /dev/null
+++ b/tests/tests/media/libaudiojni/Android.mk
@@ -0,0 +1,39 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE    := libaudio_jni
+
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := \
+	appendix-b-1-1-buffer-queue.cpp \
+	appendix-b-1-2-recording.cpp \
+	audio-record-native.cpp \
+	audio-track-native.cpp \
+	sl-utils.cpp
+
+LOCAL_C_INCLUDES := \
+	$(JNI_H_INCLUDE) \
+	system/core/include
+
+LOCAL_C_INCLUDES += $(call include-path-for, libaudiojni) \
+	$(call include-path-for, wilhelm)
+
+LOCAL_SHARED_LIBRARIES := libandroid liblog libnativehelper libOpenSLES libutils
+
+include $(BUILD_SHARED_LIBRARY)
diff --git a/tests/tests/media/libaudiojni/Blob.h b/tests/tests/media/libaudiojni/Blob.h
new file mode 100644
index 0000000..134232c
--- /dev/null
+++ b/tests/tests/media/libaudiojni/Blob.h
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_BLOB_H
+#define ANDROID_BLOB_H
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+namespace android {
+
+// read only byte buffer like object
+
+class BlobReadOnly {
+public:
+    BlobReadOnly(const void *data, size_t size, bool byReference) :
+        mMem(byReference ? NULL : malloc(size)),
+        mData(byReference ? data : mMem),
+        mSize(size) {
+        if (!byReference) {
+            memcpy(mMem, data, size);
+        }
+    }
+    ~BlobReadOnly() {
+        free(mMem);
+    }
+
+private:
+          void * const mMem;
+
+public:
+    const void * const mData;
+          const size_t mSize;
+};
+
+// read/write byte buffer like object
+
+class Blob {
+public:
+    Blob(size_t size) :
+        mData(malloc(size)),
+        mOffset(0),
+        mSize(size),
+        mMem(mData) { }
+
+    // by reference
+    Blob(void *data, size_t size) :
+        mData(data),
+        mOffset(0),
+        mSize(size),
+        mMem(NULL) { }
+
+    ~Blob() {
+        free(mMem);
+    }
+
+    void * const mData;
+          size_t mOffset;
+    const size_t mSize;
+
+private:
+    void * const mMem;
+};
+
+} // namespace android
+
+#endif // ANDROID_BLOB_H
diff --git a/tests/tests/media/libaudiojni/Gate.h b/tests/tests/media/libaudiojni/Gate.h
new file mode 100644
index 0000000..dfc15b7
--- /dev/null
+++ b/tests/tests/media/libaudiojni/Gate.h
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_GATE_H
+#define ANDROID_GATE_H
+
+#include <stdint.h>
+#include <mutex>
+
+namespace android {
+
+// Gate is a synchronization object.
+//
+// Threads will pass if it is open.
+// Threads will block (wait) if it is closed.
+//
+// When a gate is opened, all waiting threads will pass through.
+//
+// Since gate holds no external locks, consistency with external
+// state needs to be handled elsewhere.
+//
+// We use mWaitCount to indicate the number of threads that have
+// arrived at the gate via wait().  Each thread entering
+// wait obtains a unique waitId (which is the current mWaitCount).
+// This can be viewed as a sequence number.
+//
+// We use mPassCount to indicate the number of threads that have
+// passed the gate.  If the waitId is less than or equal to the mPassCount
+// then that thread has passed the gate.  An open gate sets mPassedCount
+// to the current mWaitCount, allowing all prior threads to pass.
+//
+// See sync_timeline, sync_pt, etc. for graphics.
+
+class Gate {
+public:
+    Gate(bool open = false) :
+        mOpen(open),
+        mExit(false),
+        mWaitCount(0),
+        mPassCount(0)
+    { }
+
+    // waits for the gate to open, returns immediately if gate is already open.
+    //
+    // Do not hold a monitor lock while calling this.
+    //
+    // returns true if we passed the gate normally
+    //         false if gate is terminated and we didn't pass the gate.
+    bool wait() {
+        std::unique_lock<std::mutex> l(mLock);
+        size_t waitId = ++mWaitCount;
+        if (mOpen) {
+            mPassCount = waitId; // let me through
+        }
+        while (!passedGate_l(waitId) && !mExit) {
+            mCondition.wait(l);
+        }
+        return passedGate_l(waitId);
+    }
+
+    // close the gate.
+    void closeGate() {
+        std::lock_guard<std::mutex> l(mLock);
+        mOpen = false;
+        mExit = false;
+    }
+
+    // open the gate.
+    // signal to all waiters it is okay to go.
+    void openGate() {
+        std::lock_guard<std::mutex> l(mLock);
+        mOpen = true;
+        mExit = false;
+        if (waiters_l() > 0) {
+            mPassCount = mWaitCount;  // allow waiting threads to go through
+            // unoptimized pthreads will wake thread to find we still hold lock.
+            mCondition.notify_all();
+        }
+    }
+
+    // terminate (term has expired).
+    // all threads allowed to pass regardless of whether the gate is open or closed.
+    void terminate() {
+        std::lock_guard<std::mutex> l(mLock);
+        mExit = true;
+        if (waiters_l() > 0) {
+            // unoptimized pthreads will wake thread to find we still hold lock.
+            mCondition.notify_all();
+        }
+    }
+
+    bool isOpen() {
+        std::lock_guard<std::mutex> l(mLock);
+        return mOpen;
+    }
+
+    // return how many waiters are at the gate.
+    size_t waiters() {
+        std::lock_guard<std::mutex> l(mLock);
+        return waiters_l();
+    }
+
+private:
+    bool                    mOpen;
+    bool                    mExit;
+    size_t                  mWaitCount;  // total number of threads that have called wait()
+    size_t                  mPassCount;  // total number of threads passed the gate.
+    std::condition_variable mCondition;
+    std::mutex              mLock;
+
+    // return how many waiters are at the gate.
+    inline size_t waiters_l() {
+        return mWaitCount - mPassCount;
+    }
+
+    // return whether the waitId (from mWaitCount) has passed through the gate
+    inline bool passedGate_l(size_t waitId) {
+        return (ssize_t)(waitId - mPassCount) <= 0;
+    }
+};
+
+} // namespace android
+
+#endif // ANDROID_GATE_H
diff --git a/tests/tests/media/libaudiojni/appendix-b-1-1-buffer-queue.cpp b/tests/tests/media/libaudiojni/appendix-b-1-1-buffer-queue.cpp
new file mode 100644
index 0000000..5bb88a7
--- /dev/null
+++ b/tests/tests/media/libaudiojni/appendix-b-1-1-buffer-queue.cpp
@@ -0,0 +1,250 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "OpenSL-ES-Test-B-1-1-Buffer-Queue"
+
+#include "sl-utils.h"
+
+/*
+ * See https://www.khronos.org/registry/sles/specs/OpenSL_ES_Specification_1.0.1.pdf
+ * Appendix B.1.1 sample code.
+ *
+ * Minor edits made to conform to Android coding style.
+ *
+ * Correction to code: SL_IID_VOLUME is now made optional for the mixer.
+ * It isn't supported on the standard Android mixer, but it is supported on the player.
+ */
+
+#define MAX_NUMBER_INTERFACES 3
+
+/* Local storage for Audio data in 16 bit words */
+#define AUDIO_DATA_STORAGE_SIZE 4096
+
+#define AUDIO_DATA_SEGMENTS 8
+
+/* Audio data buffer size in 16 bit words. 8 data segments are used in
+   this simple example */
+#define AUDIO_DATA_BUFFER_SIZE (AUDIO_DATA_STORAGE_SIZE / AUDIO_DATA_SEGMENTS)
+
+/* Structure for passing information to callback function */
+typedef struct  {
+    SLPlayItf playItf;
+    SLint16  *pDataBase; // Base address of local audio data storage
+    SLint16  *pData;     // Current address of local audio data storage
+    SLuint32  size;
+} CallbackCntxt;
+
+/* Local storage for Audio data */
+static SLint16 pcmData[AUDIO_DATA_STORAGE_SIZE];
+
+/* Callback for Buffer Queue events */
+static void BufferQueueCallback(
+        SLBufferQueueItf queueItf,
+        void *pContext)
+{
+    SLresult res;
+    CallbackCntxt *pCntxt = (CallbackCntxt*)pContext;
+    if (pCntxt->pData < (pCntxt->pDataBase + pCntxt->size)) {
+        res = (*queueItf)->Enqueue(queueItf, (void *)pCntxt->pData,
+                sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
+        ALOGE_IF(res != SL_RESULT_SUCCESS, "error: %s", android::getSLErrStr(res));
+        /* Increase data pointer by buffer size */
+        pCntxt->pData += AUDIO_DATA_BUFFER_SIZE;
+    }
+}
+
+/* Play some music from a buffer queue */
+static void TestPlayMusicBufferQueue(SLObjectItf sl)
+{
+    SLEngineItf EngineItf;
+
+    SLresult res;
+
+    SLDataSource audioSource;
+    SLDataLocator_BufferQueue bufferQueue;
+    SLDataFormat_PCM pcm;
+
+    SLDataSink audioSink;
+    SLDataLocator_OutputMix locator_outputmix;
+
+    SLObjectItf player;
+    SLPlayItf playItf;
+    SLBufferQueueItf bufferQueueItf;
+    SLBufferQueueState state;
+
+    SLObjectItf OutputMix;
+    SLVolumeItf volumeItf;
+
+    int i;
+
+    SLboolean required[MAX_NUMBER_INTERFACES];
+    SLInterfaceID iidArray[MAX_NUMBER_INTERFACES];
+
+    /* Callback context for the buffer queue callback function */
+    CallbackCntxt cntxt;
+
+    /* Get the SL Engine Interface which is implicit */
+    res = (*sl)->GetInterface(sl, SL_IID_ENGINE, (void *)&EngineItf);
+    CheckErr(res);
+
+    /* Initialize arrays required[] and iidArray[] */
+    for (i = 0; i < MAX_NUMBER_INTERFACES; i++) {
+        required[i] = SL_BOOLEAN_FALSE;
+        iidArray[i] = SL_IID_NULL;
+    }
+
+    // Set arrays required[] and iidArray[] for VOLUME interface
+    required[0] = SL_BOOLEAN_FALSE; // ANDROID: we don't require this interface
+    iidArray[0] = SL_IID_VOLUME;
+
+#if 0
+    const unsigned interfaces = 1;
+#else
+
+    /* FIXME: Android doesn't properly support optional interfaces (required == false).
+    [3.1.6] When an application requests explicit interfaces during object creation,
+    it can flag any interface as required. If an implementation is unable to satisfy
+    the request for an interface that is not flagged as required (i.e. it is not required),
+    this will not cause the object to fail creation. On the other hand, if the interface
+    is flagged as required and the implementation is unable to satisfy the request
+    for the interface, the object will not be created.
+    */
+    const unsigned interfaces = 0;
+#endif
+    // Create Output Mix object to be used by player
+    res = (*EngineItf)->CreateOutputMix(EngineItf, &OutputMix, interfaces,
+            iidArray, required);
+    CheckErr(res);
+
+    // Realizing the Output Mix object in synchronous mode.
+    res = (*OutputMix)->Realize(OutputMix, SL_BOOLEAN_FALSE);
+    CheckErr(res);
+
+    volumeItf = NULL; // ANDROID: Volume interface on mix object may not be supported
+    res = (*OutputMix)->GetInterface(OutputMix, SL_IID_VOLUME,
+            (void *)&volumeItf);
+
+    /* Setup the data source structure for the buffer queue */
+    bufferQueue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
+    bufferQueue.numBuffers = 4; /* Four buffers in our buffer queue */
+
+    /* Setup the format of the content in the buffer queue */
+    pcm.formatType = SL_DATAFORMAT_PCM;
+    pcm.numChannels = 2;
+    pcm.samplesPerSec = SL_SAMPLINGRATE_44_1;
+    pcm.bitsPerSample = SL_PCMSAMPLEFORMAT_FIXED_16;
+    pcm.containerSize = 16;
+    pcm.channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+    pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
+    audioSource.pFormat = (void *)&pcm;
+    audioSource.pLocator = (void *)&bufferQueue;
+
+    /* Setup the data sink structure */
+    locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
+    locator_outputmix.outputMix = OutputMix;
+    audioSink.pLocator = (void *)&locator_outputmix;
+    audioSink.pFormat = NULL;
+
+    /* Initialize the context for Buffer queue callbacks */
+    cntxt.pDataBase = pcmData;
+    cntxt.pData = cntxt.pDataBase;
+    cntxt.size = sizeof(pcmData) / sizeof(pcmData[0]); // ANDROID: Bug
+
+    /* Set arrays required[] and iidArray[] for SEEK interface
+       (PlayItf is implicit) */
+    required[0] = SL_BOOLEAN_TRUE;
+    iidArray[0] = SL_IID_BUFFERQUEUE;
+
+    /* Create the music player */
+
+    res = (*EngineItf)->CreateAudioPlayer(EngineItf, &player,
+            &audioSource, &audioSink, 1, iidArray, required);
+    CheckErr(res);
+
+    /* Realizing the player in synchronous mode. */
+    res = (*player)->Realize(player, SL_BOOLEAN_FALSE);
+    CheckErr(res);
+
+    /* Get seek and play interfaces */
+    res = (*player)->GetInterface(player, SL_IID_PLAY, (void *)&playItf);
+    CheckErr(res);
+    res = (*player)->GetInterface(player, SL_IID_BUFFERQUEUE,
+            (void *)&bufferQueueItf);
+    CheckErr(res);
+
+    /* Setup to receive buffer queue event callbacks */
+    res = (*bufferQueueItf)->RegisterCallback(bufferQueueItf,
+            BufferQueueCallback, &cntxt /* BUG, was NULL */);
+    CheckErr(res);
+
+    /* Before we start set volume to -3dB (-300mB) */
+    if (volumeItf != NULL) { // ANDROID: Volume interface may not be supported.
+        res = (*volumeItf)->SetVolumeLevel(volumeItf, -300);
+        CheckErr(res);
+    }
+
+    /* Enqueue a few buffers to get the ball rolling */
+    res = (*bufferQueueItf)->Enqueue(bufferQueueItf, cntxt.pData,
+            sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
+    CheckErr(res);
+    cntxt.pData += AUDIO_DATA_BUFFER_SIZE;
+    res = (*bufferQueueItf)->Enqueue(bufferQueueItf, cntxt.pData,
+            sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
+    CheckErr(res);
+    cntxt.pData += AUDIO_DATA_BUFFER_SIZE;
+    res = (*bufferQueueItf)->Enqueue(bufferQueueItf, cntxt.pData,
+            sizeof(SLint16) * AUDIO_DATA_BUFFER_SIZE); /* Size given in bytes. */
+    CheckErr(res);
+    cntxt.pData += AUDIO_DATA_BUFFER_SIZE;
+
+    /* Play the PCM samples using a buffer queue */
+    res = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_PLAYING);
+    CheckErr(res);
+
+    /* Wait until the PCM data is done playing, the buffer queue callback
+       will continue to queue buffers until the entire PCM data has been
+       played. This is indicated by waiting for the count member of the
+       SLBufferQueueState to go to zero.
+     */
+    res = (*bufferQueueItf)->GetState(bufferQueueItf, &state);
+    CheckErr(res);
+
+    while (state.count) {
+        usleep(5 * 1000 /* usec */); // ANDROID: avoid busy waiting
+        (*bufferQueueItf)->GetState(bufferQueueItf, &state);
+    }
+
+    /* Make sure player is stopped */
+    res = (*playItf)->SetPlayState(playItf, SL_PLAYSTATE_STOPPED);
+    CheckErr(res);
+
+    /* Destroy the player */
+    (*player)->Destroy(player);
+
+    /* Destroy Output Mix object */
+    (*OutputMix)->Destroy(OutputMix);
+}
+
+extern "C" void Java_android_media_cts_AudioNativeTest_nativeAppendixBBufferQueue(
+        JNIEnv * /* env */, jclass /* clazz */)
+{
+    SLObjectItf engineObject = android::OpenSLEngine();
+    LOG_ALWAYS_FATAL_IF(engineObject == NULL, "cannot open OpenSL ES engine");
+
+    TestPlayMusicBufferQueue(engineObject);
+    android::CloseSLEngine(engineObject);
+}
diff --git a/tests/tests/media/libaudiojni/appendix-b-1-2-recording.cpp b/tests/tests/media/libaudiojni/appendix-b-1-2-recording.cpp
new file mode 100644
index 0000000..5f6f3aa
--- /dev/null
+++ b/tests/tests/media/libaudiojni/appendix-b-1-2-recording.cpp
@@ -0,0 +1,221 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "OpenSL-ES-Test-B-1-2-Recording"
+
+#include "sl-utils.h"
+
+/*
+ * See https://www.khronos.org/registry/sles/specs/OpenSL_ES_Specification_1.0.1.pdf
+ * Appendix B.1.2 sample code.
+ *
+ * Minor edits made to conform to Android coding style.
+ *
+ * Correction to code: SL_IID_AUDIOIODEVICECAPABILITIES is not supported.
+ * Detection of microphone should be made in Java layer.
+ */
+
+#define MAX_NUMBER_INTERFACES 5
+#define MAX_NUMBER_INPUT_DEVICES 3
+#define POSITION_UPDATE_PERIOD 1000 /* 1 sec */
+
+static void RecordEventCallback(SLRecordItf caller __unused,
+        void *pContext __unused,
+        SLuint32 recordevent __unused)
+{
+    /* Callback code goes here */
+}
+
+/*
+ * Test recording of audio from a microphone into a specified file
+ */
+static void TestAudioRecording(SLObjectItf sl)
+{
+    SLObjectItf recorder;
+    SLRecordItf recordItf;
+    SLEngineItf EngineItf;
+    SLAudioIODeviceCapabilitiesItf AudioIODeviceCapabilitiesItf;
+    SLAudioInputDescriptor AudioInputDescriptor;
+    SLresult res;
+
+    SLDataSource audioSource;
+    SLDataLocator_IODevice locator_mic;
+    SLDeviceVolumeItf devicevolumeItf;
+    SLDataSink audioSink;
+    SLDataLocator_URI uri;
+    SLDataFormat_MIME mime;
+
+    int i;
+    SLboolean required[MAX_NUMBER_INTERFACES];
+    SLInterfaceID iidArray[MAX_NUMBER_INTERFACES];
+
+    SLuint32 InputDeviceIDs[MAX_NUMBER_INPUT_DEVICES];
+    SLint32 numInputs = 0;
+    SLboolean mic_available = SL_BOOLEAN_FALSE;
+    SLuint32 mic_deviceID = 0;
+
+    /* Get the SL Engine Interface which is implicit */
+    res = (*sl)->GetInterface(sl, SL_IID_ENGINE, (void *)&EngineItf);
+    CheckErr(res);
+
+    AudioIODeviceCapabilitiesItf = NULL;
+    /* Get the Audio IO DEVICE CAPABILITIES interface, which is also
+       implicit */
+    res = (*sl)->GetInterface(sl, SL_IID_AUDIOIODEVICECAPABILITIES,
+            (void *)&AudioIODeviceCapabilitiesItf);
+    // ANDROID: obtaining SL_IID_AUDIOIODEVICECAPABILITIES may fail
+    if (AudioIODeviceCapabilitiesItf != NULL ) {
+        numInputs = MAX_NUMBER_INPUT_DEVICES;
+        res = (*AudioIODeviceCapabilitiesItf)->GetAvailableAudioInputs(
+                AudioIODeviceCapabilitiesItf, &numInputs, InputDeviceIDs);
+        CheckErr(res);
+        /* Search for either earpiece microphone or headset microphone input
+           device - with a preference for the latter */
+        for (i = 0; i < numInputs; i++) {
+            res = (*AudioIODeviceCapabilitiesItf)->QueryAudioInputCapabilities(
+                    AudioIODeviceCapabilitiesItf, InputDeviceIDs[i], &AudioInputDescriptor);
+            CheckErr(res);
+            if ((AudioInputDescriptor.deviceConnection == SL_DEVCONNECTION_ATTACHED_WIRED)
+                    && (AudioInputDescriptor.deviceScope == SL_DEVSCOPE_USER)
+                    && (AudioInputDescriptor.deviceLocation == SL_DEVLOCATION_HEADSET)) {
+                mic_deviceID = InputDeviceIDs[i];
+                mic_available = SL_BOOLEAN_TRUE;
+                break;
+            }
+            else if ((AudioInputDescriptor.deviceConnection == SL_DEVCONNECTION_INTEGRATED)
+                    && (AudioInputDescriptor.deviceScope == SL_DEVSCOPE_USER)
+                    && (AudioInputDescriptor.deviceLocation == SL_DEVLOCATION_HANDSET)) {
+                mic_deviceID = InputDeviceIDs[i];
+                mic_available = SL_BOOLEAN_TRUE;
+                break;
+            }
+        }
+    } else {
+        mic_deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
+        mic_available = true;
+    }
+
+    /* If neither of the preferred input audio devices is available, no
+       point in continuing */
+    if (!mic_available) {
+        /* Appropriate error message here */
+        ALOGW("No microphone available");
+        return;
+    }
+
+    /* Initialize arrays required[] and iidArray[] */
+    for (i = 0; i < MAX_NUMBER_INTERFACES; i++) {
+        required[i] = SL_BOOLEAN_FALSE;
+        iidArray[i] = SL_IID_NULL;
+    }
+
+    // ANDROID: the following may fail for volume
+    devicevolumeItf = NULL;
+    /* Get the optional DEVICE VOLUME interface from the engine */
+    res = (*sl)->GetInterface(sl, SL_IID_DEVICEVOLUME,
+            (void *)&devicevolumeItf);
+
+    /* Set recording volume of the microphone to -3 dB */
+    if (devicevolumeItf != NULL) { // ANDROID: Volume may not be supported
+        res = (*devicevolumeItf)->SetVolume(devicevolumeItf, mic_deviceID, -300);
+        CheckErr(res);
+    }
+
+    /* Setup the data source structure */
+    locator_mic.locatorType = SL_DATALOCATOR_IODEVICE;
+    locator_mic.deviceType = SL_IODEVICE_AUDIOINPUT;
+    locator_mic.deviceID = mic_deviceID;
+    locator_mic.device= NULL;
+
+    audioSource.pLocator = (void *)&locator_mic;
+    audioSource.pFormat = NULL;
+
+#if 0
+    /* Setup the data sink structure */
+    uri.locatorType = SL_DATALOCATOR_URI;
+    uri.URI = (SLchar *) "file:///recordsample.wav";
+    mime.formatType = SL_DATAFORMAT_MIME;
+    mime.mimeType = (SLchar *) "audio/x-wav";
+    mime.containerType = SL_CONTAINERTYPE_WAV;
+    audioSink.pLocator = (void *)&uri;
+    audioSink.pFormat = (void *)&mime;
+#else
+    // FIXME: Android requires SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
+    // because the recorder makes the distinction from SL_DATALOCATOR_BUFFERQUEUE
+    // which the player does not.
+    SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
+            SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, 2
+    };
+    SLDataFormat_PCM format_pcm = {
+            SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_16,
+            SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
+            SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN
+    };
+    audioSink = { &loc_bq, &format_pcm };
+#endif
+
+    /* Create audio recorder */
+    res = (*EngineItf)->CreateAudioRecorder(EngineItf, &recorder,
+            &audioSource, &audioSink, 0, iidArray, required);
+    CheckErr(res);
+
+    /* Realizing the recorder in synchronous mode. */
+    res = (*recorder)->Realize(recorder, SL_BOOLEAN_FALSE);
+    CheckErr(res);
+
+    /* Get the RECORD interface - it is an implicit interface */
+    res = (*recorder)->GetInterface(recorder, SL_IID_RECORD, (void *)&recordItf);
+    CheckErr(res);
+
+    // ANDROID: Should register SL_IID_ANDROIDSIMPLEBUFFERQUEUE interface for callback.
+    // but does original SL_DATALOCATOR_BUFFERQUEUE variant work just as well ?
+
+    /* Setup to receive position event callbacks */
+    res = (*recordItf)->RegisterCallback(recordItf, RecordEventCallback, NULL);
+    CheckErr(res);
+
+    /* Set notifications to occur after every second - may be useful in
+       updating a recording progress bar */
+    res = (*recordItf)->SetPositionUpdatePeriod(recordItf, POSITION_UPDATE_PERIOD);
+    CheckErr(res);
+    res = (*recordItf)->SetCallbackEventsMask(recordItf, SL_RECORDEVENT_HEADATNEWPOS);
+    CheckErr(res);
+
+    /* Set the duration of the recording - 30 seconds (30,000
+       milliseconds) */
+    res = (*recordItf)->SetDurationLimit(recordItf, 30000);
+    CheckErr(res);
+
+    /* Record the audio */
+    res = (*recordItf)->SetRecordState(recordItf, SL_RECORDSTATE_RECORDING);
+    CheckErr(res);
+
+    // ANDROID: BUG - we don't wait for anything to record!
+
+    /* Destroy the recorder object */
+    (*recorder)->Destroy(recorder);
+}
+
+extern "C" void Java_android_media_cts_AudioNativeTest_nativeAppendixBRecording(
+        JNIEnv * /* env */, jclass /* clazz */)
+{
+    SLObjectItf engineObject = android::OpenSLEngine();
+    LOG_ALWAYS_FATAL_IF(engineObject == NULL, "cannot open OpenSL ES engine");
+
+    TestAudioRecording(engineObject);
+    android::CloseSLEngine(engineObject);
+}
diff --git a/tests/tests/media/libaudiojni/audio-record-native.cpp b/tests/tests/media/libaudiojni/audio-record-native.cpp
new file mode 100644
index 0000000..9103cdc
--- /dev/null
+++ b/tests/tests/media/libaudiojni/audio-record-native.cpp
@@ -0,0 +1,631 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "audio-record-native"
+
+#include "Blob.h"
+#include "Gate.h"
+#include "sl-utils.h"
+
+#include <deque>
+#include <utils/Errors.h>
+
+// Select whether to use STL shared pointer or to use Android strong pointer.
+// We really don't promote any sharing of this object for its lifetime, but nevertheless could
+// change the shared pointer value on the fly if desired.
+#define USE_SHARED_POINTER
+
+#ifdef USE_SHARED_POINTER
+#include <memory>
+template <typename T> using shared_pointer = std::shared_ptr<T>;
+#else
+#include <utils/RefBase.h>
+template <typename T> using shared_pointer = android::sp<T>;
+#endif
+
+using namespace android;
+
+// Must be kept in sync with Java android.media.cts.AudioRecordNative.ReadFlags
+enum {
+    READ_FLAG_BLOCKING = (1 << 0),
+};
+
+// buffer queue buffers on the OpenSL ES side.
+// The choice can be >= 1.  There is also internal buffering by AudioRecord.
+
+static const size_t BUFFER_SIZE_MSEC = 20;
+
+// TODO: Add a single buffer blocking read mode which does not require additional memory.
+// TODO: Add internal buffer memory (e.g. use circular buffer, right now mallocs on heap).
+
+class AudioRecordNative
+#ifndef USE_SHARED_POINTER
+        : public RefBase // android strong pointers require RefBase
+#endif
+{
+public:
+    AudioRecordNative() :
+        mEngineObj(NULL),
+        mEngine(NULL),
+        mRecordObj(NULL),
+        mRecord(NULL),
+        mBufferQueue(NULL),
+        mRecordState(SL_RECORDSTATE_STOPPED),
+        mBufferSize(0),
+        mNumBuffers(0)
+    { }
+
+    ~AudioRecordNative() {
+        close();
+    }
+
+    typedef std::lock_guard<std::recursive_mutex> auto_lock;
+
+    status_t open(uint32_t numChannels, uint32_t sampleRate, bool useFloat, uint32_t numBuffers) {
+        close();
+        auto_lock l(mLock);
+        mEngineObj = OpenSLEngine();
+        if (mEngineObj == NULL) {
+            ALOGW("cannot create OpenSL ES engine");
+            return INVALID_OPERATION;
+        }
+
+        SLresult res;
+        for (;;) {
+            /* Get the SL Engine Interface which is implicit */
+            res = (*mEngineObj)->GetInterface(mEngineObj, SL_IID_ENGINE, (void *)&mEngine);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            SLDataLocator_IODevice locator_mic;
+            /* Setup the data source structure */
+            locator_mic.locatorType = SL_DATALOCATOR_IODEVICE;
+            locator_mic.deviceType = SL_IODEVICE_AUDIOINPUT;
+            locator_mic.deviceID = SL_DEFAULTDEVICEID_AUDIOINPUT;
+            locator_mic.device= NULL;
+            SLDataSource audioSource;
+            audioSource.pLocator = (void *)&locator_mic;
+            audioSource.pFormat = NULL;
+
+            // FIXME: Android requires SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE
+            // because the recorder makes the distinction from SL_DATALOCATOR_BUFFERQUEUE
+            // which the player does not.
+            SLDataLocator_AndroidSimpleBufferQueue loc_bq = {
+                    SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE, numBuffers
+            };
+#if 0
+            SLDataFormat_PCM pcm = {
+                    SL_DATAFORMAT_PCM, 1, SL_SAMPLINGRATE_16,
+                    SL_PCMSAMPLEFORMAT_FIXED_16, SL_PCMSAMPLEFORMAT_FIXED_16,
+                    SL_SPEAKER_FRONT_LEFT, SL_BYTEORDER_LITTLEENDIAN
+            };
+#else
+            SLAndroidDataFormat_PCM_EX pcm;
+            pcm.formatType = useFloat ? SL_ANDROID_DATAFORMAT_PCM_EX : SL_DATAFORMAT_PCM;
+            pcm.numChannels = numChannels;
+            pcm.sampleRate = sampleRate * 1000;
+            pcm.bitsPerSample = useFloat ?
+                    SL_PCMSAMPLEFORMAT_FIXED_32 : SL_PCMSAMPLEFORMAT_FIXED_16;
+            pcm.containerSize = pcm.bitsPerSample;
+            pcm.channelMask = channelCountToMask(numChannels);
+            pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
+            // additional
+            pcm.representation = useFloat ? SL_ANDROID_PCM_REPRESENTATION_FLOAT
+                                    : SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+#endif
+            SLDataSink audioSink;
+            audioSink = { &loc_bq, &pcm };
+
+            SLboolean required[2];
+            SLInterfaceID iidArray[2];
+            /* Request the AndroidSimpleBufferQueue and AndroidConfiguration interfaces */
+            required[0] = SL_BOOLEAN_TRUE;
+            iidArray[0] = SL_IID_ANDROIDSIMPLEBUFFERQUEUE;
+            required[1] = SL_BOOLEAN_TRUE;
+            iidArray[1] = SL_IID_ANDROIDCONFIGURATION;
+
+            ALOGV("creating recorder");
+            /* Create audio recorder */
+            res = (*mEngine)->CreateAudioRecorder(mEngine, &mRecordObj,
+                    &audioSource, &audioSink, 2, iidArray, required);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            ALOGV("realizing recorder");
+            /* Realizing the recorder in synchronous mode. */
+            res = (*mRecordObj)->Realize(mRecordObj, SL_BOOLEAN_FALSE /* async */);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            ALOGV("geting record interface");
+            /* Get the RECORD interface - it is an implicit interface */
+            res = (*mRecordObj)->GetInterface(mRecordObj, SL_IID_RECORD, (void *)&mRecord);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            ALOGV("geting buffer queue interface");
+            /* Get the buffer queue interface which was explicitly requested */
+            res = (*mRecordObj)->GetInterface(mRecordObj, SL_IID_ANDROIDSIMPLEBUFFERQUEUE,
+                    (void *)&mBufferQueue);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            ALOGV("registering buffer queue interface");
+            /* Setup to receive buffer queue event callbacks */
+            res = (*mBufferQueue)->RegisterCallback(mBufferQueue, BufferQueueCallback, this);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            mBufferSize = (BUFFER_SIZE_MSEC * sampleRate / 1000)
+                    * numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
+            mNumBuffers = numBuffers;
+            // success
+            break;
+        }
+        if (res != SL_RESULT_SUCCESS) {
+            close(); // should be safe to close even with lock held
+            ALOGW("open error %s", android::getSLErrStr(res));
+            return INVALID_OPERATION;
+        }
+        return OK;
+    }
+
+    void close() {
+        SLObjectItf engineObj;
+        SLObjectItf recordObj;
+        {
+            auto_lock l(mLock);
+            (void)stop();
+            // once stopped, we can unregister the callback
+            if (mBufferQueue != NULL) {
+                (void)(*mBufferQueue)->RegisterCallback(
+                        mBufferQueue, NULL /* callback */, NULL /* *pContext */);
+            }
+            (void)flush();
+            engineObj = mEngineObj;
+            recordObj = mRecordObj;
+            // clear out interfaces and objects
+            mRecord = NULL;
+            mBufferQueue = NULL;
+            mEngine = NULL;
+            mRecordObj = NULL;
+            mEngineObj = NULL;
+            mRecordState = SL_RECORDSTATE_STOPPED;
+            mBufferSize = 0;
+            mNumBuffers = 0;
+        }
+        // destroy without lock
+        if (recordObj != NULL) {
+            (*recordObj)->Destroy(recordObj);
+        }
+        if (engineObj) {
+            CloseSLEngine(engineObj);
+        }
+    }
+
+    status_t setRecordState(SLuint32 recordState) {
+        auto_lock l(mLock);
+        if (mRecord == NULL) {
+            return INVALID_OPERATION;
+        }
+        if (recordState == SL_RECORDSTATE_RECORDING) {
+            queueBuffers();
+        }
+        SLresult res = (*mRecord)->SetRecordState(mRecord, recordState);
+        if (res != SL_RESULT_SUCCESS) {
+            ALOGW("setRecordState %d error %s", recordState, android::getSLErrStr(res));
+            return INVALID_OPERATION;
+        }
+        mRecordState = recordState;
+        return OK;
+    }
+
+    SLuint32 getRecordState() {
+        auto_lock l(mLock);
+        if (mRecord == NULL) {
+            return SL_RECORDSTATE_STOPPED;
+        }
+        SLuint32 recordState;
+        SLresult res = (*mRecord)->GetRecordState(mRecord, &recordState);
+        if (res != SL_RESULT_SUCCESS) {
+            ALOGW("getRecordState error %s", android::getSLErrStr(res));
+            return SL_RECORDSTATE_STOPPED;
+        }
+        return recordState;
+    }
+
+    status_t getPositionInMsec(int64_t *position) {
+        auto_lock l(mLock);
+        if (mRecord == NULL) {
+            return INVALID_OPERATION;
+        }
+        if (position == NULL) {
+            return BAD_VALUE;
+        }
+        SLuint32 pos;
+        SLresult res = (*mRecord)->GetPosition(mRecord, &pos);
+        if (res != SL_RESULT_SUCCESS) {
+            ALOGW("getPosition error %s", android::getSLErrStr(res));
+            return INVALID_OPERATION;
+        }
+        // only lower 32 bits valid
+        *position = pos;
+        return OK;
+    }
+
+    status_t start() {
+        return setRecordState(SL_RECORDSTATE_RECORDING);
+    }
+
+    status_t pause() {
+        return setRecordState(SL_RECORDSTATE_PAUSED);
+    }
+
+    status_t stop() {
+        return setRecordState(SL_RECORDSTATE_STOPPED);
+    }
+
+    status_t flush() {
+        auto_lock l(mLock);
+        status_t result = OK;
+        if (mBufferQueue != NULL) {
+            SLresult res = (*mBufferQueue)->Clear(mBufferQueue);
+            if (res != SL_RESULT_SUCCESS) {
+                return INVALID_OPERATION;
+            }
+        }
+        mReadyQueue.clear();
+        // possible race if the engine is in the callback
+        // safety is only achieved if the recorder is paused or stopped.
+        mDeliveredQueue.clear();
+        mReadBlob = NULL;
+        mReadReady.terminate();
+        return result;
+    }
+
+    ssize_t read(void *buffer, size_t size, bool blocking = false) {
+        std::lock_guard<std::mutex> rl(mReadLock);
+        // not needed if we assume that a single thread is doing the reading
+        // or we always operate in non-blocking mode.
+
+        ALOGV("reading:%p  %zu", buffer, size);
+        size_t copied;
+        std::shared_ptr<Blob> blob;
+        {
+            auto_lock l(mLock);
+            if (mEngine == NULL) {
+                return INVALID_OPERATION;
+            }
+            size_t osize = size;
+            while (!mReadyQueue.empty() && size > 0) {
+                auto b = mReadyQueue.front();
+                size_t tocopy = min(size, b->mSize - b->mOffset);
+                // ALOGD("buffer:%p  size:%zu  b->mSize:%zu  b->mOffset:%zu tocopy:%zu ",
+                //        buffer, size, b->mSize, b->mOffset, tocopy);
+                memcpy(buffer, (char *)b->mData + b->mOffset, tocopy);
+                buffer = (char *)buffer + tocopy;
+                size -= tocopy;
+                b->mOffset += tocopy;
+                if (b->mOffset == b->mSize) {
+                    mReadyQueue.pop_front();
+                }
+            }
+            copied = osize - size;
+            if (!blocking || size == 0 || mReadBlob.get() != NULL) {
+                return copied;
+            }
+            blob = std::make_shared<Blob>(buffer, size);
+            mReadBlob = blob;
+            mReadReady.closeGate(); // the callback will open gate when read is completed.
+        }
+        if (mReadReady.wait()) {
+            // success then the blob is ours with valid data otherwise a flush has occurred
+            // and we return a short count.
+            copied += blob->mOffset;
+        }
+        return copied;
+    }
+
+    void logBufferState() {
+        auto_lock l(mLock);
+        SLBufferQueueState state;
+        SLresult res = (*mBufferQueue)->GetState(mBufferQueue, &state);
+        CheckErr(res);
+        ALOGD("logBufferState state.count:%d  state.playIndex:%d", state.count, state.playIndex);
+    }
+
+    size_t getBuffersPending() {
+        auto_lock l(mLock);
+        return mReadyQueue.size();
+    }
+
+private:
+    status_t queueBuffers() {
+        if (mBufferQueue == NULL) {
+            return INVALID_OPERATION;
+        }
+        if (mReadyQueue.size() + mDeliveredQueue.size() < mNumBuffers) {
+            // add new empty buffer
+            auto b = std::make_shared<Blob>(mBufferSize);
+            mDeliveredQueue.emplace_back(b);
+            (*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
+        }
+        return OK;
+    }
+
+    void bufferQueueCallback(SLBufferQueueItf queueItf) {
+        auto_lock l(mLock);
+        if (queueItf != mBufferQueue) {
+            ALOGW("invalid buffer queue interface, ignoring");
+            return;
+        }
+        // logBufferState();
+
+        // remove from delivered queue
+        if (mDeliveredQueue.size()) {
+            auto b = mDeliveredQueue.front();
+            mDeliveredQueue.pop_front();
+            if (mReadBlob.get() != NULL) {
+                size_t tocopy = min(mReadBlob->mSize - mReadBlob->mOffset, b->mSize - b->mOffset);
+                memcpy((char *)mReadBlob->mData + mReadBlob->mOffset,
+                        (char *)b->mData + b->mOffset, tocopy);
+                b->mOffset += tocopy;
+                mReadBlob->mOffset += tocopy;
+                if (mReadBlob->mOffset == mReadBlob->mSize) {
+                    mReadBlob = NULL;      // we're done, clear our reference.
+                    mReadReady.openGate(); // allow read to continue.
+                }
+                if (b->mOffset == b->mSize) {
+                    b = NULL;
+                }
+            }
+            if (b.get() != NULL) {
+                if (mReadyQueue.size() + mDeliveredQueue.size() < mNumBuffers) {
+                    mReadyQueue.emplace_back(b); // save onto ready queue for future reads
+                } else {
+                    ALOGW("dropping data");
+                }
+            }
+        } else {
+            ALOGW("no delivered data!");
+        }
+        queueBuffers();
+    }
+
+    static void BufferQueueCallback(SLBufferQueueItf queueItf, void *pContext) {
+        SLresult res;
+        // naked native record
+        AudioRecordNative *record = (AudioRecordNative *)pContext;
+        record->bufferQueueCallback(queueItf);
+    }
+
+    SLObjectItf           mEngineObj;
+    SLEngineItf           mEngine;
+    SLObjectItf           mRecordObj;
+    SLRecordItf           mRecord;
+    SLBufferQueueItf      mBufferQueue;
+    SLuint32              mRecordState;
+    size_t                mBufferSize;
+    size_t                mNumBuffers;
+    std::recursive_mutex  mLock;          // monitor lock - locks public API methods and callback.
+                                          // recursive since it may call itself through API.
+    std::mutex            mReadLock;      // read lock - for blocking mode, prevents multiple
+                                          // reader threads from overlapping reads.  this is
+                                          // generally unnecessary as reads occur from
+                                          // one thread only.  acquire this before mLock.
+    std::shared_ptr<Blob> mReadBlob;
+    Gate                  mReadReady;
+    std::deque<std::shared_ptr<Blob>> mReadyQueue;     // ready for read.
+    std::deque<std::shared_ptr<Blob>> mDeliveredQueue; // delivered to BufferQueue
+};
+
+/* Java static methods.
+ *
+ * These are not directly exposed to the user, so we can assume a valid "jrecord" handle
+ * to be passed in.
+ */
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeTest(
+    JNIEnv * /* env */, jclass /* clazz */,
+    jint numChannels, jint sampleRate, jboolean useFloat,
+    jint msecPerBuffer, jint numBuffers)
+{
+    AudioRecordNative record;
+    const size_t frameSize = numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
+    const size_t framesPerBuffer = msecPerBuffer * sampleRate / 1000;
+
+    status_t res;
+    void *buffer = calloc(framesPerBuffer * numBuffers, frameSize);
+    for (;;) {
+        res = record.open(numChannels, sampleRate, useFloat, numBuffers);
+        if (res != OK) break;
+
+        record.logBufferState();
+        res = record.start();
+        if (res != OK) break;
+
+        size_t size = framesPerBuffer * numBuffers * frameSize;
+        for (size_t offset = 0; size - offset > 0; ) {
+            ssize_t amount = record.read((char *)buffer + offset, size -offset);
+            // ALOGD("read amount: %zd", amount);
+            if (amount < 0) break;
+            offset += amount;
+            usleep(5 * 1000 /* usec */);
+        }
+
+        res = record.stop();
+        break;
+    }
+    record.close();
+    free(buffer);
+    return res;
+}
+
+extern "C" jlong Java_android_media_cts_AudioRecordNative_nativeCreateRecord(
+    JNIEnv * /* env */, jclass /* clazz */)
+{
+    return (jlong)(new shared_pointer<AudioRecordNative>(new AudioRecordNative()));
+}
+
+extern "C" void Java_android_media_cts_AudioRecordNative_nativeDestroyRecord(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    delete (shared_pointer<AudioRecordNative> *)jrecord;
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeOpen(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord,
+    jint numChannels, jint sampleRate, jboolean useFloat, jint numBuffers)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)record->open(numChannels, sampleRate, useFloat == JNI_TRUE,
+            numBuffers);
+}
+
+extern "C" void Java_android_media_cts_AudioRecordNative_nativeClose(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() != NULL) {
+        record->close();
+    }
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeStart(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)record->start();
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeStop(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)record->stop();
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativePause(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)record->pause();
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeFlush(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)record->flush();
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeGetPositionInMsec(
+    JNIEnv *env, jclass /* clazz */, jlong jrecord, jlongArray jPosition)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    int64_t pos;
+    status_t res = record->getPositionInMsec(&pos);
+    if (res != OK) {
+        return res;
+    }
+    jlong *nPostition = (jlong *) env->GetPrimitiveArrayCritical(jPosition, NULL /* isCopy */);
+    if (nPostition == NULL) {
+        ALOGE("Unable to get array for nativeGetPositionInMsec()");
+        return BAD_VALUE;
+    }
+    nPostition[0] = (jlong)pos;
+    env->ReleasePrimitiveArrayCritical(jPosition, nPostition, 0 /* mode */);
+    return OK;
+}
+
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeGetBuffersPending(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jrecord)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)0;
+    }
+    return (jint)record->getBuffersPending();
+}
+
+template <typename T>
+static inline jint readFromRecord(jlong jrecord, T *data,
+    jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+    auto record = *(shared_pointer<AudioRecordNative> *)jrecord;
+    if (record.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+
+    const bool isBlocking = readFlags & READ_FLAG_BLOCKING;
+    const size_t sizeInBytes = sizeInSamples * sizeof(T);
+    ssize_t ret = record->read(data + offsetInSamples, sizeInBytes, isBlocking == JNI_TRUE);
+    return (jint)(ret > 0 ? ret / sizeof(T) : ret);
+}
+
+template <typename T>
+static inline jint readArray(JNIEnv *env, jclass /* clazz */, jlong jrecord,
+        T javaAudioData, jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+    if (javaAudioData == NULL) {
+        return (jint)BAD_VALUE;
+    }
+
+    auto cAudioData = envGetArrayElements(env, javaAudioData, NULL /* isCopy */);
+    if (cAudioData == NULL) {
+        ALOGE("Error retrieving destination of audio data to record");
+        return (jint)BAD_VALUE;
+    }
+
+    jint ret = readFromRecord(jrecord, cAudioData, offsetInSamples, sizeInSamples, readFlags);
+    envReleaseArrayElements(env, javaAudioData, cAudioData, 0 /* mode */);
+    return ret;
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeReadByteArray(
+    JNIEnv *env, jclass clazz, jlong jrecord,
+    jbyteArray byteArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+    return readArray(env, clazz, jrecord, byteArray, offsetInSamples, sizeInSamples, readFlags);
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeReadShortArray(
+    JNIEnv *env, jclass clazz, jlong jrecord,
+    jshortArray shortArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+    return readArray(env, clazz, jrecord, shortArray, offsetInSamples, sizeInSamples, readFlags);
+}
+
+extern "C" jint Java_android_media_cts_AudioRecordNative_nativeReadFloatArray(
+    JNIEnv *env, jclass clazz, jlong jrecord,
+    jfloatArray floatArray, jint offsetInSamples, jint sizeInSamples, jint readFlags)
+{
+    return readArray(env, clazz, jrecord, floatArray, offsetInSamples, sizeInSamples, readFlags);
+}
diff --git a/tests/tests/media/libaudiojni/audio-track-native.cpp b/tests/tests/media/libaudiojni/audio-track-native.cpp
new file mode 100644
index 0000000..d51a751
--- /dev/null
+++ b/tests/tests/media/libaudiojni/audio-track-native.cpp
@@ -0,0 +1,579 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "audio-track-native"
+
+#include "Blob.h"
+#include "Gate.h"
+#include "sl-utils.h"
+
+#include <deque>
+#include <utils/Errors.h>
+
+// Select whether to use STL shared pointer or to use Android strong pointer.
+// We really don't promote any sharing of this object for its lifetime, but nevertheless could
+// change the shared pointer value on the fly if desired.
+#define USE_SHARED_POINTER
+
+#ifdef USE_SHARED_POINTER
+#include <memory>
+template <typename T> using shared_pointer = std::shared_ptr<T>;
+#else
+#include <utils/RefBase.h>
+template <typename T> using shared_pointer = android::sp<T>;
+#endif
+
+using namespace android;
+
+// Must be kept in sync with Java android.media.cts.AudioTrackNative.WriteFlags
+enum {
+    WRITE_FLAG_BLOCKING = (1 << 0),
+};
+
+// TODO: Add a single buffer blocking write mode which does not require additional memory.
+// TODO: Add internal buffer memory (e.g. use circular buffer, right now mallocs on heap).
+
+class AudioTrackNative
+#ifndef USE_SHARED_POINTER
+        : public RefBase // android strong pointers require RefBase
+#endif
+{
+public:
+    AudioTrackNative() :
+        mEngineObj(NULL),
+        mEngine(NULL),
+        mOutputMixObj(NULL),
+        mPlayerObj(NULL),
+        mPlay(NULL),
+        mBufferQueue(NULL),
+        mPlayState(SL_PLAYSTATE_STOPPED),
+        mNumBuffers(0)
+    { }
+
+    ~AudioTrackNative() {
+        close();
+    }
+
+    typedef std::lock_guard<std::recursive_mutex> auto_lock;
+
+    status_t open(uint32_t numChannels, uint32_t sampleRate, bool useFloat,
+            uint32_t numBuffers) {
+        close();
+        auto_lock l(mLock);
+        mEngineObj = OpenSLEngine();
+        if (mEngineObj == NULL) {
+            ALOGW("cannot create OpenSL ES engine");
+            return INVALID_OPERATION;
+        }
+
+        SLresult res;
+        for (;;) {
+            /* Get the SL Engine Interface which is implicit */
+            res = (*mEngineObj)->GetInterface(mEngineObj, SL_IID_ENGINE, (void *)&mEngine);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            // Create Output Mix object to be used by player
+            res = (*mEngine)->CreateOutputMix(
+                    mEngine, &mOutputMixObj, 0 /* numInterfaces */,
+                    NULL /* pInterfaceIds */, NULL /* pInterfaceRequired */);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            // Realizing the Output Mix object in synchronous mode.
+            res = (*mOutputMixObj)->Realize(mOutputMixObj, SL_BOOLEAN_FALSE /* async */);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            /* Setup the data source structure for the buffer queue */
+            SLDataLocator_BufferQueue bufferQueue;
+            bufferQueue.locatorType = SL_DATALOCATOR_BUFFERQUEUE;
+            bufferQueue.numBuffers = numBuffers;
+            mNumBuffers = numBuffers;
+
+            /* Setup the format of the content in the buffer queue */
+
+            SLAndroidDataFormat_PCM_EX pcm;
+            pcm.formatType = useFloat ? SL_ANDROID_DATAFORMAT_PCM_EX : SL_DATAFORMAT_PCM;
+            pcm.numChannels = numChannels;
+            pcm.sampleRate = sampleRate * 1000;
+            pcm.bitsPerSample = useFloat ?
+                    SL_PCMSAMPLEFORMAT_FIXED_32 : SL_PCMSAMPLEFORMAT_FIXED_16;
+            pcm.containerSize = pcm.bitsPerSample;
+            pcm.channelMask = channelCountToMask(numChannels);
+            pcm.endianness = SL_BYTEORDER_LITTLEENDIAN;
+            // additional
+            pcm.representation = useFloat ? SL_ANDROID_PCM_REPRESENTATION_FLOAT
+                                    : SL_ANDROID_PCM_REPRESENTATION_SIGNED_INT;
+            SLDataSource audioSource;
+            audioSource.pFormat = (void *)&pcm;
+            audioSource.pLocator = (void *)&bufferQueue;
+
+            /* Setup the data sink structure */
+            SLDataLocator_OutputMix locator_outputmix;
+            locator_outputmix.locatorType = SL_DATALOCATOR_OUTPUTMIX;
+            locator_outputmix.outputMix = mOutputMixObj;
+
+            SLDataSink audioSink;
+            audioSink.pLocator = (void *)&locator_outputmix;
+            audioSink.pFormat = NULL;
+
+            SLboolean required[1];
+            SLInterfaceID iidArray[1];
+            required[0] = SL_BOOLEAN_TRUE;
+            iidArray[0] = SL_IID_BUFFERQUEUE;
+
+            res = (*mEngine)->CreateAudioPlayer(mEngine, &mPlayerObj,
+                    &audioSource, &audioSink, 1 /* numInterfaces */, iidArray, required);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            res = (*mPlayerObj)->Realize(mPlayerObj, SL_BOOLEAN_FALSE /* async */);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            res = (*mPlayerObj)->GetInterface(mPlayerObj, SL_IID_PLAY, (void*)&mPlay);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            res = (*mPlayerObj)->GetInterface(
+                    mPlayerObj, SL_IID_BUFFERQUEUE, (void*)&mBufferQueue);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            /* Setup to receive buffer queue event callbacks */
+            res = (*mBufferQueue)->RegisterCallback(mBufferQueue, BufferQueueCallback, this);
+            if (res != SL_RESULT_SUCCESS) break;
+
+            // success
+            break;
+        }
+        if (res != SL_RESULT_SUCCESS) {
+            close(); // should be safe to close even with lock held
+            ALOGW("open error %s", android::getSLErrStr(res));
+            return INVALID_OPERATION;
+        }
+        return OK;
+    }
+
+    void close() {
+        SLObjectItf engineObj;
+        SLObjectItf outputMixObj;
+        SLObjectItf playerObj;
+        {
+            auto_lock l(mLock);
+            if (mPlay != NULL && mPlayState != SL_PLAYSTATE_STOPPED) {
+                (void)stop();
+            }
+            // once stopped, we can unregister the callback
+            if (mBufferQueue != NULL) {
+                (void)(*mBufferQueue)->RegisterCallback(
+                        mBufferQueue, NULL /* callback */, NULL /* *pContext */);
+            }
+            (void)flush();
+            engineObj = mEngineObj;
+            outputMixObj = mOutputMixObj;
+            playerObj = mPlayerObj;
+            // clear out interfaces and objects
+            mPlay = NULL;
+            mBufferQueue = NULL;
+            mEngine = NULL;
+            mPlayerObj = NULL;
+            mOutputMixObj = NULL;
+            mEngineObj = NULL;
+            mPlayState = SL_PLAYSTATE_STOPPED;
+        }
+        // destroy without lock
+        if (playerObj != NULL) {
+            (*playerObj)->Destroy(playerObj);
+        }
+        if (outputMixObj != NULL) {
+            (*outputMixObj)->Destroy(outputMixObj);
+        }
+        if (engineObj != NULL) {
+            CloseSLEngine(engineObj);
+        }
+    }
+
+    status_t setPlayState(SLuint32 playState) {
+        auto_lock l(mLock);
+        if (mPlay == NULL) {
+            return INVALID_OPERATION;
+        }
+        SLresult res = (*mPlay)->SetPlayState(mPlay, playState);
+        if (res != SL_RESULT_SUCCESS) {
+            ALOGW("setPlayState %d error %s", playState, android::getSLErrStr(res));
+            return INVALID_OPERATION;
+        }
+        mPlayState = playState;
+        return OK;
+    }
+
+    SLuint32 getPlayState() {
+        auto_lock l(mLock);
+        if (mPlay == NULL) {
+            return SL_PLAYSTATE_STOPPED;
+        }
+        SLuint32 playState;
+        SLresult res = (*mPlay)->GetPlayState(mPlay, &playState);
+        if (res != SL_RESULT_SUCCESS) {
+            ALOGW("getPlayState error %s", android::getSLErrStr(res));
+            return SL_PLAYSTATE_STOPPED;
+        }
+        return playState;
+    }
+
+    status_t getPositionInMsec(int64_t *position) {
+        auto_lock l(mLock);
+        if (mPlay == NULL) {
+            return INVALID_OPERATION;
+        }
+        if (position == NULL) {
+            return BAD_VALUE;
+        }
+        SLuint32 pos;
+        SLresult res = (*mPlay)->GetPosition(mPlay, &pos);
+        if (res != SL_RESULT_SUCCESS) {
+            ALOGW("getPosition error %s", android::getSLErrStr(res));
+            return INVALID_OPERATION;
+        }
+        // only lower 32 bits valid
+        *position = pos;
+        return OK;
+    }
+
+    status_t start() {
+        return setPlayState(SL_PLAYSTATE_PLAYING);
+    }
+
+    status_t pause() {
+        return setPlayState(SL_PLAYSTATE_PAUSED);
+    }
+
+    status_t stop() {
+        return setPlayState(SL_PLAYSTATE_STOPPED);
+    }
+
+    status_t flush() {
+        auto_lock l(mLock);
+        status_t result = OK;
+        if (mBufferQueue != NULL) {
+            SLresult res = (*mBufferQueue)->Clear(mBufferQueue);
+            if (res != SL_RESULT_SUCCESS) {
+                return INVALID_OPERATION;
+            }
+        }
+
+        // possible race if the engine is in the callback
+        // safety is only achieved if the player is paused or stopped.
+        mDeliveredQueue.clear();
+        return result;
+    }
+
+    status_t write(const void *buffer, size_t size, bool isBlocking = false) {
+        std::lock_guard<std::mutex> rl(mWriteLock);
+        // not needed if we assume that a single thread is doing the reading
+        // or we always operate in non-blocking mode.
+
+        {
+            auto_lock l(mLock);
+            if (mBufferQueue == NULL) {
+                return INVALID_OPERATION;
+            }
+            if (mDeliveredQueue.size() < mNumBuffers) {
+                auto b = std::make_shared<BlobReadOnly>(buffer, size, false /* byReference */);
+                mDeliveredQueue.emplace_back(b);
+                (*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
+                return size;
+            }
+            if (!isBlocking) {
+                return 0;
+            }
+            mWriteReady.closeGate(); // we're full.
+        }
+        if (mWriteReady.wait()) {
+            auto_lock l(mLock);
+            if (mDeliveredQueue.size() < mNumBuffers) {
+                auto b = std::make_shared<BlobReadOnly>(buffer, size, false /* byReference */);
+                mDeliveredQueue.emplace_back(b);
+                (*mBufferQueue)->Enqueue(mBufferQueue, b->mData, b->mSize);
+                return size;
+            }
+        }
+        ALOGW("unable to deliver write");
+        return 0;
+    }
+
+    void logBufferState() {
+        auto_lock l(mLock);
+        SLBufferQueueState state;
+        SLresult res = (*mBufferQueue)->GetState(mBufferQueue, &state);
+        CheckErr(res);
+        ALOGD("logBufferState state.count:%d  state.playIndex:%d", state.count, state.playIndex);
+    }
+
+    size_t getBuffersPending() {
+        auto_lock l(mLock);
+        return mDeliveredQueue.size();
+    }
+
+private:
+    void bufferQueueCallback(SLBufferQueueItf queueItf) {
+        auto_lock l(mLock);
+        if (queueItf != mBufferQueue) {
+            ALOGW("invalid buffer queue interface, ignoring");
+            return;
+        }
+        // logBufferState();
+
+        // remove from delivered queue
+        if (mDeliveredQueue.size()) {
+            mDeliveredQueue.pop_front();
+        } else {
+            ALOGW("no delivered data!");
+        }
+        if (!mWriteReady.isOpen()) {
+            mWriteReady.openGate();
+        }
+    }
+
+    static void BufferQueueCallback(SLBufferQueueItf queueItf, void *pContext) {
+        SLresult res;
+        // naked native track
+        AudioTrackNative *track = (AudioTrackNative *)pContext;
+        track->bufferQueueCallback(queueItf);
+    }
+
+    SLObjectItf          mEngineObj;
+    SLEngineItf          mEngine;
+    SLObjectItf          mOutputMixObj;
+    SLObjectItf          mPlayerObj;
+    SLPlayItf            mPlay;
+    SLBufferQueueItf     mBufferQueue;
+    SLuint32             mPlayState;
+    SLuint32             mNumBuffers;
+    std::recursive_mutex mLock;           // monitor lock - locks public API methods and callback.
+                                          // recursive since it may call itself through API.
+    std::mutex           mWriteLock;      // write lock - for blocking mode, prevents multiple
+                                          // writer threads from overlapping writes.  this is
+                                          // generally unnecessary as writes occur from
+                                          // one thread only.  acquire this before mLock.
+    Gate                 mWriteReady;
+    std::deque<std::shared_ptr<BlobReadOnly>> mDeliveredQueue; // delivered to mBufferQueue
+};
+
+/* Java static methods.
+ *
+ * These are not directly exposed to the user, so we can assume a valid "jtrack" handle
+ * to be passed in.
+ */
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeTest(
+    JNIEnv * /* env */, jclass /* clazz */,
+    jint numChannels, jint sampleRate, jboolean useFloat,
+    jint msecPerBuffer, jint numBuffers)
+{
+    AudioTrackNative track;
+    const size_t frameSize = numChannels * (useFloat ? sizeof(float) : sizeof(int16_t));
+    const size_t framesPerBuffer = msecPerBuffer * sampleRate / 1000;
+
+    status_t res;
+    void *buffer = calloc(framesPerBuffer * numBuffers, frameSize);
+    for (;;) {
+        res = track.open(numChannels, sampleRate, useFloat, numBuffers);
+        if (res != OK) break;
+
+        for (int i = 0; i < numBuffers; ++i) {
+            track.write((char *)buffer + i * (framesPerBuffer * frameSize),
+                    framesPerBuffer * frameSize);
+        }
+
+        track.logBufferState();
+        res = track.start();
+        if (res != OK) break;
+
+        size_t buffers;
+        while ((buffers = track.getBuffersPending()) > 0) {
+            // ALOGD("outstanding buffers: %zu", buffers);
+            usleep(5 * 1000 /* usec */);
+        }
+        res = track.stop();
+        break;
+    }
+    track.close();
+    free(buffer);
+    return res;
+}
+
+extern "C" jlong Java_android_media_cts_AudioTrackNative_nativeCreateTrack(
+    JNIEnv * /* env */, jclass /* clazz */)
+{
+    return (jlong)(new shared_pointer<AudioTrackNative>(new AudioTrackNative()));
+}
+
+extern "C" void Java_android_media_cts_AudioTrackNative_nativeDestroyTrack(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+    delete (shared_pointer<AudioTrackNative> *)jtrack;
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeOpen(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack,
+    jint numChannels, jint sampleRate, jboolean useFloat, jint numBuffers)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)track->open(numChannels, sampleRate, useFloat == JNI_TRUE,
+            numBuffers);
+}
+
+extern "C" void Java_android_media_cts_AudioTrackNative_nativeClose(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() != NULL) {
+        track->close();
+    }
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeStart(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)track->start();
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeStop(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)track->stop();
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativePause(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)track->pause();
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeFlush(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    return (jint)track->flush();
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeGetPositionInMsec(
+    JNIEnv *env, jclass /* clazz */, jlong jtrack, jlongArray jPosition)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+    int64_t pos;
+    status_t res = track->getPositionInMsec(&pos);
+    if (res != OK) {
+        return res;
+    }
+    jlong *nPostition = (jlong *) env->GetPrimitiveArrayCritical(jPosition, NULL /* isCopy */);
+    if (nPostition == NULL) {
+        ALOGE("Unable to get array for nativeGetPositionInMsec()");
+        return BAD_VALUE;
+    }
+    nPostition[0] = (jlong)pos;
+    env->ReleasePrimitiveArrayCritical(jPosition, nPostition, 0 /* mode */);
+    return OK;
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeGetBuffersPending(
+    JNIEnv * /* env */, jclass /* clazz */, jlong jtrack)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)0;
+    }
+    return (jint)track->getBuffersPending();
+}
+
+template <typename T>
+static inline jint writeToTrack(jlong jtrack, const T *data,
+    jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+    auto track = *(shared_pointer<AudioTrackNative> *)jtrack;
+    if (track.get() == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+
+    const bool isBlocking = writeFlags & WRITE_FLAG_BLOCKING;
+    const size_t sizeInBytes = sizeInSamples * sizeof(T);
+    ssize_t ret = track->write(data + offsetInSamples, sizeInBytes, isBlocking);
+    return (jint)(ret > 0 ? ret / sizeof(T) : ret);
+}
+
+template <typename T>
+static inline jint writeArray(JNIEnv *env, jclass /* clazz */, jlong jtrack,
+        T javaAudioData, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+    if (javaAudioData == NULL) {
+        return (jint)INVALID_OPERATION;
+    }
+
+    auto cAudioData = envGetArrayElements(env, javaAudioData, NULL /* isCopy */);
+    if (cAudioData == NULL) {
+        ALOGE("Error retrieving source of audio data to play");
+        return (jint)BAD_VALUE;
+    }
+
+    jint ret = writeToTrack(jtrack, cAudioData, offsetInSamples, sizeInSamples, writeFlags);
+    envReleaseArrayElements(env, javaAudioData, cAudioData, 0 /* mode */);
+    return ret;
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteByteArray(
+    JNIEnv *env, jclass clazz, jlong jtrack,
+    jbyteArray byteArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+    ALOGV("nativeWriteByteArray(%p, %d, %d, %d)",
+            byteArray, offsetInSamples, sizeInSamples, writeFlags);
+    return writeArray(env, clazz, jtrack, byteArray, offsetInSamples, sizeInSamples, writeFlags);
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteShortArray(
+    JNIEnv *env, jclass clazz, jlong jtrack,
+    jshortArray shortArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+    ALOGV("nativeWriteShortArray(%p, %d, %d, %d)",
+            shortArray, offsetInSamples, sizeInSamples, writeFlags);
+    return writeArray(env, clazz, jtrack, shortArray, offsetInSamples, sizeInSamples, writeFlags);
+}
+
+extern "C" jint Java_android_media_cts_AudioTrackNative_nativeWriteFloatArray(
+    JNIEnv *env, jclass clazz, jlong jtrack,
+    jfloatArray floatArray, jint offsetInSamples, jint sizeInSamples, jint writeFlags)
+{
+    ALOGV("nativeWriteFloatArray(%p, %d, %d, %d)",
+            floatArray, offsetInSamples, sizeInSamples, writeFlags);
+    return writeArray(env, clazz, jtrack, floatArray, offsetInSamples, sizeInSamples, writeFlags);
+}
diff --git a/tests/tests/media/libaudiojni/sl-utils.cpp b/tests/tests/media/libaudiojni/sl-utils.cpp
new file mode 100644
index 0000000..1aa89ba
--- /dev/null
+++ b/tests/tests/media/libaudiojni/sl-utils.cpp
@@ -0,0 +1,145 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "SL-Utils"
+
+#include "sl-utils.h"
+#include <utils/Mutex.h>
+
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
+
+// These will wind up in <SLES/OpenSLES_Android.h>
+#define SL_ANDROID_SPEAKER_QUAD (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT \
+ | SL_SPEAKER_BACK_LEFT | SL_SPEAKER_BACK_RIGHT)
+
+#define SL_ANDROID_SPEAKER_5DOT1 (SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT \
+ | SL_SPEAKER_FRONT_CENTER  | SL_SPEAKER_LOW_FREQUENCY| SL_SPEAKER_BACK_LEFT \
+ | SL_SPEAKER_BACK_RIGHT)
+
+#define SL_ANDROID_SPEAKER_7DOT1 (SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_SIDE_LEFT \
+ |SL_SPEAKER_SIDE_RIGHT)
+
+namespace android {
+
+static Mutex gLock;
+static SLObjectItf gEngineObject;
+static unsigned gRefCount;
+
+static const char *gErrorStrings[] = {
+    "SL_RESULT_SUCCESS",                // 0
+    "SL_RESULT_PRECONDITIONS_VIOLATE",  // 1
+    "SL_RESULT_PARAMETER_INVALID",      // 2
+    "SL_RESULT_MEMORY_FAILURE",         // 3
+    "SL_RESULT_RESOURCE_ERROR",         // 4
+    "SL_RESULT_RESOURCE_LOST",          // 5
+    "SL_RESULT_IO_ERROR",               // 6
+    "SL_RESULT_BUFFER_INSUFFICIENT",    // 7
+    "SL_RESULT_CONTENT_CORRUPTED",      // 8
+    "SL_RESULT_CONTENT_UNSUPPORTED",    // 9
+    "SL_RESULT_CONTENT_NOT_FOUND",      // 10
+    "SL_RESULT_PERMISSION_DENIED",      // 11
+    "SL_RESULT_FEATURE_UNSUPPORTED",    // 12
+    "SL_RESULT_INTERNAL_ERROR",         // 13
+    "SL_RESULT_UNKNOWN_ERROR",          // 14
+    "SL_RESULT_OPERATION_ABORTED",      // 15
+    "SL_RESULT_CONTROL_LOST",           // 16
+};
+
+const char *getSLErrStr(int code) {
+    if ((size_t)code >= ARRAY_SIZE(gErrorStrings)) {
+        return "SL_RESULT_UNKNOWN";
+    }
+    return gErrorStrings[code];
+}
+
+SLuint32 channelCountToMask(unsigned channelCount) {
+    switch (channelCount) {
+    case 1:
+        return SL_SPEAKER_FRONT_LEFT; // we prefer left over center
+    case 2:
+        return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT;
+    case 3:
+        return SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT | SL_SPEAKER_FRONT_CENTER;
+    case 4:
+        return SL_ANDROID_SPEAKER_QUAD;
+    case 5:
+        return SL_ANDROID_SPEAKER_QUAD | SL_SPEAKER_FRONT_CENTER;
+    case 6:
+        return SL_ANDROID_SPEAKER_5DOT1;
+    case 7:
+        return SL_ANDROID_SPEAKER_5DOT1 | SL_SPEAKER_BACK_CENTER;
+    case 8:
+        return SL_ANDROID_SPEAKER_7DOT1;
+    default:
+        return 0;
+    }
+}
+
+static SLObjectItf createEngine() {
+    static SLEngineOption EngineOption[] = {
+            (SLuint32) SL_ENGINEOPTION_THREADSAFE,
+            (SLuint32) SL_BOOLEAN_TRUE
+    };
+    // create engine in thread-safe mode
+    SLObjectItf engine;
+    SLresult result = slCreateEngine(&engine,
+            1 /* numOptions */, EngineOption /* pEngineOptions */,
+            0 /* numInterfaces */, NULL /* pInterfaceIds */, NULL /* pInterfaceRequired */);
+    if (result != SL_RESULT_SUCCESS) {
+        ALOGE("slCreateEngine() failed: %s", getSLErrStr(result));
+        return NULL;
+    }
+    // realize the engine
+    result = (*engine)->Realize(engine, SL_BOOLEAN_FALSE /* async */);
+    if (result != SL_RESULT_SUCCESS) {
+        ALOGE("Realize() failed: %s", getSLErrStr(result));
+        (*engine)->Destroy(engine);
+        return NULL;
+    }
+    return engine;
+}
+
+SLObjectItf OpenSLEngine(bool global) {
+
+    if (!global) {
+        return createEngine();
+    }
+    Mutex::Autolock l(gLock);
+    if (gRefCount == 0) {
+        gEngineObject = createEngine();
+    }
+    gRefCount++;
+    return gEngineObject;
+}
+
+void CloseSLEngine(SLObjectItf engine) {
+    Mutex::Autolock l(gLock);
+    if (engine == gEngineObject) {
+        if (gRefCount == 0) {
+            ALOGE("CloseSLEngine(%p): refcount already 0", engine);
+            return;
+        }
+        if (--gRefCount != 0) {
+            return;
+        }
+        gEngineObject = NULL;
+    }
+    (*engine)->Destroy(engine);
+}
+
+} // namespace android
+
diff --git a/tests/tests/media/libaudiojni/sl-utils.h b/tests/tests/media/libaudiojni/sl-utils.h
new file mode 100644
index 0000000..8582648
--- /dev/null
+++ b/tests/tests/media/libaudiojni/sl-utils.h
@@ -0,0 +1,95 @@
+/*
+ * 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.
+ */
+
+#ifndef ANDROID_SL_UTILS_H
+#define ANDROID_SL_UTILS_H
+
+#include <SLES/OpenSLES.h>
+#include <SLES/OpenSLES_Android.h>
+
+#include <jni.h>
+#include <mutex>
+#include <utils/Log.h>
+
+#define CheckErr(res) LOG_ALWAYS_FATAL_IF( \
+        (res) != SL_RESULT_SUCCESS, "result error %s", android::getSLErrStr(res));
+
+namespace android {
+
+// FIXME: Move to common file.
+template <typename T>
+static inline
+const T &min(const T &a, const T &b) {
+    return a < b ? a : b;
+}
+
+/* Returns the error string for the OpenSL ES error code
+ */
+const char *getSLErrStr(int code);
+
+/* Returns the OpenSL ES equivalent standard channel mask
+ * for a given channel count, 0 if no such mask is available.
+ */
+SLuint32 channelCountToMask(unsigned channelCount);
+
+/* Returns an OpenSL ES Engine object interface.
+ * The engine created will be thread safe [3.2]
+ * The underlying implementation may not support more than one engine. [4.1.1]
+ *
+ * @param global if true, return and open the global engine instance or make
+ *   a local engine instance if false.
+ * @return NULL if unsuccessful or the Engine SLObjectItf.
+ */
+SLObjectItf OpenSLEngine(bool global = true);
+
+/* Closes an OpenSL ES Engine object returned by OpenSLEngine().
+ */
+void CloseSLEngine(SLObjectItf engine);
+
+// overloaded JNI array helper functions (same as in android_media_AudioRecord)
+inline
+jbyte *envGetArrayElements(JNIEnv *env, jbyteArray array, jboolean *isCopy) {
+    return env->GetByteArrayElements(array, isCopy);
+}
+
+inline
+void envReleaseArrayElements(JNIEnv *env, jbyteArray array, jbyte *elems, jint mode) {
+    env->ReleaseByteArrayElements(array, elems, mode);
+}
+
+inline
+jshort *envGetArrayElements(JNIEnv *env, jshortArray array, jboolean *isCopy) {
+    return env->GetShortArrayElements(array, isCopy);
+}
+
+inline
+void envReleaseArrayElements(JNIEnv *env, jshortArray array, jshort *elems, jint mode) {
+    env->ReleaseShortArrayElements(array, elems, mode);
+}
+
+inline
+jfloat *envGetArrayElements(JNIEnv *env, jfloatArray array, jboolean *isCopy) {
+    return env->GetFloatArrayElements(array, isCopy);
+}
+
+inline
+void envReleaseArrayElements(JNIEnv *env, jfloatArray array, jfloat *elems, jint mode) {
+    env->ReleaseFloatArrayElements(array, elems, mode);
+}
+
+} // namespace android
+
+#endif // ANDROID_SL_UTILS_H
diff --git a/tests/tests/media/src/android/media/cts/AudioHelper.java b/tests/tests/media/src/android/media/cts/AudioHelper.java
index efee024..6707ea6 100644
--- a/tests/tests/media/src/android/media/cts/AudioHelper.java
+++ b/tests/tests/media/src/android/media/cts/AudioHelper.java
@@ -211,13 +211,13 @@
      * This affects AudioRecord timing.
      */
     public static class AudioRecordAudit extends AudioRecord {
-        AudioRecordAudit(int audioSource, int sampleRate, int channelMask,
+        public AudioRecordAudit(int audioSource, int sampleRate, int channelMask,
                 int format, int bufferSize, boolean isChannelIndex) {
             this(audioSource, sampleRate, channelMask, format, bufferSize, isChannelIndex,
                     AudioManager.STREAM_MUSIC, 500 /*delayMs*/);
         }
 
-        AudioRecordAudit(int audioSource, int sampleRate, int channelMask,
+        public AudioRecordAudit(int audioSource, int sampleRate, int channelMask,
                 int format, int bufferSize,
                 boolean isChannelIndex, int auditStreamType, int delayMs) {
             // without channel index masks, one could call:
@@ -408,4 +408,84 @@
         private int mPosition;
         private long mFinishAtMs;
     }
+
+    /* AudioRecordAudit extends AudioRecord to allow concurrent playback
+     * of read content to an AudioTrack.  This is for testing only.
+     * For general applications, it is NOT recommended to extend AudioRecord.
+     * This affects AudioRecord timing.
+     */
+    public static class AudioRecordAuditNative extends AudioRecordNative {
+        public AudioRecordAuditNative() {
+            super();
+            // Caution: delayMs too large results in buffer sizes that cannot be created.
+            mTrack = new AudioTrackNative();
+        }
+
+        @Override
+        public boolean open(int numChannels, int sampleRate, boolean useFloat, int numBuffers) {
+            if (super.open(numChannels, sampleRate, useFloat, numBuffers)) {
+                if (!mTrack.open(numChannels, sampleRate, useFloat, 2 /* numBuffers */)) {
+                    mTrack = null; // remove track
+                }
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public void close() {
+            super.close();
+            if (mTrack != null) {
+                mTrack.close();
+            }
+        }
+
+        @Override
+        public boolean start() {
+            if (super.start()) {
+                if (mTrack != null) {
+                    mTrack.start();
+                }
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public boolean stop() {
+            if (super.stop()) {
+                if (mTrack != null) {
+                    mTrack.stop(); // doesn't allow remaining data to play out
+                }
+                return true;
+            }
+            return false;
+        }
+
+        @Override
+        public int read(short[] audioData, int offsetInShorts, int sizeInShorts, int readFlags) {
+            int samples = super.read(audioData, offsetInShorts, sizeInShorts, readFlags);
+            if (mTrack != null) {
+                Assert.assertEquals(samples, mTrack.write(audioData, offsetInShorts, samples,
+                        AudioTrackNative.WRITE_FLAG_BLOCKING));
+                mPosition += samples / mTrack.getChannelCount();
+            }
+            return samples;
+        }
+
+        @Override
+        public int read(float[] audioData, int offsetInFloats, int sizeInFloats, int readFlags) {
+            int samples = super.read(audioData, offsetInFloats, sizeInFloats, readFlags);
+            if (mTrack != null) {
+                Assert.assertEquals(samples, mTrack.write(audioData, offsetInFloats, samples,
+                        AudioTrackNative.WRITE_FLAG_BLOCKING));
+                mPosition += samples / mTrack.getChannelCount();
+            }
+            return samples;
+        }
+
+        public AudioTrackNative mTrack;
+        private final static String TAG = "AudioRecordAuditNative";
+        private int mPosition;
+    }
 }
diff --git a/tests/tests/media/src/android/media/cts/AudioNativeTest.java b/tests/tests/media/src/android/media/cts/AudioNativeTest.java
new file mode 100644
index 0000000..967bdcd
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/AudioNativeTest.java
@@ -0,0 +1,240 @@
+/*
+ * 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.media.cts;
+
+import android.content.pm.PackageManager;
+import android.cts.util.CtsAndroidTestCase;
+import android.util.Log;
+
+public class AudioNativeTest extends CtsAndroidTestCase {
+    public void testAppendixBBufferQueue() {
+        nativeAppendixBBufferQueue();
+    }
+
+    public void testAppendixBRecording() {
+        // better to detect presence of microphone here.
+        if (!hasMicrophone()) {
+            return;
+        }
+        nativeAppendixBRecording();
+    }
+
+    public void testStereo16Playback() {
+        assertTrue(AudioTrackNative.test(
+                2 /* numChannels */, 48000 /* sampleRate */, false /* useFloat */,
+                20 /* msecPerBuffer */, 8 /* numBuffers */));
+    }
+
+    public void testStereo16Record() {
+        if (!hasMicrophone()) {
+            return;
+        }
+        assertTrue(AudioRecordNative.test(
+                2 /* numChannels */, 48000 /* sampleRate */, false /* useFloat */,
+                20 /* msecPerBuffer */, 8 /* numBuffers */));
+    }
+
+    public void testPlayStreamData() throws Exception {
+        final String TEST_NAME = "testPlayStreamData";
+        final boolean TEST_FLOAT_ARRAY[] = {
+                false,
+                true,
+        };
+        // due to downmixer algorithmic latency, source channels greater than 2 may
+        // sound shorter in duration at 4kHz sampling rate.
+        final int TEST_SR_ARRAY[] = {
+                /* 4000, */ // below limit of OpenSL ES
+                12345, // irregular sampling rate
+                44100,
+                48000,
+                96000,
+                192000,
+        };
+        // OpenSL ES Bug: MNC does not support channel counts of 3, 5, 7.
+        final int TEST_CHANNELS_ARRAY[] = {
+                1,
+                2,
+                // 3,
+                4,
+                // 5,
+                6,
+                // 7,
+                // 8  // can fail due to memory issues
+        };
+        final float TEST_SWEEP = 0; // sine wave only
+        final int TEST_TIME_IN_MSEC = 300;
+        final int TOLERANCE_MSEC = 20;
+
+        for (boolean TEST_FLOAT : TEST_FLOAT_ARRAY) {
+            double frequency = 400; // frequency changes for each test
+            for (int TEST_SR : TEST_SR_ARRAY) {
+                for (int TEST_CHANNELS : TEST_CHANNELS_ARRAY) {
+                    // OpenSL ES BUG: we run out of AudioTrack memory for this config on MNC
+                    // Log.d(TEST_NAME, "open channels:" + TEST_CHANNELS + " sr:" + TEST_SR);
+                    if (TEST_FLOAT == true && TEST_CHANNELS >= 6 && TEST_SR >= 192000) {
+                        continue;
+                    }
+                    AudioTrackNative track = new AudioTrackNative();
+                    assertTrue(TEST_NAME,
+                            track.open(TEST_CHANNELS, TEST_SR, TEST_FLOAT, 1 /* numBuffers */));
+                    assertTrue(TEST_NAME, track.start());
+
+                    final int sourceSamples =
+                            (int)((long)TEST_SR * TEST_TIME_IN_MSEC * TEST_CHANNELS / 1000);
+                    final double testFrequency = frequency / TEST_CHANNELS;
+                    if (TEST_FLOAT) {
+                        float data[] = AudioHelper.createSoundDataInFloatArray(
+                                sourceSamples, TEST_SR,
+                                testFrequency, TEST_SWEEP);
+                        assertEquals(sourceSamples,
+                                track.write(data, 0 /* offset */, sourceSamples,
+                                        AudioTrackNative.WRITE_FLAG_BLOCKING));
+                    } else {
+                        short data[] = AudioHelper.createSoundDataInShortArray(
+                                sourceSamples, TEST_SR,
+                                testFrequency, TEST_SWEEP);
+                        assertEquals(sourceSamples,
+                                track.write(data, 0 /* offset */, sourceSamples,
+                                        AudioTrackNative.WRITE_FLAG_BLOCKING));
+                    }
+
+                    while (true) {
+                        // OpenSL ES BUG: getPositionInMsec returns 0 after a data underrun.
+
+                        long position = track.getPositionInMsec();
+                        //Log.d(TEST_NAME, "position: " + position[0]);
+                        if (position >= (long)(TEST_TIME_IN_MSEC - TOLERANCE_MSEC)) {
+                            break;
+                        }
+
+                        // It is safer to use a buffer count of 0 to determine termination
+                        if (track.getBuffersPending() == 0) {
+                            break;
+                        }
+                        Thread.sleep(5 /* millis */);
+                    }
+                    track.stop();
+                    track.close();
+                    Thread.sleep(40 /* millis */);  // put a gap in the tone sequence
+                    frequency += 50; // increment test tone frequency
+                }
+            }
+        }
+    }
+
+    public void testRecordStreamData() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        final String TEST_NAME = "testRecordStreamData";
+        final boolean TEST_FLOAT_ARRAY[] = {
+                false,
+                true,
+        };
+        final int TEST_SR_ARRAY[] = {
+                //4000, // below limit of OpenSL ES
+                12345, // irregular sampling rate
+                44100,
+                48000,
+                96000,
+                192000,
+        };
+        final int TEST_CHANNELS_ARRAY[] = {
+                1,
+                2,
+                // 3,
+                4,
+                // 5,
+                6,
+                // 7,
+                8,
+        };
+        final int SEGMENT_DURATION_IN_MSEC = 20;
+        final int NUMBER_SEGMENTS = 10;
+
+        for (boolean TEST_FLOAT : TEST_FLOAT_ARRAY) {
+            for (int TEST_SR : TEST_SR_ARRAY) {
+                for (int TEST_CHANNELS : TEST_CHANNELS_ARRAY) {
+                    // OpenSL ES BUG: we run out of AudioTrack memory for this config on MNC
+                    if (TEST_FLOAT == true && TEST_CHANNELS >= 8 && TEST_SR >= 192000) {
+                        continue;
+                    }
+                    AudioRecordNative record = new AudioRecordNative();
+                    doRecordTest(record, TEST_CHANNELS, TEST_SR, TEST_FLOAT,
+                            SEGMENT_DURATION_IN_MSEC, NUMBER_SEGMENTS);
+                }
+            }
+        }
+    }
+
+    public void testRecordAudit() throws Exception {
+        if (!hasMicrophone()) {
+            return;
+        }
+        AudioRecordNative record = new AudioHelper.AudioRecordAuditNative();
+        doRecordTest(record, 4 /* numChannels */, 44100 /* sampleRate */, false /* useFloat */,
+                1000 /* segmentDurationMs */, 10 /* numSegments */);
+    }
+
+    static {
+        System.loadLibrary("audio_jni");
+    }
+
+    private static final String TAG = "AudioNativeTest";
+
+    private void doRecordTest(AudioRecordNative record,
+            int numChannels, int sampleRate, boolean useFloat,
+            int segmentDurationMs, int numSegments) {
+        final String TEST_NAME = "doRecordTest";
+        try {
+            // Log.d(TEST_NAME, "open numChannels:" + numChannels + " sampleRate:" + sampleRate);
+            assertTrue(TEST_NAME, record.open(numChannels, sampleRate, useFloat,
+                    numSegments /* numBuffers */));
+            assertTrue(TEST_NAME, record.start());
+
+            final int sourceSamples =
+                    (int)((long)sampleRate * segmentDurationMs * numChannels / 1000);
+
+            if (useFloat) {
+                float data[] = new float[sourceSamples];
+                for (int i = 0; i < numSegments; ++i) {
+                    assertEquals(sourceSamples,
+                            record.read(data, 0 /* offset */, sourceSamples,
+                                    AudioRecordNative.READ_FLAG_BLOCKING));
+                }
+            } else {
+                short data[] = new short[sourceSamples];
+                for (int i = 0; i < numSegments; ++i) {
+                    assertEquals(sourceSamples,
+                            record.read(data, 0 /* offset */, sourceSamples,
+                                    AudioRecordNative.READ_FLAG_BLOCKING));
+                }
+            }
+            assertTrue(TEST_NAME, record.stop());
+        } finally {
+            record.close();
+        }
+    }
+
+    private boolean hasMicrophone() {
+        return getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_MICROPHONE);
+    }
+
+    private static native void nativeAppendixBBufferQueue();
+    private static native void nativeAppendixBRecording();
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordNative.java b/tests/tests/media/src/android/media/cts/AudioRecordNative.java
new file mode 100644
index 0000000..18df8ee
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/AudioRecordNative.java
@@ -0,0 +1,153 @@
+/*
+ * 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.media.cts;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.cts.util.CtsAndroidTestCase;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class AudioRecordNative {
+    // Must be kept in sync with C++ JNI audio-record-native (AudioRecordNative) READ_FLAG_*
+    public static final int READ_FLAG_BLOCKING = 1 << 0;
+    /** @hide */
+    @IntDef(flag = true,
+            value = {
+                    READ_FLAG_BLOCKING,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ReadFlags { }
+
+    public AudioRecordNative() {
+        mNativeRecordInJavaObj = nativeCreateRecord();
+    }
+
+    public boolean open(int numChannels, int sampleRate, boolean useFloat, int numBuffers) {
+        if (nativeOpen(mNativeRecordInJavaObj, numChannels, sampleRate, useFloat, numBuffers)
+                == STATUS_OK) {
+            mChannelCount = numChannels;
+            return true;
+        }
+        return false;
+    }
+
+    public void close() {
+        nativeClose(mNativeRecordInJavaObj);
+    }
+
+    public boolean start() {
+        return nativeStart(mNativeRecordInJavaObj) == STATUS_OK;
+    }
+
+    public boolean stop() {
+        return nativeStop(mNativeRecordInJavaObj) == STATUS_OK;
+    }
+
+    public boolean pause() {
+        return nativePause(mNativeRecordInJavaObj) == STATUS_OK;
+    }
+
+    public boolean flush() {
+        return nativeFlush(mNativeRecordInJavaObj) == STATUS_OK;
+    }
+
+    public long getPositionInMsec() {
+        long[] position = new long[1];
+        if (nativeGetPositionInMsec(mNativeRecordInJavaObj, position) != STATUS_OK) {
+            throw new IllegalStateException();
+        }
+        return position[0];
+    }
+
+    public int getBuffersPending() {
+        return nativeGetBuffersPending(mNativeRecordInJavaObj);
+    }
+
+    public int read(@NonNull byte[] byteArray,
+            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags) {
+        return nativeReadByteArray(
+                mNativeRecordInJavaObj, byteArray, offsetInSamples, sizeInSamples, readFlags);
+    }
+
+    public int read(@NonNull short[] shortArray,
+            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags) {
+        return nativeReadShortArray(
+                mNativeRecordInJavaObj, shortArray, offsetInSamples, sizeInSamples, readFlags);
+    }
+
+    public int read(@NonNull float[] floatArray,
+            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags) {
+        return nativeReadFloatArray(
+                mNativeRecordInJavaObj, floatArray, offsetInSamples, sizeInSamples, readFlags);
+    }
+
+    public int getChannelCount() {
+        return mChannelCount;
+    }
+
+    public static boolean test(int numChannels, int sampleRate, boolean useFloat,
+            int msecPerBuffer, int numBuffers) {
+        return nativeTest(numChannels, sampleRate, useFloat, msecPerBuffer, numBuffers)
+                == STATUS_OK;
+    }
+
+    @Override
+    protected void finalize() {
+        nativeClose(mNativeRecordInJavaObj);
+        nativeDestroyRecord(mNativeRecordInJavaObj);
+    }
+
+    static {
+        System.loadLibrary("audio_jni");
+    }
+
+    private static final String TAG = "AudioRecordNative";
+    private int mChannelCount;
+    private final long mNativeRecordInJavaObj;
+    private static final int STATUS_OK = 0;
+
+    // static native API.
+    // The native API uses a long "record handle" created by nativeCreateRecord.
+    // The handle must be destroyed after use by nativeDestroyRecord.
+    //
+    // Return codes from the native layer are status_t.
+    // Converted to Java booleans or exceptions at the public API layer.
+    private static native long nativeCreateRecord();
+    private static native void nativeDestroyRecord(long record);
+    private static native int nativeOpen(
+            long record, int numChannels, int sampleRate, boolean useFloat, int numBuffers);
+    private static native void nativeClose(long record);
+    private static native int nativeStart(long record);
+    private static native int nativeStop(long record);
+    private static native int nativePause(long record);
+    private static native int nativeFlush(long record);
+    private static native int nativeGetPositionInMsec(long record, @NonNull long[] position);
+    private static native int nativeGetBuffersPending(long record);
+    private static native int nativeReadByteArray(long record, @NonNull byte[] byteArray,
+            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags);
+    private static native int nativeReadShortArray(long record, @NonNull short[] shortArray,
+            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags);
+    private static native int nativeReadFloatArray(long record, @NonNull float[] floatArray,
+            int offsetInSamples, int sizeInSamples, @ReadFlags int readFlags);
+
+    // native interface for all-in-one testing, no record handle required.
+    private static native int nativeTest(
+            int numChannels, int sampleRate, boolean useFloat, int msecPerBuffer, int numBuffers);
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioRecordTest.java b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
index 968a382..b1ee3f5 100644
--- a/tests/tests/media/src/android/media/cts/AudioRecordTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioRecordTest.java
@@ -698,6 +698,7 @@
         // blank final variables: all successful paths will initialize the times.
         final long endTime;
         final long startTime;
+        final long stopRequestTime;
         final long stopTime;
 
         try {
@@ -850,6 +851,7 @@
             // One must sleep to make sure the last event(s) come in.
             Thread.sleep(30);
 
+            stopRequestTime = System.currentTimeMillis();
             record.stop();
             assertEquals(AudioRecord.RECORDSTATE_STOPPED, record.getRecordingState());
 
@@ -911,10 +913,14 @@
         // and there is no record.getPosition(), we consider only differential timing
         // from the first marker or periodic event.
         final int toleranceInFrames = TEST_SR * 80 / 1000; // 80 ms
+        final int testTimeInFrames = (int)((long)TEST_TIME_MS * TEST_SR / 1000);
 
         AudioHelper.Statistics markerStat = new AudioHelper.Statistics();
         for (int i = 1; i < markerList.size(); ++i) {
             final int expected = mMarkerPeriodInFrames * i;
+            if (markerList.get(i) > testTimeInFrames) {
+                break; // don't consider any notifications when we might be stopping.
+            }
             final int actual = markerList.get(i) - markerList.get(0);
             //Log.d(TAG, "Marker: " + i + " expected(" + expected + ")  actual(" + actual
             //        + ")  diff(" + (actual - expected) + ")"
@@ -926,6 +932,9 @@
         AudioHelper.Statistics periodicStat = new AudioHelper.Statistics();
         for (int i = 1; i < periodicList.size(); ++i) {
             final int expected = updatePeriodInFrames * i;
+            if (periodicList.get(i) > testTimeInFrames) {
+                break; // don't consider any notifications when we might be stopping.
+            }
             final int actual = periodicList.get(i) - periodicList.get(0);
             //Log.d(TAG, "Update: " + i + " expected(" + expected + ")  actual(" + actual
             //        + ")  diff(" + (actual - expected) + ")"
@@ -938,9 +947,11 @@
         ReportLog log = getReportLog();
         log.printValue(reportName + ": startRecording lag", firstSampleTime - startTime,
                 ResultType.LOWER_BETTER, ResultUnit.MS);
+        log.printValue(reportName + ": stop execution time", stopTime - stopRequestTime,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
         log.printValue(reportName + ": Total record time expected", TEST_TIME_MS,
                 ResultType.NEUTRAL, ResultUnit.MS);
-        log.printValue(reportName + ": Total record time actual", (endTime - firstSampleTime),
+        log.printValue(reportName + ": Total record time actual", endTime - firstSampleTime,
                 ResultType.NEUTRAL, ResultUnit.MS);
         log.printValue(reportName + ": Total markers expected", markerPeriods,
                 ResultType.NEUTRAL, ResultUnit.COUNT);
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackNative.java b/tests/tests/media/src/android/media/cts/AudioTrackNative.java
new file mode 100644
index 0000000..1ce44ef
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/AudioTrackNative.java
@@ -0,0 +1,160 @@
+/*
+ * 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.media.cts;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.cts.util.CtsAndroidTestCase;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+public class AudioTrackNative {
+    // Must be kept in sync with C++ JNI audio-track-native (AudioTrackNative) WRITE_FLAG_*
+    public static final int WRITE_FLAG_BLOCKING = 1 << 0;
+    /** @hide */
+    @IntDef(flag = true,
+            value = {
+                    WRITE_FLAG_BLOCKING,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface WriteFlags { }
+
+    public AudioTrackNative() {
+        mNativeTrackInJavaObj = nativeCreateTrack();
+    }
+
+    // TODO: eventually accept AudioFormat
+    // numBuffers is the number of internal buffers before hitting the OpenSL ES.
+    // A value of 0 means that all writes are blocking.
+    public boolean open(int numChannels, int sampleRate, boolean useFloat, int numBuffers) {
+        if (nativeOpen(mNativeTrackInJavaObj, numChannels, sampleRate, useFloat, numBuffers)
+                == STATUS_OK) {
+            mChannelCount = numChannels;
+            return true;
+        }
+        return false;
+    }
+
+    public void close() {
+        nativeClose(mNativeTrackInJavaObj);
+    }
+
+    public boolean start() {
+        return nativeStart(mNativeTrackInJavaObj) == STATUS_OK;
+    }
+
+    public boolean stop() {
+        return nativeStop(mNativeTrackInJavaObj) == STATUS_OK;
+    }
+
+    public boolean pause() {
+        return nativePause(mNativeTrackInJavaObj) == STATUS_OK;
+    }
+
+    public boolean flush() {
+        return nativeFlush(mNativeTrackInJavaObj) == STATUS_OK;
+    }
+
+    public long getPositionInMsec() {
+        long[] position = new long[1];
+        if (nativeGetPositionInMsec(mNativeTrackInJavaObj, position) != STATUS_OK) {
+            throw new IllegalStateException();
+        }
+        return position[0];
+    }
+
+    public int getBuffersPending() {
+        return nativeGetBuffersPending(mNativeTrackInJavaObj);
+    }
+
+    /* returns number of samples written.
+     * 0 may be returned if !isBlocking.
+     * negative value indicates error.
+     */
+    public int write(@NonNull byte[] byteArray,
+            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags) {
+        return nativeWriteByteArray(
+                mNativeTrackInJavaObj, byteArray, offsetInSamples, sizeInSamples, writeFlags);
+    }
+
+    public int write(@NonNull short[] shortArray,
+            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags) {
+        return nativeWriteShortArray(
+                mNativeTrackInJavaObj, shortArray, offsetInSamples, sizeInSamples, writeFlags);
+    }
+
+    public int write(@NonNull float[] floatArray,
+            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags) {
+        return nativeWriteFloatArray(
+                mNativeTrackInJavaObj, floatArray, offsetInSamples, sizeInSamples, writeFlags);
+    }
+
+    public int getChannelCount() {
+        return mChannelCount;
+    }
+
+    public static boolean test(int numChannels, int sampleRate, boolean useFloat,
+            int msecPerBuffer, int numBuffers) {
+        return nativeTest(numChannels, sampleRate, useFloat, msecPerBuffer, numBuffers)
+                == STATUS_OK;
+    }
+
+    @Override
+    protected void finalize() {
+        nativeClose(mNativeTrackInJavaObj);
+        nativeDestroyTrack(mNativeTrackInJavaObj);
+    }
+
+    static {
+        System.loadLibrary("audio_jni");
+    }
+
+    private static final String TAG = "AudioTrackNative";
+    private int mChannelCount;
+    private final long mNativeTrackInJavaObj;
+    private static final int STATUS_OK = 0;
+
+    // static native API.
+    // The native API uses a long "track handle" created by nativeCreateTrack.
+    // The handle must be destroyed after use by nativeDestroyTrack.
+    //
+    // Return codes from the native layer are status_t.
+    // Converted to Java booleans or exceptions at the public API layer.
+    private static native long nativeCreateTrack();
+    private static native void nativeDestroyTrack(long track);
+    private static native int nativeOpen(
+            long track, int numChannels, int sampleRate, boolean useFloat, int numBuffers);
+    private static native void nativeClose(long track);
+    private static native int nativeStart(long track);
+    private static native int nativeStop(long track);
+    private static native int nativePause(long track);
+    private static native int nativeFlush(long track);
+    private static native int nativeGetPositionInMsec(long track, @NonNull long[] position);
+    private static native int nativeGetBuffersPending(long track);
+    private static native int nativeWriteByteArray(long track, @NonNull byte[] byteArray,
+            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags);
+    private static native int nativeWriteShortArray(long track, @NonNull short[] shortArray,
+            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags);
+    private static native int nativeWriteFloatArray(long track, @NonNull float[] floatArray,
+            int offsetInSamples, int sizeInSamples, @WriteFlags int writeFlags);
+
+    // native interface for all-in-one testing, no track handle required.
+    private static native int nativeTest(
+            int numChannels, int sampleRate, boolean useFloat, int msecPerBuffer, int numBuffers);
+}
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index 13afb97..b3fcce1 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -549,13 +549,15 @@
         MediaFormat format =
                 createMinFormat(mime, caps.getVideoCapabilities(), caps.colorFormats[0]);
         Vector<MediaCodec> codecs = new Vector<MediaCodec>();
+        MediaCodec codec = null;
         for (int i = 0; i < max; ++i) {
             try {
                 Log.d(TAG, "Create codec " + name + " #" + i);
-                MediaCodec codec = MediaCodec.createByCodecName(name);
+                codec = MediaCodec.createByCodecName(name);
                 codec.configure(format, null, null, flag);
                 codec.start();
                 codecs.add(codec);
+                codec = null;
             } catch (IllegalArgumentException e) {
                 fail("Got unexpected IllegalArgumentException " + e.getMessage());
             } catch (IOException e) {
@@ -569,12 +571,20 @@
                 } else {
                     fail("Unexpected CodecException " + e.getDiagnosticInfo());
                 }
+            } finally {
+                if (codec != null) {
+                    Log.d(TAG, "release codec");
+                    codec.release();
+                    codec = null;
+                }
             }
         }
         int actualMax = codecs.size();
         for (int i = 0; i < codecs.size(); ++i) {
+            Log.d(TAG, "release codec #" + i);
             codecs.get(i).release();
         }
+        codecs.clear();
         return actualMax;
     }
 
diff --git a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
index b6ee1db..ade790a 100644
--- a/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaRecorderTest.java
@@ -174,13 +174,29 @@
     }
 
     public void testRecorderCamera() throws Exception {
+        int width;
+        int height;
+        Camera camera = null;
         if (!hasCamera()) {
             return;
         }
+        // Try to get camera first supported resolution.
+        // If we fail for any reason, set the video size to default value.
+        try {
+            camera = Camera.open();
+            width = camera.getParameters().getSupportedPreviewSizes().get(0).width;
+            height = camera.getParameters().getSupportedPreviewSizes().get(0).height;
+        } catch (Exception e) {
+            width = VIDEO_WIDTH;
+            height = VIDEO_HEIGHT;
+        }
+        if (camera != null) {
+            camera.release();
+        }
         mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
         mMediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
         mMediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.DEFAULT);
-        mMediaRecorder.setVideoSize(VIDEO_WIDTH, VIDEO_HEIGHT);
+        mMediaRecorder.setVideoSize(width, height);
         mMediaRecorder.setVideoEncodingBitRate(VIDEO_BIT_RATE_IN_BPS);
         mMediaRecorder.setPreviewDisplay(mActivity.getSurfaceHolder().getSurface());
         mMediaRecorder.prepare();
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionTest.java b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
index 78ad1f9..3ebe6e4 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
@@ -26,6 +26,7 @@
 import android.media.VolumeProvider;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
+import android.media.session.MediaSession.QueueItem;
 import android.media.session.PlaybackState;
 import android.os.Bundle;
 import android.os.Handler;
@@ -165,8 +166,8 @@
 
             // test setQueue and setQueueTitle
             mCallback.resetLocked();
-            List<MediaSession.QueueItem> queue = new ArrayList<MediaSession.QueueItem>();
-            MediaSession.QueueItem item = new MediaSession.QueueItem(new MediaDescription.Builder()
+            List<QueueItem> queue = new ArrayList<>();
+            QueueItem item = new QueueItem(new MediaDescription.Builder()
                     .setMediaId(TEST_VALUE).setTitle("title").build(), TEST_QUEUE_ID);
             queue.add(item);
             mSession.setQueue(queue);
@@ -289,6 +290,23 @@
         }
     }
 
+    /**
+     * Tests MediaSession.QueueItem.
+     */
+    public void testQueueItem() {
+        QueueItem item = new QueueItem(new MediaDescription.Builder()
+                .setMediaId("media-id").setTitle("title").build(), TEST_QUEUE_ID);
+        assertEquals(TEST_QUEUE_ID, item.getQueueId());
+        assertEquals("media-id", item.getDescription().getMediaId());
+        assertEquals("title", item.getDescription().getTitle());
+
+        Parcel p = Parcel.obtain();
+        item.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        QueueItem other = QueueItem.CREATOR.createFromParcel(p);
+        assertEquals(item.toString(), other.toString());
+        p.recycle();
+    }
 
     /**
      * Verifies that a new session hasn't had any configuration bits set yet.
@@ -334,7 +352,7 @@
 
         private volatile PlaybackState mPlaybackState;
         private volatile MediaMetadata mMediaMetadata;
-        private volatile List<MediaSession.QueueItem> mQueue;
+        private volatile List<QueueItem> mQueue;
         private volatile CharSequence mTitle;
         private volatile String mEvent;
         private volatile Bundle mExtras;
@@ -377,7 +395,7 @@
         }
 
         @Override
-        public void onQueueChanged(List<MediaSession.QueueItem> queue) {
+        public void onQueueChanged(List<QueueItem> queue) {
             synchronized (mWaitLock) {
                 mOnQueueChangedCalled = true;
                 mQueue = queue;
diff --git a/tests/tests/media/src/android/media/cts/ResourceManagerStubActivity.java b/tests/tests/media/src/android/media/cts/ResourceManagerStubActivity.java
index f77b245..5595800 100644
--- a/tests/tests/media/src/android/media/cts/ResourceManagerStubActivity.java
+++ b/tests/tests/media/src/android/media/cts/ResourceManagerStubActivity.java
@@ -100,6 +100,7 @@
         };
         thread.start();
         thread.join(20000 /* millis */);
+        System.gc();
         Thread.sleep(5000);  // give the gc a chance to release test activities.
 
         boolean result = true;
diff --git a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity1.java b/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity1.java
index 938324c..a11d773 100644
--- a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity1.java
+++ b/tests/tests/media/src/android/media/cts/ResourceManagerTestActivity1.java
@@ -31,26 +31,16 @@
         super.onCreate(savedInstanceState);
         moveTaskToBack(true);
 
-        if (allocateCodecs(MAX_INSTANCES) == MAX_INSTANCES) {
-            // haven't reached the limit with MAX_INSTANCES, report RESULT_OK directly and
-            // skip additional test.
-            finishWithResult(RESULT_OK);
-        }
-        useCodecs();
-
-        boolean waitForReclaim = true;
         Bundle extras = getIntent().getExtras();
         if (extras != null) {
-            waitForReclaim = extras.getBoolean("wait-for-reclaim", waitForReclaim);
+            mWaitForReclaim = extras.getBoolean("wait-for-reclaim", mWaitForReclaim);
         }
 
-        try {
-            Thread.sleep(15000);  // timeout to ensure the activity is finished.
-        } catch (InterruptedException e) {
+        if (allocateCodecs(MAX_INSTANCES) == MAX_INSTANCES) {
+            // haven't reached the limit with MAX_INSTANCES, no need to wait for reclaim exception.
+            mWaitForReclaim = false;
         }
-        stopUsingCodecs();
-        // if the test is supposed to wait for reclaim event then this is a failure, otherwise
-        // this is a pass.
-        finishWithResult(waitForReclaim ? RESULT_CANCELED : RESULT_OK);
+
+        useCodecs();
     }
 }
diff --git a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivityBase.java b/tests/tests/media/src/android/media/cts/ResourceManagerTestActivityBase.java
index 38d1a92..689babb 100644
--- a/tests/tests/media/src/android/media/cts/ResourceManagerTestActivityBase.java
+++ b/tests/tests/media/src/android/media/cts/ResourceManagerTestActivityBase.java
@@ -34,6 +34,7 @@
     public static final int TYPE_MIX = 2;
 
     protected String TAG;
+    private static final int FRAME_RATE = 10;
     private static final int IFRAME_INTERVAL = 10;  // 10 seconds between I-frames
     private static final String MIME = MediaFormat.MIMETYPE_VIDEO_AVC;
 
@@ -64,18 +65,16 @@
 
     private MediaCodec.Callback mCallback = new TestCodecCallback();
 
-    private static MediaFormat getTestFormat(VideoCapabilities vcaps, boolean securePlayback) {
-        int maxWidth = vcaps.getSupportedWidths().getUpper();
-        int maxHeight = vcaps.getSupportedHeightsFor(maxWidth).getUpper();
-        int maxBitrate = vcaps.getBitrateRange().getUpper();
-        int maxFramerate = vcaps.getSupportedFrameRatesFor(maxWidth, maxHeight)
-                .getUpper().intValue();
+    private MediaFormat getTestFormat(CodecCapabilities caps, boolean securePlayback) {
+        VideoCapabilities vcaps = caps.getVideoCapabilities();
+        int width = vcaps.getSupportedWidths().getLower();
+        int height = vcaps.getSupportedHeightsFor(width).getLower();
+        int bitrate = vcaps.getBitrateRange().getLower();
 
-        MediaFormat format = MediaFormat.createVideoFormat(MIME, maxWidth, maxHeight);
-        format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
-                CodecCapabilities.COLOR_FormatYUV420Flexible);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, maxBitrate);
-        format.setInteger(MediaFormat.KEY_FRAME_RATE, maxFramerate);
+        MediaFormat format = MediaFormat.createVideoFormat(MIME, width, height);
+        format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
+        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+        format.setInteger(MediaFormat.KEY_FRAME_RATE, FRAME_RATE);
         format.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, IFRAME_INTERVAL);
         format.setFeatureEnabled(CodecCapabilities.FEATURE_SecurePlayback, securePlayback);
         return format;
@@ -122,23 +121,27 @@
             Log.d(TAG, "type is: " + type);
         }
 
-        boolean shouldSkip = true;
+        boolean shouldSkip = false;
         boolean securePlayback;
         if (type == TYPE_NONSECURE || type == TYPE_MIX) {
             securePlayback = false;
             MediaCodecInfo info = getTestCodecInfo(securePlayback);
             if (info != null) {
-                shouldSkip = false;
                 allocateCodecs(max, info, securePlayback);
+            } else {
+                shouldSkip = true;
             }
         }
 
-        if (type == TYPE_SECURE || type == TYPE_MIX) {
-            securePlayback = true;
-            MediaCodecInfo info = getTestCodecInfo(securePlayback);
-            if (info != null) {
-                shouldSkip = false;
-                allocateCodecs(max, info, securePlayback);
+        if (!shouldSkip) {
+            if (type == TYPE_SECURE || type == TYPE_MIX) {
+                securePlayback = true;
+                MediaCodecInfo info = getTestCodecInfo(securePlayback);
+                if (info != null) {
+                    allocateCodecs(max, info, securePlayback);
+                } else {
+                    shouldSkip = true;
+                }
             }
         }
 
@@ -154,8 +157,7 @@
     protected void allocateCodecs(int max, MediaCodecInfo info, boolean securePlayback) {
         String name = info.getName();
         CodecCapabilities caps = info.getCapabilitiesForType(MIME);
-        VideoCapabilities vcaps = caps.getVideoCapabilities();
-        MediaFormat format = getTestFormat(vcaps, securePlayback);
+        MediaFormat format = getTestFormat(caps, securePlayback);
         MediaCodec codec = null;
         for (int i = mCodecs.size(); i < max; ++i) {
             try {
@@ -177,13 +179,14 @@
             } catch (MediaCodec.CodecException e) {
                 Log.d(TAG, "CodecException 0x" + Integer.toHexString(e.getErrorCode()));
                 break;
+            } finally {
+                if (codec != null) {
+                    Log.d(TAG, "release codec");
+                    codec.release();
+                    codec = null;
+                }
             }
         }
-        if (codec != null) {
-            Log.d(TAG, "release codec");
-            codec.release();
-            codec = null;
-        }
     }
 
     protected void finishWithResult(int result) {
@@ -191,6 +194,7 @@
             Log.d(TAG, "release codec #" + i);
             mCodecs.get(i).release();
         }
+        mCodecs.clear();
         setResult(result);
         finish();
         Log.d(TAG, "activity finished");
@@ -214,6 +218,7 @@
         }
     }
 
+    protected boolean mWaitForReclaim = true;
     private Thread mWorkerThread;
     private volatile boolean mUseCodecs = true;
     private volatile boolean mGotReclaimedException = false;
@@ -221,28 +226,29 @@
         mWorkerThread = new Thread(new Runnable() {
             @Override
             public void run() {
-                while (mUseCodecs) {
+                long start = System.currentTimeMillis();
+                long timeSinceStartedMs = 0;
+                while (mUseCodecs && (timeSinceStartedMs < 15000)) {  // timeout in 15s
                     doUseCodecs();
                     try {
                         Thread.sleep(50 /* millis */);
                     } catch (InterruptedException e) {}
+                    timeSinceStartedMs = System.currentTimeMillis() - start;
                 }
                 if (mGotReclaimedException) {
+                    Log.d(TAG, "Got expected reclaim exception.");
                     finishWithResult(RESULT_OK);
+                } else {
+                    Log.d(TAG, "Stopped without getting reclaim exception.");
+                    // if the test is supposed to wait for reclaim event then this is a failure,
+                    // otherwise this is a pass.
+                    finishWithResult(mWaitForReclaim ? RESULT_CANCELED : RESULT_OK);
                 }
             }
         });
         mWorkerThread.start();
     }
 
-    protected void stopUsingCodecs() {
-        mUseCodecs = false;
-        try {
-            mWorkerThread.join(1000);
-        } catch (InterruptedException e) {
-        }
-    }
-
     @Override
     protected void onDestroy() {
         Log.d(TAG, "onDestroy called.");
diff --git a/tests/tests/media/src/android/media/cts/RingtoneTest.java b/tests/tests/media/src/android/media/cts/RingtoneTest.java
index f5218e3..3527b1a 100644
--- a/tests/tests/media/src/android/media/cts/RingtoneTest.java
+++ b/tests/tests/media/src/android/media/cts/RingtoneTest.java
@@ -16,6 +16,7 @@
 
 package android.media.cts;
 
+import android.app.UiAutomation;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.media.AudioManager;
@@ -23,10 +24,10 @@
 import android.media.RingtoneManager;
 import android.net.Uri;
 import android.provider.Settings;
-import android.test.AndroidTestCase;
+import android.test.InstrumentationTestCase;
 import android.util.Log;
 
-public class RingtoneTest extends AndroidTestCase {
+public class RingtoneTest extends InstrumentationTestCase {
     private static final String TAG = "RingtoneTest";
 
     private Context mContext;
@@ -40,7 +41,8 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mContext = getContext();
+        enableAppOps();
+        mContext = getInstrumentation().getContext();
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         mRingtone = RingtoneManager.getRingtone(mContext, Settings.System.DEFAULT_RINGTONE_URI);
         // backup ringer settings
@@ -58,6 +60,18 @@
                 RingtoneManager.TYPE_RINGTONE);
     }
 
+    private void enableAppOps() {
+        StringBuilder cmd = new StringBuilder();
+        cmd.append("appops set ");
+        cmd.append(getInstrumentation().getContext().getPackageName());
+        cmd.append(" android:write_settings allow");
+        getInstrumentation().getUiAutomation().executeShellCommand(cmd.toString());
+        try {
+            Thread.sleep(2200);
+        } catch (InterruptedException e) {
+        }
+    }
+
     @Override
     protected void tearDown() throws Exception {
         // restore original settings
@@ -76,7 +90,7 @@
     }
 
     private boolean hasAudioOutput() {
-        return getContext().getPackageManager()
+        return getInstrumentation().getContext().getPackageManager()
             .hasSystemFeature(PackageManager.FEATURE_AUDIO_OUTPUT);
     }
 
diff --git a/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java b/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
index cbb1a5d..5937253 100644
--- a/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
+++ b/tests/tests/media/src/android/media/cts/VideoDecoderPerfTest.java
@@ -29,7 +29,6 @@
 import android.media.MediaCodecInfo.CodecCapabilities;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
-import android.os.Build;
 import android.util.Log;
 import android.view.Surface;
 
@@ -231,12 +230,6 @@
         String message = "average fps for " + testConfig;
         double fps = (double)outputNum / ((finish - start) / 1000.0);
         mReportLog.printValue(message, fps, ResultType.HIGHER_BETTER, ResultUnit.FPS);
-
-        // STOPSHIP(ronghuawu): Remove after 07/26/2015, b/22537593
-        if (Build.TYPE.equals("eng") || Build.TYPE.equals("userdebug")) {
-            message = "frame time diff for " + testConfig + ": " + Arrays.toString(frameTimeDiff);
-            mReportLog.printValue(message, 0, ResultType.NEUTRAL, ResultUnit.NONE);
-        }
     }
 
     public void testH264320x240() throws Exception {
diff --git a/tests/tests/net/jni/NativeMultinetworkJni.c b/tests/tests/net/jni/NativeMultinetworkJni.c
index 9a5ab9c..4da0c1c4 100644
--- a/tests/tests/net/jni/NativeMultinetworkJni.c
+++ b/tests/tests/net/jni/NativeMultinetworkJni.c
@@ -122,9 +122,7 @@
     struct addrinfo *res = NULL;
     net_handle_t handle = (net_handle_t) nethandle;
 
-    // Quoth Ian Swett:
-    //     "QUIC always uses 80 and 443, but only 443 is used for secure(HTTPS) traffic."
-    int rval = android_getaddrinfofornetwork(handle, kHostname, "80", &kHints, &res);
+    int rval = android_getaddrinfofornetwork(handle, kHostname, "443", &kHints, &res);
     if (rval != 0) {
         ALOGD("android_getaddrinfofornetwork(%llu, %s) returned rval=%d errno=%d",
               handle, kHostname, rval, errno);
diff --git a/tests/tests/permission/src/android/permission/cts/ProviderPermissionTest.java b/tests/tests/permission/src/android/permission/cts/ProviderPermissionTest.java
index 2dd5892..6d55a69 100644
--- a/tests/tests/permission/src/android/permission/cts/ProviderPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/ProviderPermissionTest.java
@@ -16,8 +16,10 @@
 
 package android.permission.cts;
 
+import android.content.ContentValues;
 import android.provider.CallLog;
 import android.provider.Contacts;
+import android.provider.Settings;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.MediumTest;
 
@@ -73,7 +75,19 @@
      *   {@link android.Manifest.permission#WRITE_SETTINGS}
      */
     public void testWriteSettings() {
-        assertWritingContentUriRequiresPermission(android.provider.Settings.System.CONTENT_URI,
-                android.Manifest.permission.WRITE_SETTINGS);
+        final String permission = android.Manifest.permission.WRITE_SETTINGS;
+        ContentValues value = new ContentValues();
+        value.put(Settings.System.NAME, "name");
+        value.put(Settings.System.VALUE, "value_insert");
+
+        try {
+            getContext().getContentResolver().insert(Settings.System.CONTENT_URI, value);
+            fail("expected SecurityException requiring " + permission);
+        } catch (SecurityException expected) {
+            assertNotNull("security exception's error message.", expected.getMessage());
+            assertTrue("error message should contain \"" + permission + "\". Got: \""
+                    + expected.getMessage() + "\".",
+                    expected.getMessage().contains(permission));
+        }
     }
 }
diff --git a/tests/tests/print/src/android/print/cts/BasePrintTest.java b/tests/tests/print/src/android/print/cts/BasePrintTest.java
index 31dfb9f..e75ec94 100644
--- a/tests/tests/print/src/android/print/cts/BasePrintTest.java
+++ b/tests/tests/print/src/android/print/cts/BasePrintTest.java
@@ -60,9 +60,13 @@
 import org.mockito.InOrder;
 import org.mockito.stubbing.Answer;
 
+import java.io.BufferedReader;
 import java.io.File;
+import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 import java.util.concurrent.TimeoutException;
@@ -80,6 +84,12 @@
 
     private static final String PM_CLEAR_SUCCESS_OUTPUT = "Success";
 
+    private static final String COMMAND_LIST_ENABLED_IME_COMPONENTS = "ime list -s";
+
+    private static final String COMMAND_PREFIX_ENABLE_IME = "ime enable ";
+
+    private static final String COMMAND_PREFIX_DISABLE_IME = "ime disable ";
+
     private PrintDocumentActivity mActivity;
 
     private Locale mOldLocale;
@@ -91,11 +101,49 @@
     private CallCounter mPrintJobQueuedCallCounter;
     private CallCounter mDestroySessionCallCounter;
 
+    private String[] mEnabledImes;
+
+    private String[] getEnabledImes() throws IOException {
+        List<String> imeList = new ArrayList<>();
+
+        ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation()
+                .executeShellCommand(COMMAND_LIST_ENABLED_IME_COMPONENTS);
+        BufferedReader reader = new BufferedReader(
+                new InputStreamReader(new FileInputStream(pfd.getFileDescriptor())));
+
+        String line;
+        while ((line = reader.readLine()) != null) {
+            imeList.add(line);
+        }
+
+        String[] imeArray = new String[imeList.size()];
+        imeList.toArray(imeArray);
+
+        return imeArray;
+    }
+
+    private void disableImes() throws Exception {
+        mEnabledImes = getEnabledImes();
+        for (String ime : mEnabledImes) {
+            String disableImeCommand = COMMAND_PREFIX_DISABLE_IME + ime;
+            SystemUtil.runShellCommand(getInstrumentation(), disableImeCommand);
+        }
+    }
+
+    private void enableImes() throws Exception {
+        for (String ime : mEnabledImes) {
+            String enableImeCommand = COMMAND_PREFIX_ENABLE_IME + ime;
+            SystemUtil.runShellCommand(getInstrumentation(), enableImeCommand);
+        }
+        mEnabledImes = null;
+    }
+
     @Override
     public void setUp() throws Exception {
         // Make sure we start with a clean slate.
         clearPrintSpoolerData();
         enablePrintServices();
+        disableImes();
 
         // Workaround for dexmaker bug: https://code.google.com/p/dexmaker/issues/detail?id=2
         // Dexmaker is used by mockito.
@@ -130,6 +178,7 @@
     public void tearDown() throws Exception {
         // Done with the activity.
         getActivity().finish();
+        enableImes();
 
         // Restore the locale if needed.
         if (mOldLocale != null) {
diff --git a/tests/tests/rscpp/src/android/cts/rscpp/RSResizeTest.java b/tests/tests/rscpp/src/android/cts/rscpp/RSResizeTest.java
index 51ae26e..6826b59 100644
--- a/tests/tests/rscpp/src/android/cts/rscpp/RSResizeTest.java
+++ b/tests/tests/rscpp/src/android/cts/rscpp/RSResizeTest.java
@@ -86,6 +86,7 @@
         } else {
             rsCppOutput.copyFromUnchecked(nativeFloatAlloc);
         }
+        mVerify.set_image_tolerance(0.04f); // Kept loose till a better test designed
         mVerify.invoke_verify(rsOutput, rsCppOutput, rsInput);
         checkForErrors();
     }
diff --git a/tests/tests/rscpp/src/android/cts/rscpp/verify.rs b/tests/tests/rscpp/src/android/cts/rscpp/verify.rs
index 7fd44d3..700b9d5 100644
--- a/tests/tests/rscpp/src/android/cts/rscpp/verify.rs
+++ b/tests/tests/rscpp/src/android/cts/rscpp/verify.rs
@@ -24,10 +24,10 @@
 int gAllowedIntError = 0;
 static bool hadError = false;
 static int2 errorLoc = {0,0};
-
+float image_tolerance = 0.0001f;
 
 static bool compare_float(float f1, float f2) {
-    if (fabs(f1-f2) > 0.0001f) {
+    if (fabs(f1-f2) > image_tolerance) {
         hadError = true;
         return false;
     }
diff --git a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index c862cc3..ba94884 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -18,6 +18,10 @@
 
 import static android.telecom.cts.TestUtils.*;
 
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.junit.Assert.assertThat;
+
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -37,6 +41,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -69,7 +75,7 @@
     TelecomManager mTelecomManager;
     InCallServiceCallbacks mInCallCallbacks;
     String mPreviousDefaultDialer = null;
-    MockConnectionService connectionService = new MockConnectionService();
+    MockConnectionService connectionService;
 
     @Override
     protected void setUp() throws Exception {
@@ -96,13 +102,16 @@
         super.tearDown();
     }
 
-    protected PhoneAccount setupConnectionService(CtsConnectionService connectionService,
+    protected PhoneAccount setupConnectionService(MockConnectionService connectionService,
             int flags)
             throws Exception {
-        if (connectionService == null) {
-            connectionService = this.connectionService;
+        if (connectionService != null) {
+            this.connectionService = connectionService;
+        } else {
+            // Generate a vanilla mock connection service, if not provided.
+            this.connectionService = new MockConnectionService();
         }
-        CtsConnectionService.setUp(TEST_PHONE_ACCOUNT, connectionService);
+        CtsConnectionService.setUp(TEST_PHONE_ACCOUNT, this.connectionService);
 
         if ((flags & FLAG_REGISTER) != 0) {
             mTelecomManager.registerPhoneAccount(TEST_PHONE_ACCOUNT);
@@ -141,6 +150,26 @@
         mInCallCallbacks = new InCallServiceCallbacks() {
             @Override
             public void onCallAdded(Call call, int numCalls) {
+                Log.i(TAG, "onCallAdded, Call: " + call + "Num Calls: " + numCalls);
+                this.lock.release();
+            }
+            public void onCallRemoved(Call call, int numCalls) {
+                Log.i(TAG, "onCallRemoved, Call: " + call + "Num Calls: " + numCalls);
+            }
+            @Override
+            public void onParentChanged(Call call, Call parent) {
+                Log.i(TAG, "onParentChanged, Call: " + call + "Parent: " + parent);
+                this.lock.release();
+            }
+            @Override
+            public void onChildrenChanged(Call call, List<Call> children) {
+                Log.i(TAG, "onChildrenChanged, Call: " + call + "Children: " + children);
+                this.lock.release();
+            }
+            @Override
+            public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
+                Log.i(TAG, "onConferenceableCallsChanged, Call: " + call + "Conferenceables: " +
+                        conferenceableCalls);
                 this.lock.release();
             }
         };
@@ -153,6 +182,12 @@
      * {@link CtsConnectionService} which can be tested.
      */
     void addAndVerifyNewIncomingCall(Uri incomingHandle, Bundle extras) {
+        assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
+        int currentCallCount = 0;
+        if (mInCallCallbacks.getService() != null) {
+            currentCallCount = mInCallCallbacks.getService().getCallCount();
+        }
+
         if (extras == null) {
             extras = new Bundle();
         }
@@ -167,7 +202,8 @@
             Log.i(TAG, "Test interrupted!");
         }
 
-        assertEquals("InCallService should contain 1 call after adding a call.", 1,
+        assertEquals("InCallService should contain 1 more call after adding a call.",
+                currentCallCount + 1,
                 mInCallCallbacks.getService().getCallCount());
     }
 
@@ -202,6 +238,11 @@
      *  {@link CtsConnectionService} which can be tested.
      */
     void placeAndVerifyCall(Bundle extras, int videoState) {
+        assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
+        int currentCallCount = 0;
+        if (mInCallCallbacks.getService() != null) {
+            currentCallCount = mInCallCallbacks.getService().getCallCount();
+        }
         placeNewCallWithPhoneAccount(extras, videoState);
 
         try {
@@ -212,11 +253,17 @@
             Log.i(TAG, "Test interrupted!");
         }
 
-        assertEquals("InCallService should contain 1 call after adding a call.", 1,
+        assertEquals("InCallService should contain 1 more call after adding a call.",
+                currentCallCount + 1,
                 mInCallCallbacks.getService().getCallCount());
     }
 
     MockConnection verifyConnectionForOutgoingCall() {
+        // Assuming only 1 connection present
+        return verifyConnectionForOutgoingCall(0);
+    }
+
+    MockConnection verifyConnectionForOutgoingCall(int connectionIndex) {
         try {
             if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
                     TimeUnit.MILLISECONDS)) {
@@ -226,19 +273,27 @@
             Log.i(TAG, "Test interrupted!");
         }
 
-        assertNotNull("Telecom should create outgoing connection for outgoing call",
-                connectionService.outgoingConnection);
-        assertNull("Telecom should not create incoming connection for outgoing call",
-                connectionService.incomingConnection);
+        assertThat("Telecom should create outgoing connection for outgoing call",
+                connectionService.outgoingConnections.size(), not(equalTo(0)));
+        assertEquals("Telecom should not create incoming connections for outgoing calls",
+                0, connectionService.incomingConnections.size());
+        MockConnection connection = connectionService.outgoingConnections.get(connectionIndex);
+        setAndverifyConnectionForOutgoingCall(connection);
+        return connection;
+    }
 
-        connectionService.outgoingConnection.setDialing();
-        connectionService.outgoingConnection.setActive();
-        assertEquals(Connection.STATE_ACTIVE,
-                connectionService.outgoingConnection.getState());
-        return connectionService.outgoingConnection;
+    void setAndverifyConnectionForOutgoingCall(MockConnection connection) {
+        connection.setDialing();
+        connection.setActive();
+        assertEquals(Connection.STATE_ACTIVE, connection.getState());
     }
 
     MockConnection verifyConnectionForIncomingCall() {
+        // Assuming only 1 connection present
+        return verifyConnectionForIncomingCall(0);
+    }
+
+    MockConnection verifyConnectionForIncomingCall(int connectionIndex) {
         try {
             if (!connectionService.lock.tryAcquire(TestUtils.WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
                     TimeUnit.MILLISECONDS)) {
@@ -248,23 +303,97 @@
             Log.i(TAG, "Test interrupted!");
         }
 
-        assertNull("Telecom should not create outgoing connection for outgoing call",
-                connectionService.outgoingConnection);
-        assertNotNull("Telecom should create incoming connection for outgoing call",
-                connectionService.incomingConnection);
-
-        connectionService.incomingConnection.setRinging();
-        assertEquals(Connection.STATE_RINGING,
-                connectionService.incomingConnection.getState());
-        return connectionService.incomingConnection;
+        assertThat("Telecom should create incoming connections for incoming calls",
+                connectionService.incomingConnections.size(), not(equalTo(0)));
+        assertEquals("Telecom should not create outgoing connections for incoming calls",
+                0, connectionService.outgoingConnections.size());
+        MockConnection connection = connectionService.incomingConnections.get(connectionIndex);
+        setAndverifyConnectionForIncomingCall(connection);
+        return connection;
     }
 
+    void setAndverifyConnectionForIncomingCall(MockConnection connection) {
+        connection.setRinging();
+        assertEquals(Connection.STATE_RINGING, connection.getState());
+    }
+
+    void setAndVerifyConferenceablesForOutgoingConnection(int connectionIndex) {
+        /**
+         * Make all other outgoing connections as conferenceable with this
+         * new connection.
+         */
+        MockConnection connection = connectionService.outgoingConnections.get(connectionIndex);
+        List<Connection> confConnections = new ArrayList<>(connectionService.outgoingConnections.size());
+        for (Connection c : connectionService.outgoingConnections) {
+            if (c != connection) {
+                confConnections.add(c);
+            }
+        }
+        connection.setConferenceableConnections(confConnections);
+
+        try {
+            if (!mInCallCallbacks.lock.tryAcquire(3, TimeUnit.SECONDS)) {
+                fail("No call added to the conferenceables list.");
+            }
+        } catch (InterruptedException e) {
+            Log.i(TAG, "Test interrupted!");
+        }
+        assertEquals(connection.getConferenceables(), confConnections);
+    }
+
+    void addAndVerifyConferenceCall(Call call1, Call call2) {
+        assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
+        int currentConfCallCount = 0;
+        if (mInCallCallbacks.getService() != null) {
+            currentConfCallCount = mInCallCallbacks.getService().getConferenceCallCount();
+        }
+        List<Call> call1ConfList = call1.getConferenceableCalls();
+        List<Call> call2ConfList = call2.getConferenceableCalls();
+        if (call1ConfList.contains(call2) && call2ConfList.contains(call1)) {
+            call1.conference(call2);
+        } else {
+            fail("Calls cannot be conferenced!");
+        }
+
+        /**
+         * We should have 1 onCallAdded, 2 onChildrenChanged and 2 onParentChanged invoked, so
+         * we should have 5 available permits on the incallService lock.
+         */
+        try {
+            if (!mInCallCallbacks.lock.tryAcquire(5, 3, TimeUnit.SECONDS)) {
+                fail("Conference addition failed.");
+            }
+        } catch (InterruptedException e) {
+            Log.i(TAG, "Test interrupted!");
+        }
+
+        assertEquals("InCallService should contain 1 more call after adding a conf call.",
+                currentConfCallCount + 1,
+                mInCallCallbacks.getService().getConferenceCallCount());
+    }
+
+    void splitFromConferenceCall(Call call1) {
+        assertEquals("Lock should have no permits!", 0, mInCallCallbacks.lock.availablePermits());
+
+        call1.splitFromConference();
+        /**
+         * We should have 1 onChildrenChanged and 1 onParentChanged invoked, so
+         * we should have 2 available permits on the incallService lock.
+         */
+        try {
+            if (!mInCallCallbacks.lock.tryAcquire(2, 3, TimeUnit.SECONDS)) {
+                fail("Conference split failed");
+            }
+        } catch (InterruptedException e) {
+            Log.i(TAG, "Test interrupted!");
+        }
+    }
     /**
      * Disconnect the created test call and verify that Telecom has cleared all calls.
      */
     void cleanupCalls() {
         if (mInCallCallbacks != null && mInCallCallbacks.getService() != null) {
-            mInCallCallbacks.getService().disconnectLastCall();
+            mInCallCallbacks.getService().disconnectAllCalls();
             assertNumCalls(mInCallCallbacks.getService(), 0);
         }
     }
@@ -314,6 +443,23 @@
     );
     }
 
+    void assertNumConferenceCalls(final MockInCallService inCallService, final int numCalls) {
+        waitUntilConditionIsTrueOrTimeout(new Condition() {
+            @Override
+            public Object expected() {
+                return numCalls;
+            }
+            @Override
+            public Object actual() {
+                return inCallService.getConferenceCallCount();
+            }
+        },
+        WAIT_FOR_STATE_CHANGE_TIMEOUT_MS,
+        "InCallService should contain " + numCalls + " conference calls."
+    );
+    }
+
+
     void assertMuteState(final InCallService incallService, final boolean isMuted) {
         waitUntilConditionIsTrueOrTimeout(
                 new Condition() {
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index 940ce48..a7aeb8b 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -165,7 +165,9 @@
      * Tests whether the getExtras() getter returns the correct object.
      */
     public void testExtras() {
-        assertThat(mCall.getDetails().getExtras(), is(Bundle.class));
+        if (mCall.getDetails().getExtras() != null) {
+            assertThat(mCall.getDetails().getExtras(), is(Bundle.class));
+        }
     }
 
     /**
@@ -179,7 +181,9 @@
      * Tests whether the getGatewayInfo() getter returns the correct object.
      */
     public void testGatewayInfo() {
-        assertThat(mCall.getDetails().getGatewayInfo(), is(GatewayInfo.class));
+        if (mCall.getDetails().getGatewayInfo() != null) {
+            assertThat(mCall.getDetails().getGatewayInfo(), is(GatewayInfo.class));
+        }
     }
 
     /**
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java b/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
new file mode 100644
index 0000000..50c08ff
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.telecom.cts;
+
+import static android.telecom.cts.TestUtils.*;
+
+import android.telecom.Call;
+import android.telecom.Connection;
+import android.telecom.ConnectionRequest;
+import android.telecom.PhoneAccount;
+import android.telecom.PhoneAccountHandle;
+import android.util.Log;
+
+import java.util.List;
+
+/**
+ * Extended suite of tests that use {@link CtsConnectionService} and {@link MockInCallService} to
+ * verify the functionality of Call Conferencing.
+ */
+public class ConferenceTest extends BaseTelecomTestWithMockServices {
+    public static final int CONF_CAPABILITIES = Connection.CAPABILITY_SEPARATE_FROM_CONFERENCE |
+            Connection.CAPABILITY_DISCONNECT_FROM_CONFERENCE | Connection.CAPABILITY_HOLD;
+    public static final int MERGE_CONF_CAPABILITIES = Connection.CAPABILITY_MERGE_CONFERENCE |
+            Connection.CAPABILITY_SWAP_CONFERENCE;
+
+    private Call mCall1;
+    private Call mCall2;
+    MockInCallService mInCallService;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        mContext = getInstrumentation().getContext();
+        if (shouldTestTelecom(mContext)) {
+            // Let's create 2 calls and mark them conferenceable in the setup itself.
+            PhoneAccount account = setupConnectionService(
+                    new MockConnectionService() {
+                        @Override
+                        public Connection onCreateOutgoingConnection(
+                                PhoneAccountHandle connectionManagerPhoneAccount,
+                                ConnectionRequest request) {
+                            Connection connection = super.onCreateOutgoingConnection(
+                                    connectionManagerPhoneAccount,
+                                    request);
+                            // Modify the connection object created with local values.
+                            int capabilities = connection.getConnectionCapabilities();
+                            connection.setConnectionCapabilities(capabilities | CONF_CAPABILITIES);
+                            return connection;
+                        }
+                    }, FLAG_REGISTER | FLAG_ENABLE);
+
+            placeAndVerifyCall();
+            verifyConnectionForOutgoingCall(0);
+            mInCallService = mInCallCallbacks.getService();
+            mCall1 = mInCallService.getLastCall();
+
+            placeAndVerifyCall();
+            verifyConnectionForOutgoingCall(1);
+            mCall2 = mInCallService.getLastCall();
+
+            setAndVerifyConferenceablesForOutgoingConnection(0);
+            setAndVerifyConferenceablesForOutgoingConnection(1);
+        }
+    }
+
+    public void testConferenceCreate() {
+        if (!shouldTestTelecom(mContext)) {
+            return;
+        }
+        addAndVerifyConferenceCall(mCall1, mCall2);
+        final Call conf = mInCallService.getLastConferenceCall();
+
+        if (mCall1.getParent() != conf || mCall2.getParent() != conf) {
+            fail("The 2 pariticipating calls should contain the conference call as its parent");
+        }
+        if (!(conf.getChildren().contains(mCall1) && conf.getChildren().contains(mCall2))) {
+            fail("The conference call should contain the 2 pariticipating calls as its children");
+        }
+    }
+
+    public void testConferenceSplit() {
+        if (!shouldTestTelecom(mContext)) {
+            return;
+        }
+
+        addAndVerifyConferenceCall(mCall1, mCall2);
+        final Call conf = mInCallService.getLastConferenceCall();
+        splitFromConferenceCall(mCall1);
+
+        if ((mCall1.getParent() == conf) || (conf.getChildren().contains(mCall1))) {
+            fail("Call 1 should not be still conferenced");
+        }
+    }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java b/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
index 6a5f7fb5..5f271e8 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConnectionTest.java
@@ -264,7 +264,7 @@
 
     public void testStateToString() {
         assertEquals("INITIALIZING", Connection.stateToString(Connection.STATE_INITIALIZING));
-        assertEquals("NEW", Connection.stateToString(Connection.STATE_INITIALIZING));
+        assertEquals("NEW", Connection.stateToString(Connection.STATE_NEW));
         assertEquals("RINGING", Connection.stateToString(Connection.STATE_INITIALIZING));
         assertEquals("DIALING", Connection.stateToString(Connection.STATE_DIALING));
         assertEquals("ACTIVE", Connection.stateToString(Connection.STATE_ACTIVE));
diff --git a/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
index 9cfa0e3..3a5e157 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CtsConnectionService.java
@@ -16,6 +16,7 @@
 
 package android.telecom.cts;
 
+import android.telecom.Conference;
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
 import android.telecom.ConnectionService;
@@ -32,9 +33,28 @@
  * tell CtsConnectionService to forward any method invocations to that test's implementation.
  * This is set up using {@link #setUp} and should be cleaned up before the end of the test using
  * {@link #tearDown}.
+ *
+ * sConnectionService: Contains the connection service object provided by the current test in
+ *                     progress. We use this object to forward any communication received from the
+ *                     Telecom framework to the test connection service.
+ * sTelecomConnectionService: Contains the connection service object registered to the Telecom
+ *                            framework. We use this object to forward any communication from the
+ *                            test connection service to the Telecom framework.
+ *
  */
 public class CtsConnectionService extends ConnectionService {
+    // This is the connection service implemented by the test
     private static ConnectionService sConnectionService;
+    // This is the connection service registered with Telecom
+    private static ConnectionService sTelecomConnectionService;
+
+    public CtsConnectionService() throws Exception {
+        super();
+        if (sTelecomConnectionService != null) {
+            throw new Exception("Telecom ConnectionService exists");
+        }
+        sTelecomConnectionService = this;
+    }
 
     // ConnectionService used by default as a fallback if no connection service is specified
     // during test setup.
@@ -85,9 +105,27 @@
             if (sConnectionService != null) {
                 return sConnectionService.onCreateIncomingConnection(
                         connectionManagerPhoneAccount, request);
+            } else {
+                return mMockConnectionService.onCreateIncomingConnection(
+                        connectionManagerPhoneAccount, request);
             }
-            return mMockConnectionService.onCreateIncomingConnection(
-                    connectionManagerPhoneAccount, request);
+        }
+    }
+
+    @Override
+    public void onConference(Connection connection1, Connection connection2) {
+        synchronized(sLock) {
+            if (sConnectionService != null) {
+                sConnectionService.onConference(connection1, connection2);
+            } else {
+                mMockConnectionService.onConference(connection1, connection2);
+            }
+        }
+    }
+
+    public static void addConferenceToTelecom(Conference conference) {
+        synchronized(sLock) {
+            sTelecomConnectionService.addConference(conference);
         }
     }
 }
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConference.java b/tests/tests/telecom/src/android/telecom/cts/MockConference.java
new file mode 100644
index 0000000..4edcc4f
--- /dev/null
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConference.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telecom.cts;
+
+import android.telecom.Conference;
+import android.telecom.Connection;
+import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
+
+/**
+ * {@link Conference} subclass that immediately performs any state changes that are a result of
+ * callbacks sent from Telecom.
+ */
+public class MockConference extends Conference {
+
+    public MockConference(PhoneAccountHandle phoneAccount) {
+        super(phoneAccount);
+    }
+
+    public MockConference(Connection a, Connection b) {
+        super(null);
+        setConnectionCapabilities(a.getConnectionCapabilities());
+        addConnection(a);
+        addConnection(b);
+        setActive();
+    }
+
+    @Override
+    public void onDisconnect() {
+        for (Connection c : getConnections()) {
+            c.setDisconnected(new DisconnectCause(DisconnectCause.REMOTE));
+            c.destroy();
+        }
+    }
+
+    @Override
+    public void onSeparate(Connection connection) {
+        if (getConnections().contains(connection)) {
+            removeConnection(connection);
+        }
+    }
+
+    @Override
+    public void onHold() {
+        for (Connection c : getConnections()) {
+            c.setOnHold();
+        }
+        setOnHold();
+    }
+
+    @Override
+    public void onUnhold() {
+        for (Connection c : getConnections()) {
+            c.setActive();
+        }
+        setActive();
+    }
+}
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
index 460b060..820d6e8 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConnection.java
@@ -20,6 +20,7 @@
 import android.telecom.CallAudioState;
 import android.telecom.Connection;
 import android.telecom.DisconnectCause;
+import android.telecom.PhoneAccountHandle;
 import android.telecom.VideoProfile;
 import android.util.Log;
 
@@ -35,6 +36,7 @@
     public int videoState = VideoProfile.STATE_AUDIO_ONLY;
     private String mDtmfString = "";
     private MockVideoProvider mMockVideoProvider;
+    private PhoneAccountHandle mPhoneAccountHandle;
 
     @Override
     public void onAnswer() {
@@ -154,4 +156,13 @@
     public MockVideoProvider getMockVideoProvider() {
         return mMockVideoProvider;
     }
+
+    public void setPhoneAccountHandle(PhoneAccountHandle handle)  {
+        mPhoneAccountHandle = handle;
+    }
+
+    public PhoneAccountHandle getPhoneAccountHandle()  {
+        return mPhoneAccountHandle;
+    }
+
 }
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java b/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java
index 250f197..3c993e6 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockConnectionService.java
@@ -18,9 +18,12 @@
 
 import android.telecom.Connection;
 import android.telecom.ConnectionRequest;
+import android.telecom.ConnectionService;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Semaphore;
 
 /**
@@ -28,7 +31,7 @@
  * of Telecom CTS tests that simply require that a outgoing call is placed, or incoming call is
  * received.
  */
-public class MockConnectionService extends CtsConnectionService {
+public class MockConnectionService extends ConnectionService {
     public static final int CONNECTION_PRESENTATION =  TelecomManager.PRESENTATION_ALLOWED;
 
     /**
@@ -39,14 +42,15 @@
     private boolean mCreateVideoProvider = true;
 
     public Semaphore lock = new Semaphore(0);
-    public MockConnection outgoingConnection;
-    public MockConnection incomingConnection;
+    public List<MockConnection> outgoingConnections = new ArrayList<MockConnection>();
+    public List<MockConnection> incomingConnections = new ArrayList<MockConnection>();
 
     @Override
     public Connection onCreateOutgoingConnection(PhoneAccountHandle connectionManagerPhoneAccount,
             ConnectionRequest request) {
         final MockConnection connection = new MockConnection();
         connection.setAddress(request.getAddress(), CONNECTION_PRESENTATION);
+        connection.setPhoneAccountHandle(connectionManagerPhoneAccount);
         if (mCreateVideoProvider) {
             connection.createMockVideoProvider();
         } else {
@@ -54,7 +58,7 @@
         }
         connection.setVideoState(request.getVideoState());
 
-        outgoingConnection = connection;
+        outgoingConnections.add(connection);
         lock.release();
         return connection;
     }
@@ -67,11 +71,20 @@
         connection.createMockVideoProvider();
         connection.setVideoState(request.getVideoState());
 
-        incomingConnection = connection;
+        incomingConnections.add(connection);
         lock.release();
         return connection;
     }
 
+    @Override
+    public void onConference(Connection connection1, Connection connection2) {
+        // Make sure that these connections are already not conferenced.
+        if (connection1.getConference() == null && connection2.getConference() == null) {
+            MockConference conference = new MockConference(connection1, connection2);
+            CtsConnectionService.addConferenceToTelecom(conference);
+        }
+    }
+
     public void setCreateVideoProvider(boolean createVideoProvider) {
         mCreateVideoProvider = createVideoProvider;
     }
diff --git a/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java b/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
index e725466..c1f35c3 100644
--- a/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
+++ b/tests/tests/telecom/src/android/telecom/cts/MockInCallService.java
@@ -27,6 +27,7 @@
 
 public class MockInCallService extends InCallService {
     private ArrayList<Call> mCalls = new ArrayList<>();
+    private ArrayList<Call> mConferenceCalls = new ArrayList<>();
     private static InCallServiceCallbacks sCallbacks;
     private Map<Call, MockVideoCallCallback> mVideoCallCallbacks =
             new ArrayMap<Call, MockVideoCallCallback>();
@@ -40,6 +41,9 @@
         public void onCallAdded(Call call, int numCalls) {};
         public void onCallRemoved(Call call, int numCalls) {};
         public void onCallStateChanged(Call call, int state) {};
+        public void onParentChanged(Call call, Call parent) {};
+        public void onChildrenChanged(Call call, List<Call> children) {};
+        public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {};
 
         final public MockInCallService getService() {
             return mService;
@@ -68,6 +72,30 @@
             super.onVideoCallChanged(call, videoCall);
             saveVideoCall(call, videoCall);
         }
+
+        @Override
+        public void onParentChanged(Call call, Call parent) {
+            super.onParentChanged(call, parent);
+            if (getCallbacks() != null) {
+                getCallbacks().onParentChanged(call, parent);
+            }
+        }
+
+        @Override
+        public void onChildrenChanged(Call call, List<Call> children) {
+            super.onChildrenChanged(call, children);
+            if (getCallbacks() != null) {
+                getCallbacks().onChildrenChanged(call, children);
+            }
+        }
+
+        @Override
+        public void onConferenceableCallsChanged(Call call, List<Call> conferenceableCalls) {
+            super.onConferenceableCallsChanged(call, conferenceableCalls);
+            if (getCallbacks() != null) {
+                getCallbacks().onConferenceableCallsChanged(call, conferenceableCalls);
+            }
+        }
     };
 
     private void saveVideoCall(Call call, VideoCall videoCall) {
@@ -93,26 +121,36 @@
     @Override
     public void onCallAdded(Call call) {
         super.onCallAdded(call);
-        if (!mCalls.contains(call)) {
-            mCalls.add(call);
-            call.registerCallback(mCallCallback);
-
-            VideoCall videoCall = call.getVideoCall();
-            if (videoCall != null) {
-                saveVideoCall(call, videoCall);
+        if (call.getDetails().hasProperty(Call.Details.PROPERTY_CONFERENCE) == true) {
+            if (!mConferenceCalls.contains(call)) {
+                mConferenceCalls.add(call);
+                call.registerCallback(mCallCallback);
+            }
+        } else {
+            if (!mCalls.contains(call)) {
+                mCalls.add(call);
+                call.registerCallback(mCallCallback);
+                VideoCall videoCall = call.getVideoCall();
+                if (videoCall != null) {
+                    saveVideoCall(call, videoCall);
+                }
             }
         }
         if (getCallbacks() != null) {
-            getCallbacks().onCallAdded(call, mCalls.size());
+            getCallbacks().onCallAdded(call, mCalls.size() + mConferenceCalls.size());
         }
     }
 
     @Override
     public void onCallRemoved(Call call) {
         super.onCallRemoved(call);
-        mCalls.remove(call);
+        if (call.getDetails().hasProperty(Call.Details.PROPERTY_CONFERENCE) == true) {
+            mConferenceCalls.remove(call);
+        } else {
+            mCalls.remove(call);
+        }
         if (getCallbacks() != null) {
-            getCallbacks().onCallRemoved(call, mCalls.size());
+            getCallbacks().onCallRemoved(call, mCalls.size() + mConferenceCalls.size());
             saveVideoCall(call, null /* remove videoCall */);
         }
     }
@@ -125,15 +163,32 @@
     }
 
     /**
+     * @return the number of conference calls currently added to the {@code InCallService}.
+     */
+    public int getConferenceCallCount() {
+        return mConferenceCalls.size();
+    }
+
+    /**
      * @return the most recently added call that exists inside the {@code InCallService}
      */
     public Call getLastCall() {
-        if (mCalls.size() >= 1) {
+        if (!mCalls.isEmpty()) {
             return mCalls.get(mCalls.size() - 1);
         }
         return null;
     }
 
+    /**
+     * @return the most recently added conference call that exists inside the {@code InCallService}
+     */
+    public Call getLastConferenceCall() {
+        if (!mConferenceCalls.isEmpty()) {
+            return mConferenceCalls.get(mConferenceCalls.size() - 1);
+        }
+        return null;
+    }
+
     public void disconnectLastCall() {
         final Call call = getLastCall();
         if (call != null) {
@@ -141,6 +196,25 @@
         }
     }
 
+    public void disconnectLastConferenceCall() {
+        final Call call = getLastConferenceCall();
+        if (call != null) {
+            call.disconnect();
+        }
+    }
+
+    public void disconnectAllCalls() {
+        for (final Call call: mCalls) {
+            call.disconnect();
+        }
+    }
+
+    public void disconnectAllConferenceCalls() {
+        for (final Call call: mConferenceCalls) {
+            call.disconnect();
+        }
+    }
+
     public static void setCallbacks(InCallServiceCallbacks callbacks) {
         synchronized (sLock) {
             sCallbacks = callbacks;
diff --git a/tests/tests/telephony/src/android/telephony/cts/CellInfoTest.java b/tests/tests/telephony/src/android/telephony/cts/CellInfoTest.java
index 5b88525..9a93a60 100644
--- a/tests/tests/telephony/src/android/telephony/cts/CellInfoTest.java
+++ b/tests/tests/telephony/src/android/telephony/cts/CellInfoTest.java
@@ -18,7 +18,9 @@
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.telephony.CellInfo;
-import android.telephony.PhoneStateListener;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellInfoWcdma;
 import android.telephony.TelephonyManager;
 import android.test.AndroidTestCase;
 import android.util.Log;
@@ -36,6 +38,9 @@
     private TelephonyManager mTelephonyManager;
     private static ConnectivityManager mCm;
     private static final String TAG = "android.telephony.cts.CellInfoTest";
+    // Maximum and minimum possible RSSI values(in dbm).
+    private static final int MAX_RRSI = -10;
+    private static final int MIN_RSSI = -150;
 
     @Override
     protected void setUp() throws Exception {
@@ -57,5 +62,66 @@
         assertNotNull("TelephonyManager.getAllCellInfo() returned NULL!", allCellInfo);
         assertTrue("TelephonyManager.getAllCellInfo() returned zero-length list!",
             allCellInfo.size() > 0);
+
+        int numRegisteredCells = 0;
+        for (CellInfo cellInfo : allCellInfo) {
+            if (cellInfo.isRegistered()) {
+                ++numRegisteredCells;
+            }
+            if (cellInfo instanceof CellInfoLte) {
+                verifyLteInfo((CellInfoLte) cellInfo);
+            } else if (cellInfo instanceof CellInfoWcdma) {
+                verifyWcdmaInfo((CellInfoWcdma) cellInfo);
+            } else if (cellInfo instanceof CellInfoGsm) {
+                verifyGsmInfo((CellInfoGsm) cellInfo);
+            }
+        }
+        // At most two cells could be registered.
+        assertTrue("None or too many registered cells : " + numRegisteredCells,
+                numRegisteredCells > 0 && numRegisteredCells <= 2);
+    }
+
+    // Verify lte cell information is within correct range.
+    private void verifyLteInfo(CellInfoLte lte) {
+        verifyRssiDbm(lte.getCellSignalStrength().getDbm());
+        // Verify LTE neighbor information.
+        if (!lte.isRegistered()) {
+            // Only physical cell id is available for LTE neighbor.
+            int pci = lte.getCellIdentity().getPci();
+            // Physical cell id should be within [0, 503].
+            assertTrue("getPci() out of range [0, 503]", pci >= 0 && pci <= 503);
+        }
+    }
+
+    // Verify wcdma cell information is within correct range.
+    private void verifyWcdmaInfo(CellInfoWcdma wcdma) {
+        verifyRssiDbm(wcdma.getCellSignalStrength().getDbm());
+        // Verify wcdma neighbor.
+        if (!wcdma.isRegistered()) {
+            // For wcdma neighbor, only primary scrambling code is available.
+            // Primary scrambling code should be within [0, 511].
+            int psc = wcdma.getCellIdentity().getPsc();
+            assertTrue("getPsc() out of range [0, 511]", psc >= 0 && psc <= 511);
+        }
+    }
+
+    // Verify gsm cell information is within correct range.
+    private void verifyGsmInfo(CellInfoGsm gsm) {
+        verifyRssiDbm(gsm.getCellSignalStrength().getDbm());
+        // Verify gsm neighbor.
+        if (!gsm.isRegistered()) {
+            // lac and cid are available in GSM neighbor information.
+            // Local area code and cellid should be with [0, 65535].
+            int lac = gsm.getCellIdentity().getLac();
+            assertTrue("getLac() out of range [0, 65535]", lac >= 0 && lac <= 65535);
+            int cid = gsm.getCellIdentity().getCid();
+            assertTrue("getCid() out range [0, 65535]", cid >= 0 && cid <= 65535);
+        }
+    }
+
+    // Rssi(in dbm) should be within [MIN_RSSI, MAX_RSSI].
+    private void verifyRssiDbm(int dbm) {
+        assertTrue("getCellSignalStrength().getDbm() out of range",
+                dbm >= MIN_RSSI && dbm <= MAX_RRSI);
     }
 }
diff --git a/tests/tests/text/src/android/text/format/cts/DateFormatTest.java b/tests/tests/text/src/android/text/format/cts/DateFormatTest.java
index 61fa37d..8ca1fea 100644
--- a/tests/tests/text/src/android/text/format/cts/DateFormatTest.java
+++ b/tests/tests/text/src/android/text/format/cts/DateFormatTest.java
@@ -19,19 +19,23 @@
 
 import android.content.ContentResolver;
 import android.content.Context;
+import android.os.ParcelFileDescriptor;
 import android.provider.Settings;
-import android.test.AndroidTestCase;
+import android.test.InstrumentationTestCase;
 import android.text.format.DateFormat;
 
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
+import java.io.FileInputStream;
+import java.io.InputStream;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.Locale;
+import java.util.Scanner;
 import java.util.TimeZone;
 
-public class DateFormatTest extends AndroidTestCase {
+public class DateFormatTest extends InstrumentationTestCase {
 
     private Context mContext;
     private ContentResolver mContentResolver;
@@ -50,12 +54,41 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
-        mContext = getContext();
+        enableAppOps();
+        mContext = getInstrumentation().getContext();
         mContentResolver = mContext.getContentResolver();
         mIs24HourFormat = DateFormat.is24HourFormat(mContext);
         mDefaultLocale = Locale.getDefault();
     }
 
+    private void enableAppOps() {
+        StringBuilder cmd = new StringBuilder();
+        cmd.append("appops set ");
+        cmd.append(getInstrumentation().getContext().getPackageName());
+        cmd.append(" android:write_settings allow");
+        getInstrumentation().getUiAutomation().executeShellCommand(cmd.toString());
+
+        StringBuilder query = new StringBuilder();
+        query.append("appops get ");
+        query.append(getInstrumentation().getContext().getPackageName());
+        query.append(" android:write_settings");
+        String queryStr = query.toString();
+
+        String result = "No operations.";
+        while (result.contains("No operations")) {
+            ParcelFileDescriptor pfd = getInstrumentation().getUiAutomation().executeShellCommand(
+                                        queryStr);
+            InputStream inputStream = new FileInputStream(pfd.getFileDescriptor());
+            result = convertStreamToString(inputStream);
+        }
+    }
+
+    private String convertStreamToString(InputStream is) {
+        try (Scanner scanner = new Scanner(is).useDelimiter("\\A")) {
+            return scanner.hasNext() ? scanner.next() : "";
+        }
+    }
+
     @Override
     protected void tearDown() throws Exception {
         if (!mIs24HourFormat) {
diff --git a/tests/tests/util/src/android/util/cts/ArrayMapTest.java b/tests/tests/util/src/android/util/cts/ArrayMapTest.java
index 7fdd0da..130b354 100644
--- a/tests/tests/util/src/android/util/cts/ArrayMapTest.java
+++ b/tests/tests/util/src/android/util/cts/ArrayMapTest.java
@@ -310,7 +310,7 @@
 
     private static void dump(ArrayMap map1, ArrayMap map2) {
         Log.e("test", "ArrayMap of " + map1.size() + " entries:");
-        for (int i=0; i<map2.size(); i++) {
+        for (int i=0; i<map1.size(); i++) {
             Log.e("test", "    " + map1.keyAt(i) + " -> " + map1.valueAt(i));
         }
         Log.e("test", "ArrayMap of " + map2.size() + " entries:");
diff --git a/tests/tests/util/src/android/util/cts/ArraySetTest.java b/tests/tests/util/src/android/util/cts/ArraySetTest.java
new file mode 100644
index 0000000..112459c
--- /dev/null
+++ b/tests/tests/util/src/android/util/cts/ArraySetTest.java
@@ -0,0 +1,495 @@
+/*
+ * 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.util.cts;
+
+import android.test.AndroidTestCase;
+import android.util.ArraySet;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+// As is the case with ArraySet itself, ArraySetTest borrows heavily from ArrayMapTest.
+
+public class ArraySetTest extends AndroidTestCase {
+    private static final String TAG = "ArraySetTest";
+
+    private static final boolean DEBUG = false;
+
+    private static final int OP_ADD = 1;
+    private static final int OP_REM = 2;
+
+    private static int[] OPS = new int[] {
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD, OP_ADD,
+            OP_ADD, OP_ADD, OP_ADD,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+            OP_REM, OP_REM, OP_REM,
+            OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM, OP_REM,
+    };
+
+    private static int[] KEYS = new int[] {
+            // General adding and removing.
+              -1,   1900,    600,    200,   1200,   1500,   1800,    100,   1900,
+            2100,    300,    800,    600,   1100,   1300,   2000,   1000,   1400,
+             600,     -1,   1900,    600,    300,   2100,    200,    800,    800,
+            1800,   1500,   1300,   1100,   2000,   1400,   1000,   1200,   1900,
+
+            // Shrink when removing item from end.
+             100,    200,    300,    400,    500,    600,    700,    800,    900,
+             900,    800,    700,    600,    500,    400,    300,    200,    100,
+
+            // Shrink when removing item from middle.
+             100,    200,    300,    400,    500,    600,    700,    800,    900,
+             900,    800,    700,    600,    500,    400,    200,    300,    100,
+
+            // Shrink when removing item from front.
+             100,    200,    300,    400,    500,    600,    700,    800,    900,
+             900,    800,    700,    600,    500,    400,    100,    200,    300,
+
+            // Test hash collisions.
+             105,    106,    108,    104,    102,    102,    107,      5,    205,
+               4,    202,    203,      3,      5,    101,    109,    200,    201,
+               0,     -1,    100,
+             106,    108,    104,    102,    103,    105,    107,    101,    109,
+              -1,    100,      0,
+               4,      5,      3,      5,    200,    203,    202,    201,    205,
+    };
+
+    public static class ControlledHash {
+        final int mValue;
+
+        ControlledHash(int value) {
+            mValue = value;
+        }
+
+        @Override
+        public final boolean equals(Object o) {
+            if (o == null) {
+                return false;
+            }
+            return mValue == ((ControlledHash)o).mValue;
+        }
+
+        @Override
+        public final int hashCode() {
+            return mValue/100;
+        }
+
+        @Override
+        public final String toString() {
+            return Integer.toString(mValue);
+        }
+    }
+
+    private static boolean compare(Object v1, Object v2) {
+        if (v1 == null) {
+            return v2 == null;
+        }
+        if (v2 == null) {
+            return false;
+        }
+        return v1.equals(v2);
+    }
+
+    private static <E> void compareSets(HashSet<E> set, ArraySet<E> array) {
+        assertEquals("Bad size", set.size(), array.size());
+
+        // Check that every entry in HashSet is in ArraySet.
+        for (E entry : set) {
+            assertTrue("ArraySet missing value: " + entry, array.contains(entry));
+        }
+
+        // Check that every entry in ArraySet is in HashSet using ArraySet.iterator().
+        for (E entry : array) {
+            assertTrue("ArraySet (via iterator) has unexpected value: " + entry,
+                    set.contains(entry));
+        }
+
+        // Check that every entry in ArraySet is in HashSet using ArraySet.valueAt().
+        for (int i = 0; i < array.size(); ++i) {
+            E entry = array.valueAt(i);
+            assertTrue("ArraySet (via valueAt) has unexpected value: " + entry,
+                    set.contains(entry));
+        }
+
+        if (set.hashCode() != array.hashCode()) {
+            assertEquals("Set hash codes differ", set.hashCode(), array.hashCode());
+        }
+
+        assertTrue("HashSet.equals(ArraySet) failed", set.equals(array));
+        assertTrue("ArraySet.equals(HashSet) failed", array.equals(set));
+
+        assertTrue("HashSet.containsAll(ArraySet) failed", set.containsAll(array));
+        assertTrue("ArraySet.containsAll(HashSet) failed", array.containsAll(set));
+    }
+
+    private static <E> void compareArraySetAndRawArray(ArraySet<E> arraySet, Object[] rawArray) {
+        assertEquals("Bad size", arraySet.size(), rawArray.length);
+        for (int i = 0; i < rawArray.length; ++i) {
+            assertEquals("ArraySet<E> and raw array unequal at index " + i,
+                    arraySet.valueAt(i), rawArray[i]);
+        }
+    }
+
+    private static <E> void validateArraySet(ArraySet<E> array) {
+        int index = 0;
+        Iterator<E> iter = array.iterator();
+        while (iter.hasNext()) {
+            E value = iter.next();
+            E realValue = array.valueAt(index);
+            if (!compare(realValue, value)) {
+                fail("Bad array set entry: expected " + realValue
+                        + ", got " + value + " at index " + index);
+            }
+            index++;
+        }
+
+        assertEquals("Length of iteration was unequal to size()", array.size(), index);
+    }
+
+    private static <E> void dump(HashSet<E> set, ArraySet<E> array) {
+        Log.e(TAG, "HashSet of " + set.size() + " entries:");
+        for (E entry : set) {
+            Log.e(TAG, "    " + entry);
+        }
+        Log.e(TAG, "ArraySet of " + array.size() + " entries:");
+        for (int i = 0; i < array.size(); i++) {
+            Log.e(TAG, "    " + array.valueAt(i));
+        }
+    }
+
+    private static void dump(ArraySet set1, ArraySet set2) {
+        Log.e(TAG, "ArraySet of " + set1.size() + " entries:");
+        for (int i = 0; i < set1.size(); i++) {
+            Log.e(TAG, "    " + set1.valueAt(i));
+        }
+        Log.e(TAG, "ArraySet of " + set2.size() + " entries:");
+        for (int i = 0; i < set2.size(); i++) {
+            Log.e(TAG, "    " + set2.valueAt(i));
+        }
+    }
+
+    public void testTest() {
+        assertEquals("OPS and KEYS must be equal length", OPS.length, KEYS.length);
+    }
+
+    public void testBasicArraySet() {
+        HashSet<ControlledHash> hashSet = new HashSet<ControlledHash>();
+        ArraySet<ControlledHash> arraySet = new ArraySet<ControlledHash>();
+
+        for (int i = 0; i < OPS.length; i++) {
+            ControlledHash key = KEYS[i] < 0 ? null : new ControlledHash(KEYS[i]);
+            String strKey = KEYS[i] < 0 ? null : Integer.toString(KEYS[i]);
+            switch (OPS[i]) {
+                case OP_ADD:
+                    if (DEBUG) Log.i(TAG, "Adding key: " + key);
+                    boolean hashAdded = hashSet.add(key);
+                    boolean arrayAdded = arraySet.add(key);
+                    assertEquals("Adding key " + key + " was not symmetric in HashSet and "
+                            + "ArraySet", hashAdded, arrayAdded);
+                    break;
+                case OP_REM:
+                    if (DEBUG) Log.i(TAG, "Removing key: " + key);
+                    boolean hashRemoved = hashSet.remove(key);
+                    boolean arrayRemoved = arraySet.remove(key);
+                    assertEquals("Removing key " + key + " was not symmetric in HashSet and "
+                            + "ArraySet", hashRemoved, arrayRemoved);
+                    break;
+                default:
+                    fail("Bad operation " + OPS[i] + " @ " + i);
+                    return;
+            }
+            if (DEBUG) dump(hashSet, arraySet);
+
+            try {
+                validateArraySet(arraySet);
+            } catch (Throwable e) {
+                Log.e(TAG, e.getMessage());
+                dump(hashSet, arraySet);
+                throw e;
+            }
+
+            try {
+                compareSets(hashSet, arraySet);
+            } catch (Throwable e) {
+                Log.e(TAG, e.getMessage());
+                dump(hashSet, arraySet);
+                throw e;
+            }
+        }
+
+        // Check to see if HashSet.iterator().remove() works as expected.
+        arraySet.add(new ControlledHash(50000));
+        ControlledHash lookup = new ControlledHash(50000);
+        Iterator<ControlledHash> it = arraySet.iterator();
+        while (it.hasNext()) {
+            if (it.next().equals(lookup)) {
+                it.remove();
+            }
+        }
+        if (arraySet.contains(lookup)) {
+            String msg = "Bad ArraySet iterator: didn't remove test key";
+            Log.e(TAG, msg);
+            dump(hashSet, arraySet);
+            fail(msg);
+        }
+
+        Log.e(TAG, "Test successful; printing final map.");
+        dump(hashSet, arraySet);
+    }
+
+    public void testCopyArraySet() {
+        // set copy constructor test
+        ArraySet newSet = new ArraySet<Integer>();
+        for (int i = 0; i < 10; ++i) {
+            newSet.add(i);
+        }
+
+        ArraySet copySet = new ArraySet(newSet);
+        if (!compare(copySet, newSet)) {
+            String msg = "ArraySet copy constructor failure: expected " +
+                    newSet + ", got " + copySet;
+            Log.e(TAG, msg);
+            dump(newSet, copySet);
+            fail(msg);
+            return;
+        }
+    }
+
+    public void testEqualsArrayMap() {
+        ArraySet<Integer> set1 = new ArraySet<Integer>();
+        ArraySet<Integer> set2 = new ArraySet<Integer>();
+        HashSet<Integer> set3 = new HashSet<Integer>();
+        if (!compare(set1, set2) || !compare(set1, set3) || !compare(set3, set2)) {
+            fail("ArraySet equals failure for empty sets " + set1 + ", " +
+                    set2 + ", " + set3);
+        }
+
+        for (int i = 0; i < 10; ++i) {
+            set1.add(i);
+            set2.add(i);
+            set3.add(i);
+        }
+        if (!compare(set1, set2) || !compare(set1, set3) || !compare(set3, set2)) {
+            fail("ArraySet equals failure for populated sets " + set1 + ", " +
+                    set2 + ", " + set3);
+        }
+
+        set1.remove(0);
+        if (compare(set1, set2) || compare(set1, set3) || compare(set3, set1)) {
+            fail("ArraySet equals failure for set size " + set1 + ", " +
+                    set2 + ", " + set3);
+        }
+    }
+
+    public void testIsEmpty() {
+        ArraySet<Integer> set = new ArraySet<Integer>();
+        assertEquals("New ArraySet should have size==0", 0, set.size());
+        assertTrue("New ArraySet should be isEmptry", set.isEmpty());
+
+        set.add(3);
+        assertEquals("ArraySet has incorrect size", 1, set.size());
+        assertFalse("ArraySet should not be isEmptry", set.isEmpty());
+
+        set.remove(3);
+        assertEquals("ArraySet should have size==0", 0, set.size());
+        assertTrue("ArraySet should be isEmptry", set.isEmpty());
+    }
+
+    public void testRemoveAt() {
+        ArraySet<Integer> set = new ArraySet<Integer>();
+
+        for (int i = 0; i < 10; ++i) {
+            set.add(i * 10);
+        }
+
+        int indexToDelete = 6;
+        assertEquals(10, set.size());
+        assertEquals(indexToDelete * 10, set.valueAt(indexToDelete).intValue());
+        assertEquals(indexToDelete * 10, set.removeAt(indexToDelete).intValue());
+        assertEquals(9, set.size());
+
+        for (int i = 0; i < 9; ++i) {
+            int expectedValue = ((i >= indexToDelete) ? (i + 1) : i) * 10;
+            assertEquals(expectedValue, set.valueAt(i).intValue());
+        }
+
+        for (int i = 9; i > 0; --i) {
+            set.removeAt(0);
+            assertEquals(i - 1, set.size());
+        }
+
+        assertTrue(set.isEmpty());
+
+        try {
+            set.removeAt(0);
+            fail("Expected ArrayIndexOutOfBoundsException");
+        } catch (ArrayIndexOutOfBoundsException expected) {
+            // expected
+        }
+    }
+
+    public void testIndexOf() {
+        ArraySet<Integer> set = new ArraySet<Integer>();
+
+        for (int i = 0; i < 10; ++i) {
+            set.add(i * 10);
+        }
+
+        for (int i = 0; i < 10; ++i) {
+            assertEquals("indexOf(" + (i * 10) + ")", i, set.indexOf(i * 10));
+        }
+    }
+
+    public void testAddAll() {
+        ArraySet<Integer> arraySet = new ArraySet<Integer>();
+        ArraySet<Integer> testArraySet = new ArraySet<Integer>();
+        ArrayList<Integer> testArrayList = new ArrayList<Integer>();
+
+        for (int i = 0; i < 10; ++i) {
+            testArraySet.add(i * 10);
+            testArrayList.add(i * 10);
+        }
+
+        assertTrue(arraySet.isEmpty());
+
+        // addAll(ArraySet) has no return value.
+        arraySet.addAll(testArraySet);
+        assertTrue("ArraySet.addAll(ArraySet) failed", arraySet.containsAll(testArraySet));
+
+        arraySet.clear();
+        assertTrue(arraySet.isEmpty());
+
+        // addAll(Collection) returns true if any items were added.
+        assertTrue(arraySet.addAll(testArrayList));
+        assertTrue("ArraySet.addAll(Container) failed", arraySet.containsAll(testArrayList));
+        assertTrue("ArraySet.addAll(Container) failed", arraySet.containsAll(testArraySet));
+        // Adding the same Collection should return false.
+        assertFalse(arraySet.addAll(testArrayList));
+        assertTrue("ArraySet.addAll(Container) failed", arraySet.containsAll(testArrayList));
+    }
+
+    public void testRemoveAll() {
+        ArraySet<Integer> arraySet = new ArraySet<Integer>();
+        ArraySet<Integer> arraySetToRemove = new ArraySet<Integer>();
+        ArrayList<Integer> arrayListToRemove = new ArrayList<Integer>();
+
+        for (int i = 0; i < 10; ++i) {
+            arraySet.add(i * 10);
+        }
+
+        for (int i = 6; i < 15; ++i) {
+            arraySetToRemove.add(i * 10);
+        }
+
+        for (int i = 3; i > -3; --i) {
+            arrayListToRemove.add(i * 10);
+        }
+
+        assertEquals(10, arraySet.size());
+
+        // Remove [6,14] (really [6,9]) via another ArraySet.
+        assertTrue(arraySet.removeAll(arraySetToRemove));
+        assertEquals(6, arraySet.size());
+        assertFalse(arraySet.removeAll(arraySetToRemove));
+        assertEquals(6, arraySet.size());
+
+        // Remove [-2,3] (really [0,3]) via an ArrayList (ie Collection).
+        assertTrue(arraySet.removeAll(arrayListToRemove));
+        assertEquals(2, arraySet.size());
+        assertFalse(arraySet.removeAll(arrayListToRemove));
+        assertEquals(2, arraySet.size());
+
+        // Remove the rest of the items.
+        ArraySet<Integer> copy = new ArraySet<Integer>(arraySet);
+        assertTrue(arraySet.removeAll(copy));
+        assertEquals(0, arraySet.size());
+        assertFalse(arraySet.removeAll(copy));
+        assertEquals(0, arraySet.size());
+    }
+
+    public void testRetainAll() {
+        ArraySet<Integer> arraySet = new ArraySet<Integer>();
+        ArrayList<Integer> arrayListToRetain = new ArrayList<Integer>();
+
+        for (int i = 0; i < 10; ++i) {
+            arraySet.add(i * 10);
+        }
+
+        arrayListToRetain.add(30);
+        arrayListToRetain.add(50);
+        arrayListToRetain.add(51); // bogus value
+
+        assertEquals(10, arraySet.size());
+
+        assertTrue(arraySet.retainAll(arrayListToRetain));
+        assertEquals(2, arraySet.size());
+
+        assertTrue(arraySet.contains(30));
+        assertTrue(arraySet.contains(50));
+        assertFalse(arraySet.contains(51));
+
+        // Nothing should change.
+        assertFalse(arraySet.retainAll(arrayListToRetain));
+        assertEquals(2, arraySet.size());
+    }
+
+    public void testToArray() {
+        ArraySet<Integer> arraySet = new ArraySet<Integer>();
+        for (int i = 0; i < 10; ++i) {
+            arraySet.add(i * 10);
+        }
+
+        // Allocate a new array with the right type given a zero-length ephemeral array.
+        Integer[] copiedArray = arraySet.toArray(new Integer[0]);
+        compareArraySetAndRawArray(arraySet, copiedArray);
+
+        // Allocate a new array with the right type given an undersized array.
+        Integer[] undersizedArray = new Integer[5];
+        copiedArray = arraySet.toArray(undersizedArray);
+        compareArraySetAndRawArray(arraySet, copiedArray);
+        assertNotSame(undersizedArray, copiedArray);
+
+        // Use the passed array that is large enough to hold the ArraySet.
+        Integer[] rightSizedArray = new Integer[10];
+        copiedArray = arraySet.toArray(rightSizedArray);
+        compareArraySetAndRawArray(arraySet, copiedArray);
+        assertSame(rightSizedArray, copiedArray);
+
+        // Create a new Object[] array.
+        Object[] objectArray = arraySet.toArray();
+        compareArraySetAndRawArray(arraySet, objectArray);
+    }
+}
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 65e95d5..b7e0076 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -92,7 +92,7 @@
         </activity>
 
         <activity android:name="android.view.animation.cts.GridLayoutAnimCtsActivity"
-            android:label="GridLayoutAnimCtsActivity">
+            android:label="GridLayoutAnimCtsActivity" android:configChanges="orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
@@ -100,7 +100,7 @@
         </activity>
 
         <activity android:name="android.view.animation.cts.LayoutAnimCtsActivity"
-            android:label="LayoutAnimCtsActivity">
+            android:label="LayoutAnimCtsActivity" android:configChanges="orientation|screenSize">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
index 31e1f8d..f5abd5e5 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiClass.java
@@ -34,10 +34,25 @@
 
     private final List<ApiMethod> mApiMethods = new ArrayList<ApiMethod>();
 
-    ApiClass(String name, boolean deprecated, boolean classAbstract) {
+    private final String mSuperClassName;
+
+    private ApiClass mSuperClass;
+
+    /**
+     * @param name The name of the class
+     * @param deprecated true iff the class is marked as deprecated
+     * @param classAbstract true iff the class is abstract
+     * @param superClassName The fully qualified name of the super class
+     */
+    ApiClass(
+            String name,
+            boolean deprecated,
+            boolean classAbstract,
+            String superClassName) {
         mName = name;
         mDeprecated = deprecated;
         mAbstract = classAbstract;
+        mSuperClassName = superClassName;
     }
 
     @Override
@@ -54,22 +69,20 @@
         return mDeprecated;
     }
 
+    public String getSuperClassName() {
+        return mSuperClassName;
+    }
+
     public boolean isAbstract() {
         return mAbstract;
     }
 
+    public void setSuperClass(ApiClass superClass) { mSuperClass = superClass; }
+
     public void addConstructor(ApiConstructor constructor) {
         mApiConstructors.add(constructor);
     }
 
-    public ApiConstructor getConstructor(List<String> parameterTypes) {
-        for (ApiConstructor constructor : mApiConstructors) {
-            if (parameterTypes.equals(constructor.getParameterTypes())) {
-                return constructor;
-            }
-        }
-        return null;
-    }
 
     public Collection<ApiConstructor> getConstructors() {
         return Collections.unmodifiableList(mApiConstructors);
@@ -79,15 +92,29 @@
         mApiMethods.add(method);
     }
 
-    public ApiMethod getMethod(String name, List<String> parameterTypes, String returnType) {
-        for (ApiMethod method : mApiMethods) {
-            if (name.equals(method.getName())
-                    && parameterTypes.equals(method.getParameterTypes())
-                    && returnType.equals(method.getReturnType())) {
-                return method;
-            }
+    /** Look for a matching constructor and mark it as covered */
+    public void markConstructorCovered(List<String> parameterTypes) {
+        if (mSuperClass != null) {
+            // Mark matching constructors in the superclass
+            mSuperClass.markConstructorCovered(parameterTypes);
         }
-        return null;
+        ApiConstructor apiConstructor = getConstructor(parameterTypes);
+        if (apiConstructor != null) {
+            apiConstructor.setCovered(true);
+        }
+
+    }
+
+    /** Look for a matching method and if found and mark it as covered */
+    public void markMethodCovered(String name, List<String> parameterTypes, String returnType) {
+        if (mSuperClass != null) {
+            // Mark matching methods in the super class
+            mSuperClass.markMethodCovered(name, parameterTypes, returnType);
+        }
+        ApiMethod apiMethod = getMethod(name, parameterTypes, returnType);
+        if (apiMethod != null) {
+            apiMethod.setCovered(true);
+        }
     }
 
     public Collection<ApiMethod> getMethods() {
@@ -126,4 +153,24 @@
     public int getMemberSize() {
         return getTotalMethods();
     }
+
+    private ApiMethod getMethod(String name, List<String> parameterTypes, String returnType) {
+        for (ApiMethod method : mApiMethods) {
+            if (name.equals(method.getName())
+                    && parameterTypes.equals(method.getParameterTypes())
+                    && returnType.equals(method.getReturnType())) {
+                return method;
+            }
+        }
+        return null;
+    }
+
+    private ApiConstructor getConstructor(List<String> parameterTypes) {
+        for (ApiConstructor constructor : mApiConstructors) {
+            if (parameterTypes.equals(constructor.getParameterTypes())) {
+                return constructor;
+            }
+        }
+        return null;
+    }
 }
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiCoverage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiCoverage.java
index adf2ea9..953aab3 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiCoverage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiCoverage.java
@@ -39,10 +39,11 @@
         return Collections.unmodifiableCollection(mPackages.values());
     }
 
-    public void removeEmptyAbstractClasses() {
+    /** Iterate through all packages and update all classes to include its superclass */
+    public void resolveSuperClasses() {
         for (Map.Entry<String, ApiPackage> entry : mPackages.entrySet()) {
             ApiPackage pkg = entry.getValue();
-            pkg.removeEmptyAbstractClasses();
+            pkg.resolveSuperClasses(mPackages);
         }
     }
 }
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java
index 053cd12..582c2b6 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiMethod.java
@@ -29,15 +29,35 @@
 
     private final String mReturnType;
 
-    private boolean mDeprecated;
+    private final boolean mDeprecated;
+
+    private final String mVisibility;
+
+    private final boolean mStaticMethod;
+
+    private final boolean mFinalMethod;
+
+    private final boolean mAbstractMethod;
 
     private boolean mIsCovered;
 
-    ApiMethod(String name, List<String> parameterTypes, String returnType, boolean deprecated) {
+    ApiMethod(
+            String name,
+            List<String> parameterTypes,
+            String returnType,
+            boolean deprecated,
+            String visibility,
+            boolean staticMethod,
+            boolean finalMethod,
+            boolean abstractMethod) {
         mName = name;
         mParameterTypes = new ArrayList<String>(parameterTypes);
         mReturnType = returnType;
         mDeprecated = deprecated;
+        mVisibility = visibility;
+        mStaticMethod = staticMethod;
+        mFinalMethod = finalMethod;
+        mAbstractMethod = abstractMethod;
     }
 
     @Override
@@ -65,6 +85,14 @@
         return mIsCovered;
     }
 
+    public String getVisibility() { return mVisibility; }
+
+    public boolean isAbstractMethod() { return mAbstractMethod; }
+
+    public boolean isStaticMethod() { return mStaticMethod; }
+
+    public boolean isFinalMethod() { return mFinalMethod; }
+
     public void setCovered(boolean covered) {
         mIsCovered = covered;
     }
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiPackage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiPackage.java
index e0bf73f..7be7e3c 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiPackage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/ApiPackage.java
@@ -77,14 +77,26 @@
         return getTotalMethods();
     }
 
-    public void removeEmptyAbstractClasses() {
+    /** Iterate through all classes and add superclass. */
+    public void resolveSuperClasses(Map<String, ApiPackage> packageMap) {
         Iterator<Entry<String, ApiClass>> it = mApiClassMap.entrySet().iterator();
         while (it.hasNext()) {
             Map.Entry<String, ApiClass> entry = it.next();
-            ApiClass cls = entry.getValue();
-            if (cls.isAbstract() && (cls.getTotalMethods() == 0)) {
-                // this is essentially interface
-                it.remove();
+            ApiClass apiClass = entry.getValue();
+            if (apiClass.getSuperClassName() != null) {
+                String superClassName = apiClass.getSuperClassName();
+                // Split the fully qualified class name into package and class name.
+                String packageName = superClassName.substring(0, superClassName.lastIndexOf('.'));
+                String className = superClassName.substring(
+                        superClassName.lastIndexOf('.') + 1, superClassName.length());
+                if (packageMap.containsKey(packageName)) {
+                    ApiPackage apiPackage = packageMap.get(packageName);
+                    ApiClass superClass = apiPackage.getClass(className);
+                    if (superClass != null) {
+                        // Add the super class
+                        apiClass.setSuperClass(superClass);
+                    }
+                }
             }
         }
     }
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
index 05cb4e19..3f2f353 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CtsApiCoverage.java
@@ -117,7 +117,8 @@
          */
 
         ApiCoverage apiCoverage = getEmptyApiCoverage(apiXmlPath);
-        apiCoverage.removeEmptyAbstractClasses();
+        // Add superclass information into api coverage.
+        apiCoverage.resolveSuperClasses();
         for (File testApk : testApks) {
             addApiCoverage(apiCoverage, testApk, dexDeps);
         }
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CurrentXmlHandler.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CurrentXmlHandler.java
index b9f9e9c..de9f5d5 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/CurrentXmlHandler.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/CurrentXmlHandler.java
@@ -40,6 +40,12 @@
 
     private boolean mCurrentMethodIsAbstract;
 
+    private String mCurrentMethodVisibility;
+
+    private boolean mCurrentMethodStaticMethod;
+
+    private boolean mCurrentMethodFinalMethod;
+
     private boolean mDeprecated;
 
 
@@ -69,7 +75,9 @@
             mIgnoreCurrentClass = false;
             mCurrentClassName = getValue(attributes, "name");
             mDeprecated = isDeprecated(attributes);
-            ApiClass apiClass = new ApiClass(mCurrentClassName, mDeprecated, isAbstract(attributes));
+            String superClass = attributes.getValue("extends");
+            ApiClass apiClass = new ApiClass(
+                    mCurrentClassName, mDeprecated, is(attributes, "abstract"), superClass);
             ApiPackage apiPackage = mApiCoverage.getPackage(mCurrentPackageName);
             apiPackage.addClass(apiClass);
         } else if ("interface".equalsIgnoreCase(localName)) {
@@ -82,7 +90,10 @@
             mDeprecated = isDeprecated(attributes);
             mCurrentMethodName = getValue(attributes, "name");
             mCurrentMethodReturnType = getValue(attributes, "return");
-            mCurrentMethodIsAbstract = isAbstract(attributes);
+            mCurrentMethodIsAbstract = is(attributes, "abstract");
+            mCurrentMethodVisibility = getValue(attributes, "visibility");
+            mCurrentMethodStaticMethod = is(attributes, "static");
+            mCurrentMethodFinalMethod = is(attributes, "final");
             mCurrentParameterTypes.clear();
         } else if ("parameter".equalsIgnoreCase(localName)) {
             mCurrentParameterTypes.add(getValue(attributes, "type"));
@@ -107,11 +118,15 @@
             ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
             apiClass.addConstructor(apiConstructor);
         }  else if ("method".equalsIgnoreCase(localName)) {
-            if (mCurrentMethodIsAbstract) { // do not add abstract method
-                return;
-            }
-            ApiMethod apiMethod = new ApiMethod(mCurrentMethodName, mCurrentParameterTypes,
-                    mCurrentMethodReturnType, mDeprecated);
+            ApiMethod apiMethod = new ApiMethod(
+                    mCurrentMethodName,
+                    mCurrentParameterTypes,
+                    mCurrentMethodReturnType,
+                    mDeprecated,
+                    mCurrentMethodVisibility,
+                    mCurrentMethodStaticMethod,
+                    mCurrentMethodFinalMethod,
+                    mCurrentMethodIsAbstract);
             ApiPackage apiPackage = mApiCoverage.getPackage(mCurrentPackageName);
             ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
             apiClass.addMethod(apiMethod);
@@ -129,8 +144,8 @@
         return "deprecated".equals(attributes.getValue("deprecated"));
     }
 
-    private boolean isAbstract(Attributes attributes) {
-        return "true".equals(attributes.getValue("abstract"));
+    private static boolean is(Attributes attributes, String valueName) {
+        return "true".equals(attributes.getValue(valueName));
     }
 
     private boolean isEnum(Attributes attributes) {
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
index 0a90bdd..3df532e 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/DexDepsXmlHandler.java
@@ -73,10 +73,7 @@
             if (apiPackage != null) {
                 ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
                 if (apiClass != null) {
-                    ApiConstructor apiConstructor = apiClass.getConstructor(mCurrentParameterTypes);
-                    if (apiConstructor != null) {
-                        apiConstructor.setCovered(true);
-                    }
+                    apiClass.markConstructorCovered(mCurrentParameterTypes);
                 }
             }
         }  else if ("method".equalsIgnoreCase(localName)) {
@@ -84,11 +81,8 @@
             if (apiPackage != null) {
                 ApiClass apiClass = apiPackage.getClass(mCurrentClassName);
                 if (apiClass != null) {
-                    ApiMethod apiMethod = apiClass.getMethod(mCurrentMethodName,
-                            mCurrentParameterTypes, mCurrentMethodReturnType);
-                    if (apiMethod != null) {
-                        apiMethod.setCovered(true);
-                    }
+                    apiClass.markMethodCovered(
+                            mCurrentMethodName, mCurrentParameterTypes, mCurrentMethodReturnType);
                 }
             }
         }
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
index e3e2e7c..3adc020 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/TextReport.java
@@ -103,7 +103,18 @@
     private static void printMethod(ApiMethod method, PrintStream out) {
         StringBuilder builder = new StringBuilder("    [")
                 .append(method.isCovered() ? "X" : " ")
-                .append("] ").append(method.getReturnType()).append(" ")
+                .append("] ")
+                .append(method.getVisibility()).append(" ");
+        if (method.isAbstractMethod()) {
+            builder.append("abstract ");
+        }
+        if (method.isStaticMethod()) {
+            builder.append("static ");
+        }
+        if (method.isFinalMethod()) {
+            builder.append("final ");
+        }
+        builder.append(method.getReturnType()).append(" ")
                 .append(method.getName()).append("(");
         List<String> parameterTypes = method.getParameterTypes();
         int numParameterTypes = parameterTypes.size();
diff --git a/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
index 570b316..4310d20 100644
--- a/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
+++ b/tools/cts-api-coverage/src/com/android/cts/apicoverage/XmlReport.java
@@ -66,7 +66,7 @@
                         + "\" numCovered=\"" + pkgTotalCovered
                         + "\" numTotal=\"" + pkgTotal
                         + "\" coveragePercentage=\""
-                            + Math.round(pkg.getCoveragePercentage())
+                        + Math.round(pkg.getCoveragePercentage())
                         + "\">");
 
                 List<ApiClass> classes = new ArrayList<ApiClass>(pkg.getClasses());
@@ -103,6 +103,10 @@
                             out.println("<method name=\"" + method.getName()
                                     + "\" returnType=\"" + method.getReturnType()
                                     + "\" deprecated=\"" + method.isDeprecated()
+                                    + "\" static=\"" + method.isStaticMethod()
+                                    + "\" final=\"" + method.isFinalMethod()
+                                    + "\" visibility=\"" + method.getVisibility()
+                                    + "\" abstract=\"" + method.isAbstractMethod()
                                     + "\" covered=\"" + method.isCovered() + "\">");
                             if (method.isDeprecated()) {
                                 if (method.isCovered()) {
diff --git a/tools/cts-api-coverage/src/res/api-coverage.xsl b/tools/cts-api-coverage/src/res/api-coverage.xsl
index b11a8c4..1a56eb0 100644
--- a/tools/cts-api-coverage/src/res/api-coverage.xsl
+++ b/tools/cts-api-coverage/src/res/api-coverage.xsl
@@ -101,14 +101,17 @@
                     <xsl:for-each select="api-coverage/api/package">
                         <xsl:call-template name="packageOrClassListItem">
                             <xsl:with-param name="bulletClass" select="'package'" />
+                            <xsl:with-param name="toggleId" select="@name" />
                         </xsl:call-template>
                         <div class="packageDetails" id="{@name}" style="display: none">
                             <ul>
                                 <xsl:for-each select="class">
+                                    <xsl:variable name="packageClassId" select="concat(../@name, '.', @name)"/>
                                     <xsl:call-template name="packageOrClassListItem">
                                         <xsl:with-param name="bulletClass" select="'class'" />
+                                        <xsl:with-param name="toggleId" select="$packageClassId" />
                                     </xsl:call-template>
-                                    <div class="classDetails" id="{@name}" style="display: none">
+                                    <div class="classDetails" id="{$packageClassId}" style="display: none">
                                         <xsl:for-each select="constructor">
                                             <xsl:call-template name="methodListItem" />
                                         </xsl:for-each>
@@ -124,9 +127,10 @@
             </body>
         </html>
     </xsl:template>
-    
+
     <xsl:template name="packageOrClassListItem">
         <xsl:param name="bulletClass" />
+        <xsl:param name="toggleId"/>
 
         <xsl:variable name="colorClass">
             <xsl:choose>
@@ -135,7 +139,7 @@
                 <xsl:otherwise>green</xsl:otherwise>
             </xsl:choose>
         </xsl:variable>
-        
+
         <xsl:variable name="deprecatedClass">
             <xsl:choose>
                 <xsl:when test="@deprecated = 'true'">deprecated</xsl:when>
@@ -143,15 +147,15 @@
             </xsl:choose>
         </xsl:variable>
 
-        <li class="{$bulletClass}" onclick="toggleVisibility('{@name}')">
+        <li class="{$bulletClass}" onclick="toggleVisibility('{$toggleId}')">
             <span class="{$colorClass} {$deprecatedClass}">
                 <b><xsl:value-of select="@name" /></b>
                 &nbsp;<xsl:value-of select="@coveragePercentage" />%
                 &nbsp;(<xsl:value-of select="@numCovered" />/<xsl:value-of select="@numTotal" />)
             </span>
-        </li>   
+        </li>
     </xsl:template>
-  
+
   <xsl:template name="methodListItem">
 
     <xsl:variable name="deprecatedClass">
@@ -166,6 +170,10 @@
         <xsl:when test="@covered = 'true'">[X]</xsl:when>
         <xsl:otherwise>[ ]</xsl:otherwise>
       </xsl:choose>
+      <xsl:if test="@visibility != ''">&nbsp;<xsl:value-of select="@visibility" /></xsl:if>
+      <xsl:if test="@abstract = 'true'">&nbsp;abstract</xsl:if>
+      <xsl:if test="@static = 'true'">&nbsp;static</xsl:if>
+      <xsl:if test="@final = 'true'">&nbsp;final</xsl:if>
       <xsl:if test="@returnType != ''">&nbsp;<xsl:value-of select="@returnType" /></xsl:if>
       <b>&nbsp;<xsl:value-of select="@name" /></b><xsl:call-template name="formatParameters" />
     </span>
diff --git a/tools/cts-device-info/Android.mk b/tools/cts-device-info/Android.mk
new file mode 100644
index 0000000..5f2b223
--- /dev/null
+++ b/tools/cts-device-info/Android.mk
@@ -0,0 +1,28 @@
+# Copyright (C) 2015 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+DEVICE_INFO_PERMISSIONS :=
+
+DEVICE_INFO_ACTIVITIES :=
+
+LOCAL_PACKAGE_NAME := CtsDeviceInfo
+
+include $(BUILD_CTS_DEVICE_INFO_PACKAGE)
+
diff --git a/tools/cts-device-info/src/com/android/cts/deviceinfo/SampleDeviceInfo.java b/tools/cts-device-info/src/com/android/cts/deviceinfo/SampleDeviceInfo.java
new file mode 100644
index 0000000..886193c
--- /dev/null
+++ b/tools/cts-device-info/src/com/android/cts/deviceinfo/SampleDeviceInfo.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.deviceinfo;
+
+import android.os.Bundle;
+
+import com.android.compatibility.common.deviceinfo.DeviceInfoActivity;
+
+/**
+ * Sample device info collector.
+ */
+public class SampleDeviceInfo extends DeviceInfoActivity {
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+    }
+
+    @Override
+    protected void collectDeviceInfo() {
+        boolean[] booleans = {Boolean.TRUE, Boolean.FALSE};
+        double[] doubles = {Double.MAX_VALUE, Double.MIN_VALUE};
+        int[] ints = {Integer.MAX_VALUE, Integer.MIN_VALUE};
+        long[] longs = {Long.MAX_VALUE, Long.MIN_VALUE};
+
+        // Group Foo
+        startGroup("foo");
+        addResult("foo_boolean", Boolean.TRUE);
+
+        // Group Bar
+        startGroup("bar");
+        addArray("bar_string", new String[] {
+                "bar-string-1",
+                "bar-string-2",
+                "bar-string-3"});
+
+        addArray("bar_boolean", booleans);
+        addArray("bar_double", doubles);
+        addArray("bar_int", ints);
+        addArray("bar_long", longs);
+        endGroup(); // bar
+
+        addResult("foo_double", Double.MAX_VALUE);
+        addResult("foo_int", Integer.MAX_VALUE);
+        addResult("foo_long", Long.MAX_VALUE);
+        addResult("foo_string", "foo-string");
+        endGroup(); // foo
+    }
+}
+
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildInfo.java b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildInfo.java
new file mode 100644
index 0000000..fafdb91
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildInfo.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.tradefed.build;
+
+import com.android.tradefed.build.FolderBuildInfo;
+
+import java.io.File;
+
+/**
+ * An extension of {@link FolderBuildInfo} that includes additional CTS build info.
+ */
+public class CtsBuildInfo extends FolderBuildInfo implements ICtsBuildInfo {
+
+    private File mResultDir = null;
+
+    /**
+     * Creates a {@link CtsBuildInfo}
+     *
+     * @param buildId
+     * @param testTarget
+     * @param buildName
+     */
+    public CtsBuildInfo(String buildId, String testTarget, String buildName) {
+        super(buildId, testTarget, buildName);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public File getResultDir() {
+        return mResultDir;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void setResultDir(File resultDir) {
+        mResultDir = resultDir;
+    }
+}
\ No newline at end of file
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
index a33fc01..6169e77 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/build/CtsBuildProvider.java
@@ -15,7 +15,6 @@
  */
 package com.android.cts.tradefed.build;
 
-import com.android.tradefed.build.FolderBuildInfo;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.build.IBuildProvider;
 import com.android.tradefed.build.IFolderBuildInfo;
@@ -42,7 +41,7 @@
         if (mCtsRootDirPath == null) {
             throw new IllegalArgumentException("Missing --cts-install-path");
         }
-        IFolderBuildInfo ctsBuild = new FolderBuildInfo(CTS_BUILD_VERSION, "cts", "cts");
+        CtsBuildInfo ctsBuild = new CtsBuildInfo(CTS_BUILD_VERSION, "cts", "cts");
         ctsBuild.setRootDir(new File(mCtsRootDirPath));
         return ctsBuild;
     }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/build/ICtsBuildInfo.java b/tools/tradefed-host/src/com/android/cts/tradefed/build/ICtsBuildInfo.java
new file mode 100644
index 0000000..0b3ad65
--- /dev/null
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/build/ICtsBuildInfo.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.tradefed.build;
+
+import com.android.tradefed.build.IFolderBuildInfo;
+
+import java.io.File;
+
+/**
+ * An {@link IFolderBuildInfo} that contains local CTS result directory.
+ */
+public interface ICtsBuildInfo extends IFolderBuildInfo{
+
+    /**
+     * Returns the local CTS result directory.
+     */
+    public File getResultDir();
+
+    /**
+     * Sets the local CTS result directory.
+     */
+    public void setResultDir(File resultDir);
+}
\ No newline at end of file
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/device/DeviceInfoCollector.java b/tools/tradefed-host/src/com/android/cts/tradefed/device/DeviceInfoCollector.java
index 733eb4f..7883fce 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/device/DeviceInfoCollector.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/device/DeviceInfoCollector.java
@@ -15,8 +15,10 @@
  */
 package com.android.cts.tradefed.device;
 
+import com.android.cts.tradefed.build.ICtsBuildInfo;
 import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.Log;
+import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.result.ITestInvocationListener;
@@ -39,10 +41,21 @@
     private static final String APK_NAME = "TestDeviceSetup";
     public static final String APP_PACKAGE_NAME = "android.tests.devicesetup";
     private static final String INSTRUMENTATION_NAME = "android.tests.getinfo.DeviceInfoInstrument";
+
+    private static final String EXTENDED_APK_NAME = "CtsDeviceInfo";
+    public static final String EXTENDED_APP_PACKAGE_NAME =
+            "com.android.compatibility.common.deviceinfo";
+    private static final String EXTENDED_INSTRUMENTATION_NAME =
+            "com.android.compatibility.common.deviceinfo.DeviceInfoInstrument";
+    private static final String DEVICE_RESULT_DIR = "/sdcard/device-info-files";
+
     public static final Set<String> IDS = new HashSet<String>();
+    public static final Set<String> EXTENDED_IDS = new HashSet<String>();
+
     static {
         for (String abi : AbiUtils.getAbisSupportedByCts()) {
             IDS.add(AbiUtils.createId(abi, APP_PACKAGE_NAME));
+            EXTENDED_IDS.add(AbiUtils.createId(abi, EXTENDED_APP_PACKAGE_NAME));
         }
     }
 
@@ -56,7 +69,33 @@
      */
     public static void collectDeviceInfo(ITestDevice device, String abi, File testApkDir,
             ITestInvocationListener listener) throws DeviceNotAvailableException {
-        File apkFile = new File(testApkDir, String.format("%s.apk", APK_NAME));
+        runInstrumentation(device, abi, testApkDir, listener, APK_NAME, APP_PACKAGE_NAME,
+            INSTRUMENTATION_NAME);
+    }
+
+    /**
+     * Installs and runs the extended device info collector instrumentation, and forwards results
+     * to the listener.
+     *
+     * @param device
+     * @param listener
+     * @throws DeviceNotAvailableException
+     */
+    public static void collectExtendedDeviceInfo(ITestDevice device, String abi, File testApkDir,
+            ITestInvocationListener listener, IBuildInfo buildInfo)
+            throws DeviceNotAvailableException {
+        // Clear files in device test result directory
+        device.executeShellCommand(String.format("rm -rf %s", DEVICE_RESULT_DIR));
+        runInstrumentation(device, abi, testApkDir, listener, EXTENDED_APK_NAME,
+            EXTENDED_APP_PACKAGE_NAME, EXTENDED_INSTRUMENTATION_NAME);
+        // Copy files in remote result directory to local directory
+        pullExtendedDeviceInfoResults(device, buildInfo);
+    }
+
+    private static void runInstrumentation(ITestDevice device, String abi, File testApkDir,
+            ITestInvocationListener listener, String apkName, String packageName,
+            String instrumentName) throws DeviceNotAvailableException {
+        File apkFile = new File(testApkDir, String.format("%s.apk", apkName));
         if (!apkFile.exists()) {
             Log.e(LOG_TAG, String.format("Could not find %s", apkFile.getAbsolutePath()));
             return;
@@ -68,9 +107,39 @@
         instrTest.setInstallFile(apkFile);
         // no need to collect tests and re-run
         instrTest.setRerunMode(false);
-        instrTest.setPackageName(APP_PACKAGE_NAME);
-        instrTest.setRunName(AbiUtils.createId(abi, APP_PACKAGE_NAME));
-        instrTest.setRunnerName(INSTRUMENTATION_NAME);
+        instrTest.setPackageName(packageName);
+        instrTest.setRunName(AbiUtils.createId(abi, packageName));
+        instrTest.setRunnerName(instrumentName);
         instrTest.run(listener);
     }
+
+    private static void pullExtendedDeviceInfoResults(ITestDevice device, IBuildInfo buildInfo)
+            throws DeviceNotAvailableException {
+        if (!(buildInfo instanceof ICtsBuildInfo)) {
+            Log.e(LOG_TAG, "Invalid instance of buildInfo");
+            return;
+        }
+        ICtsBuildInfo ctsBuildInfo = (ICtsBuildInfo) buildInfo;
+        File localResultDir = ctsBuildInfo.getResultDir();
+        if (localResultDir == null || !localResultDir.isDirectory()) {
+            Log.e(LOG_TAG, "Local result directory is null or is not a directory");
+            return;
+        }
+        // Pull files from device result directory to local result directory
+        String command = String.format("adb -s %s pull %s %s", device.getSerialNumber(),
+                DEVICE_RESULT_DIR, localResultDir.getAbsolutePath());
+        if (!execute(command)) {
+            Log.e(LOG_TAG, String.format("Failed to run %s", command));
+        }
+    }
+
+    private static boolean execute(String command) {
+        try {
+            Process p = Runtime.getRuntime().exec(new String[] {"/bin/bash", "-c", command});
+            return (p.waitFor() == 0);
+        } catch (Exception e) {
+            Log.e(LOG_TAG, e);
+            return false;
+        }
+    }
 }
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsTestLogReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsTestLogReporter.java
index 5029dfe..b7f064f 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsTestLogReporter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsTestLogReporter.java
@@ -43,6 +43,7 @@
     private TestResults mResults = new TestResults();
     private TestPackageResult mCurrentPkgResult = null;
     private boolean mIsDeviceInfoRun = false;
+    private boolean mIsExtendedDeviceInfoRun = false;
 
     @Override
     public void invocationStarted(IBuildInfo buildInfo) {
@@ -63,8 +64,11 @@
             logCompleteRun(mCurrentPkgResult);
         }
         mIsDeviceInfoRun = DeviceInfoCollector.IDS.contains(id);
+        mIsExtendedDeviceInfoRun = DeviceInfoCollector.EXTENDED_IDS.contains(id);
         if (mIsDeviceInfoRun) {
             logResult("Collecting device info");
+        } else if (mIsExtendedDeviceInfoRun) {
+            logResult("Collecting extended device info");
         } else  {
             if (mCurrentPkgResult == null || !id.equals(mCurrentPkgResult.getId())) {
                 logResult("-----------------------------------------");
@@ -132,9 +136,13 @@
     }
 
     private void logCompleteRun(TestPackageResult pkgResult) {
-        if (pkgResult.getAppPackageName().equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
+        String appPackageName = pkgResult.getAppPackageName();
+        if (appPackageName.equals(DeviceInfoCollector.APP_PACKAGE_NAME)) {
             logResult("Device info collection complete");
             return;
+        } else if (appPackageName.equals(DeviceInfoCollector.EXTENDED_APP_PACKAGE_NAME)) {
+            logResult("Extended device info collection complete");
+            return;
         }
         logResult("%s package complete: Passed %d, Failed %d, Not Executed %d",
                 pkgResult.getId(), pkgResult.countTests(CtsTestStatus.PASS),
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
index 8cb4072..f4f133a 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/CtsXmlResultReporter.java
@@ -17,13 +17,13 @@
 package com.android.cts.tradefed.result;
 
 import com.android.cts.tradefed.build.CtsBuildHelper;
+import com.android.cts.tradefed.build.ICtsBuildInfo;
 import com.android.cts.tradefed.device.DeviceInfoCollector;
 import com.android.cts.tradefed.testtype.CtsTest;
 import com.android.ddmlib.Log;
 import com.android.ddmlib.Log.LogLevel;
 import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.build.IFolderBuildInfo;
 import com.android.tradefed.config.Option;
 import com.android.tradefed.config.Option.Importance;
 import com.android.tradefed.log.LogUtil.CLog;
@@ -104,6 +104,7 @@
     private TestPackageResult mCurrentPkgResult = null;
     private Test mCurrentTest = null;
     private boolean mIsDeviceInfoRun = false;
+    private boolean mIsExtendedDeviceInfoRun = false;
     private ResultReporter mReporter;
     private File mLogDir;
     private String mSuiteName;
@@ -124,10 +125,10 @@
     @Override
     public void invocationStarted(IBuildInfo buildInfo) {
         mBuildInfo = buildInfo;
-        if (!(buildInfo instanceof IFolderBuildInfo)) {
-            throw new IllegalArgumentException("build info is not a IFolderBuildInfo");
+        if (!(buildInfo instanceof ICtsBuildInfo)) {
+            throw new IllegalArgumentException("build info is not a ICtsBuildInfo");
         }
-        IFolderBuildInfo ctsBuild = (IFolderBuildInfo)buildInfo;
+        ICtsBuildInfo ctsBuild = (ICtsBuildInfo)buildInfo;
         CtsBuildHelper ctsBuildHelper = getBuildHelper(ctsBuild);
         mDeviceSerial = buildInfo.getDeviceSerial() == null ? "unknown_device" :
             buildInfo.getDeviceSerial();
@@ -155,6 +156,8 @@
         mSuiteName = ctsBuildHelper.getSuiteName();
         mReporter = new ResultReporter(mResultServer, mSuiteName);
 
+        ctsBuild.setResultDir(mReportDir);
+
         // TODO: allow customization of log dir
         // create a unique directory for saving logs, with same name as result dir
         File rootLogDir = getBuildHelper(ctsBuild).getLogsDir();
@@ -199,7 +202,7 @@
      * Helper method to retrieve the {@link CtsBuildHelper}.
      * @param ctsBuild
      */
-    CtsBuildHelper getBuildHelper(IFolderBuildInfo ctsBuild) {
+    CtsBuildHelper getBuildHelper(ICtsBuildInfo ctsBuild) {
         CtsBuildHelper buildHelper = new CtsBuildHelper(ctsBuild.getRootDir());
         try {
             buildHelper.validateStructure();
@@ -255,7 +258,8 @@
     @Override
     public void testRunStarted(String id, int numTests) {
         mIsDeviceInfoRun = DeviceInfoCollector.IDS.contains(id);
-        if (!mIsDeviceInfoRun) {
+        mIsExtendedDeviceInfoRun = DeviceInfoCollector.EXTENDED_IDS.contains(id);
+        if (!mIsDeviceInfoRun && !mIsExtendedDeviceInfoRun) {
             mCurrentPkgResult = mResults.getOrCreatePackage(id);
             mCurrentPkgResult.setDeviceSerial(mDeviceSerial);
         }
@@ -266,7 +270,7 @@
      */
     @Override
     public void testStarted(TestIdentifier test) {
-        if (!mIsDeviceInfoRun) {
+        if (!mIsDeviceInfoRun && !mIsExtendedDeviceInfoRun) {
             mCurrentTest = mCurrentPkgResult.insertTest(test);
         }
     }
@@ -276,7 +280,7 @@
      */
     @Override
     public void testFailed(TestIdentifier test, String trace) {
-        if (!mIsDeviceInfoRun) {
+        if (!mIsDeviceInfoRun && !mIsExtendedDeviceInfoRun) {
             mCurrentPkgResult.reportTestFailure(test, CtsTestStatus.FAIL, trace);
         }
     }
@@ -287,7 +291,7 @@
     @Override
     public void testAssumptionFailure(TestIdentifier test, String trace) {
         // TODO: do something different here?
-        if (!mIsDeviceInfoRun) {
+        if (!mIsDeviceInfoRun && !mIsExtendedDeviceInfoRun) {
             mCurrentPkgResult.reportTestFailure(test, CtsTestStatus.FAIL, trace);
         }
     }
@@ -305,7 +309,7 @@
      */
     @Override
     public void testEnded(TestIdentifier test, Map<String, String> testMetrics) {
-        if (!mIsDeviceInfoRun) {
+        if (!mIsDeviceInfoRun && !mIsExtendedDeviceInfoRun) {
             mCurrentPkgResult.reportTestEnded(test, testMetrics);
         }
     }
@@ -317,11 +321,22 @@
     public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) {
         if (mIsDeviceInfoRun) {
             mResults.populateDeviceInfoMetrics(runMetrics);
+        } else if (mIsExtendedDeviceInfoRun) {
+            checkExtendedDeviceInfoMetrics(runMetrics);
         } else {
             mCurrentPkgResult.populateMetrics(runMetrics);
         }
     }
 
+    private void checkExtendedDeviceInfoMetrics(Map<String, String> runMetrics) {
+        for (Map.Entry<String, String> metricEntry : runMetrics.entrySet()) {
+            String value = metricEntry.getValue();
+            if (!value.endsWith(".deviceinfo.json")) {
+                CLog.e(String.format("%s failed: %s", metricEntry.getKey(), value));
+            }
+        }
+    }
+
     /**
      * {@inheritDoc}
      */
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
index b3bc7da..01f148ae 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/testtype/CtsTest.java
@@ -1058,6 +1058,8 @@
         if (!mSkipDeviceInfo) {
             String abi = AbiFormatter.getDefaultAbi(device, "");
             DeviceInfoCollector.collectDeviceInfo(device, abi, ctsBuild.getTestCasesDir(), listener);
+            DeviceInfoCollector.collectExtendedDeviceInfo(
+                device, abi, ctsBuild.getTestCasesDir(), listener, mBuildInfo);
         }
     }
 
diff --git a/tools/tradefed-host/tests/run_unit_func_tests.sh b/tools/tradefed-host/tests/run_unit_func_tests.sh
new file mode 100755
index 0000000..461a80d
--- /dev/null
+++ b/tools/tradefed-host/tests/run_unit_func_tests.sh
@@ -0,0 +1,48 @@
+#!/bin/bash
+
+# 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.
+
+# helper script for running the cts-tradefed unit tests
+
+checkFile() {
+    if [ ! -f "$1" ]; then
+        echo "Unable to locate $1"
+        exit
+    fi;
+}
+
+# check if in Android build env
+if [ ! -z ${ANDROID_BUILD_TOP} ]; then
+    HOST=`uname`
+    if [ "$HOST" == "Linux" ]; then
+        OS="linux-x86"
+    elif [ "$HOST" == "Darwin" ]; then
+        OS="darwin-x86"
+    else
+        echo "Unrecognized OS"
+        exit
+    fi;
+fi;
+
+JAR_DIR=${ANDROID_BUILD_TOP}/out/host/$OS/framework
+JARS="tradefed-prebuilt.jar hosttestlib.jar cts-tradefed.jar cts-tradefed-tests.jar"
+
+for JAR in $JARS; do
+    checkFile ${JAR_DIR}/${JAR}
+    JAR_PATH=${JAR_PATH}:${JAR_DIR}/${JAR}
+done
+
+java $RDBG_FLAG \
+  -cp ${JAR_PATH} com.android.tradefed.command.Console run singleCommand host --class com.android.cts.tradefed.FuncTests "$@"
diff --git a/tools/tradefed-host/tests/run_unit_tests.sh b/tools/tradefed-host/tests/run_unit_tests.sh
index 771dc75..d089c05 100755
--- a/tools/tradefed-host/tests/run_unit_tests.sh
+++ b/tools/tradefed-host/tests/run_unit_tests.sh
@@ -46,4 +46,3 @@
 
 java $RDBG_FLAG \
   -cp ${JAR_PATH} com.android.tradefed.command.Console run singleCommand host -n --class com.android.cts.tradefed.UnitTests "$@"
-
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/FuncTests.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/FuncTests.java
new file mode 100644
index 0000000..a9420d2
--- /dev/null
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/FuncTests.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.cts.tradefed;
+
+import com.android.cts.tradefed.device.DeviceInfoCollectorFuncTest;
+import com.android.tradefed.testtype.DeviceTestSuite;
+
+import junit.framework.Test;
+
+/**
+ * A test suite for all cts-tradefed functional tests.
+ * <p/>
+ * Tests listed here should require a device.
+ */
+public class FuncTests extends DeviceTestSuite {
+
+    public FuncTests() {
+        super();
+
+        // device package
+        addTestSuite(DeviceInfoCollectorFuncTest.class);
+    }
+
+    public static Test suite() {
+        return new FuncTests();
+    }
+}
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/device/DeviceInfoCollectorFuncTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/device/DeviceInfoCollectorFuncTest.java
index 52a205b..f60dc03 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/device/DeviceInfoCollectorFuncTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/device/DeviceInfoCollectorFuncTest.java
@@ -15,33 +15,75 @@
  */
 package com.android.cts.tradefed.device;
 
+import com.android.ddmlib.Log.LogLevel;
+import com.android.cts.tradefed.build.ICtsBuildInfo;
 import com.android.cts.tradefed.UnitTests;
 import com.android.tradefed.build.BuildInfo;
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.CollectingTestListener;
 import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.util.FileUtil;
 
 import java.io.File;
 import java.util.Map;
 
+import org.easymock.EasyMock;
+
 /**
  * Functional test for {@link DeviceInfoCollector}.
  * <p/>
- * TODO: this test assumes the TestDeviceSetup apk is located in the "java.io.tmpdir"
+ * TODO: this test assumes the TestDeviceSetup and DeviceInfoCollector apks are located in the
+ * "java.io.tmpdir"
  */
 public class DeviceInfoCollectorFuncTest extends DeviceTestCase {
 
-    public void testCollectDeviceInfo() throws DeviceNotAvailableException {
-        CollectingTestListener testListener = new CollectingTestListener();
+    private CollectingTestListener testListener;
+    private BuildInfo buildInfo;
+    private File mResultDir;
+    private ICtsBuildInfo mMockCtsBuildInfo;
 
-        testListener.invocationStarted(new BuildInfo());
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        testListener = new CollectingTestListener();
+        buildInfo = new BuildInfo();
+        mResultDir = FileUtil.createTempDir("cts-result-dir");
+        mMockCtsBuildInfo = EasyMock.createMock(ICtsBuildInfo.class);
+        EasyMock.expect(mMockCtsBuildInfo.getResultDir()).andStubReturn(mResultDir);
+        EasyMock.replay(mMockCtsBuildInfo);
+
+        assertNotNull(getDevice().getSerialNumber());
+    }
+
+    public void testCollectDeviceInfo() throws DeviceNotAvailableException {
+        testListener.invocationStarted(buildInfo);
         DeviceInfoCollector.collectDeviceInfo(getDevice(), UnitTests.ABI.getName(), new File(
                 System.getProperty("java.io.tmpdir")), testListener);
         assertNotNull(testListener.getCurrentRunResults());
-        assertTrue(testListener.getCurrentRunResults().getRunMetrics().size() > 0);
-        for (Map.Entry<String, String> metricEntry : testListener.getCurrentRunResults().getRunMetrics().entrySet()) {
-            System.out.println(String.format("%s=%s", metricEntry.getKey(), metricEntry.getValue()));
-        }
+
+        Map<String, String> runMetrics = testListener.getCurrentRunResults().getRunMetrics();
+        assertTrue(runMetrics.size() > 0);
+        displayMetrics(runMetrics);
         testListener.invocationEnded(0);
     }
+
+    public void testExtendedDeviceInfo() throws DeviceNotAvailableException {
+        testListener.invocationStarted(buildInfo);
+        DeviceInfoCollector.collectExtendedDeviceInfo(getDevice(), UnitTests.ABI.getName(),
+                new File(System.getProperty("java.io.tmpdir")), testListener, mMockCtsBuildInfo);
+        assertNotNull(testListener.getCurrentRunResults());
+
+        Map<String, String> runMetrics = testListener.getCurrentRunResults().getRunMetrics();
+        assertTrue(runMetrics.size() > 0);
+        displayMetrics(runMetrics);
+        testListener.invocationEnded(0);
+    }
+
+    private void displayMetrics(Map<String, String> runMetrics) {
+        for (Map.Entry<String, String> metricEntry : runMetrics.entrySet()) {
+            CLog.logAndDisplay(LogLevel.INFO,
+                    String.format("%s=%s", metricEntry.getKey(), metricEntry.getValue()));
+        }
+    }
 }
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
index ae4a41e..7ba1741 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/result/CtsXmlResultReporterTest.java
@@ -17,10 +17,10 @@
 
 import static com.android.cts.tradefed.result.CtsXmlResultReporter.CTS_RESULT_FILE_VERSION;
 
+import com.android.cts.tradefed.build.ICtsBuildInfo;
 import com.android.cts.tradefed.UnitTests;
 import com.android.cts.util.AbiUtils;
 import com.android.ddmlib.testrunner.TestIdentifier;
-import com.android.tradefed.build.IFolderBuildInfo;
 import com.android.tradefed.log.LogUtil.CLog;
 import com.android.tradefed.result.LogDataType;
 import com.android.tradefed.result.LogFile;
@@ -53,7 +53,7 @@
     private ByteArrayOutputStream mOutputStream;
     private File mBuildDir;
     private File mReportDir;
-    private IFolderBuildInfo mMockBuild;
+    private ICtsBuildInfo mMockBuild;
 
     /**
      * {@inheritDoc}
@@ -84,9 +84,11 @@
         File plansDir = new File(repoDir, "plans");
         assertTrue(casesDir.mkdirs());
         assertTrue(plansDir.mkdirs());
-        mMockBuild = EasyMock.createNiceMock(IFolderBuildInfo.class);
+        mMockBuild = EasyMock.createMock(ICtsBuildInfo.class);
         EasyMock.expect(mMockBuild.getDeviceSerial()).andStubReturn(null);
         EasyMock.expect(mMockBuild.getRootDir()).andStubReturn(mBuildDir);
+        mMockBuild.setResultDir((File) EasyMock.anyObject());
+        EasyMock.expectLastCall();
         EasyMock.replay(mMockBuild);
     }
 
diff --git a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/CtsTestTest.java b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/CtsTestTest.java
index 5dc2e5a..792b15e 100644
--- a/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/CtsTestTest.java
+++ b/tools/tradefed-host/tests/src/com/android/cts/tradefed/testtype/CtsTestTest.java
@@ -23,6 +23,7 @@
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
 import com.android.tradefed.result.ITestInvocationListener;
+import com.android.tradefed.targetprep.ITargetPreparer;
 import com.android.tradefed.testtype.IRemoteTest;
 import com.android.tradefed.util.xml.AbstractXmlParser.ParseException;
 
@@ -286,6 +287,8 @@
         EasyMock.expect(mMockPackageDef.getAbi()).andReturn(UnitTests.ABI).atLeastOnce();
         EasyMock.expect(mMockPackageDef.getId()).andReturn(ID).atLeastOnce();
         EasyMock.expect(mMockPackageDef.getDigest()).andReturn("digest").atLeastOnce();
+        EasyMock.expect(mMockPackageDef.getPackagePreparers()).andReturn(
+                    new ArrayList<ITargetPreparer>()).atLeastOnce();
         mMockTest.run((ITestInvocationListener) EasyMock.anyObject());
     }
 
diff --git a/tools/utils/buildCts.py b/tools/utils/buildCts.py
index e7a2f3c..0531c49 100755
--- a/tools/utils/buildCts.py
+++ b/tools/utils/buildCts.py
@@ -503,6 +503,11 @@
           'android.content.cts.ContentResolverTest#testUnstableToStableRefs',
           'android.content.cts.ContentResolverTest#testUpdate',
           'android.content.cts.ContentResolverTest#testValidateSyncExtrasBundle',],
+      'android.bluetooth' : [
+          'android.bluetooth.cts.BluetoothLeScanTest#testBasicBleScan',
+          'android.bluetooth.cts.BluetoothLeScanTest#testBatchScan',
+          'android.bluetooth.cts.BluetoothLeScanTest#testOpportunisticScan',
+          'android.bluetooth.cts.BluetoothLeScanTest#testScanFilter',],
       '' : []}
 
 def LogGenerateDescription(name):