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):