Merge "Simplify CTS testing for policies that apply to DO and PO" into mnc-dev
diff --git a/CtsTestCaseList.mk b/CtsTestCaseList.mk
index 29c6480b..2b53b21 100644
--- a/CtsTestCaseList.mk
+++ b/CtsTestCaseList.mk
@@ -87,6 +87,7 @@
     CtsIntentSenderApp \
     CtsLauncherAppsTests \
     CtsLauncherAppsTestsSupport \
+    CtsLeanbackJank \
     CtsManagedProfileApp \
     CtsMonkeyApp \
     CtsMonkeyApp2 \
@@ -149,6 +150,7 @@
     CtsGraphics2TestCases \
     CtsHardwareTestCases \
     CtsJankTestCases \
+    CtsLeanbackJankTestCases \
     CtsJobSchedulerDeviceTestCases \
     CtsJniTestCases \
     CtsKeystoreTestCases \
@@ -212,6 +214,7 @@
     CtsHostUi \
     CtsMonkeyTestCases \
     CtsThemeHostTestCases \
+    CtsUsageHostTestCases \
     CtsSecurityHostTestCases \
     CtsUsbTests
 
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 0f2b013..d8637ca 100644
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -124,6 +124,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
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/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/AdoptionHostTest.java b/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AdoptionHostTest.java
deleted file mode 100644
index 147e1da..0000000
--- a/hostsidetests/appsecurity/src/com/android/cts/appsecurity/AdoptionHostTest.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.cts.appsecurity;
-
-import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.testtype.DeviceTestCase;
-import com.android.tradefed.testtype.IAbi;
-import com.android.tradefed.testtype.IAbiReceiver;
-import com.android.tradefed.testtype.IBuildReceiver;
-
-/**
- * Set of tests that verify behavior of adopted storage media, if supported.
- */
-public class AdoptionHostTest 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);
-    }
-
-    @Override
-    protected void tearDown() throws Exception {
-        super.tearDown();
-    }
-
-    public void testAdoptionApps() throws Exception {
-    }
-
-    public void testAdoptionStorage() throws Exception {
-    }
-}
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/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/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/hostsidetests/usage/app/src/com/android/cts/app/usage/test/TestActivity.java b/hostsidetests/usage/app/src/com/android/cts/app/usage/test/TestActivity.java
new file mode 100644
index 0000000..9432477
--- /dev/null
+++ b/hostsidetests/usage/app/src/com/android/cts/app/usage/test/TestActivity.java
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.cts.app.usage.test;
+
+import android.app.Activity;
+
+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/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..6c121e6
--- /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..4258160
--- /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/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/hardware/AndroidManifest.xml b/tests/tests/hardware/AndroidManifest.xml
index 7b15b61..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,6 +72,10 @@
             android:process=":camera2ActivityProcess">
         </activity>
 
+        <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/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/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/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/ResourceManagerTestActivityBase.java b/tests/tests/media/src/android/media/cts/ResourceManagerTestActivityBase.java
index ace8f82..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;
 
@@ -66,16 +67,14 @@
 
     private MediaFormat getTestFormat(CodecCapabilities caps, boolean securePlayback) {
         VideoCapabilities vcaps = caps.getVideoCapabilities();
-        int maxWidth = vcaps.getSupportedWidths().getUpper();
-        int maxHeight = vcaps.getSupportedHeightsFor(maxWidth).getUpper();
-        int maxBitrate = vcaps.getBitrateRange().getUpper();
-        int maxFramerate = vcaps.getSupportedFrameRatesFor(maxWidth, maxHeight)
-                .getUpper().intValue();
+        int width = vcaps.getSupportedWidths().getLower();
+        int height = vcaps.getSupportedHeightsFor(width).getLower();
+        int bitrate = vcaps.getBitrateRange().getLower();
 
-        MediaFormat format = MediaFormat.createVideoFormat(MIME, maxWidth, maxHeight);
+        MediaFormat format = MediaFormat.createVideoFormat(MIME, width, height);
         format.setInteger(MediaFormat.KEY_COLOR_FORMAT, caps.colorFormats[0]);
-        format.setInteger(MediaFormat.KEY_BIT_RATE, maxBitrate);
-        format.setInteger(MediaFormat.KEY_FRAME_RATE, maxFramerate);
+        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;
+                }
             }
         }
 
@@ -176,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) {
@@ -190,6 +194,7 @@
             Log.d(TAG, "release codec #" + i);
             mCodecs.get(i).release();
         }
+        mCodecs.clear();
         setResult(result);
         finish();
         Log.d(TAG, "activity finished");
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/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/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):