Merge "Backfill OWNERS for CTS module CtsHarmfulAppWarningHostTestCases"
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
index 4ad8e09..b804b45 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
@@ -140,6 +140,7 @@
if (tryEncrypt()) {
showToast("Test failed. Key accessible without auth.");
} else {
+ prepareEncrypt();
showAuthenticationScreen();
}
}
@@ -212,13 +213,13 @@
private boolean encryptInternal(boolean doEncrypt) {
try {
- KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
- keyStore.load(null);
- SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null);
- if (DEBUG) {
- Log.i(TAG, "encryptInternal: [1]: key retrieved");
- }
if (!doEncrypt) {
+ KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+ keyStore.load(null);
+ SecretKey secretKey = (SecretKey) keyStore.getKey(KEY_NAME, null);
+ if (DEBUG) {
+ Log.i(TAG, "encryptInternal: [1]: key retrieved");
+ }
if (mCipher == null) {
mCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
@@ -240,7 +241,7 @@
// All we want it to see the event in the log;
// Extra exception info is not valuable
if (DEBUG) {
- Log.i(TAG, "encryptInternal: [4]: Encryption failed");
+ Log.w(TAG, "encryptInternal: [4]: Encryption failed", e);
}
return false;
} catch (KeyPermanentlyInvalidatedException e) {
@@ -253,8 +254,9 @@
} catch (UserNotAuthenticatedException e) {
Log.w(TAG, "encryptInternal: [6]: User not authenticated", e);
return false;
- } catch (NoSuchPaddingException | KeyStoreException | CertificateException | UnrecoverableKeyException | IOException
- | NoSuchAlgorithmException | InvalidKeyException e) {
+ } catch (NoSuchPaddingException | KeyStoreException | CertificateException
+ | UnrecoverableKeyException | IOException
+ | NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException("Failed to init Cipher", e);
}
}
@@ -309,7 +311,8 @@
}
hasStrongBox = getContext().getPackageManager()
.hasSystemFeature(PackageManager.FEATURE_STRONGBOX_KEYSTORE);
- if (mActivity.tryEncrypt() && mActivity.doValidityDurationTest(false)) {
+ if (mActivity.tryEncrypt() &&
+ mActivity.doValidityDurationTest(false)) {
try {
Thread.sleep(3000);
} catch (Exception e) {
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java
index 54b3737..c0e2cf8 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java
@@ -19,6 +19,7 @@
import android.app.UiAutomation;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import org.junit.rules.TestRule;
@@ -36,12 +37,20 @@
private final UiAutomation mUiAutomation;
+ private final String[] mPermissions;
+
public AdoptShellPermissionsRule() {
this(InstrumentationRegistry.getInstrumentation().getUiAutomation());
}
public AdoptShellPermissionsRule(@NonNull UiAutomation uiAutomation) {
+ this(uiAutomation, (String[]) null);
+ }
+
+ public AdoptShellPermissionsRule(@NonNull UiAutomation uiAutomation,
+ @Nullable String... permissions) {
mUiAutomation = uiAutomation;
+ mPermissions = permissions;
}
@Override
@@ -50,7 +59,11 @@
@Override
public void evaluate() throws Throwable {
- mUiAutomation.adoptShellPermissionIdentity();
+ if (mPermissions != null) {
+ mUiAutomation.adoptShellPermissionIdentity(mPermissions);
+ } else {
+ mUiAutomation.adoptShellPermissionIdentity();
+ }
try {
base.evaluate();
} finally {
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/AmUtils.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/AmUtils.java
index f3e178b..09abbb3 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/AmUtils.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/AmUtils.java
@@ -20,6 +20,8 @@
private static final String DUMPSYS_ACTIVITY_PROCESSES = "dumpsys activity --proto processes";
+ public static int STANDBY_BUCKET_DOES_NOT_EXIST = -1;
+
private AmUtils() {
}
@@ -61,6 +63,19 @@
+ " " + value);
}
+ /**
+ * Run "adb shell am get-standby-bucket",
+ * return #STANDBY_BUCKET_DOES_NOT_EXIST for invalid packages
+ * */
+ public static int getStandbyBucket(String packageName) {
+ final String value = SystemUtil.runShellCommand("am get-standby-bucket " + packageName);
+ try {
+ return Integer.parseInt(value.trim());
+ } catch (NumberFormatException nfe) {
+ return STANDBY_BUCKET_DOES_NOT_EXIST;
+ }
+ }
+
/** Wait until all broad queues are idle. */
public static void waitForBroadcastIdle() {
SystemUtil.runCommandAndPrintOnLogcat(TAG, "am wait-for-broadcast-idle");
diff --git a/common/device-side/util/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java b/common/device-side/util/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java
index 23e22c6..f7b50b4 100644
--- a/common/device-side/util/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java
+++ b/common/device-side/util/src/com/android/compatibility/common/util/AdoptShellPermissionsRule.java
@@ -20,6 +20,7 @@
import android.support.test.InstrumentationRegistry;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -29,19 +30,28 @@
* Custom JUnit4 rule that runs a test adopting Shell's permissions, revoking them at the end.
*
* <p>NOTE: should only be used in the cases where *every* test in a class requires the permission.
- * For a more fine-grained access, use {@link SystemUtil#runWithShellPermissionIdentity(Runnable)}
+ * For a more fine-grained access, use
+ * {@link SystemUtil#runWithShellPermissionIdentity(ThrowingRunnable)}
* or {@link SystemUtil#callWithShellPermissionIdentity(java.util.concurrent.Callable)} instead.
*/
public class AdoptShellPermissionsRule implements TestRule {
private final UiAutomation mUiAutomation;
+ private final String[] mPermissions;
+
public AdoptShellPermissionsRule() {
this(InstrumentationRegistry.getInstrumentation().getUiAutomation());
}
public AdoptShellPermissionsRule(@NonNull UiAutomation uiAutomation) {
+ this(uiAutomation, (String[]) null);
+ }
+
+ public AdoptShellPermissionsRule(@NonNull UiAutomation uiAutomation,
+ @Nullable String... permissions) {
mUiAutomation = uiAutomation;
+ mPermissions = permissions;
}
@Override
@@ -50,7 +60,11 @@
@Override
public void evaluate() throws Throwable {
- mUiAutomation.adoptShellPermissionIdentity();
+ if (mPermissions != null) {
+ mUiAutomation.adoptShellPermissionIdentity(mPermissions);
+ } else {
+ mUiAutomation.adoptShellPermissionIdentity();
+ }
try {
base.evaluate();
} finally {
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
index d9eab89..105490e 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
@@ -104,6 +104,22 @@
wipePrimaryExternalStorage();
}
+ @Test
+ public void testExternalStorageRename() throws Exception {
+ try {
+ wipePrimaryExternalStorage();
+
+ getDevice().uninstallPackage(PKG_A);
+ installPackage(APK_A);
+
+ for (int user : mUsers) {
+ runDeviceTests(PKG_A, CLASS, "testExternalStorageRename", user);
+ }
+ } finally {
+ getDevice().uninstallPackage(PKG_A);
+ }
+ }
+
/**
* Verify that app with no external storage permissions works correctly.
*/
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
index aa992d4..a546893 100644
--- a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStorageTest.java
@@ -41,6 +41,9 @@
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiSelector;
+import com.android.cts.mediastorageapp.MediaStoreUtils.PendingParams;
+import com.android.cts.mediastorageapp.MediaStoreUtils.PendingSession;
+
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
@@ -232,10 +235,10 @@
private static Uri createImage(Uri collectionUri) throws IOException {
final Context context = InstrumentationRegistry.getTargetContext();
final String displayName = "cts" + System.nanoTime();
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
+ final PendingParams params = new PendingParams(
collectionUri, displayName, "image/png");
- final Uri pendingUri = MediaStore.createPending(context, params);
- try (MediaStore.PendingSession session = MediaStore.openPending(context, pendingUri)) {
+ final Uri pendingUri = MediaStoreUtils.createPending(context, params);
+ try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) {
try (OutputStream out = session.openOutputStream()) {
final Bitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);
bitmap.compress(Bitmap.CompressFormat.PNG, 90, out);
@@ -247,10 +250,10 @@
private static Uri createAudio(Uri collectionUri) throws IOException {
final Context context = InstrumentationRegistry.getTargetContext();
final String displayName = "cts" + System.nanoTime();
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
+ final PendingParams params = new PendingParams(
collectionUri, displayName, "audio/mpeg");
- final Uri pendingUri = MediaStore.createPending(context, params);
- try (MediaStore.PendingSession session = MediaStore.openPending(context, pendingUri)) {
+ final Uri pendingUri = MediaStoreUtils.createPending(context, params);
+ try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) {
try (InputStream in = context.getResources().openRawResource(R.raw.testmp3);
OutputStream out = session.openOutputStream()) {
FileUtils.copy(in, out);
diff --git a/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStoreUtils.java b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStoreUtils.java
new file mode 100644
index 0000000..c2d5a85
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/MediaStorageApp/src/com/android/cts/mediastorageapp/MediaStoreUtils.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2019 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.mediastorageapp;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore;
+import android.provider.MediaStore.DownloadColumns;
+import android.provider.MediaStore.Downloads;
+import android.provider.MediaStore.MediaColumns;
+import android.text.format.DateUtils;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
+
+import java.io.FileNotFoundException;
+import java.io.OutputStream;
+import java.util.Objects;
+
+public class MediaStoreUtils {
+ /**
+ * Create a new pending media item using the given parameters. Pending items
+ * are expected to have a short lifetime, and owners should either
+ * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
+ * pending item within a few hours after first creating it.
+ *
+ * @return token which can be passed to {@link #openPending(Context, Uri)}
+ * to work with this pending item.
+ * @see MediaColumns#IS_PENDING
+ * @see MediaStore#setIncludePending(Uri)
+ * @see MediaStore#createPending(Context, PendingParams)
+ * @removed
+ */
+ @Deprecated
+ public static @NonNull Uri createPending(@NonNull Context context,
+ @NonNull PendingParams params) {
+ return context.getContentResolver().insert(params.insertUri, params.insertValues);
+ }
+
+ /**
+ * Open a pending media item to make progress on it. You can open a pending
+ * item multiple times before finally calling either
+ * {@link PendingSession#publish()} or {@link PendingSession#abandon()}.
+ *
+ * @param uri token which was previously returned from
+ * {@link #createPending(Context, PendingParams)}.
+ * @removed
+ */
+ @Deprecated
+ public static @NonNull PendingSession openPending(@NonNull Context context, @NonNull Uri uri) {
+ return new PendingSession(context, uri);
+ }
+
+ /**
+ * Parameters that describe a pending media item.
+ *
+ * @removed
+ */
+ @Deprecated
+ public static class PendingParams {
+ /** {@hide} */
+ public final Uri insertUri;
+ /** {@hide} */
+ public final ContentValues insertValues;
+
+ /**
+ * Create parameters that describe a pending media item.
+ *
+ * @param insertUri the {@code content://} Uri where this pending item
+ * should be inserted when finally published. For example, to
+ * publish an image, use
+ * {@link MediaStore.Images.Media#getContentUri(String)}.
+ */
+ public PendingParams(@NonNull Uri insertUri, @NonNull String displayName,
+ @NonNull String mimeType) {
+ this.insertUri = Objects.requireNonNull(insertUri);
+ final long now = System.currentTimeMillis() / 1000;
+ this.insertValues = new ContentValues();
+ this.insertValues.put(MediaColumns.DISPLAY_NAME, Objects.requireNonNull(displayName));
+ this.insertValues.put(MediaColumns.MIME_TYPE, Objects.requireNonNull(mimeType));
+ this.insertValues.put(MediaColumns.DATE_ADDED, now);
+ this.insertValues.put(MediaColumns.DATE_MODIFIED, now);
+ this.insertValues.put(MediaColumns.IS_PENDING, 1);
+ this.insertValues.put(MediaColumns.DATE_EXPIRES,
+ (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000);
+ }
+
+ /**
+ * Optionally set the primary directory under which this pending item
+ * should be persisted. Only specific well-defined directories from
+ * {@link Environment} are allowed based on the media type being
+ * inserted.
+ * <p>
+ * For example, when creating pending {@link MediaStore.Images.Media}
+ * items, only {@link Environment#DIRECTORY_PICTURES} or
+ * {@link Environment#DIRECTORY_DCIM} are allowed.
+ * <p>
+ * You may leave this value undefined to store the media in a default
+ * location. For example, when this value is left undefined, pending
+ * {@link MediaStore.Audio.Media} items are stored under
+ * {@link Environment#DIRECTORY_MUSIC}.
+ *
+ * @see MediaColumns#PRIMARY_DIRECTORY
+ */
+ public void setPrimaryDirectory(@Nullable String primaryDirectory) {
+ if (primaryDirectory == null) {
+ this.insertValues.remove(MediaColumns.PRIMARY_DIRECTORY);
+ } else {
+ this.insertValues.put(MediaColumns.PRIMARY_DIRECTORY, primaryDirectory);
+ }
+ }
+
+ /**
+ * Optionally set the secondary directory under which this pending item
+ * should be persisted. Any valid directory name is allowed.
+ * <p>
+ * You may leave this value undefined to store the media as a direct
+ * descendant of the {@link #setPrimaryDirectory(String)} location.
+ *
+ * @see MediaColumns#SECONDARY_DIRECTORY
+ */
+ public void setSecondaryDirectory(@Nullable String secondaryDirectory) {
+ if (secondaryDirectory == null) {
+ this.insertValues.remove(MediaColumns.SECONDARY_DIRECTORY);
+ } else {
+ this.insertValues.put(MediaColumns.SECONDARY_DIRECTORY, secondaryDirectory);
+ }
+ }
+
+ /**
+ * Optionally set the Uri from where the file has been downloaded. This is used
+ * for files being added to {@link Downloads} table.
+ *
+ * @see DownloadColumns#DOWNLOAD_URI
+ */
+ public void setDownloadUri(@Nullable Uri downloadUri) {
+ if (downloadUri == null) {
+ this.insertValues.remove(DownloadColumns.DOWNLOAD_URI);
+ } else {
+ this.insertValues.put(DownloadColumns.DOWNLOAD_URI, downloadUri.toString());
+ }
+ }
+
+ /**
+ * Optionally set the Uri indicating HTTP referer of the file. This is used for
+ * files being added to {@link Downloads} table.
+ *
+ * @see DownloadColumns#REFERER_URI
+ */
+ public void setRefererUri(@Nullable Uri refererUri) {
+ if (refererUri == null) {
+ this.insertValues.remove(DownloadColumns.REFERER_URI);
+ } else {
+ this.insertValues.put(DownloadColumns.REFERER_URI, refererUri.toString());
+ }
+ }
+ }
+
+ /**
+ * Session actively working on a pending media item. Pending items are
+ * expected to have a short lifetime, and owners should either
+ * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
+ * pending item within a few hours after first creating it.
+ *
+ * @removed
+ */
+ @Deprecated
+ public static class PendingSession implements AutoCloseable {
+ /** {@hide} */
+ private final Context mContext;
+ /** {@hide} */
+ private final Uri mUri;
+
+ /** {@hide} */
+ public PendingSession(Context context, Uri uri) {
+ mContext = Objects.requireNonNull(context);
+ mUri = Objects.requireNonNull(uri);
+ }
+
+ /**
+ * Open the underlying file representing this media item. When a media
+ * item is successfully completed, you should
+ * {@link ParcelFileDescriptor#close()} and then {@link #publish()} it.
+ *
+ * @see #notifyProgress(int)
+ */
+ public @NonNull ParcelFileDescriptor open() throws FileNotFoundException {
+ return mContext.getContentResolver().openFileDescriptor(mUri, "rw");
+ }
+
+ /**
+ * Open the underlying file representing this media item. When a media
+ * item is successfully completed, you should
+ * {@link OutputStream#close()} and then {@link #publish()} it.
+ *
+ * @see #notifyProgress(int)
+ */
+ public @NonNull OutputStream openOutputStream() throws FileNotFoundException {
+ return mContext.getContentResolver().openOutputStream(mUri);
+ }
+
+ /**
+ * When this media item is successfully completed, call this method to
+ * publish and make the final item visible to the user.
+ *
+ * @return the final {@code content://} Uri representing the newly
+ * published media.
+ */
+ public @NonNull Uri publish() {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.IS_PENDING, 0);
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ mContext.getContentResolver().update(mUri, values, null, null);
+ return mUri;
+ }
+
+ /**
+ * When this media item has failed to be completed, call this method to
+ * destroy the pending item record and any data related to it.
+ */
+ public void abandon() {
+ mContext.getContentResolver().delete(mUri, null, null);
+ }
+
+ @Override
+ public void close() {
+ // No resources to close, but at least we can inform people that no
+ // progress is being actively made.
+ }
+ }
+}
diff --git a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
index fc3af59..246e0f9 100644
--- a/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
+++ b/hostsidetests/appsecurity/test-apps/StorageApp/src/com/android/cts/storageapp/StorageTest.java
@@ -47,9 +47,10 @@
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiSelector;
import android.test.InstrumentationTestCase;
-import android.util.Log;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
@@ -252,6 +253,35 @@
try { sm.setCacheBehaviorTombstone(ext, false); fail(); } catch (IOException expected) { }
}
+ public void testExternalStorageRename() throws Exception {
+ final String name = "cts_" + System.nanoTime();
+
+ // Stage some contents to move around
+ File cur = Environment.getExternalStorageDirectory();
+ try (FileOutputStream fos = new FileOutputStream(new File(cur, name))) {
+ fos.write(42);
+ }
+
+ for (File next : new File[] {
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS),
+ getContext().getExternalCacheDir(),
+ getContext().getExternalFilesDir(null),
+ getContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),
+ getContext().getExternalMediaDirs()[0],
+ getContext().getObbDir(),
+ }) {
+ next.mkdirs();
+ assertTrue("Failed to move from " + cur + " to " + next,
+ new File(cur, name).renameTo(new File(next, name)));
+ cur = next;
+ }
+
+ // Make sure the data made the journey
+ try (FileInputStream fis = new FileInputStream(new File(cur, name))) {
+ assertEquals(42, fis.read());
+ }
+ }
+
/**
* Create "cts" probe files in every possible common storage location that
* we can think of.
diff --git a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
index 49e793f..644ba72 100644
--- a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/InputMethodServiceLifecycleTest.java
@@ -58,12 +58,26 @@
private static final long POLLING_INTERVAL = 100;
/**
+ * {@code true} if {@link #tearDown()} needs to be fully executed.
+ *
+ * <p>When {@link #setUp()} is interrupted by {@link org.junit.AssumptionViolatedException}
+ * before the actual setup tasks are executed, all the corresponding cleanup tasks should also
+ * be skipped.</p>
+ *
+ * <p>Once JUnit 5 becomes available in Android, we can remove this by moving the assumption
+ * checks into a non-static {@link org.junit.BeforeClass} method.</p>
+ */
+ private boolean mNeedsTearDown = false;
+
+ /**
* Set up test case.
*/
@Before
public void setUp() throws Exception {
// Skip whole tests when DUT has no android.software.input_methods feature.
assumeTrue(hasDeviceFeature(ShellCommandUtils.FEATURE_INPUT_METHODS));
+ mNeedsTearDown = true;
+
cleanUpTestImes();
installPackage(DeviceTestConstants.APK, "-r");
shell(ShellCommandUtils.deleteContent(EventTableConstants.CONTENT_URI));
@@ -74,6 +88,9 @@
*/
@After
public void tearDown() throws Exception {
+ if (!mNeedsTearDown) {
+ return;
+ }
shell(ShellCommandUtils.resetImes());
}
diff --git a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/MultiUserTest.java b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/MultiUserTest.java
index 0b77d7d..d4f40f2 100644
--- a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/MultiUserTest.java
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/MultiUserTest.java
@@ -57,6 +57,18 @@
*/
private static final long WAIT_AFTER_USER_SWITCH = TimeUnit.SECONDS.toMillis(10);
+ private boolean mNeedsTearDown = false;
+
+ /**
+ * {@code true} if {@link #tearDown()} needs to be fully executed.
+ *
+ * <p>When {@link #setUp()} is interrupted by {@link org.junit.AssumptionViolatedException}
+ * before the actual setup tasks are executed, all the corresponding cleanup tasks should also
+ * be skipped.</p>
+ *
+ * <p>Once JUnit 5 becomes available in Android, we can remove this by moving the assumption
+ * checks into a non-static {@link org.junit.BeforeClass} method.</p>
+ */
private ArrayList<Integer> mOriginalUsers;
/**
@@ -67,6 +79,7 @@
// Skip whole tests when DUT has no android.software.input_methods feature.
assumeTrue(hasDeviceFeature(ShellCommandUtils.FEATURE_INPUT_METHODS));
assumeTrue(getDevice().isMultiUserSupported());
+ mNeedsTearDown = true;
mOriginalUsers = new ArrayList<>(getDevice().listUsers());
mOriginalUsers.forEach(
@@ -78,6 +91,10 @@
*/
@After
public void tearDown() throws Exception {
+ if (!mNeedsTearDown) {
+ return;
+ }
+
getDevice().switchUser(getDevice().getPrimaryUserId());
// We suspect that the optimization made for Bug 38143512 was a bit unstable. Let's see
// if adding a sleep improves the stability or not.
diff --git a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/ShellCommandFromAppTest.java b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/ShellCommandFromAppTest.java
index 17b31b5..6c66949 100644
--- a/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/ShellCommandFromAppTest.java
+++ b/hostsidetests/inputmethodservice/hostside/src/android/inputmethodservice/cts/hostside/ShellCommandFromAppTest.java
@@ -28,6 +28,7 @@
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -37,6 +38,19 @@
*/
@RunWith(DeviceJUnit4ClassRunner.class)
public class ShellCommandFromAppTest extends BaseHostJUnit4Test {
+
+ /**
+ * {@code true} if {@link #tearDown()} needs to be fully executed.
+ *
+ * <p>When {@link #setUp()} is interrupted by {@link org.junit.AssumptionViolatedException}
+ * before the actual setup tasks are executed, all the corresponding cleanup tasks should also
+ * be skipped.</p>
+ *
+ * <p>Once JUnit 5 becomes available in Android, we can remove this by moving the assumption
+ * checks into a non-static {@link org.junit.BeforeClass} method.</p>
+ */
+ private boolean mNeedsTearDown = false;
+
/**
* Run device test with disabling hidden API check.
*
@@ -64,6 +78,18 @@
public void setUp() throws Exception {
// Skip whole tests when DUT has no android.software.input_methods feature.
assumeTrue(hasDeviceFeature(ShellCommandUtils.FEATURE_INPUT_METHODS));
+ mNeedsTearDown = true;
+ }
+
+ /**
+ * Tear down test case.
+ */
+ @After
+ public void tearDown() throws Exception {
+ if (!mNeedsTearDown) {
+ return;
+ }
+ // Tear down logic must be placed here.
}
/**
diff --git a/hostsidetests/seccomp/OWNERS b/hostsidetests/seccomp/OWNERS
new file mode 100644
index 0000000..2beb3cc
--- /dev/null
+++ b/hostsidetests/seccomp/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 36824
+paullawrence@google.com
+maco@google.com
diff --git a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerManagedUserTest.java b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerManagedUserTest.java
index 48f8578..9616176 100644
--- a/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerManagedUserTest.java
+++ b/hostsidetests/shortcuts/deviceside/multiuser/src/android/content/pm/cts/shortcut/multiuser/ShortcutManagerManagedUserTest.java
@@ -26,6 +26,7 @@
import android.content.pm.cts.shortcut.device.common.ShortcutManagerDeviceTestBase;
import android.os.UserHandle;
import android.os.UserManager;
+import android.test.suitebuilder.annotation.Suppress;
import java.util.List;
@@ -96,6 +97,7 @@
userOther);
}
+ @Suppress // Having a launcher on managed profile is not supported, so don't run.
public void test05_getAndLaunch_managed() {
Launcher.setAsDefaultLauncher(getInstrumentation(), getContext());
diff --git a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerMultiuserTest.java b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerMultiuserTest.java
index 2e14a85..2bcaa94 100644
--- a/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerMultiuserTest.java
+++ b/hostsidetests/shortcuts/hostside/src/android/content/pm/cts/shortcuthost/ShortcutManagerMultiuserTest.java
@@ -55,8 +55,6 @@
runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerManagedUserTest",
"test04_getAndLaunch_primary", getPrimaryUserId());
- runDeviceTestsAsUser(TARGET_PKG, ".ShortcutManagerManagedUserTest",
- "test05_getAndLaunch_managed", profileId);
}
@Test
diff --git a/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
index 100a0f2..f9b0fd5 100644
--- a/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
+++ b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
@@ -56,6 +56,7 @@
</activity>
<activity android:name=".DaveyActivity" android:exported="true" />
+ <activity android:name=".HiddenApiUsedActivity" android:exported="true" />
<service android:name=".StatsdAuthenticator"
android:exported="false">
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/HiddenApiUsedActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/HiddenApiUsedActivity.java
new file mode 100644
index 0000000..2132f3c
--- /dev/null
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/HiddenApiUsedActivity.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2019 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.server.cts.device.statsd;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+import java.lang.reflect.Field;
+
+
+public class HiddenApiUsedActivity extends Activity {
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ try {
+ Field field = Activity.class.getDeclaredField("mWindow");
+ field.setAccessible(true);
+ Object object = field.get(this);
+ } catch(NoSuchFieldException e) {
+ } catch(IllegalAccessException e) {
+ }
+ finish();
+ }
+
+}
\ No newline at end of file
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
index abb293b..cdb023f 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -43,6 +43,7 @@
import com.android.os.AtomsProto.FlashlightStateChanged;
import com.android.os.AtomsProto.ForegroundServiceStateChanged;
import com.android.os.AtomsProto.GpsScanStateChanged;
+import com.android.os.AtomsProto.HiddenApiUsed;
import com.android.os.AtomsProto.LooperStats;
import com.android.os.AtomsProto.MediaCodecStateChanged;
import com.android.os.AtomsProto.NativeProcessMemoryState;
@@ -246,6 +247,45 @@
assertTrue(a0.getNumResults() >= 1);
}
+ public void testHiddenApiUsed() throws Exception {
+ if (statsdDisabled()) {
+ return;
+ }
+
+ String oldRate = getDevice().executeShellCommand(
+ "settings get global hidden_api_access_statslog_sampling_rate").trim();
+
+ getDevice().executeShellCommand(
+ "settings put global hidden_api_access_statslog_sampling_rate 65536");
+ try {
+ final int atomTag = Atom.HIDDEN_API_USED_FIELD_NUMBER;
+
+ createAndUploadConfig(atomTag, false);
+
+ runActivity("HiddenApiUsedActivity", null, null);
+
+
+ List<EventMetricData> data = getEventMetricDataList();
+ assertTrue(data.size() == 1);
+
+ HiddenApiUsed atom = data.get(0).getAtom().getHiddenApiUsed();
+
+ int uid = getUid();
+ assertEquals(uid, atom.getUid());
+ assertFalse(atom.getAccessDenied());
+ assertEquals("Landroid/app/Activity;->mWindow:Landroid/view/Window;",
+ atom.getSignature());
+ } finally {
+ if (!oldRate.equals("null")) {
+ getDevice().executeShellCommand(
+ "settings put global hidden_api_access_statslog_sampling_rate " + oldRate);
+ } else {
+ getDevice().executeShellCommand(
+ "settings delete global hidden_api_access_statslog_sampling_rate");
+ }
+ }
+ }
+
public void testCameraState() throws Exception {
if (statsdDisabled()) {
return;
diff --git a/tests/JobScheduler/Android.mk b/tests/JobScheduler/Android.mk
index 622c06e..116d74f 100755
--- a/tests/JobScheduler/Android.mk
+++ b/tests/JobScheduler/Android.mk
@@ -22,7 +22,7 @@
# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util ub-uiautomator android-support-test
+LOCAL_STATIC_JAVA_LIBRARIES := compatibility-device-util-axt ub-uiautomator androidx.test.rules
LOCAL_JAVA_LIBRARIES := android.test.base.stubs
diff --git a/tests/JobScheduler/AndroidManifest.xml b/tests/JobScheduler/AndroidManifest.xml
index 915536c..db0b6bb 100755
--- a/tests/JobScheduler/AndroidManifest.xml
+++ b/tests/JobScheduler/AndroidManifest.xml
@@ -41,7 +41,7 @@
<!-- self-instrumenting test package. -->
<instrumentation
- android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:name="androidx.test.runner.AndroidJUnitRunner"
android:label="JobScheduler device-side tests"
android:targetPackage="android.jobscheduler.cts" >
</instrumentation>
diff --git a/tests/JobScheduler/AndroidTest.xml b/tests/JobScheduler/AndroidTest.xml
index 83365d7..8469a75 100644
--- a/tests/JobScheduler/AndroidTest.xml
+++ b/tests/JobScheduler/AndroidTest.xml
@@ -16,6 +16,8 @@
<configuration description="Config for CTS Job Scheduler test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsJobSchedulerTestCases.apk" />
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java
index 10b84d6..8fc13be 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java
@@ -19,9 +19,10 @@
import android.annotation.TargetApi;
import android.app.job.JobInfo;
import android.os.SystemClock;
-import android.support.test.InstrumentationRegistry;
import android.support.test.uiautomator.UiDevice;
+import androidx.test.InstrumentationRegistry;
+
/**
* Make sure the state of {@link android.app.job.JobScheduler} is correct.
*/
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
index 0ab2662..8d9edfc 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobThrottlingTest.java
@@ -42,12 +42,13 @@
import android.os.PowerManager;
import android.os.SystemClock;
import android.os.Temperature;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.filters.LargeTest;
-import android.support.test.runner.AndroidJUnit4;
import android.support.test.uiautomator.UiDevice;
import android.util.Log;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
import com.android.compatibility.common.util.AppOpsUtils;
import com.android.compatibility.common.util.AppStandbyUtils;
import com.android.compatibility.common.util.BatteryUtils;
diff --git a/tests/JobSchedulerSharedUid/AndroidTest.xml b/tests/JobSchedulerSharedUid/AndroidTest.xml
index 183551d..e3baf5a 100644
--- a/tests/JobSchedulerSharedUid/AndroidTest.xml
+++ b/tests/JobSchedulerSharedUid/AndroidTest.xml
@@ -16,6 +16,9 @@
<configuration description="Config for CTS Job Scheduler shared-uid test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
+ <!-- shared UID is not available with instant apps -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsJobSchedulerSharedUidTestCases.apk" />
diff --git a/tests/app/src/android/app/cts/DownloadManagerTest.java b/tests/app/src/android/app/cts/DownloadManagerTest.java
index 0282b58..3172481 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTest.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTest.java
@@ -593,14 +593,16 @@
}
private Uri getMediaStoreUri(Uri downloadUri) throws Exception {
- final String res = runShellCommand(
- "content query --uri " + downloadUri + " --projection mediastore_uri").trim();
- final String str = "mediastore_uri=";
+ final String cmd = String.format("content query --uri %s --projection %s",
+ downloadUri, DownloadManager.COLUMN_MEDIASTORE_URI);
+ final String res = runShellCommand(cmd).trim();
+ final String str = DownloadManager.COLUMN_MEDIASTORE_URI + "=";
final int i = res.indexOf(str);
if (i >= 0) {
return Uri.parse(res.substring(i + str.length()));
} else {
- throw new FileNotFoundException("Failed to find mediastore_uri for "
+ throw new FileNotFoundException("Failed to find "
+ + DownloadManager.COLUMN_MEDIASTORE_URI + " for "
+ downloadUri + "; found " + res);
}
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
index d5325f7..1f21893 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityTest.java
@@ -372,6 +372,8 @@
final FillRequest request = sReplier.getNextFillRequest();
assertWithMessage("CancelationSignal is null").that(request.cancellationSignal).isNotNull();
assertTextIsSanitized(request.structure, ID_PASSWORD);
+ assertThat(request.contexts.get(request.contexts.size() - 1).getFocusedId())
+ .isEqualTo(mActivity.getUsername().getAutofillId());
// Make sure initial focus was properly set.
assertWithMessage("Username node is not focused").that(
diff --git a/tests/tests/alarmclock/Android.mk b/tests/contentsuggestions/Android.mk
similarity index 64%
rename from tests/tests/alarmclock/Android.mk
rename to tests/contentsuggestions/Android.mk
index feb5792..b19b11f 100644
--- a/tests/tests/alarmclock/Android.mk
+++ b/tests/contentsuggestions/Android.mk
@@ -1,4 +1,4 @@
-# Copyright (C) 2015 The Android Open Source Project
+# Copyright (C) 2019 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.
@@ -16,23 +16,26 @@
include $(CLEAR_VARS)
-# don't include this package in any target
+# Don't include this package in any target.
LOCAL_MODULE_TAGS := optional
-# and when built explicitly put it in the data partition
+
+# When built, explicitly put it in the data partition.
LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-LOCAL_STATIC_JAVA_LIBRARIES := CtsAlarmClockCommon ctstestrunner-axt compatibility-device-util-axt
-
-LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ androidx.annotation_annotation \
+ compatibility-device-util \
+ ctstestrunner \
+ truth-prebuilt \
+ testng # TODO: remove once Android migrates to JUnit 4.12, which provide assertThrows
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_PACKAGE_NAME := CtsAlarmClockTestCases
-
-LOCAL_SDK_VERSION := current
-
# Tag this module as a cts test artifact
LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
+LOCAL_PACKAGE_NAME := CtsContentSuggestionsTestCases
+
+LOCAL_SDK_VERSION := system_current
+
include $(BUILD_CTS_PACKAGE)
-include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/contentsuggestions/AndroidManifest.xml b/tests/contentsuggestions/AndroidManifest.xml
new file mode 100644
index 0000000..84845e8
--- /dev/null
+++ b/tests/contentsuggestions/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 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.contentsuggestions.cts"
+ android:targetSandboxVersion="2">
+
+ <application>
+
+ <uses-library android:name="android.test.runner" />
+
+ <service
+ android:name=".CtsContentSuggestionsService"
+ android:label="CtsContentSuggestionsService">
+ <intent-filter>
+ <action android:name="android.service.contentsuggestions.ContentSuggestionsService" />
+ </intent-filter>
+ </service>
+ </application>
+
+ <instrumentation
+ android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:label="CTS tests for the ContentSuggestionsManager APIs."
+ android:targetPackage="android.contentsuggestions.cts" >
+ </instrumentation>
+
+</manifest>
diff --git a/tests/contentsuggestions/AndroidTest.xml b/tests/contentsuggestions/AndroidTest.xml
new file mode 100644
index 0000000..e18c609
--- /dev/null
+++ b/tests/contentsuggestions/AndroidTest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2019 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration description="Config for ContentSuggestions CTS tests.">
+ <option name="test-suite-tag" value="cts" />
+ <option name="config-descriptor:metadata" key="component" value="framework" />
+
+ <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+ <option name="cleanup-apks" value="true" />
+ <option name="test-file-name" value="CtsContentSuggestionsTestCases.apk" />
+ </target_preparer>
+ <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+ <option name="package" value="android.contentsuggestions.cts" />
+ </test>
+
+</configuration>
diff --git a/tests/contentsuggestions/src/android/contentsuggestions/cts/ContentSuggestionsManagerTest.java b/tests/contentsuggestions/src/android/contentsuggestions/cts/ContentSuggestionsManagerTest.java
new file mode 100644
index 0000000..a8b0777
--- /dev/null
+++ b/tests/contentsuggestions/src/android/contentsuggestions/cts/ContentSuggestionsManagerTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2019 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.contentsuggestions.cts;
+
+import static android.support.test.InstrumentationRegistry.getContext;
+import static android.support.test.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.ContentSuggestionsManager;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.content.Context;
+import android.os.Bundle;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.collect.Lists;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests for {@link ContentSuggestionsManager}.
+ */
+@RunWith(AndroidJUnit4.class)
+public class ContentSuggestionsManagerTest {
+ private static final String TAG = ContentSuggestionsManagerTest.class.getSimpleName();
+
+ private static final long VERIFY_TIMEOUT_MS = 5_000;
+ private static final long SERVICE_LIFECYCLE_TIMEOUT_MS = 10_000;
+
+ private ContentSuggestionsManager mManager;
+ private CtsContentSuggestionsService.Watcher mWatcher;
+
+ @Before
+ public void setup() {
+ mWatcher = CtsContentSuggestionsService.setWatcher();
+
+ mManager = (ContentSuggestionsManager) getContext()
+ .getSystemService(Context.CONTENT_SUGGESTIONS_SERVICE);
+ setService(CtsContentSuggestionsService.SERVICE_COMPONENT.flattenToString());
+
+ // TODO: b/126587631 remove when the manager calls no longer need this.
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ "android.permission.READ_FRAME_BUFFER");
+
+ // The ContentSuggestions services are created lazily, have to call one method on it to
+ // start the service for the tests.
+ mManager.notifyInteraction("SETUP", new Bundle());
+
+ await(mWatcher.created, "Waiting for create");
+ reset(mWatcher.verifier);
+ }
+
+ @After
+ public void tearDown() {
+ resetService();
+ await(mWatcher.destroyed, "Waiting for service destroyed");
+
+ mWatcher = null;
+ CtsContentSuggestionsService.clearWatcher();
+
+ // TODO: b/126587631 remove when the manager calls no longer need this.
+ getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+ }
+
+ @Test
+ public void managerForwards_notifyInteraction() {
+ String requestId = "TEST";
+
+ mManager.notifyInteraction(requestId, new Bundle());
+ verifyService().notifyInteraction(eq(requestId), any());
+ }
+
+ @Test
+ public void managerForwards_provideContextImage() {
+ int taskId = 1;
+
+ mManager.provideContextImage(taskId, new Bundle());
+ verifyService().processContextImage(eq(taskId), any(), any());
+ }
+
+ @Test
+ public void managerForwards_suggestContentSelections() {
+ SelectionsRequest request = new SelectionsRequest.Builder(1).build();
+ ContentSuggestionsManager.SelectionsCallback callback = (statusCode, selections) -> {};
+
+
+ mManager.suggestContentSelections(request, Executors.newSingleThreadExecutor(), callback);
+ verifyService().suggestContentSelections(any(), any());
+ }
+
+ @Test
+ public void managerForwards_classifyContentSelections() {
+ ClassificationsRequest request = new ClassificationsRequest.Builder(
+ Lists.newArrayList()).build();
+ ContentSuggestionsManager.ClassificationsCallback callback =
+ (statusCode, classifications) -> {};
+
+
+ mManager.classifyContentSelections(request, Executors.newSingleThreadExecutor(), callback);
+ verifyService().classifyContentSelections(any(), any());
+ }
+
+ private CtsContentSuggestionsService verifyService() {
+ return verify(mWatcher.verifier, timeout(VERIFY_TIMEOUT_MS));
+ }
+
+ private void await(@NonNull CountDownLatch latch, @NonNull String message) {
+ try {
+ assertWithMessage(message).that(
+ latch.await(SERVICE_LIFECYCLE_TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException("Interrupted while: " + message);
+ }
+ }
+
+ /**
+ * Sets the content capture service.
+ */
+ private static void setService(@NonNull String service) {
+ Log.d(TAG, "Setting service to " + service);
+ runShellCommand(
+ "cmd content_suggestions set temporary-service 0 %s 120000", service);
+ }
+
+ private static void resetService() {
+ Log.d(TAG, "Resetting service");
+ runShellCommand("cmd content_suggestions set temporary-service 0");
+ }
+}
diff --git a/tests/contentsuggestions/src/android/contentsuggestions/cts/CtsContentSuggestionsService.java b/tests/contentsuggestions/src/android/contentsuggestions/cts/CtsContentSuggestionsService.java
new file mode 100644
index 0000000..52444bd
--- /dev/null
+++ b/tests/contentsuggestions/src/android/contentsuggestions/cts/CtsContentSuggestionsService.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2019 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.contentsuggestions.cts;
+
+import android.app.contentsuggestions.ClassificationsRequest;
+import android.app.contentsuggestions.ContentSuggestionsManager.ClassificationsCallback;
+import android.app.contentsuggestions.ContentSuggestionsManager.SelectionsCallback;
+import android.app.contentsuggestions.SelectionsRequest;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.os.Bundle;
+import android.service.contentsuggestions.ContentSuggestionsService;
+import android.util.Log;
+
+import org.mockito.Mockito;
+
+import java.util.concurrent.CountDownLatch;
+
+public class CtsContentSuggestionsService extends ContentSuggestionsService {
+
+ private static final String TAG = CtsContentSuggestionsService.class.getSimpleName();
+ private static final boolean DEBUG = false;
+
+ private static Watcher sWatcher;
+
+ public static final ComponentName SERVICE_COMPONENT = new ComponentName(
+ "android.contentsuggestions.cts", CtsContentSuggestionsService.class.getName());
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (DEBUG) Log.d(TAG, "onCreate: ");
+ if (sWatcher.verifier != null) {
+ Log.e(TAG, "onCreate, trying to set verifier when it already exists");
+ }
+ sWatcher.verifier = Mockito.mock(CtsContentSuggestionsService.class);
+ sWatcher.created.countDown();
+ }
+
+ @Override
+ public void onDestroy() {
+ if (DEBUG) Log.d(TAG, "onDestroy");
+ super.onDestroy();
+ sWatcher.destroyed.countDown();
+ }
+
+ @Override
+ public boolean onUnbind(Intent intent) {
+ if (DEBUG) Log.d(TAG, "unbind");
+ return super.onUnbind(intent);
+ }
+
+ @Override
+ public void processContextImage(int taskId, Bitmap contextImage, Bundle extras) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "processContextImage() called with: taskId = [" + taskId + "], contextImage = ["
+ + contextImage + "], extras = [" + extras + "]");
+ }
+ sWatcher.verifier.processContextImage(taskId, contextImage, extras);
+ }
+
+ @Override
+ public void suggestContentSelections(SelectionsRequest request, SelectionsCallback callback) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "suggestContentSelections() called with: request = [" + request
+ + "], callback = ["
+ + callback + "]");
+ }
+ sWatcher.verifier.suggestContentSelections(request, callback);
+ }
+
+ @Override
+ public void classifyContentSelections(ClassificationsRequest request,
+ ClassificationsCallback callback) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "classifyContentSelections() called with: request = [" + request
+ + "], callback = ["
+ + callback + "]");
+ }
+ sWatcher.verifier.classifyContentSelections(request, callback);
+ }
+
+ @Override
+ public void notifyInteraction(String requestId, Bundle interaction) {
+ if (DEBUG) {
+ Log.d(TAG,
+ "notifyInteraction() called with: requestId = [" + requestId
+ + "], interaction = ["
+ + interaction + "]");
+ }
+ sWatcher.verifier.notifyInteraction(requestId, interaction);
+ }
+
+ public static Watcher setWatcher() {
+ if (sWatcher != null) {
+ throw new IllegalStateException("Set watcher with watcher already set");
+ }
+ sWatcher = new Watcher();
+ return sWatcher;
+ }
+
+ public static void clearWatcher() {
+ sWatcher = null;
+ }
+
+ public static final class Watcher {
+ public CountDownLatch created = new CountDownLatch(1);
+ public CountDownLatch destroyed = new CountDownLatch(1);
+
+ /**
+ * Can be used to verify that API specific service methods are called. Not a real mock as
+ * the system isn't talking to this directly, it has calls proxied to it.
+ */
+ public CtsContentSuggestionsService verifier;
+ }
+}
diff --git a/tests/framework/base/activitymanager/AndroidManifest.xml b/tests/framework/base/activitymanager/AndroidManifest.xml
index 270c582..5144b8c 100644
--- a/tests/framework/base/activitymanager/AndroidManifest.xml
+++ b/tests/framework/base/activitymanager/AndroidManifest.xml
@@ -113,6 +113,14 @@
<activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$ShowWhenLockedCallbackTrackingActivity" />
+ <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$SecondProcessCallbackTrackingActivity"
+ android:process=":SecondProcess"
+ android:exported="true"/>
+
+ <provider android:name="android.server.am.lifecycle.LifecycleLog"
+ android:authorities="android.server.am.lifecycle.logprovider"
+ android:exported="true" />
+
<activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$LaunchForResultActivity"/>
<activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$ResultActivity"/>
@@ -134,6 +142,8 @@
androidprv:alwaysFocusable="true"
android:exported="true"/>
+ <activity android:name="android.server.am.lifecycle.ActivityLifecycleClientTestBase$SlowActivity"/>
+
<activity android:name="android.server.am.StartActivityTests$TestActivity2" />
<activity android:name="android.server.am.ActivityManagerMultiDisplayTests$ImeTestActivity" />
diff --git a/tests/framework/base/activitymanager/app_base/src/android/server/am/AbstractLifecycleLogActivity.java b/tests/framework/base/activitymanager/app_base/src/android/server/am/AbstractLifecycleLogActivity.java
index 59ab274..304d4db 100644
--- a/tests/framework/base/activitymanager/app_base/src/android/server/am/AbstractLifecycleLogActivity.java
+++ b/tests/framework/base/activitymanager/app_base/src/android/server/am/AbstractLifecycleLogActivity.java
@@ -45,6 +45,12 @@
}
@Override
+ public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
+ super.onTopResumedActivityChanged(isTopResumedActivity);
+ Log.i(getTag(), "onTopResumedActivityChanged: " + isTopResumedActivity);
+ }
+
+ @Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
Log.i(getTag(), "onConfigurationChanged");
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java
index d70c913..e038243 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerDisplayTestBase.java
@@ -17,6 +17,7 @@
package android.server.am;
import static android.content.pm.PackageManager.FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS;
+import static android.server.am.ActivityManagerDisplayTestBase.ReportedDisplayMetrics.getDisplayMetrics;
import static android.server.am.ComponentNameUtils.getActivityName;
import static android.server.am.Components.VIRTUAL_DISPLAY_ACTIVITY;
import static android.server.am.Components.VirtualDisplayActivity.COMMAND_CREATE_DISPLAY;
@@ -216,6 +217,14 @@
}
}
+ protected void tapOnDisplayCenter(int displayId) {
+ final ReportedDisplayMetrics displayMetrics = getDisplayMetrics(displayId);
+ final int x = displayMetrics.getSize().getWidth() / 2;
+ final int y = displayMetrics.getSize().getHeight() / 2;
+
+ tapOnDisplay(x, y, displayId);
+ }
+
public class VirtualDisplaySession implements AutoCloseable {
private int mDensityDpi = CUSTOM_DENSITY_DPI;
private boolean mLaunchInSplitScreen = false;
diff --git a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
index e8470f5..aa5e008 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/ActivityManagerMultiDisplayTests.java
@@ -1205,10 +1205,7 @@
waitAndAssertTopResumedActivity(TEST_ACTIVITY, newDisplay.mId,
"Activity launched on secondary display must be on top");
- final ReportedDisplayMetrics displayMetrics = getDisplayMetrics(DEFAULT_DISPLAY);
- final int width = displayMetrics.getSize().getWidth();
- final int height = displayMetrics.getSize().getHeight();
- tapOnDisplay(width / 2, height / 2, DEFAULT_DISPLAY);
+ tapOnDisplayCenter(DEFAULT_DISPLAY);
waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, DEFAULT_DISPLAY,
"Top activity must be on the primary display");
@@ -2096,10 +2093,7 @@
}}
);
- final ReportedDisplayMetrics displayMetrics = getDisplayMetrics(DEFAULT_DISPLAY);
- final int width = displayMetrics.getSize().getWidth();
- final int height = displayMetrics.getSize().getHeight();
- tapOnDisplay(width / 2, height / 2, DEFAULT_DISPLAY);
+ tapOnDisplayCenter(DEFAULT_DISPLAY);
// Check that the activity on the primary display is the topmost resumed
waitAndAssertTopResumedActivity(RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
@@ -2156,18 +2150,15 @@
mAmWmState.assertFocusedAppOnDisplay("Activity on second display must be focused.",
VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId);
- final ReportedDisplayMetrics displayMetrics = getDisplayMetrics(DEFAULT_DISPLAY);
- final int width = displayMetrics.getSize().getWidth();
- final int height = displayMetrics.getSize().getHeight();
- tapOnDisplay(width / 2, height / 2, DEFAULT_DISPLAY);
+ tapOnDisplayCenter(DEFAULT_DISPLAY);
waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
"Activity should be top resumed when tapped.");
mAmWmState.assertFocusedActivity("Activity on default display must be top focused.",
TEST_ACTIVITY);
- tapOnDisplay(VirtualDisplayHelper.WIDTH / 2, VirtualDisplayHelper.HEIGHT / 2,
- newDisplay.mId);
+ tapOnDisplayCenter(newDisplay.mId);
+
waitAndAssertTopResumedActivity(VIRTUAL_DISPLAY_ACTIVITY, newDisplay.mId,
"Virtual display activity should be top resumed when tapped.");
mAmWmState.assertFocusedActivity("Activity on second display must be top focused.",
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
index 913f277..a5f9507 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleClientTestBase.java
@@ -19,13 +19,19 @@
import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP;
import static android.server.am.StateLogger.log;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_CREATE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_DESTROY;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_NEW_INTENT;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_PAUSE;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_POST_CREATE;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESTART;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_RESUME;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_START;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_STOP;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_GAINED;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_TOP_POSITION_LOST;
+import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.PRE_ON_CREATE;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
@@ -43,8 +49,6 @@
import android.util.Pair;
import androidx.test.rule.ActivityTestRule;
-import androidx.test.runner.lifecycle.ActivityLifecycleMonitor;
-import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry;
import org.junit.After;
import org.junit.Before;
@@ -109,8 +113,9 @@
AlwaysFocusablePipActivity.class, true /* initialTouchMode */,
false /* launchActivity */);
- private final ActivityLifecycleMonitor mLifecycleMonitor = ActivityLifecycleMonitorRegistry
- .getInstance();
+ final ActivityTestRule mSlowActivityTestRule = new ActivityTestRule(
+ SlowActivity.class, true /* initialTouchMode */, false /* launchActivity */);
+
private static LifecycleLog mLifecycleLog;
protected Context mTargetContext;
@@ -124,19 +129,10 @@
mTargetContext = getInstrumentation().getTargetContext();
// Log transitions for all activities that belong to this app.
mLifecycleLog = new LifecycleLog();
- mLifecycleMonitor.addLifecycleCallback(mLifecycleLog);
+ mLifecycleLog.clear();
// Track transitions and allow waiting for pending activity states.
mLifecycleTracker = new LifecycleTracker(mLifecycleLog);
- mLifecycleMonitor.addLifecycleCallback(mLifecycleTracker);
- }
-
- @After
- @Override
- public void tearDown() throws Exception {
- mLifecycleMonitor.removeLifecycleCallback(mLifecycleLog);
- mLifecycleMonitor.removeLifecycleCallback(mLifecycleTracker);
- super.tearDown();
}
/** Launch an activity given a class. */
@@ -185,7 +181,12 @@
static Pair<Class<? extends Activity>, ActivityCallback> state(Activity activity,
ActivityCallback stage) {
- return new Pair<>(activity.getClass(), stage);
+ return state(activity.getClass(), stage);
+ }
+
+ static Pair<Class<? extends Activity>, ActivityCallback> state(
+ Class<? extends Activity> activityClass, ActivityCallback stage) {
+ return new Pair<>(activityClass, stage);
}
/**
@@ -217,57 +218,108 @@
return ActivityInfo.isTranslucentOrFloating(activity.getWindow().getWindowStyle());
}
- // Test activity
- public static class FirstActivity extends Activity {
+ /** Base activity that only tracks fundamental activity lifecycle states. */
+ public static class LifecycleTrackingActivity extends Activity {
+ LifecycleLog.LifecycleLogClient mLifecycleLogClient;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mLifecycleLogClient = LifecycleLog.LifecycleLogClient.create(this);
+ mLifecycleLogClient.onActivityCallback(PRE_ON_CREATE);
+ mLifecycleLogClient.onActivityCallback(ON_CREATE);
+ }
+
+ @Override
+ protected void onStart() {
+ super.onStart();
+ mLifecycleLogClient.onActivityCallback(ON_START);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ mLifecycleLogClient.onActivityCallback(ON_RESUME);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mLifecycleLogClient.onActivityCallback(ON_PAUSE);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ mLifecycleLogClient.onActivityCallback(ON_STOP);
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mLifecycleLogClient.onActivityCallback(ON_DESTROY);
+ mLifecycleLogClient.close();
+ }
+
+ @Override
+ protected void onRestart() {
+ super.onRestart();
+ mLifecycleLogClient.onActivityCallback(ON_RESTART);
+ }
}
// Test activity
- public static class SecondActivity extends Activity {
+ public static class FirstActivity extends LifecycleTrackingActivity {
}
// Test activity
- public static class ThirdActivity extends Activity {
+ public static class SecondActivity extends LifecycleTrackingActivity {
+ }
+
+ // Test activity
+ public static class ThirdActivity extends LifecycleTrackingActivity {
}
// Translucent test activity
- public static class TranslucentActivity extends Activity {
+ public static class TranslucentActivity extends LifecycleTrackingActivity {
}
// Translucent test activity
- public static class SecondTranslucentActivity extends Activity {
+ public static class SecondTranslucentActivity extends LifecycleTrackingActivity {
}
/**
- * Base activity that records callbacks other then main lifecycle transitions.
+ * Base activity that records callbacks in addition to main lifecycle transitions.
*/
- public static class CallbackTrackingActivity extends Activity {
+ public static class CallbackTrackingActivity extends LifecycleTrackingActivity {
+
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
- mLifecycleLog.onActivityCallback(this, ON_ACTIVITY_RESULT);
+ mLifecycleLogClient.onActivityCallback(ON_ACTIVITY_RESULT);
}
@Override
protected void onPostCreate(Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
- mLifecycleLog.onActivityCallback(this, ON_POST_CREATE);
+ mLifecycleLogClient.onActivityCallback(ON_POST_CREATE);
}
@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
- mLifecycleLog.onActivityCallback(this, ON_NEW_INTENT);
+ mLifecycleLogClient.onActivityCallback(ON_NEW_INTENT);
}
@Override
public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
- mLifecycleLog.onActivityCallback(this,
+ mLifecycleLogClient.onActivityCallback(
isTopResumedActivity ? ON_TOP_POSITION_GAINED : ON_TOP_POSITION_LOST);
}
@Override
public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
- mLifecycleLog.onActivityCallback(this, ON_MULTI_WINDOW_MODE_CHANGED);
+ mLifecycleLogClient.onActivityCallback(ON_MULTI_WINDOW_MODE_CHANGED);
}
}
@@ -333,11 +385,15 @@
public static class ConfigChangeHandlingActivity extends CallbackTrackingActivity {
}
+ // Callback tracking activity that runs in a separate process
+ public static class SecondProcessCallbackTrackingActivity extends CallbackTrackingActivity {
+ }
+
// Pip-capable activity
// TODO(b/123013403): Disabled onMultiWindowMode changed callbacks to make the tests pass, so
// that they can verify other lifecycle transitions. This should be fixed and switched to
// extend CallbackTrackingActivity.
- public static class PipActivity extends Activity {
+ public static class PipActivity extends LifecycleTrackingActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@@ -352,6 +408,47 @@
public static class AlwaysFocusablePipActivity extends CallbackTrackingActivity {
}
+ public static class SlowActivity extends CallbackTrackingActivity {
+
+ static final String EXTRA_CONTROL_FLAGS = "extra_control_flags";
+ static final int FLAG_SLOW_TOP_RESUME_RELEASE = 0x00000001;
+ static final int FLAG_TIMEOUT_TOP_RESUME_RELEASE = 0x00000002;
+
+ private int mFlags;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mFlags = getIntent().getIntExtra(EXTRA_CONTROL_FLAGS, 0);
+ }
+
+ @Override
+ protected void onNewIntent(Intent intent) {
+ super.onNewIntent(intent);
+ mFlags = getIntent().getIntExtra(EXTRA_CONTROL_FLAGS, 0);
+ }
+
+ @Override
+ public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
+ if (!isTopResumedActivity && (mFlags & FLAG_SLOW_TOP_RESUME_RELEASE) != 0) {
+ sleep(200);
+ } else if (!isTopResumedActivity && (mFlags & FLAG_TIMEOUT_TOP_RESUME_RELEASE) != 0) {
+ sleep(2000);
+ }
+ // Intentionally moving the logging of the state change to after sleep to facilitate
+ // race condition with other activity getting top state before this releases its.
+ super.onTopResumedActivityChanged(isTopResumedActivity);
+ }
+
+ private void sleep(long millis) {
+ try {
+ Thread.sleep(millis);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
static ComponentName getComponentName(Class<? extends Activity> activity) {
return new ComponentName(getInstrumentation().getContext(), activity);
}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTopResumedStateTests.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTopResumedStateTests.java
index 3c5c56a..7c2afbf 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTopResumedStateTests.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/ActivityLifecycleTopResumedStateTests.java
@@ -3,7 +3,6 @@
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.server.am.ActivityManagerDisplayTestBase.ReportedDisplayMetrics.getDisplayMetrics;
import static android.server.am.Components.PipActivity.EXTRA_ENTER_PIP;
import static android.server.am.UiDeviceUtils.pressHomeButton;
import static android.server.am.lifecycle.LifecycleLog.ActivityCallback.ON_ACTIVITY_RESULT;
@@ -23,17 +22,19 @@
import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static androidx.test.InstrumentationRegistry.getTargetContext;
import static org.junit.Assert.assertEquals;
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
import android.app.ActivityOptions;
+import android.content.ComponentName;
import android.content.Intent;
-import android.graphics.Rect;
import android.platform.test.annotations.Presubmit;
import android.server.am.ActivityManagerState;
import android.server.am.ActivityManagerState.ActivityStack;
+import android.server.am.ActivityManagerState.ActivityTask;
import android.util.Pair;
import androidx.test.filters.FlakyTest;
@@ -92,6 +93,68 @@
}
@Test
+ public void testTopPositionSwitchToActivityOnTopSlowDifferentProcess() throws Exception {
+ // Launch first activity, which will be slow to release top position
+ final Intent slowTopReleaseIntent = new Intent();
+ slowTopReleaseIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
+ SlowActivity.FLAG_SLOW_TOP_RESUME_RELEASE);
+
+ final Activity firstActivity =
+ mSlowActivityTestRule.launchActivity(slowTopReleaseIntent);
+ waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+
+ // Launch second activity on top
+ getLifecycleLog().clear();
+ final Class<? extends Activity> secondActivityClass =
+ SecondProcessCallbackTrackingActivity.class;
+ launchActivity(new ComponentName(firstActivity, secondActivityClass));
+
+ // Wait and assert top position switch
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
+ state(firstActivity, ON_STOP));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(firstActivity.getClass(), ON_TOP_POSITION_LOST),
+ transition(secondActivityClass, ON_TOP_POSITION_GAINED)),
+ "launchOnTop");
+ }
+
+ @Test
+ public void testTopPositionSwitchToActivityOnTopTimeoutDifferentProcess() throws Exception {
+ // Launch first activity, which will be slow to release top position
+ final Intent slowTopReleaseIntent = new Intent();
+ slowTopReleaseIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
+ SlowActivity.FLAG_TIMEOUT_TOP_RESUME_RELEASE);
+
+ final Activity firstActivity =
+ mSlowActivityTestRule.launchActivity(slowTopReleaseIntent);
+ waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+ final Class<? extends Activity> firstActivityClass = firstActivity.getClass();
+
+ // Launch second activity on top
+ getLifecycleLog().clear();
+ final Class<? extends Activity> secondActivityClass =
+ SecondProcessCallbackTrackingActivity.class;
+ launchActivity(new ComponentName(firstActivity, secondActivityClass));
+
+ // Wait and assert top position switch,
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
+ state(firstActivity, ON_STOP));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(secondActivityClass, ON_TOP_POSITION_GAINED),
+ transition(firstActivityClass, ON_TOP_POSITION_LOST)),
+ "launchOnTop");
+
+ // Wait 5 seconds more to make sure that no new messages received after top resumed state
+ // released by the slow activity
+ getLifecycleLog().clear();
+ Thread.sleep(5000);
+ LifecycleVerifier.assertEmptySequence(firstActivityClass, getLifecycleLog(),
+ "topStateLossTimeout");
+ LifecycleVerifier.assertEmptySequence(secondActivityClass, getLifecycleLog(),
+ "topStateLossTimeout");
+ }
+
+ @Test
public void testTopPositionSwitchToTranslucentActivityOnTop() throws Exception {
final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
@@ -109,7 +172,8 @@
@Test
public void testTopPositionSwitchOnDoubleLaunch() throws Exception {
- final Activity baseActivity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
+ final Activity baseActivity =
+ mCallbackTrackingActivityTestRule.launchActivity(new Intent());
waitAndAssertActivityStates(state(baseActivity, ON_TOP_POSITION_GAINED));
getLifecycleLog().clear();
@@ -283,9 +347,14 @@
state(secondActivity, ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertSequence(CallbackTrackingActivity.class, getLifecycleLog(),
Arrays.asList(ON_TOP_POSITION_LOST), "switchTop");
- LifecycleVerifier.assertSequence(SingleTopActivity.class, getLifecycleLog(),
+ List<LifecycleLog.ActivityCallback> expectedNewIntentSequence = Arrays.asList(
+ ON_PAUSE, ON_NEW_INTENT, ON_RESUME, ON_TOP_POSITION_GAINED);
+ List<LifecycleLog.ActivityCallback> extraPositionTransitionNewIntentSequence =
Arrays.asList(ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE, ON_NEW_INTENT,
- ON_RESUME, ON_TOP_POSITION_GAINED), "switchTop");
+ ON_RESUME, ON_TOP_POSITION_GAINED);
+ LifecycleVerifier.assertSequenceMatchesOneOf(SingleTopActivity.class, getLifecycleLog(),
+ Arrays.asList(expectedNewIntentSequence, extraPositionTransitionNewIntentSequence),
+ "switchTop");
}
@Test
@@ -411,10 +480,7 @@
// Tap on first activity to switch the focus
getLifecycleLog().clear();
final ActivityStack dockedStack = getStackForTaskId(firstActivity.getTaskId());
- final Rect dockedStackBounds = dockedStack.getBounds();
- int tapX = dockedStackBounds.left + dockedStackBounds.width() / 2;
- int tapY = dockedStackBounds.top + dockedStackBounds.height() / 2;
- tapOnDisplay(tapX, tapY, dockedStack.mDisplayId);
+ tapOnStackCenter(dockedStack);
// Wait and assert focus switch
waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED),
@@ -427,10 +493,7 @@
// Tap on second activity to switch the focus again
getLifecycleLog().clear();
final ActivityStack sideStack = getStackForTaskId(secondActivity.getTaskId());
- final Rect sideStackBounds = sideStack.getBounds();
- tapX = sideStackBounds.left + sideStackBounds.width() / 2;
- tapY = sideStackBounds.top + sideStackBounds.height() / 2;
- tapOnDisplay(tapX, tapY, sideStack.mDisplayId);
+ tapOnStackCenter(sideStack);
// Wait and assert focus switch
waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_LOST),
@@ -442,6 +505,138 @@
}
@Test
+ public void testTopPositionSwitchOnTapSlowDifferentProcess() throws Exception {
+ assumeTrue(supportsSplitScreenMultiWindow());
+
+ // Launch first activity
+ final Intent slowTopReleaseIntent = new Intent();
+ slowTopReleaseIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
+ SlowActivity.FLAG_SLOW_TOP_RESUME_RELEASE);
+ final Activity firstActivity =
+ mSlowActivityTestRule.launchActivity(slowTopReleaseIntent);
+ waitAndAssertActivityStates(state(firstActivity, ON_TOP_POSITION_GAINED));
+ final Class<? extends Activity> firstActivityClass = firstActivity.getClass();
+
+ // Enter split screen
+ moveTaskToPrimarySplitScreenAndVerify(firstActivity);
+
+ // Launch second activity to side
+ getLifecycleLog().clear();
+ final Class<? extends Activity> secondActivityClass =
+ SecondProcessCallbackTrackingActivity.class;
+ final ComponentName secondActivityComponent =
+ new ComponentName(firstActivity, secondActivityClass);
+ getLaunchActivityBuilder()
+ .setTargetActivity(secondActivityComponent)
+ .setUseInstrumentation()
+ .setNewTask(true)
+ .setMultipleTask(true)
+ .execute();
+
+ // Wait and assert top position switch
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED));
+
+ // Tap on first activity to switch the top resumed one
+ getLifecycleLog().clear();
+ final ActivityStack dockedStack = getStackForTaskId(firstActivity.getTaskId());
+ tapOnStackCenter(dockedStack);
+
+ // Wait and assert top resumed position switch
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
+ state(firstActivityClass, ON_TOP_POSITION_GAINED));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(secondActivityClass, ON_TOP_POSITION_LOST),
+ transition(firstActivityClass, ON_TOP_POSITION_GAINED)),
+ "tapOnStack");
+
+ // Tap on second activity to switch the top resumed activity again
+ getLifecycleLog().clear();
+ final ActivityTask sideTask = mAmWmState.getAmState()
+ .getTaskByActivity(secondActivityComponent);
+ final ActivityStack sideStack = getStackForTaskId(sideTask.getTaskId());
+ tapOnStackCenter(sideStack);
+
+ // Wait and assert top resumed position switch
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
+ state(firstActivityClass, ON_TOP_POSITION_LOST));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(firstActivityClass, ON_TOP_POSITION_LOST),
+ transition(secondActivityClass, ON_TOP_POSITION_GAINED)),
+ "tapOnStack");
+ }
+
+ @Test
+ public void testTopPositionSwitchOnTapTimeoutDifferentProcess() throws Exception {
+ assumeTrue(supportsSplitScreenMultiWindow());
+
+ // Launch first activity
+ final Intent slowTopReleaseIntent = new Intent();
+ slowTopReleaseIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
+ SlowActivity.FLAG_TIMEOUT_TOP_RESUME_RELEASE);
+ final Activity slowActivity =
+ mSlowActivityTestRule.launchActivity(slowTopReleaseIntent);
+ waitAndAssertActivityStates(state(slowActivity, ON_TOP_POSITION_GAINED));
+ final Class<? extends Activity> slowActivityClass = slowActivity.getClass();
+
+ // Enter split screen
+ moveTaskToPrimarySplitScreenAndVerify(slowActivity);
+
+ // Launch second activity to side
+ getLifecycleLog().clear();
+ final Class<? extends Activity> secondActivityClass =
+ SecondProcessCallbackTrackingActivity.class;
+ final ComponentName secondActivityComponent =
+ new ComponentName(slowActivity, secondActivityClass);
+ getLaunchActivityBuilder()
+ .setTargetActivity(secondActivityComponent)
+ .setUseInstrumentation()
+ .setNewTask(true)
+ .setMultipleTask(true)
+ .execute();
+
+ // Wait and assert top position switch
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED));
+
+ // Tap on first activity to switch the top resumed one
+ getLifecycleLog().clear();
+ final ActivityStack dockedStack = getStackForTaskId(slowActivity.getTaskId());
+ tapOnStackCenter(dockedStack);
+
+ // Wait and assert top resumed position switch.
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
+ state(slowActivityClass, ON_TOP_POSITION_GAINED));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(secondActivityClass, ON_TOP_POSITION_LOST),
+ transition(slowActivityClass, ON_TOP_POSITION_GAINED)),
+ "tapOnStack");
+
+ // Tap on second activity to switch the top resumed activity again
+ getLifecycleLog().clear();
+ final ActivityTask sideTask = mAmWmState.getAmState()
+ .getTaskByActivity(secondActivityComponent);
+ final ActivityStack sideStack = getStackForTaskId(sideTask.getTaskId());
+ tapOnStackCenter(sideStack);
+
+ // Wait and assert top resumed position switch. Because of timeout the new top position will
+ // be reported to the first activity before second will finish handling it.
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
+ state(slowActivityClass, ON_TOP_POSITION_LOST));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(secondActivityClass, ON_TOP_POSITION_GAINED),
+ transition(slowActivityClass, ON_TOP_POSITION_LOST)),
+ "tapOnStack");
+
+ // Wait 5 seconds more to make sure that no new messages received after top resumed state
+ // released by the slow activity
+ getLifecycleLog().clear();
+ Thread.sleep(5000);
+ LifecycleVerifier.assertEmptySequence(slowActivityClass, getLifecycleLog(),
+ "topStateLossTimeout");
+ LifecycleVerifier.assertEmptySequence(secondActivityClass, getLifecycleLog(),
+ "topStateLossTimeout");
+ }
+
+ @Test
public void testTopPositionPreservedOnLocalRelaunch() throws Exception {
final Activity activity = mCallbackTrackingActivityTestRule.launchActivity(new Intent());
waitAndAssertActivityStates(state(activity, ON_TOP_POSITION_GAINED));
@@ -570,16 +765,10 @@
}
// Secondary display was removed - activity will be moved to the default display
- waitAndAssertActivityTransitions(SingleTopActivity.class,
- LifecycleVerifier.getResumeToDestroySequence(SingleTopActivity.class),
- "hostingDisplayRemoved");
- waitAndAssertActivityTransitions(CallbackTrackingActivity.class,
- Arrays.asList(ON_TOP_POSITION_GAINED, ON_TOP_POSITION_LOST, ON_PAUSE, ON_STOP),
- "hostingDisplayRemoved");
+ waitForActivityTransitions(SingleTopActivity.class,
+ LifecycleVerifier.getRelaunchSequence(ON_TOP_POSITION_GAINED));
LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
transition(SingleTopActivity.class, ON_TOP_POSITION_LOST),
- transition(CallbackTrackingActivity.class, ON_TOP_POSITION_GAINED),
- transition(CallbackTrackingActivity.class, ON_TOP_POSITION_LOST),
transition(SingleTopActivity.class, ON_TOP_POSITION_GAINED)),
"hostingDisplayRemoved");
}
@@ -615,10 +804,7 @@
getLifecycleLog().clear();
// Tap on default display to switch the top activity
- ReportedDisplayMetrics displayMetrics = getDisplayMetrics(DEFAULT_DISPLAY);
- int width = displayMetrics.getSize().getWidth();
- int height = displayMetrics.getSize().getHeight();
- tapOnDisplay(width / 2, height / 2, DEFAULT_DISPLAY);
+ tapOnDisplayCenter(DEFAULT_DISPLAY);
// Wait and assert focus switch
waitAndAssertActivityTransitions(SingleTopActivity.class,
@@ -633,10 +819,7 @@
getLifecycleLog().clear();
// Tap on new display to switch the top activity
- displayMetrics = getDisplayMetrics(newDisplay.mId);
- width = displayMetrics.getSize().getWidth();
- height = displayMetrics.getSize().getHeight();
- tapOnDisplay(width / 2, height / 2, newDisplay.mId);
+ tapOnDisplayCenter(newDisplay.mId);
// Wait and assert focus switch
waitAndAssertActivityTransitions(CallbackTrackingActivity.class,
@@ -651,6 +834,144 @@
}
@Test
+ public void testTopPositionSwitchAcrossDisplaysOnTapSlowDifferentProcess() throws Exception {
+ assumeTrue(supportsMultiDisplay());
+
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new simulated display
+ final ActivityManagerState.ActivityDisplay newDisplay
+ = virtualDisplaySession.setSimulateDisplay(true).createDisplay();
+
+ // Launch an activity on new secondary display.
+ final Class<? extends Activity> secondActivityClass =
+ SecondProcessCallbackTrackingActivity.class;
+ final ComponentName secondActivityComponent =
+ new ComponentName(getTargetContext(), secondActivityClass);
+
+ getLaunchActivityBuilder()
+ .setTargetActivity(secondActivityComponent)
+ .setUseInstrumentation()
+ .setDisplayId(newDisplay.mId)
+ .execute();
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED));
+
+ // Launch activity on default display, which will be slow to release top position
+ getLifecycleLog().clear();
+ final ActivityOptions launchOptions = ActivityOptions.makeBasic();
+ launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
+ final Class defaultActivityClass = SlowActivity.class;
+ final Intent defaultDisplaySlowIntent = new Intent(mContext, defaultActivityClass);
+ defaultDisplaySlowIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+ defaultDisplaySlowIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
+ SlowActivity.FLAG_SLOW_TOP_RESUME_RELEASE);
+ mTargetContext.startActivity(defaultDisplaySlowIntent, launchOptions.toBundle());
+
+ waitAndAssertTopResumedActivity(getComponentName(SlowActivity.class),
+ DEFAULT_DISPLAY, "Activity launched on default display must be focused");
+
+ // Wait and assert focus switch
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
+ state(defaultActivityClass, ON_TOP_POSITION_GAINED));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(secondActivityClass, ON_TOP_POSITION_LOST),
+ transition(defaultActivityClass, ON_TOP_POSITION_GAINED)),
+ "launchOnDifferentDisplay");
+
+ // Tap on secondary display to switch the top activity
+ getLifecycleLog().clear();
+ tapOnDisplayCenter(newDisplay.mId);
+
+ // Wait and assert top resumed position switch
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
+ state(defaultActivityClass, ON_TOP_POSITION_LOST));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(defaultActivityClass, ON_TOP_POSITION_LOST),
+ transition(secondActivityClass, ON_TOP_POSITION_GAINED)),
+ "tapOnDifferentDisplay");
+
+ // Tap on default display to switch the top activity again
+ getLifecycleLog().clear();
+ tapOnDisplayCenter(DEFAULT_DISPLAY);
+
+ // Wait and assert top resumed position switch
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
+ state(defaultActivityClass, ON_TOP_POSITION_GAINED));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(secondActivityClass, ON_TOP_POSITION_LOST),
+ transition(defaultActivityClass, ON_TOP_POSITION_GAINED)),
+ "tapOnDifferentDisplay");
+ }
+ }
+
+ @Test
+ public void testTopPositionSwitchAcrossDisplaysOnTapTimeoutDifferentProcess() throws Exception {
+ assumeTrue(supportsMultiDisplay());
+
+ try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession()) {
+ // Create new simulated display
+ final ActivityManagerState.ActivityDisplay newDisplay
+ = virtualDisplaySession.setSimulateDisplay(true).createDisplay();
+
+ // Launch an activity on new secondary display.
+ final Class<? extends Activity> secondActivityClass =
+ SecondProcessCallbackTrackingActivity.class;
+ final ComponentName secondActivityComponent =
+ new ComponentName(getTargetContext(), secondActivityClass);
+
+ getLaunchActivityBuilder()
+ .setTargetActivity(secondActivityComponent)
+ .setUseInstrumentation()
+ .setDisplayId(newDisplay.mId)
+ .execute();
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED));
+
+ // Launch activity on default display, which will be slow to release top position
+ getLifecycleLog().clear();
+ final ActivityOptions launchOptions = ActivityOptions.makeBasic();
+ launchOptions.setLaunchDisplayId(DEFAULT_DISPLAY);
+ final Class defaultActivityClass = SlowActivity.class;
+ final Intent defaultDisplaySlowIntent = new Intent(mContext, defaultActivityClass);
+ defaultDisplaySlowIntent.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_MULTIPLE_TASK);
+ defaultDisplaySlowIntent.putExtra(SlowActivity.EXTRA_CONTROL_FLAGS,
+ SlowActivity.FLAG_TIMEOUT_TOP_RESUME_RELEASE);
+ mTargetContext.startActivity(defaultDisplaySlowIntent, launchOptions.toBundle());
+
+ waitAndAssertTopResumedActivity(getComponentName(SlowActivity.class),
+ DEFAULT_DISPLAY, "Activity launched on default display must be focused");
+
+ // Wait and assert focus switch
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_LOST),
+ state(defaultActivityClass, ON_TOP_POSITION_GAINED));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(secondActivityClass, ON_TOP_POSITION_LOST),
+ transition(defaultActivityClass, ON_TOP_POSITION_GAINED)),
+ "launchOnDifferentDisplay");
+
+ // Tap on secondary display to switch the top activity
+ getLifecycleLog().clear();
+ tapOnDisplayCenter(newDisplay.mId);
+
+ // Wait and assert top resumed position switch. Because of timeout top position gain
+ // will appear before top position loss handling is finished.
+ waitAndAssertActivityStates(state(secondActivityClass, ON_TOP_POSITION_GAINED),
+ state(defaultActivityClass, ON_TOP_POSITION_LOST));
+ LifecycleVerifier.assertOrder(getLifecycleLog(), Arrays.asList(
+ transition(secondActivityClass, ON_TOP_POSITION_GAINED),
+ transition(defaultActivityClass, ON_TOP_POSITION_LOST)),
+ "tapOnDifferentDisplay");
+
+ // Wait 5 seconds more to make sure that no new messages received after top resumed state
+ // released by the slow activity
+ getLifecycleLog().clear();
+ Thread.sleep(5000);
+ LifecycleVerifier.assertEmptySequence(defaultActivityClass, getLifecycleLog(),
+ "topStateLossTimeout");
+ LifecycleVerifier.assertEmptySequence(secondActivityClass, getLifecycleLog(),
+ "topStateLossTimeout");
+ }
+ }
+
+ @Test
public void testTopPositionNotSwitchedToPip() throws Exception {
assumeTrue(supportsPip());
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
index ff7b5f1..b4d442e 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleLog.java
@@ -18,15 +18,14 @@
import static android.server.am.StateLogger.log;
-import static androidx.test.runner.lifecycle.Stage.CREATED;
-import static androidx.test.runner.lifecycle.Stage.DESTROYED;
-import static androidx.test.runner.lifecycle.Stage.PAUSED;
-import static androidx.test.runner.lifecycle.Stage.PRE_ON_CREATE;
-import static androidx.test.runner.lifecycle.Stage.RESUMED;
-import static androidx.test.runner.lifecycle.Stage.STARTED;
-import static androidx.test.runner.lifecycle.Stage.STOPPED;
-
import android.app.Activity;
+import android.content.ContentProvider;
+import android.content.ContentProviderClient;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
import android.util.Pair;
import androidx.test.runner.lifecycle.ActivityLifecycleCallback;
@@ -39,9 +38,9 @@
* Used as a shared log storage of activity lifecycle transitions. Methods must be synchronized to
* prevent concurrent modification of the log store.
*/
-class LifecycleLog implements ActivityLifecycleCallback {
+public class LifecycleLog extends ContentProvider {
- enum ActivityCallback {
+ public enum ActivityCallback {
PRE_ON_CREATE,
ON_CREATE,
ON_START,
@@ -58,36 +57,52 @@
ON_TOP_POSITION_LOST
}
+ interface LifecycleTrackerCallback {
+ void onActivityLifecycleChanged();
+ }
+
+ /** Identifies the activity to which the event corresponds. */
+ private static final String EXTRA_KEY_ACTIVITY = "key_activity";
+ /** Puts a lifecycle or callback into the container. */
+ private static final String METHOD_ADD_CALLBACK = "add_callback";
+ /** Content provider URI for cross-process lifecycle transitions collecting. */
+ private static final Uri URI = Uri.parse("content://android.server.am.lifecycle.logprovider");
+
/**
* Log for encountered activity callbacks. Note that methods accessing or modifying this
* list should be synchronized as it can be accessed from different threads.
*/
- private List<Pair<String, ActivityCallback>> mLog = new ArrayList<>();
+ private static List<Pair<String, ActivityCallback>> sLog = new ArrayList<>();
+
+ /**
+ * Lifecycle tracker interface that waits for correct states or sequences.
+ */
+ private static LifecycleTrackerCallback sLifecycleTracker;
/** Clear the entire transition log. */
synchronized void clear() {
- mLog.clear();
+ sLog.clear();
}
- /** Add transition of an activity to the log. */
- @Override
- synchronized public void onActivityLifecycleChanged(Activity activity, Stage stage) {
- final String activityName = activity.getClass().getCanonicalName();
- log("Activity " + activityName + " moved to stage " + stage);
- mLog.add(new Pair<>(activityName, stageToCallback(stage)));
+ public void setLifecycleTracker(LifecycleTrackerCallback lifecycleTracker) {
+ sLifecycleTracker = lifecycleTracker;
}
/** Add activity callback to the log. */
- synchronized public void onActivityCallback(Activity activity, ActivityCallback callback) {
- final String activityName = activity.getClass().getCanonicalName();
- log("Activity " + activityName + " got a callback " + callback);
- mLog.add(new Pair<>(activityName, callback));
+ synchronized private void onActivityCallback(String activityCanonicalName,
+ ActivityCallback callback) {
+ sLog.add(new Pair<>(activityCanonicalName, callback));
+ log("Activity " + activityCanonicalName + " receiver callback " + callback);
+ // Trigger check for valid state in the tracker
+ if (sLifecycleTracker != null) {
+ sLifecycleTracker.onActivityLifecycleChanged();
+ }
}
/** Get logs for all recorded transitions. */
synchronized List<Pair<String, ActivityCallback>> getLog() {
// Wrap in a new list to prevent concurrent modification
- return new ArrayList<>(mLog);
+ return new ArrayList<>(sLog);
}
/** Get transition logs for the specified activity. */
@@ -95,7 +110,7 @@
final String activityName = activityClass.getCanonicalName();
log("Looking up log for activity: " + activityName);
final List<ActivityCallback> activityLog = new ArrayList<>();
- for (Pair<String, ActivityCallback> transition : mLog) {
+ for (Pair<String, ActivityCallback> transition : sLog) {
if (transition.first.equals(activityName)) {
activityLog.add(transition.second);
}
@@ -103,26 +118,83 @@
return activityLog;
}
- private static ActivityCallback stageToCallback(Stage stage) {
- switch (stage) {
- case PRE_ON_CREATE:
- return ActivityCallback.PRE_ON_CREATE;
- case CREATED:
- return ActivityCallback.ON_CREATE;
- case STARTED:
- return ActivityCallback.ON_START;
- case RESUMED:
- return ActivityCallback.ON_RESUME;
- case PAUSED:
- return ActivityCallback.ON_PAUSE;
- case STOPPED:
- return ActivityCallback.ON_STOP;
- case RESTARTED:
- return ActivityCallback.ON_RESTART;
- case DESTROYED:
- return ActivityCallback.ON_DESTROY;
- default:
- throw new IllegalArgumentException("Unsupported stage: " + stage);
+
+ // ContentProvider implementation for cross-process tracking
+
+ public static class LifecycleLogClient implements AutoCloseable {
+ private static final String EMPTY_ARG = "";
+ private final ContentProviderClient mClient;
+ private final String mOwner;
+
+ LifecycleLogClient(ContentProviderClient client, Activity owner) {
+ mClient = client;
+ mOwner = owner.getClass().getCanonicalName();
}
+
+ void onActivityCallback(ActivityCallback callback) {
+ final Bundle extras = new Bundle();
+ extras.putInt(METHOD_ADD_CALLBACK, callback.ordinal());
+ extras.putString(EXTRA_KEY_ACTIVITY, mOwner);
+ try {
+ mClient.call(METHOD_ADD_CALLBACK, EMPTY_ARG, extras);
+ } catch (RemoteException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void close() {
+ mClient.close();
+ }
+
+ static LifecycleLogClient create(Activity owner) {
+ final ContentProviderClient client = owner.getContentResolver()
+ .acquireContentProviderClient(URI);
+ if (client == null) {
+ throw new RuntimeException("Unable to acquire " + URI);
+ }
+ return new LifecycleLogClient(client, owner);
+ }
+ }
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ if (!METHOD_ADD_CALLBACK.equals(method)) {
+ throw new UnsupportedOperationException();
+ }
+ onActivityCallback(extras.getString(EXTRA_KEY_ACTIVITY),
+ ActivityCallback.values()[extras.getInt(method)]);
+ return null;
+ }
+
+ @Override
+ public boolean onCreate() {
+ return true;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
}
}
diff --git a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java
index ad0afb9..65047bf 100644
--- a/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java
+++ b/tests/framework/base/activitymanager/src/android/server/am/lifecycle/LifecycleTracker.java
@@ -33,12 +33,13 @@
* Gets notified about activity lifecycle updates and provides blocking mechanism to wait until
* expected activity states are reached.
*/
-public class LifecycleTracker implements ActivityLifecycleCallback {
+public class LifecycleTracker implements LifecycleLog.LifecycleTrackerCallback {
private LifecycleLog mLifecycleLog;
LifecycleTracker(LifecycleLog lifecycleLog) {
mLifecycleLog = lifecycleLog;
+ mLifecycleLog.setLifecycleTracker(this);
}
void waitAndAssertActivityStates(
@@ -65,7 +66,7 @@
}
@Override
- synchronized public void onActivityLifecycleChanged(Activity activity, Stage stage) {
+ synchronized public void onActivityLifecycleChanged() {
notify();
}
diff --git a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
index 4de4a95..ed43e7d 100644
--- a/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
+++ b/tests/framework/base/activitymanager/util/src/android/server/am/ActivityManagerTestBase.java
@@ -477,6 +477,13 @@
injectMotion(downTime, upTime, MotionEvent.ACTION_UP, x, y, displayId);
}
+ protected void tapOnStackCenter(ActivityManagerState.ActivityStack stack) {
+ final Rect sideStackBounds = stack.getBounds();
+ final int tapX = sideStackBounds.left + sideStackBounds.width() / 2;
+ final int tapY = sideStackBounds.top + sideStackBounds.height() / 2;
+ tapOnDisplay(tapX, tapY, stack.mDisplayId);
+ }
+
private static void injectMotion(long downTime, long eventTime, int action,
int x, int y, int displayId) {
final MotionEvent event = MotionEvent.obtain(downTime, eventTime, action,
diff --git a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
index d6d431a..8cb561b 100644
--- a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
+++ b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
@@ -24,6 +24,10 @@
import static android.Manifest.permission.RECORD_AUDIO;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
+import static android.media.MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO;
+import static android.media.MediaMetadataRetriever.METADATA_KEY_LOCATION;
+import static android.media.MediaMetadataRetriever.METADATA_KEY_DURATION;
+
import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentUris;
@@ -36,6 +40,7 @@
import android.content.pm.PermissionInfo;
import android.content.pm.ResolveInfo;
import android.media.ExifInterface;
+import android.media.MediaMetadataRetriever;
import android.media.MediaScannerConnection;
import android.net.Uri;
import android.os.Environment;
@@ -70,6 +75,8 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.text.SimpleDateFormat;
+import java.util.Date;
public class MediaStoreUiTest extends InstrumentationTestCase {
private static final String TAG = "MediaStoreUiTest";
@@ -188,19 +195,43 @@
}
}
+ private void maybeRevokeRuntimePermission(String pkg, Set<String> requested, String permission)
+ throws NameNotFoundException {
+ if (requested.contains(permission)) {
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .revokeRuntimePermission(pkg, permission);
+ }
+ }
+
/**
* Verify that whoever handles {@link MediaStore#ACTION_IMAGE_CAPTURE} can
* correctly write the contents into a passed {@code content://} Uri.
*/
public void testImageCapture() throws Exception {
+ testImageCaptureWithOrWithoutLocationAccess(true);
+ }
+
+ /**
+ * Verify that whoever handles {@link MediaStore#ACTION_IMAGE_CAPTURE} can
+ * correctly write the contents into a passed {@code content://} Uri, without location
+ * information.
+ */
+ public void testImageCaptureWithoutLocationAccess() throws Exception {
+ testImageCaptureWithOrWithoutLocationAccess(false);
+ }
+
+ private void testImageCaptureWithOrWithoutLocationAccess(boolean giveLocationAccess)
+ throws Exception {
final Context context = getInstrumentation().getContext();
if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
Log.d(TAG, "Skipping due to lack of camera");
return;
}
+ String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
+
final File targetDir = new File(context.getFilesDir(), "debug");
- final File target = new File(targetDir, "capture.jpg");
+ final File target = new File(targetDir, timeStamp + "capture.jpg");
targetDir.mkdirs();
assertFalse(target.exists());
@@ -219,15 +250,118 @@
final Set<String> req = new HashSet<>();
req.addAll(Arrays.asList(pi.requestedPermissions));
+ grantRequisitePermissions(pkg, req, giveLocationAccess);
+
+ Result result = getImageOrVideoCaptureIntentResult(intent, false, giveLocationAccess, pkg);
+
+ assertTrue("exists", target.exists());
+ assertTrue("has data", target.length() > 65536);
+
+ // At the very least we expect photos generated by the device to have
+ // sane baseline EXIF data
+ final ExifInterface exif = new ExifInterface(new FileInputStream(target));
+ assertAttribute(exif, ExifInterface.TAG_MAKE);
+ assertAttribute(exif, ExifInterface.TAG_MODEL);
+ assertAttribute(exif, ExifInterface.TAG_DATETIME);
+ if (!giveLocationAccess) {
+ float[] latLong = new float[2];
+ assertTrue("Should not contain location information ", !exif.getLatLong(latLong));
+ } else {
+ revokeRequisitePermissions(pkg, req);
+ }
+ }
+
+ private void grantRequisitePermissions(String pkg, Set<String> req, boolean giveLocationAccess)
+ throws Exception {
// Grant them all the permissions they might want
maybeGrantRuntimePermission(pkg, req, CAMERA);
- maybeGrantRuntimePermission(pkg, req, ACCESS_COARSE_LOCATION);
- maybeGrantRuntimePermission(pkg, req, ACCESS_FINE_LOCATION);
- maybeGrantRuntimePermission(pkg, req, ACCESS_BACKGROUND_LOCATION);
maybeGrantRuntimePermission(pkg, req, RECORD_AUDIO);
maybeGrantRuntimePermission(pkg, req, READ_EXTERNAL_STORAGE);
maybeGrantRuntimePermission(pkg, req, WRITE_EXTERNAL_STORAGE);
SystemClock.sleep(DateUtils.SECOND_IN_MILLIS);
+ if (giveLocationAccess) {
+ maybeGrantRuntimePermission(pkg, req, ACCESS_COARSE_LOCATION);
+ maybeGrantRuntimePermission(pkg, req, ACCESS_FINE_LOCATION);
+ maybeGrantRuntimePermission(pkg, req, ACCESS_BACKGROUND_LOCATION);
+ }
+ }
+
+ private void revokeRequisitePermissions(String pkg, Set<String> req) throws Exception {
+ // So that the other tests don't start with this permission granted.
+ maybeRevokeRuntimePermission(pkg, req, ACCESS_COARSE_LOCATION);
+ maybeRevokeRuntimePermission(pkg, req, ACCESS_FINE_LOCATION);
+ maybeRevokeRuntimePermission(pkg, req, ACCESS_BACKGROUND_LOCATION);
+ }
+
+ /**
+ * Verify that whoever handles {@link MediaStore#ACTION_VIDEO_CAPTURE} can
+ * correctly write the contents into a passed {@code content://} Uri.
+ */
+ public void testVideoCapture() throws Exception {
+ testVideoCaptureWithOrWithoutLocationAccess(true);
+ }
+
+ /**
+ * Verify that whoever handles {@link MediaStore#ACTION_VIDEO_CAPTURE} can
+ * correctly write the contents into a passed {@code content://} Uri, without location
+ * information.
+ */
+ public void testVideoCaptureWithoutLocationAccess() throws Exception {
+ testVideoCaptureWithOrWithoutLocationAccess(false);
+ }
+
+ private void testVideoCaptureWithOrWithoutLocationAccess(boolean giveLocationAccess)
+ throws Exception {
+ final Context context = getInstrumentation().getContext();
+ if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)) {
+ Log.d(TAG, "Skipping due to lack of camera");
+ return;
+ }
+
+ String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
+
+ final File targetDir = new File(context.getFilesDir(), "debug");
+ final File target = new File(targetDir, timeStamp + "video.mp4");
+
+ targetDir.mkdirs();
+ assertFalse(target.exists());
+
+ final Intent intent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
+ intent.putExtra(MediaStore.EXTRA_OUTPUT,
+ FileProvider.getUriForFile(context, "android.providerui.cts.fileprovider", target));
+
+ // Figure out who is going to answer the request
+ final ResolveInfo ri = context.getPackageManager().resolveActivity(intent, 0);
+ final String pkg = ri.activityInfo.packageName;
+ Log.d(TAG, "We're probably launching " + ri);
+
+ final PackageInfo pi = context.getPackageManager().getPackageInfo(pkg,
+ PackageManager.GET_PERMISSIONS);
+ final Set<String> req = new HashSet<>();
+ req.addAll(Arrays.asList(pi.requestedPermissions));
+
+ grantRequisitePermissions(pkg, req, giveLocationAccess);
+ Result result = getImageOrVideoCaptureIntentResult(intent, true, giveLocationAccess, pkg);
+
+ assertTrue("exists", target.exists());
+ assertTrue("has data", target.length() > 65536);
+
+ // Check that the metadata retriever can at least identify video being present.
+ final MediaMetadataRetriever mediaRetriever = new MediaMetadataRetriever();
+ mediaRetriever.setDataSource(target.toString());
+ assertNotNull(mediaRetriever.extractMetadata(METADATA_KEY_HAS_VIDEO));
+ Log.d(TAG, "duration of video: " + mediaRetriever.extractMetadata(METADATA_KEY_DURATION));
+ Log.d(TAG, "location of video: " + mediaRetriever.extractMetadata(METADATA_KEY_LOCATION));
+ if (!giveLocationAccess) {
+ assertNull(mediaRetriever.extractMetadata(METADATA_KEY_LOCATION));
+ } else {
+ revokeRequisitePermissions(pkg, req);
+ }
+ mediaRetriever.release();
+ }
+
+ private Result getImageOrVideoCaptureIntentResult(Intent intent, boolean isVideo,
+ boolean giveLocationAccess, String pkg) throws Exception {
mActivity.startActivityForResult(intent, REQUEST_CODE);
mDevice.waitForIdle();
@@ -235,11 +369,16 @@
// To ensure camera app is launched
SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
- // Try a couple different strategies for taking a photo: first take a
- // photo and confirm using hardware keys
+ // Try a couple different strategies for taking a photo / capturing a video: first capture
+ // and confirm using hardware keys.
mDevice.pressKeyCode(KeyEvent.KEYCODE_CAMERA);
mDevice.waitForIdle();
SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
+ if (isVideo) {
+ // Stop recording
+ mDevice.pressKeyCode(KeyEvent.KEYCODE_CAMERA);
+ }
+ // We're done.
mDevice.pressKeyCode(KeyEvent.KEYCODE_DPAD_CENTER);
mDevice.waitForIdle();
@@ -267,6 +406,9 @@
maybeClick(By.pkg(pkg).descContains("Capture"));
mDevice.waitForIdle();
SystemClock.sleep(5 * DateUtils.SECOND_IN_MILLIS);
+ if (isVideo) {
+ maybeClick(By.pkg(pkg).descContains("Stop"));
+ }
maybeClick(By.pkg(pkg).descContains("Done"));
mDevice.waitForIdle();
@@ -276,16 +418,7 @@
assertNotNull("Expected to get a IMAGE_CAPTURE result; your camera app should "
+ "respond to the CAMERA and DPAD_CENTER keycodes", result);
-
- assertTrue("exists", target.exists());
- assertTrue("has data", target.length() > 65536);
-
- // At the very least we expect photos generated by the device to have
- // sane baseline EXIF data
- final ExifInterface exif = new ExifInterface(new FileInputStream(target));
- assertAttribute(exif, ExifInterface.TAG_MAKE);
- assertAttribute(exif, ExifInterface.TAG_MODEL);
- assertAttribute(exif, ExifInterface.TAG_DATETIME);
+ return result;
}
private static void assertAttribute(ExifInterface exif, String tag) {
diff --git a/tests/signature/intent-check/AndroidTest.xml b/tests/signature/intent-check/AndroidTest.xml
index 134a175..09e1349 100644
--- a/tests/signature/intent-check/AndroidTest.xml
+++ b/tests/signature/intent-check/AndroidTest.xml
@@ -48,5 +48,7 @@
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.signature.cts.intent" />
<option name="runtime-hint" value="10s" />
+ <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
+ <option name="isolated-storage" value="false" />
</test>
</configuration>
diff --git a/tests/tests/alarmclock/AndroidManifest.xml b/tests/tests/alarmclock/AndroidManifest.xml
deleted file mode 100644
index 15b47c4..0000000
--- a/tests/tests/alarmclock/AndroidManifest.xml
+++ /dev/null
@@ -1,47 +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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.alarmclock.cts">
-
- <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
- <uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
-
- <application>
- <uses-library android:name="android.test.runner" />
-
- <activity android:name="TestStartActivity"
- android:label="The Target Activity for AlarmClock CTS Test">
- <intent-filter>
- <action android:name="android.intent.action.TEST_START_ACTIVITY_DISMISS_ALARM" />
- <action android:name="android.intent.action.TEST_START_ACTIVITY_SET_ALARM" />
- <action android:name=
- "android.intent.action.TEST_START_ACTIVITY_SET_ALARM_FOR_DISMISSAL" />
- <action android:name="android.intent.action.TEST_START_ACTIVITY_SNOOZE_ALARM" />
- <category android:name="android.intent.category.LAUNCHER" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
- </application>
-
- <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
- android:targetPackage="android.alarmclock.cts"
- android:label="CTS tests of android.alarmclock">
- <meta-data android:name="listener"
- android:value="com.android.cts.runner.CtsTestRunListener" />
- </instrumentation>
-</manifest>
-
diff --git a/tests/tests/alarmclock/AndroidTest.xml b/tests/tests/alarmclock/AndroidTest.xml
deleted file mode 100644
index 023032a..0000000
--- a/tests/tests/alarmclock/AndroidTest.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?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="Configuration for AlarmClock Tests">
- <option name="test-suite-tag" value="cts" />
- <option name="config-descriptor:metadata" key="component" value="framework" />
- <option name="not-shardable" value="true" />
- <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
- <option name="cleanup-apks" value="true" />
- <option name="test-file-name" value="CtsAlarmClockService.apk" />
- <option name="test-file-name" value="CtsAlarmClockTestCases.apk" />
- </target_preparer>
-
- <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
- <option name="run-command"
- value="settings put secure voice_interaction_service android.alarmclock.service/.MainInteractionService" />
- </target_preparer>
-
- <test class="com.android.tradefed.testtype.AndroidJUnitTest">
- <option name="package" value="android.alarmclock.cts" />
- </test>
-</configuration>
diff --git a/tests/tests/alarmclock/OWNERS b/tests/tests/alarmclock/OWNERS
deleted file mode 100644
index ddfaec0..0000000
--- a/tests/tests/alarmclock/OWNERS
+++ /dev/null
@@ -1,2 +0,0 @@
-# Bug component: 31568
-sstout@google.com
\ No newline at end of file
diff --git a/tests/tests/alarmclock/common/Android.mk b/tests/tests/alarmclock/common/Android.mk
deleted file mode 100644
index 039ca5c..0000000
--- a/tests/tests/alarmclock/common/Android.mk
+++ /dev/null
@@ -1,33 +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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# don't include this package in any target
-LOCAL_MODULE_TAGS := optional
-# and when built explicitly put it in the data partition
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_MODULE := CtsAlarmClockCommon
-
-LOCAL_SDK_VERSION := current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/tests/tests/alarmclock/common/src/android/alarmclock/common/Utils.java b/tests/tests/alarmclock/common/src/android/alarmclock/common/Utils.java
deleted file mode 100644
index 8f04d2a..0000000
--- a/tests/tests/alarmclock/common/src/android/alarmclock/common/Utils.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.alarmclock.common;
-
-import android.os.Bundle;
-
-public class Utils {
- // private constructor - so it can't be instantiated.
- private Utils() {
- }
-
- public enum TestcaseType {
- DISMISS_ALARM,
- DISMISS_TIMER,
- SET_ALARM,
- SET_ALARM_FOR_DISMISSAL,
- SET_TIMER_FOR_DISMISSAL,
- SNOOZE_ALARM,
- }
- public static final String TESTCASE_TYPE = "Testcase_type";
- public static final String BROADCAST_INTENT =
- "android.intent.action.FROM_ALARMCLOCK_CTS_TEST_";
- public static final String TEST_RESULT = "test_result";
- public static final String COMPLETION_RESULT = "completion";
- public static final String ABORT_RESULT = "abort";
-
- public static final String toBundleString(Bundle bundle) {
- if (bundle == null) {
- return "*** Bundle is null ****";
- }
- StringBuilder buf = new StringBuilder();
- if (bundle != null) {
- buf.append("extras: ");
- for (String s : bundle.keySet()) {
- buf.append("(" + s + " = " + bundle.get(s) + "), ");
- }
- }
- return buf.toString();
- }
-}
diff --git a/tests/tests/alarmclock/res/xml/interaction_service.xml b/tests/tests/alarmclock/res/xml/interaction_service.xml
deleted file mode 100644
index e37017c..0000000
--- a/tests/tests/alarmclock/res/xml/interaction_service.xml
+++ /dev/null
@@ -1,20 +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.
--->
-
-<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
- android:sessionService="android.alarmclock.service.MainInteractionSessionService"
- android:recognitionService="android.alarmclock.service.MainRecognitionService"
- android:settingsActivity="android.alarmclock.service.SettingsActivity"
- android:supportsAssist="false" />
diff --git a/tests/tests/alarmclock/service/Android.mk b/tests/tests/alarmclock/service/Android.mk
deleted file mode 100644
index f64cfe4..0000000
--- a/tests/tests/alarmclock/service/Android.mk
+++ /dev/null
@@ -1,35 +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.
-
-LOCAL_PATH:= $(call my-dir)
-
-include $(CLEAR_VARS)
-
-# don't include this package in any target
-LOCAL_MODULE_TAGS := optional
-# and when built explicitly put it in the data partition
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA_APPS)
-
-LOCAL_STATIC_JAVA_LIBRARIES := CtsAlarmClockCommon ctstestrunner-axt compatibility-device-util-axt
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_PACKAGE_NAME := CtsAlarmClockService
-
-LOCAL_SDK_VERSION := current
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts vts general-tests
-
-include $(BUILD_CTS_SUPPORT_PACKAGE)
diff --git a/tests/tests/alarmclock/service/AndroidManifest.xml b/tests/tests/alarmclock/service/AndroidManifest.xml
deleted file mode 100644
index 074df26..0000000
--- a/tests/tests/alarmclock/service/AndroidManifest.xml
+++ /dev/null
@@ -1,64 +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.
- -->
-
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
- package="android.alarmclock.service">
- <uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
-
- <application>
- <uses-library android:name="android.test.runner" />
- <service android:name=".MainInteractionService"
- android:label="CTS test voice interaction service"
- android:permission="android.permission.BIND_VOICE_INTERACTION"
- android:process=":interactor"
- android:exported="true">
- <meta-data android:name="android.voice_interaction"
- android:resource="@xml/interaction_service" />
- <intent-filter>
- <action android:name="android.service.voice.VoiceInteractionService" />
- </intent-filter>
- </service>
- <activity android:name=".VoiceInteractionMain" >
- <intent-filter>
- <action android:name="android.intent.action.VIMAIN_DISMISS_ALARM" />
- <action android:name="android.intent.action.VIMAIN_SET_ALARM" />
- <action android:name="android.intent.action.VIMAIN_SET_ALARM_FOR_DISMISSAL" />
- <action android:name="android.intent.action.VIMAIN_SNOOZE_ALARM" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
- <activity android:name=".SettingsActivity"
- android:label="Voice Interaction Settings">
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- </activity>
- <service android:name=".MainInteractionSessionService"
- android:permission="android.permission.BIND_VOICE_INTERACTION"
- android:process=":session">
- </service>
- <service android:name=".MainRecognitionService"
- android:label="CTS Voice Recognition Service">
- <intent-filter>
- <action android:name="android.speech.RecognitionService" />
- <category android:name="android.intent.category.DEFAULT" />
- </intent-filter>
- <meta-data android:name="android.speech" android:resource="@xml/recognition_service" />
- </service>
- </application>
-</manifest>
-
diff --git a/tests/tests/alarmclock/service/res/xml/interaction_service.xml b/tests/tests/alarmclock/service/res/xml/interaction_service.xml
deleted file mode 100644
index e37017c..0000000
--- a/tests/tests/alarmclock/service/res/xml/interaction_service.xml
+++ /dev/null
@@ -1,20 +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.
--->
-
-<voice-interaction-service xmlns:android="http://schemas.android.com/apk/res/android"
- android:sessionService="android.alarmclock.service.MainInteractionSessionService"
- android:recognitionService="android.alarmclock.service.MainRecognitionService"
- android:settingsActivity="android.alarmclock.service.SettingsActivity"
- android:supportsAssist="false" />
diff --git a/tests/tests/alarmclock/service/res/xml/recognition_service.xml b/tests/tests/alarmclock/service/res/xml/recognition_service.xml
deleted file mode 100644
index 132c987..0000000
--- a/tests/tests/alarmclock/service/res/xml/recognition_service.xml
+++ /dev/null
@@ -1,17 +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.
--->
-
-<recognition-service xmlns:android="http://schemas.android.com/apk/res/android"
- android:settingsActivity="android.alarmclock.service.SettingsActivity" />
diff --git a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionService.java b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionService.java
deleted file mode 100644
index f96140e..0000000
--- a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionService.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.alarmclock.service;
-
-import android.alarmclock.common.Utils;
-import android.content.ComponentName;
-import android.content.Intent;
-import android.os.Bundle;
-import android.service.voice.VoiceInteractionService;
-import android.util.Log;
-
-public class MainInteractionService extends VoiceInteractionService {
- static final String TAG = "MainInteractionService";
- private Intent mIntent;
- private boolean mReady = false;
-
- @Override
- public void onReady() {
- super.onReady();
- mReady = true;
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- Log.i(TAG, "onStartCommand received");
- mIntent = intent;
- Log.i(TAG, "received_testcasetype = " + mIntent.getStringExtra(Utils.TESTCASE_TYPE));
- maybeStart();
- return START_NOT_STICKY;
- }
-
- private void maybeStart() {
- if (mIntent == null || !mReady) {
- Log.wtf(TAG, "Can't start session because either intent is null or onReady() "
- + "is not called yet. mIntent = " + mIntent + ", mReady = " + mReady);
- } else {
- Log.i(TAG,
- "Yay! about to start MainInteractionSession as the voice_interaction_service");
- if (isActiveService(this, new ComponentName(this, getClass()))) {
- Bundle args = new Bundle();
- args.putString(Utils.TESTCASE_TYPE, mIntent.getStringExtra(Utils.TESTCASE_TYPE));
- Log.i(TAG, "xferring_testcasetype = " + args.getString(Utils.TESTCASE_TYPE));
- showSession(args, 0);
- } else {
- Log.wtf(TAG, "**** Not starting MainInteractionService because" +
- " it is not set as the current voice_interaction_service");
- }
- }
- }
-}
diff --git a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSession.java b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSession.java
deleted file mode 100644
index 99c0b6f..0000000
--- a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSession.java
+++ /dev/null
@@ -1,183 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.alarmclock.service;
-
-import android.alarmclock.common.Utils;
-import android.alarmclock.common.Utils.TestcaseType;
-import android.content.Context;
-import android.content.Intent;
-import android.os.AsyncTask;
-import android.os.Bundle;
-import android.provider.AlarmClock;
-import android.service.voice.VoiceInteractionSession;
-import android.util.Log;
-
-import java.util.ArrayList;
-import java.util.List;
-
-public class MainInteractionSession extends VoiceInteractionSession {
- static final String TAG = "MainInteractionSession";
-
- private List<MyTask> mUsedTasks = new ArrayList<MyTask>();
- private Context mContext;
- private TestcaseType mTestType;
- private String mTestResult = "";
-
- MainInteractionSession(Context context) {
- super(context);
- mContext = context;
- }
-
- @Override
- public void onCreate() {
- super.onCreate();
- }
-
- @Override
- public void onDestroy() {
- Log.i(TAG, "Canceling the Asynctasks in onDestroy()");
- for (MyTask t : mUsedTasks) {
- t.cancel(true);
- }
- super.onDestroy();
- }
-
- @Override
- public void onShow(Bundle args, int showFlags) {
- super.onShow(args, showFlags);
- String testCaseType = args.getString(Utils.TESTCASE_TYPE);
- Log.i(TAG, "received_testcasetype = " + testCaseType);
- try {
- mTestType = TestcaseType.valueOf(testCaseType);
- } catch (IllegalArgumentException e) {
- Log.wtf(TAG, e);
- return;
- } catch (NullPointerException e) {
- Log.wtf(TAG, e);
- return;
- }
- Intent intent;
- switch(mTestType) {
- case DISMISS_ALARM:
- intent = new Intent(AlarmClock.ACTION_DISMISS_ALARM);
- intent.putExtra(AlarmClock.EXTRA_ALARM_SEARCH_MODE,
- AlarmClock.ALARM_SEARCH_MODE_NEXT);
- break;
-
- case DISMISS_TIMER:
- intent = new Intent(AlarmClock.ACTION_DISMISS_TIMER);
- break;
-
- case SET_ALARM_FOR_DISMISSAL:
- case SET_ALARM:
- intent = new Intent(AlarmClock.ACTION_SET_ALARM);
- intent.putExtra(AlarmClock.EXTRA_HOUR, 14);
- break;
-
- case SET_TIMER_FOR_DISMISSAL:
- intent = new Intent(AlarmClock.ACTION_SET_TIMER);
- intent.putExtra(AlarmClock.EXTRA_LENGTH, 1);
- break;
-
- case SNOOZE_ALARM:
- intent = new Intent(AlarmClock.ACTION_SNOOZE_ALARM);
- break;
-
- default:
- Log.e(TAG, "Unexpected value");
- return;
- }
- Log.i(TAG, "starting_voiceactivity: " + intent.toString());
- startVoiceActivity(intent);
- }
-
- @Override
- public void onTaskFinished(Intent intent, int taskId) {
- // extras contain the info on what the activity started above did.
- // we probably could verify this also.
- Bundle extras = intent.getExtras();
- Log.i(TAG, "in onTaskFinished: testcasetype = " + mTestType + ", intent: " +
- intent.toString() + Utils.toBundleString(extras));
- Intent broadcastIntent = new Intent(Utils.BROADCAST_INTENT + mTestType.toString());
- broadcastIntent.putExtra(Utils.TEST_RESULT, mTestResult);
- broadcastIntent.putExtra(Utils.TESTCASE_TYPE, mTestType.toString());
- Log.i(TAG, "sending_broadcast for testcase+type = " +
- broadcastIntent.getStringExtra(Utils.TESTCASE_TYPE) +
- ", test_result = " + broadcastIntent.getStringExtra(Utils.TEST_RESULT));
- mContext.sendBroadcast(broadcastIntent);
- }
-
- synchronized MyTask newTask() {
- MyTask t = new MyTask();
- mUsedTasks.add(t);
- return t;
- }
-
- @Override
- public void onRequestCompleteVoice(CompleteVoiceRequest request) {
- mTestResult = Utils.COMPLETION_RESULT;
- CharSequence prompt = request.getVoicePrompt().getVoicePromptAt(0);
- Log.i(TAG, "in Session testcasetype = " + mTestType +
- ", onRequestCompleteVoice recvd. message = " + prompt);
- AsyncTaskArg asyncTaskArg = new AsyncTaskArg().setRequest(request).setCompleteReq(true);
- newTask().execute(asyncTaskArg);
- }
-
- @Override
- public void onRequestAbortVoice(AbortVoiceRequest request) {
- mTestResult = Utils.ABORT_RESULT;
- AsyncTaskArg asyncTaskArg = new AsyncTaskArg().setRequest(request).setCompleteReq(false);
- Log.i(TAG, "in Session sending sendAbortResult for testcasetype = " + mTestType);
- newTask().execute(asyncTaskArg);
- }
-
- private class AsyncTaskArg {
- CompleteVoiceRequest mCompReq;
- AbortVoiceRequest mAbortReq;
- boolean isCompleteRequest = true;
-
- AsyncTaskArg setRequest(CompleteVoiceRequest r) {
- mCompReq = r;
- return this;
- }
-
- AsyncTaskArg setRequest(AbortVoiceRequest r) {
- mAbortReq = r;
- return this;
- }
-
- AsyncTaskArg setCompleteReq(boolean flag) {
- isCompleteRequest = flag;
- return this;
- }
- }
-
- private class MyTask extends AsyncTask<AsyncTaskArg, Void, Void> {
- @Override
- protected Void doInBackground(AsyncTaskArg... params) {
- AsyncTaskArg arg = params[0];
- Log.i(TAG, "in MyTask - doInBackground: testType = " +
- MainInteractionSession.this.mTestType);
- if (arg.isCompleteRequest) {
- arg.mCompReq.sendCompleteResult(new Bundle());
- } else {
- arg.mAbortReq.sendAbortResult(new Bundle());
- }
- return null;
- }
- }
-}
diff --git a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSessionService.java b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSessionService.java
deleted file mode 100644
index a83c115..0000000
--- a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainInteractionSessionService.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.alarmclock.service;
-
-import android.os.Bundle;
-import android.service.voice.VoiceInteractionSession;
-import android.service.voice.VoiceInteractionSessionService;
-
-public class MainInteractionSessionService extends VoiceInteractionSessionService {
- @Override
- public VoiceInteractionSession onNewSession(Bundle args) {
- return new MainInteractionSession(this);
- }
-}
diff --git a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainRecognitionService.java b/tests/tests/alarmclock/service/src/android/alarmclock/service/MainRecognitionService.java
deleted file mode 100644
index 15a32d4..0000000
--- a/tests/tests/alarmclock/service/src/android/alarmclock/service/MainRecognitionService.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.alarmclock.service;
-
-import android.content.Intent;
-import android.speech.RecognitionService;
-import android.util.Log;
-
-/**
- * Stub recognition service needed to be a complete voice interactor.
- */
-public class MainRecognitionService extends RecognitionService {
- private static final String TAG = "MainRecognitionService";
-
- @Override
- public void onCreate() {
- super.onCreate();
- Log.i(TAG, "onCreate");
- }
-
- @Override
- protected void onStartListening(Intent recognizerIntent, Callback listener) {
- Log.i(TAG, "onStartListening");
- }
-
- @Override
- protected void onCancel(Callback listener) {
- Log.i(TAG, "onCancel");
- }
-
- @Override
- protected void onStopListening(Callback listener) {
- Log.i(TAG, "onStopListening");
- }
-
- @Override
- public void onDestroy() {
- super.onDestroy();
- Log.i(TAG, "onDestroy");
- }
-}
diff --git a/tests/tests/alarmclock/service/src/android/alarmclock/service/VoiceInteractionMain.java b/tests/tests/alarmclock/service/src/android/alarmclock/service/VoiceInteractionMain.java
deleted file mode 100644
index c1f23a0..0000000
--- a/tests/tests/alarmclock/service/src/android/alarmclock/service/VoiceInteractionMain.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.alarmclock.service;
-
-import android.alarmclock.common.Utils;
-import android.app.Activity;
-import android.content.Intent;
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.util.Log;
-
-public class VoiceInteractionMain extends Activity {
- static final String TAG = "VoiceInteractionMain";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Intent intent = new Intent();
- String testCaseType = getIntent().getStringExtra(Utils.TESTCASE_TYPE);
- Log.i(TAG, "received_testcasetype = " + testCaseType);
- intent.putExtra(Utils.TESTCASE_TYPE, testCaseType);
- intent.setComponent(new ComponentName(this, MainInteractionService.class));
- ComponentName serviceName = startService(intent);
- Log.i(TAG, "Started service: " + serviceName);
- }
-}
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockTestBase.java b/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockTestBase.java
deleted file mode 100644
index 69f19b9..0000000
--- a/tests/tests/alarmclock/src/android/alarmclock/cts/AlarmClockTestBase.java
+++ /dev/null
@@ -1,163 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.alarmclock.cts;
-
-import android.alarmclock.common.Utils;
-import android.alarmclock.common.Utils.TestcaseType;
-import android.content.BroadcastReceiver;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.pm.PackageManager;
-import android.provider.AlarmClock;
-import android.os.Bundle;
-import android.test.ActivityInstrumentationTestCase2;
-import android.util.Log;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
-public class AlarmClockTestBase extends ActivityInstrumentationTestCase2<TestStartActivity> {
- static final String TAG = "AlarmClockTestBase";
- protected static final int TIMEOUT_MS = 20 * 1000;
-
- private Context mContext;
- private String mTestResult;
- private CountDownLatch mLatch;
- private ActivityDoneReceiver mActivityDoneReceiver = null;
- private TestStartActivity mActivity;
- private TestcaseType mTestCaseType;
-
- public AlarmClockTestBase() {
- super(TestStartActivity.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mContext = getInstrumentation().getTargetContext();
- }
-
- @Override
- protected void tearDown() throws Exception {
- if (mActivityDoneReceiver != null) {
- try {
- mContext.unregisterReceiver(mActivityDoneReceiver);
- } catch (IllegalArgumentException e) {
- // This exception is thrown if mActivityDoneReceiver in
- // the above call to unregisterReceiver is never registered.
- // If so, no harm done by ignoring this exception.
- }
- mActivityDoneReceiver = null;
- }
- super.tearDown();
- }
-
- private void registerBroadcastReceiver(TestcaseType testCaseType) throws Exception {
- mTestCaseType = testCaseType;
- mLatch = new CountDownLatch(1);
- mActivityDoneReceiver = new ActivityDoneReceiver();
- mContext.registerReceiver(mActivityDoneReceiver,
- new IntentFilter(Utils.BROADCAST_INTENT + testCaseType.toString()));
- }
-
- private boolean isIntentSupported(TestcaseType testCaseType) {
- final PackageManager manager = mContext.getPackageManager();
- assertNotNull(manager);
- // If TV then not supported.
- if (manager.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)) {
- return false;
- }
- Intent intent;
- switch (testCaseType) {
- case DISMISS_ALARM:
- intent = new Intent(AlarmClock.ACTION_DISMISS_ALARM);
- break;
-
- case DISMISS_TIMER:
- intent = new Intent(AlarmClock.ACTION_DISMISS_TIMER);
- break;
-
- case SET_ALARM:
- case SET_ALARM_FOR_DISMISSAL:
- intent = new Intent(AlarmClock.ACTION_SET_ALARM);
- break;
-
- case SET_TIMER_FOR_DISMISSAL:
- intent = new Intent(AlarmClock.ACTION_SET_TIMER);
- break;
-
- case SNOOZE_ALARM:
- intent = new Intent(AlarmClock.ACTION_SNOOZE_ALARM);
- break;
-
- default:
- // shouldn't happen
- return false;
- }
- if (manager.resolveActivity(intent, 0) == null) {
- Log.i(TAG, "No Voice Activity found for the intent: " + intent.getAction());
- return false;
- }
- return true;
- }
-
- protected String runTest(TestcaseType testCaseType) throws Exception {
- Log.i(TAG, "Begin Testing: " + testCaseType);
- // Make sure the corresponding intent is supported by the platform, before testing.
- if (!isIntentSupported(testCaseType)) return Utils.COMPLETION_RESULT;
-
- if (!startTestActivity(testCaseType)) {
- fail("test activity start failed for testcase = " + testCaseType);
- return "";
- }
-
- registerBroadcastReceiver(testCaseType);
- mActivity.startTest(testCaseType.toString());
- if (!mLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
- fail("Failed to receive broadcast in " + TIMEOUT_MS + "msec");
- return "";
- }
- return mTestResult;
- }
-
- private boolean startTestActivity(TestcaseType testCaseType) {
- Log.i(TAG, "Starting test activity for test: " + testCaseType);
- Intent intent = new Intent();
- intent.setAction("android.intent.action.TEST_START_ACTIVITY_" + testCaseType.toString());
- intent.setComponent(new ComponentName(getInstrumentation().getContext(),
- TestStartActivity.class));
- setActivityIntent(intent);
- mActivity = getActivity();
- return mActivity != null;
- }
-
- class ActivityDoneReceiver extends BroadcastReceiver {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (intent.getAction().equals(
- Utils.BROADCAST_INTENT + AlarmClockTestBase.this.mTestCaseType.toString())) {
- AlarmClockTestBase.this.mTestResult = intent.getStringExtra(Utils.TEST_RESULT);
- Log.i(TAG, "received_broadcast for testcase_type = " +
- Utils.BROADCAST_INTENT + AlarmClockTestBase.this.mTestCaseType +
- ", test_result = " + AlarmClockTestBase.this.mTestResult);
- mLatch.countDown();
- }
- }
- }
-}
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/DismissAlarmTest.java b/tests/tests/alarmclock/src/android/alarmclock/cts/DismissAlarmTest.java
deleted file mode 100644
index 64ecf86..0000000
--- a/tests/tests/alarmclock/src/android/alarmclock/cts/DismissAlarmTest.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.alarmclock.cts;
-
-import android.alarmclock.common.Utils;
-import android.alarmclock.common.Utils.TestcaseType;
-
-public class DismissAlarmTest extends AlarmClockTestBase {
- public DismissAlarmTest() {
- super();
- }
-
- public void testAll() throws Exception {
- assertEquals(Utils.COMPLETION_RESULT, runTest(TestcaseType.SET_ALARM_FOR_DISMISSAL));
- assertEquals(Utils.COMPLETION_RESULT, runTest(TestcaseType.DISMISS_ALARM));
- }
-}
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/DismissTimerTest.java b/tests/tests/alarmclock/src/android/alarmclock/cts/DismissTimerTest.java
deleted file mode 100644
index a4c0d0a..0000000
--- a/tests/tests/alarmclock/src/android/alarmclock/cts/DismissTimerTest.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package android.alarmclock.cts;
-
-import android.alarmclock.common.Utils;
-import android.content.Context;
-import android.media.AudioManager;
-
-public class DismissTimerTest extends AlarmClockTestBase {
-
- private int mSavedVolume;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- // The timer may ring between expiration and dismissal; silence this.
- final AudioManager audioManager = (AudioManager) getInstrumentation().getTargetContext()
- .getSystemService(Context.AUDIO_SERVICE);
- mSavedVolume = audioManager.getStreamVolume(AudioManager.STREAM_ALARM);
- audioManager.setStreamVolume(AudioManager.STREAM_ALARM, 0, 0);
- }
-
- @Override
- protected void tearDown() throws Exception {
- super.tearDown();
- final AudioManager audioManager = (AudioManager) getInstrumentation().getTargetContext()
- .getSystemService(Context.AUDIO_SERVICE);
- audioManager.setStreamVolume(AudioManager.STREAM_ALARM, mSavedVolume, 0);
- }
-
- public void testAll() throws Exception {
- assertEquals(Utils.COMPLETION_RESULT, runTest(Utils.TestcaseType.SET_TIMER_FOR_DISMISSAL));
- try {
- Thread.sleep(5000);
- } catch (InterruptedException ignored) {
- }
- assertEquals(Utils.COMPLETION_RESULT, runTest(Utils.TestcaseType.DISMISS_TIMER));
- }
-}
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/SetAlarmTest.java b/tests/tests/alarmclock/src/android/alarmclock/cts/SetAlarmTest.java
deleted file mode 100644
index a95a1dd..0000000
--- a/tests/tests/alarmclock/src/android/alarmclock/cts/SetAlarmTest.java
+++ /dev/null
@@ -1,30 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.alarmclock.cts;
-
-import android.alarmclock.common.Utils;
-import android.alarmclock.common.Utils.TestcaseType;
-
-public class SetAlarmTest extends AlarmClockTestBase {
- public SetAlarmTest() {
- super();
- }
-
- public void testAll() throws Exception {
- assertEquals(Utils.COMPLETION_RESULT, runTest(TestcaseType.SET_ALARM));
- }
-}
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/SnoozeAlarmTest.java b/tests/tests/alarmclock/src/android/alarmclock/cts/SnoozeAlarmTest.java
deleted file mode 100644
index b67a7cf..0000000
--- a/tests/tests/alarmclock/src/android/alarmclock/cts/SnoozeAlarmTest.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.alarmclock.cts;
-
-import android.alarmclock.common.Utils;
-import android.alarmclock.common.Utils.TestcaseType;
-
-public class SnoozeAlarmTest extends AlarmClockTestBase {
- public SnoozeAlarmTest() {
- super();
- }
-
- public void testAll() throws Exception {
- String result = runTest(TestcaseType.SNOOZE_ALARM);
- // Since there is no way to figure out if there is a currently-firing alarm,
- // we should expect either of the 2 results:
- // Utils.COMPLETION_RESULT
- // Utils.ABORT_RESULT
- // For CTS test purposes, all we can do is to know that snooze-alarm intent
- // was picked up and processed by the voice_interaction_service and the associated app.
- // The above call to runTest() would have failed the test otherwise.
- assertTrue(result.equals(Utils.ABORT_RESULT) || result.equals(Utils.COMPLETION_RESULT));
- }
-}
diff --git a/tests/tests/alarmclock/src/android/alarmclock/cts/TestStartActivity.java b/tests/tests/alarmclock/src/android/alarmclock/cts/TestStartActivity.java
deleted file mode 100644
index 1a3c4eb..0000000
--- a/tests/tests/alarmclock/src/android/alarmclock/cts/TestStartActivity.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.alarmclock.cts;
-
-import android.alarmclock.common.Utils;
-import android.app.Activity;
-import android.content.Intent;
-import android.content.ComponentName;
-import android.os.Bundle;
-import android.util.Log;
-
-public class TestStartActivity extends Activity {
- static final String TAG = "TestStartActivity";
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- Log.i(TAG, " in onCreate");
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- Log.i(TAG, " in onResume");
- }
-
- void startTest(String testCaseType) {
- Intent intent = new Intent();
- Log.i(TAG, "received_testcasetype = " + testCaseType);
- intent.putExtra(Utils.TESTCASE_TYPE, testCaseType);
- intent.setAction("android.intent.action.VIMAIN_" + testCaseType);
- intent.setComponent(new ComponentName("android.alarmclock.service",
- "android.alarmclock.service.VoiceInteractionMain"));
- startActivity(intent);
- }
-
- @Override
- protected void onPause() {
- Log.i(TAG, " in onPause");
- super.onPause();
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- Log.i(TAG, " in onStart");
- }
-
- @Override
- protected void onRestart() {
- super.onRestart();
- Log.i(TAG, " in onRestart");
- }
-
- @Override
- protected void onStop() {
- Log.i(TAG, " in onStop");
- super.onStop();
- }
-
- @Override
- protected void onDestroy() {
- Log.i(TAG, " in onDestroy");
- super.onDestroy();
- }
-}
diff --git a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
index 59451cb..fa68b28 100644
--- a/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
+++ b/tests/tests/content/src/android/content/cts/AvailableIntentsTest.java
@@ -271,14 +271,25 @@
assertCanBeHandled(intent);
}
- public void testAlarmClockSetTimer() {
- Intent intent = new Intent(AlarmClock.ACTION_SET_TIMER);
- intent.putExtra(AlarmClock.EXTRA_LENGTH, 60000);
+ public void testAlarmClockShowAlarms() {
+ Intent intent = new Intent(AlarmClock.ACTION_SHOW_ALARMS);
assertCanBeHandled(intent);
}
- public void testAlarmClockShowAlarms() {
- Intent intent = new Intent(AlarmClock.ACTION_SHOW_ALARMS);
+ public void testAlarmClockDismissAlarm() {
+ Intent intent = new Intent(AlarmClock.ACTION_DISMISS_ALARM);
+ assertCanBeHandled(intent);
+ }
+
+ public void testAlarmClockSnoozeAlarm() {
+ Intent intent = new Intent(AlarmClock.ACTION_SNOOZE_ALARM);
+ intent.putExtra(AlarmClock.EXTRA_ALARM_SNOOZE_DURATION, 10);
+ assertCanBeHandled(intent);
+ }
+
+ public void testAlarmClockSetTimer() {
+ Intent intent = new Intent(AlarmClock.ACTION_SET_TIMER);
+ intent.putExtra(AlarmClock.EXTRA_LENGTH, 60000);
assertCanBeHandled(intent);
}
diff --git a/tests/tests/database/src/android/database/cts/AbstractCursorTest.java b/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
index 9a4ba50..c342e98 100644
--- a/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
+++ b/tests/tests/database/src/android/database/cts/AbstractCursorTest.java
@@ -27,6 +27,7 @@
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.Bundle;
+import android.platform.test.annotations.AppModeFull;
import android.provider.Settings;
import android.test.InstrumentationTestCase;
@@ -166,6 +167,7 @@
observer.waitForOnChange(ON_CHANGE_TIMEOUT_MS);
}
+ @AppModeFull
public void testSetNotificationsUris() throws Exception {
final Uri queryUri = Uri.parse("content://com.android.cts.providerapp");
try (Cursor cursor = mContext.getContentResolver().query(queryUri, null, null, null)) {
diff --git a/tests/tests/display/src/android/display/cts/BrightnessTest.java b/tests/tests/display/src/android/display/cts/BrightnessTest.java
index a4e3aa3..7b207b9 100644
--- a/tests/tests/display/src/android/display/cts/BrightnessTest.java
+++ b/tests/tests/display/src/android/display/cts/BrightnessTest.java
@@ -106,6 +106,7 @@
// Setup and remember some initial state.
recordSliderEvents();
+ waitForFirstSliderEvent();
setSystemSetting(Settings.System.SCREEN_BRIGHTNESS, 20);
getNewEvents(1);
@@ -289,6 +290,7 @@
// Setup and remember some initial state.
recordSliderEvents();
+ waitForFirstSliderEvent();
setSystemSetting(Settings.System.SCREEN_BRIGHTNESS, 20);
getNewEvents(1);
@@ -379,11 +381,26 @@
private void recordSliderEvents() {
mLastReadEvents = new HashMap<>();
List<BrightnessChangeEvent> eventsBefore = mDisplayManager.getBrightnessEvents();
- for (BrightnessChangeEvent event: eventsBefore) {
+ for (BrightnessChangeEvent event : eventsBefore) {
mLastReadEvents.put(event.timeStamp, event);
}
}
+ private void waitForFirstSliderEvent() throws InterruptedException {
+ // Keep changing brightness until we get an event to handle devices with sensors
+ // that take a while to warm up.
+ int brightness = 25;
+ for (int i = 0; i < 20; ++i) {
+ setSystemSetting(Settings.System.SCREEN_BRIGHTNESS, brightness);
+ brightness = brightness == 25 ? 80 : 25;
+ Thread.sleep(100);
+ if (!getNewEvents().isEmpty()) {
+ return;
+ }
+ }
+ fail("Failed to fetch first slider event. Is the ambient brightness sensor working?");
+ }
+
private int getSystemSetting(String setting) {
return Integer.parseInt(runShellCommand("settings get system " + setting));
}
diff --git a/tests/tests/jni/libjnitest/Android.mk b/tests/tests/jni/libjnitest/Android.mk
index 351c7f9..d2db74d 100644
--- a/tests/tests/jni/libjnitest/Android.mk
+++ b/tests/tests/jni/libjnitest/Android.mk
@@ -42,7 +42,7 @@
LOCAL_SHARED_LIBRARIES := libdl liblog libnativehelper_compat_libc++
-LOCAL_SDK_VERSION := 23
+LOCAL_SDK_VERSION := current
LOCAL_NDK_STL_VARIANT := c++_static
LOCAL_CFLAGS := -Wall -Werror -Wno-unused-parameter
diff --git a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
index 993c86e..643af4c 100644
--- a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
+++ b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
@@ -34,6 +34,7 @@
#include <unordered_set>
#include <vector>
+#include <android-base/properties.h>
#include <android-base/strings.h>
#include <nativehelper/JNIHelp.h>
#include <nativehelper/ScopedLocalRef.h>
@@ -172,8 +173,9 @@
const std::string& path,
const std::unordered_set<std::string>& library_search_paths,
const std::unordered_set<std::string>& public_library_basenames,
- std::vector<std::string>* errors,
- bool test_system_load_library) {
+ bool test_system_load_library,
+ bool check_absence,
+ /*out*/ std::vector<std::string>* errors) {
std::string err = load_library(env, clazz, path, test_system_load_library);
bool loaded = err.empty();
@@ -204,7 +206,7 @@
// If the library loaded successfully but is in a subdirectory then it is
// still not public. That is the case e.g. for
// /apex/com.android.runtime/lib{,64}/bionic/lib*.so.
- if (loaded && is_in_search_path) {
+ if (loaded && is_in_search_path && check_absence) {
errors->push_back("The library \"" + path + "\" is not a public library but it loaded.");
return false;
}
@@ -232,9 +234,9 @@
const std::string& library_path,
const std::unordered_set<std::string>& library_search_paths,
const std::unordered_set<std::string>& public_library_basenames,
- std::vector<std::string>* errors,
- bool test_system_load_library) {
-
+ bool test_system_load_library,
+ bool check_absence,
+ /*out*/ std::vector<std::string>* errors) {
bool success = true;
std::queue<std::string> dirs;
dirs.push(library_path);
@@ -265,7 +267,7 @@
if (is_directory(path.c_str())) {
dirs.push(path);
} else if (!check_lib(env, clazz, path, library_search_paths, public_library_basenames,
- errors, test_system_load_library)) {
+ test_system_load_library, check_absence, errors)) {
success = false;
}
}
@@ -274,16 +276,6 @@
return success;
}
-static bool check_path(JNIEnv* env,
- jclass clazz,
- const std::string& library_path,
- const std::unordered_set<std::string>& library_search_paths,
- const std::unordered_set<std::string>& public_library_basenames,
- std::vector<std::string>* errors) {
- return check_path(env, clazz, library_path, library_search_paths, public_library_basenames,
- errors, /*test_system_load_library=*/true);
-}
-
static bool jobject_array_to_set(JNIEnv* env,
jobjectArray java_libraries_array,
std::unordered_set<std::string>* libraries,
@@ -402,31 +394,38 @@
system_library_search_paths.insert(kRuntimeApexLibraryPath);
if (!check_path(env, clazz, kSystemLibraryPath, system_library_search_paths,
- system_public_libraries, &errors)) {
+ system_public_libraries,
+ /*test_system_load_library=*/false, /*check_absence=*/true, &errors)) {
success = false;
}
+ // Pre-Treble devices use ld.config.vndk_lite.txt, where the default namespace
+ // isn't isolated. That means it can successfully load libraries in /apex, so
+ // don't complain about that in that case.
+ bool check_absence = !android::base::GetBoolProperty("ro.vndk.lite", false);
+
// Check the runtime libraries.
- if (!check_path(env, clazz, kRuntimeApexLibraryPath,
- { kRuntimeApexLibraryPath },
- runtime_public_libraries, &errors,
+ if (!check_path(env, clazz, kRuntimeApexLibraryPath, {kRuntimeApexLibraryPath},
+ runtime_public_libraries,
// System.loadLibrary("icuuc") would fail since a copy exists in /system.
- // TODO(b/124218500): Remove it when the bug is resolved.
- /*test_system_load_library=*/false)) {
+ // TODO(b/124218500): Change to true when the bug is resolved.
+ /*test_system_load_library=*/false,
+ check_absence, &errors)) {
success = false;
}
// Check the product libraries, if /product/lib exists.
if (is_directory(kProductLibraryPath.c_str())) {
- if (!check_path(env, clazz, kProductLibraryPath, { kProductLibraryPath },
- product_public_libraries, &errors)) {
+ if (!check_path(env, clazz, kProductLibraryPath, {kProductLibraryPath},
+ product_public_libraries,
+ /*test_system_load_library=*/false, /*check_absence=*/true, &errors)) {
success = false;
}
}
// Check the vendor libraries.
- if (!check_path(env, clazz, kVendorLibraryPath, { kVendorLibraryPath },
- vendor_public_libraries, &errors)) {
+ if (!check_path(env, clazz, kVendorLibraryPath, {kVendorLibraryPath}, vendor_public_libraries,
+ /*test_system_load_library=*/false, /*check_absence=*/true, &errors)) {
success = false;
}
diff --git a/tests/tests/media/AndroidManifest.xml b/tests/tests/media/AndroidManifest.xml
index 69f6c89..2ab1f43 100644
--- a/tests/tests/media/AndroidManifest.xml
+++ b/tests/tests/media/AndroidManifest.xml
@@ -41,6 +41,9 @@
<uses-library android:name="android.test.runner" />
<uses-library android:name="org.apache.http.legacy" android:required="false" />
+ <activity android:name="android.media.cts.AudioPlaybackCaptureActivity"
+ android:label="AudioPlaybackCaptureActivity"
+ android:screenOrientation="locked"/>
<activity android:name="android.media.cts.AudioManagerStub"
android:label="AudioManagerStub"/>
<activity android:name="android.media.cts.AudioManagerStubHelper"
diff --git a/tests/tests/media/AndroidTest.xml b/tests/tests/media/AndroidTest.xml
index e8c7b5d..ed2d0d9 100644
--- a/tests/tests/media/AndroidTest.xml
+++ b/tests/tests/media/AndroidTest.xml
@@ -43,6 +43,7 @@
<option name="test-timeout" value="1800000" />
<option name="runtime-hint" value="4h" />
<option name="hidden-api-checks" value="false" />
+ <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
<option name="isolated-storage" value="false" />
</test>
</configuration>
diff --git a/tests/tests/media/res/raw/cp1251_3_a_ms_acm_mp3.mkv b/tests/tests/media/res/raw/cp1251_3_a_ms_acm_mp3.mkv
new file mode 100644
index 0000000..c06a542
--- /dev/null
+++ b/tests/tests/media/res/raw/cp1251_3_a_ms_acm_mp3.mkv
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureActivity.java b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureActivity.java
new file mode 100644
index 0000000..5f0012d
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureActivity.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.cts;
+
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.media.projection.MediaProjection;
+import android.media.projection.MediaProjectionManager;
+import android.os.Bundle;
+import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.UiDevice;
+import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+
+// This is a partial copy of android.view.cts.surfacevalidator.CapturedActivity.
+// Common code should be move in a shared library
+/** Start this activity to retrieve a MediaProjection through waitForMediaProjection() */
+public class AudioPlaybackCaptureActivity extends Activity {
+ private static final String TAG = "AudioPlaybackCaptureActivity";
+ private static final int PERMISSION_CODE = 1;
+ private static final int PERMISSION_DIALOG_WAIT_MS = 1000;
+ private static final String ACCEPT_RESOURCE_ID = "android:id/button1";
+
+ private MediaProjectionManager mProjectionManager;
+ private MediaProjection mMediaProjection;
+ private CountDownLatch mCountDownLatch;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mProjectionManager = getSystemService(MediaProjectionManager.class);
+ mCountDownLatch = new CountDownLatch(1);
+ startActivityForResult(mProjectionManager.createScreenCaptureIntent(), PERMISSION_CODE);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ if (requestCode != PERMISSION_CODE) {
+ throw new IllegalStateException("Unknown request code: " + requestCode);
+ }
+ if (resultCode != RESULT_OK) {
+ throw new IllegalStateException("User denied screen sharing permission");
+ }
+ Log.d(TAG, "onActivityResult");
+ mMediaProjection = mProjectionManager.getMediaProjection(resultCode, data);
+ mCountDownLatch.countDown();
+ }
+
+ public MediaProjection waitForMediaProjection() throws Exception {
+ final long timeOutMs = 125000;
+ final int retryCount = 2;
+ int count = 0;
+ // Sometimes system decides to rotate the permission activity to another orientation
+ // right after showing it. This results in: uiautomation thinks that accept button appears,
+ // we successfully click it in terms of uiautomation, but nothing happens,
+ // because permission activity is already recreated.
+ // Thus, we try to click that button multiple times.
+ do {
+ assertTrue("Can't get the permission", count <= retryCount);
+ dismissPermissionDialog();
+ count++;
+ } while (!mCountDownLatch.await(timeOutMs, TimeUnit.MILLISECONDS));
+ return mMediaProjection;
+ }
+
+ public void dismissPermissionDialog() {
+ // The permission dialog will be auto-opened by the activity - find it and accept
+ UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+ UiObject2 acceptButton = uiDevice.wait(Until.findObject(By.res(ACCEPT_RESOURCE_ID)),
+ PERMISSION_DIALOG_WAIT_MS);
+ if (acceptButton != null) {
+ Log.d(TAG, "found permission dialog after searching all windows, clicked");
+ acceptButton.click();
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ Log.i(TAG, "onResume");
+ super.onResume();
+ }
+
+ @Override
+ protected void onPause() {
+ Log.i(TAG, "onPause");
+ super.onPause();
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
new file mode 100644
index 0000000..352be37
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
+
+import android.media.AudioAttributes;
+import android.media.AudioAttributes.AttributeUsage;
+import android.media.AudioFormat;
+import android.media.AudioManager;
+import android.media.AudioPlaybackCaptureConfiguration;
+import android.media.AudioRecord;
+import android.media.MediaPlayer;
+import android.media.projection.MediaProjection;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.nio.ShortBuffer;
+
+/**
+ * Test audio playback capture through MediaProjection.
+ *
+ * The tests do the following:
+ * - retrieve a MediaProjection through AudioPlaybackCaptureActivity
+ * - play some audio
+ * - use that MediaProjection to record the audio playing
+ * - check that some audio was recorded.
+ *
+ * Currently the test that some audio was recorded just check that at least one sample is non 0.
+ * A better check needs to be used, eg: compare the power spectrum.
+ */
+public class AudioPlaybackCaptureTest {
+ private static final String TAG = "AudioPlaybackCaptureTest";
+ private static final int BUFFER_SIZE = 32768; // ~200ms at 44.1k 16b MONO
+
+ private AudioManager mAudioManager;
+ private boolean mPlaybackBeforeCapture;
+ private int mUid; //< UID of this test
+ private AudioPlaybackCaptureActivity mActivity;
+ private MediaProjection mMediaProjection;
+ @Rule
+ public ActivityTestRule<AudioPlaybackCaptureActivity> mActivityRule =
+ new ActivityTestRule<>(AudioPlaybackCaptureActivity.class);
+
+ private static class APCTestConfig {
+ public @AttributeUsage int[] matchingUsages;
+ public @AttributeUsage int[] excludeUsages;
+ public int[] matchingUids;
+ public int[] excludeUids;
+ private AudioPlaybackCaptureConfiguration build(MediaProjection projection)
+ throws Exception {
+ AudioPlaybackCaptureConfiguration.Builder apccBuilder =
+ new AudioPlaybackCaptureConfiguration.Builder(projection);
+
+ if (matchingUsages != null) {
+ for (int usage : matchingUsages) {
+ apccBuilder.addMatchingUsage(new AudioAttributes.Builder()
+ .setUsage(usage)
+ .build());
+ }
+ }
+ if (excludeUsages != null) {
+ for (int usage : excludeUsages) {
+ apccBuilder.excludeUsage(new AudioAttributes.Builder()
+ .setUsage(usage)
+ .build());
+ }
+ }
+ if (matchingUids != null) {
+ for (int uid : matchingUids) {
+ apccBuilder.addMatchingUid(uid);
+ }
+ }
+ if (excludeUids != null) {
+ for (int uid : excludeUids) {
+ apccBuilder.excludeUid(uid);
+ }
+ }
+ return apccBuilder.build();
+ }
+ };
+ private APCTestConfig mAPCTestConfig;
+
+ @Before
+ public void setup() throws Exception {
+ mPlaybackBeforeCapture = false;
+ mAPCTestConfig = new APCTestConfig();
+ mActivity = mActivityRule.getActivity();
+ mAudioManager = mActivity.getSystemService(AudioManager.class);
+ mUid = mActivity.getApplicationInfo().uid;
+ mMediaProjection = mActivity.waitForMediaProjection();
+ }
+
+ private AudioRecord createPlaybackCaptureRecord(AudioFormat audioFormat) throws Exception {
+ AudioPlaybackCaptureConfiguration apcConfig = mAPCTestConfig.build(mMediaProjection);
+
+ AudioRecord audioRecord = new AudioRecord.Builder()
+ .setAudioPlaybackCaptureConfig(apcConfig)
+ .setAudioFormat(audioFormat)
+ .build();
+ return audioRecord;
+ }
+
+ private MediaPlayer createMediaPlayer(boolean allowCapture, int resid,
+ @AttributeUsage int usage) {
+ MediaPlayer mediaPlayer = MediaPlayer.create(
+ mActivity,
+ resid,
+ new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
+ .setUsage(usage)
+ .setAllowCapture(allowCapture)
+ .build(),
+ mAudioManager.generateAudioSessionId());
+ mediaPlayer.setLooping(true);
+ return mediaPlayer;
+ }
+
+ private static ByteBuffer readToBuffer(AudioRecord audioRecord, int bufferSize)
+ throws Exception {
+ assertEquals(AudioRecord.RECORDSTATE_RECORDING, audioRecord.getRecordingState());
+ ByteBuffer buffer = ByteBuffer.allocateDirect(bufferSize);
+ int retry = 100;
+ while (buffer.hasRemaining()) {
+ assertNotSame(buffer.remaining() + "/" + bufferSize + "remaining", 0, retry--);
+ int written = audioRecord.read(buffer, buffer.remaining());
+ assertThat(written).isGreaterThan(0);
+ buffer.position(buffer.position() + written);
+ }
+ buffer.rewind();
+ return buffer;
+ }
+
+ private static boolean onlySilence(ShortBuffer buffer) {
+ boolean onlySilence = true;
+ while (buffer.hasRemaining()) {
+ onlySilence &= buffer.get() == 0;
+ }
+ return onlySilence;
+ }
+
+ public void testPlaybackCapture(boolean allowCapture,
+ @AttributeUsage int playbackUsage,
+ boolean dataPresent) throws Exception {
+ MediaPlayer mediaPlayer = createMediaPlayer(allowCapture, R.raw.testwav_16bit_44100hz,
+ playbackUsage);
+ if (mPlaybackBeforeCapture) {
+ mediaPlayer.start();
+ Thread.sleep(100); // Make sure the player is actually playing, thus forcing a rerouting
+ }
+
+ AudioRecord audioRecord = createPlaybackCaptureRecord(
+ new AudioFormat.Builder()
+ .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+ .setSampleRate(44100)
+ .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+ .build());
+
+ audioRecord.startRecording();
+ mediaPlayer.start();
+ ByteBuffer rawBuffer = readToBuffer(audioRecord, BUFFER_SIZE);
+ audioRecord.stop(); // Force an reroute
+ mediaPlayer.stop();
+ assertEquals(AudioRecord.RECORDSTATE_STOPPED, audioRecord.getRecordingState());
+ if (dataPresent) {
+ assertFalse("Expected data, but only silence was recorded",
+ onlySilence(rawBuffer.asShortBuffer()));
+ } else {
+ assertTrue("Expected silence, but some data was recorded",
+ onlySilence(rawBuffer.asShortBuffer()));
+ }
+ }
+
+ private static final boolean OPT_IN = true;
+ private static final boolean OPT_OUT = false;
+
+ private static final boolean EXPECT_DATA = true;
+ private static final boolean EXPECT_SILENCE = false;
+
+ private static final @AttributeUsage int[] ALLOWED_USAGES = new int[]{
+ AudioAttributes.USAGE_UNKNOWN,
+ AudioAttributes.USAGE_MEDIA,
+ AudioAttributes.USAGE_GAME
+ };
+ private static final @AttributeUsage int[] FORBIDEN_USAGES = new int[]{
+ AudioAttributes.USAGE_ALARM,
+ AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY,
+ AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE,
+ AudioAttributes.USAGE_ASSISTANCE_SONIFICATION,
+ AudioAttributes.USAGE_ASSISTANT,
+ AudioAttributes.USAGE_NOTIFICATION,
+ AudioAttributes.USAGE_VOICE_COMMUNICATION
+ };
+
+ @Presubmit
+ @Test
+ public void testPlaybackCaptureFast() throws Exception {
+ mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
+ testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_MEDIA, EXPECT_DATA);
+ testPlaybackCapture(OPT_OUT, AudioAttributes.USAGE_MEDIA, EXPECT_SILENCE);
+ }
+
+ @Presubmit
+ @Test
+ public void testPlaybackCaptureRerouting() throws Exception {
+ mPlaybackBeforeCapture = true;
+ mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
+ testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_MEDIA, EXPECT_DATA);
+ }
+
+ @Presubmit
+ @Test(expected = IllegalArgumentException.class)
+ public void testMatchNothing() throws Exception {
+ testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
+ }
+
+ @Presubmit
+ @Test(expected = IllegalStateException.class)
+ public void testCombineUsages() throws Exception {
+ mAPCTestConfig.matchingUsages = new int[]{ AudioAttributes.USAGE_UNKNOWN };
+ mAPCTestConfig.excludeUsages = new int[]{ AudioAttributes.USAGE_MEDIA };
+ testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
+ }
+
+ @Presubmit
+ @Test(expected = IllegalStateException.class)
+ public void testCombineUid() throws Exception {
+ mAPCTestConfig.matchingUids = new int[]{ mUid };
+ mAPCTestConfig.excludeUids = new int[]{ 0 };
+ testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
+ }
+
+ @Test
+ public void testCaptureMatchingAllowedUsage() throws Exception {
+ for (int usage : ALLOWED_USAGES) {
+ mAPCTestConfig.matchingUsages = new int[]{ usage };
+ testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
+ testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
+
+ mAPCTestConfig.matchingUsages = ALLOWED_USAGES;
+ testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
+ testPlaybackCapture(OPT_OUT, usage, EXPECT_SILENCE);
+ }
+ }
+
+ @Test
+ public void testCaptureMatchingForbidenUsage() throws Exception {
+ for (int usage : FORBIDEN_USAGES) {
+ mAPCTestConfig.matchingUsages = new int[]{ usage };
+ testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
+
+ mAPCTestConfig.matchingUsages = ALLOWED_USAGES;
+ testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
+ }
+ }
+
+ @Test
+ public void testCaptureExcludeUsage() throws Exception {
+ for (int usage : ALLOWED_USAGES) {
+ mAPCTestConfig.excludeUsages = new int[]{ usage };
+ testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
+
+ mAPCTestConfig.excludeUsages = ALLOWED_USAGES;
+ testPlaybackCapture(OPT_IN, usage, EXPECT_SILENCE);
+
+ mAPCTestConfig.excludeUsages = FORBIDEN_USAGES;
+ testPlaybackCapture(OPT_IN, usage, EXPECT_DATA);
+ }
+ }
+
+ @Test
+ public void testCaptureMatchingUid() throws Exception {
+ mAPCTestConfig.matchingUids = new int[]{ mUid };
+ testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_DATA);
+ testPlaybackCapture(OPT_OUT, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
+ testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_VOICE_COMMUNICATION, EXPECT_SILENCE);
+
+ mAPCTestConfig.matchingUids = new int[]{ 0 };
+ testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
+ }
+
+ @Test
+ public void testCaptureExcludeUid() throws Exception {
+ mAPCTestConfig.excludeUids = new int[]{ 0 };
+ testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_DATA);
+ testPlaybackCapture(OPT_OUT, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
+ testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_VOICE_COMMUNICATION, EXPECT_SILENCE);
+
+ mAPCTestConfig.excludeUids = new int[]{ mUid };
+ testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
+ }
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioPresentationTest.java b/tests/tests/media/src/android/media/cts/AudioPresentationTest.java
index f9df727..2e900ad 100644
--- a/tests/tests/media/src/android/media/cts/AudioPresentationTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPresentationTest.java
@@ -35,7 +35,7 @@
public void testGetters() throws Exception {
final int PRESENTATION_ID = 42;
final int PROGRAM_ID = 43;
- final Map<Locale, String> LABELS = generateLabels();
+ final Map<Locale, CharSequence> LABELS = generateLabels();
final Locale LOCALE = Locale.US;
final int MASTERING_INDICATION = AudioPresentation.MASTERED_FOR_STEREO;
final boolean HAS_AUDIO_DESCRIPTION = false;
@@ -63,7 +63,7 @@
public void testEqualsAndHashCode() throws Exception {
final int PRESENTATION_ID = 42;
final int PROGRAM_ID = 43;
- final Map<Locale, String> LABELS = generateLabels();
+ final Map<Locale, CharSequence> LABELS = generateLabels();
final Locale LOCALE = Locale.US;
final Locale LOCALE_3 = Locale.FRENCH;
final int MASTERING_INDICATION = AudioPresentation.MASTERED_FOR_STEREO;
@@ -123,7 +123,7 @@
assertEquals(presentation2, presentation1);
assertEquals(presentation1.hashCode(), presentation2.hashCode());
AudioPresentation presentation3 = (new AudioPresentation.Builder(PRESENTATION_ID)
- .setLabels(new HashMap<ULocale, String>())).build();
+ .setLabels(new HashMap<ULocale, CharSequence>())).build();
assertNotEquals(presentation1, presentation3);
assertNotEquals(presentation1.hashCode(), presentation3.hashCode());
}
@@ -181,17 +181,17 @@
}
}
- private static Map<Locale, String> generateLabels() {
- Map<Locale, String> result = new HashMap<Locale, String>();
+ private static Map<Locale, CharSequence> generateLabels() {
+ Map<Locale, CharSequence> result = new HashMap<Locale, CharSequence>();
result.put(Locale.US, Locale.US.getDisplayLanguage());
result.put(Locale.FRENCH, Locale.FRENCH.getDisplayLanguage());
result.put(Locale.GERMAN, Locale.GERMAN.getDisplayLanguage());
return result;
}
- private static Map<ULocale, String> localeToULocale(Map<Locale, String> locales) {
- Map<ULocale, String> ulocaleLabels = new HashMap<ULocale, String>();
- for (Map.Entry<Locale, String> entry : locales.entrySet()) {
+ private static Map<ULocale, CharSequence> localeToULocale(Map<Locale, CharSequence> locales) {
+ Map<ULocale, CharSequence> ulocaleLabels = new HashMap<ULocale, CharSequence>();
+ for (Map.Entry<Locale, CharSequence> entry : locales.entrySet()) {
ulocaleLabels.put(ULocale.forLocale(entry.getKey()), entry.getValue());
}
return ulocaleLabels;
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index c54437e..d79d828 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -1630,6 +1630,10 @@
R.raw.video_176x144_3gp_h263_300kbps_25fps_aac_stereo_128kbps_22050hz, 176, 144);
}
+ public void testLocalVideo_cp1251_3_a_ms_acm_mp3() throws Exception {
+ playVideoTest(R.raw.cp1251_3_a_ms_acm_mp3, -1, -1);
+ }
+
private void readSubtitleTracks() throws Exception {
mSubtitleTrackIndex.clear();
MediaPlayer.TrackInfo[] trackInfos = mMediaPlayer.getTrackInfo();
diff --git a/tests/tests/media/src/android/media/cts/VideoEncoderTest.java b/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
index 818c95d..55f1608 100644
--- a/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
+++ b/tests/tests/media/src/android/media/cts/VideoEncoderTest.java
@@ -40,6 +40,8 @@
import android.util.Size;
import android.view.Surface;
+import androidx.test.filters.SmallTest;
+
import com.android.compatibility.common.util.MediaUtils;
import java.io.File;
@@ -1470,7 +1472,9 @@
public void testGoogH265FlexQCIF() { specific(googH265(), 176, 144, true /* flex */); }
public void testGoogH265SurfQCIF() { specific(googH265(), 176, 144, false /* flex */); }
+ @SmallTest
public void testGoogH264FlexQCIF() { specific(googH264(), 176, 144, true /* flex */); }
+ @SmallTest
public void testGoogH264SurfQCIF() { specific(googH264(), 176, 144, false /* flex */); }
public void testGoogH263FlexQCIF() { specific(googH263(), 176, 144, true /* flex */); }
public void testGoogH263SurfQCIF() { specific(googH263(), 176, 144, false /* flex */); }
@@ -1483,7 +1487,9 @@
public void testOtherH265FlexQCIF() { specific(otherH265(), 176, 144, true /* flex */); }
public void testOtherH265SurfQCIF() { specific(otherH265(), 176, 144, false /* flex */); }
+ @SmallTest
public void testOtherH264FlexQCIF() { specific(otherH264(), 176, 144, true /* flex */); }
+ @SmallTest
public void testOtherH264SurfQCIF() { specific(otherH264(), 176, 144, false /* flex */); }
public void testOtherH263FlexQCIF() { specific(otherH263(), 176, 144, true /* flex */); }
public void testOtherH263SurfQCIF() { specific(otherH263(), 176, 144, false /* flex */); }
diff --git a/tests/tests/mediastress/AndroidTest.xml b/tests/tests/mediastress/AndroidTest.xml
index 9bf90f8..0255cfb 100644
--- a/tests/tests/mediastress/AndroidTest.xml
+++ b/tests/tests/mediastress/AndroidTest.xml
@@ -38,5 +38,7 @@
<option name="test-timeout" value="1800000" />
<option name="ajur-max-shard" value="2" />
<option name="runtime-hint" value="3h" />
+ <!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
+ <option name="isolated-storage" value="false" />
</test>
</configuration>
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
index cf8bcff..927f810 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio.cpp
@@ -62,11 +62,12 @@
};
void AAudioInputStreamTest::SetUp() {
+ mSetupSuccesful = false;
+ if (!deviceSupportsFeature(FEATURE_RECORDING)) return;
mHelper.reset(new InputStreamBuilderHelper(
std::get<PARAM_SHARING_MODE>(GetParam()),
std::get<PARAM_PERF_MODE>(GetParam())));
mHelper->initBuilder();
- mSetupSuccesful = false;
mHelper->createAndVerifyStream(&mSetupSuccesful);
if (!mSetupSuccesful) return;
@@ -172,12 +173,13 @@
};
void AAudioOutputStreamTest::SetUp() {
+ mSetupSuccesful = false;
+ if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
mHelper.reset(new OutputStreamBuilderHelper(
std::get<PARAM_SHARING_MODE>(GetParam()),
std::get<PARAM_PERF_MODE>(GetParam())));
mHelper->initBuilder();
- mSetupSuccesful = false;
mHelper->createAndVerifyStream(&mSetupSuccesful);
if (!mSetupSuccesful) return;
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp
index 4e4d022..30999aa 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio_attributes.cpp
@@ -22,6 +22,8 @@
#include <aaudio/AAudio.h>
#include <gtest/gtest.h>
+#include "utils.h"
+
constexpr int64_t kNanosPerSecond = 1000000000;
constexpr int kNumFrames = 256;
constexpr int kChannelCount = 2;
@@ -33,6 +35,10 @@
aaudio_content_type_t contentType,
aaudio_input_preset_t preset = DONT_SET,
aaudio_direction_t direction = AAUDIO_DIRECTION_OUTPUT) {
+ if (direction == AAUDIO_DIRECTION_INPUT
+ && !deviceSupportsFeature(FEATURE_RECORDING)) return;
+ else if (direction == AAUDIO_DIRECTION_OUTPUT
+ && !deviceSupportsFeature(FEATURE_PLAYBACK)) return;
float *buffer = new float[kNumFrames * kChannelCount];
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp
index 670c077..813b40c 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio_callback.cpp
@@ -143,6 +143,8 @@
}
void AAudioInputStreamCallbackTest::SetUp() {
+ mSetupSuccesful = false;
+ if (!deviceSupportsFeature(FEATURE_RECORDING)) return;
mHelper.reset(new InputStreamBuilderHelper(
std::get<PARAM_SHARING_MODE>(GetParam()),
std::get<PARAM_PERF_MODE>(GetParam())));
@@ -156,7 +158,6 @@
AAudioStreamBuilder_setFramesPerDataCallback(builder(), framesPerDataCallback);
}
- mSetupSuccesful = false;
mHelper->createAndVerifyStream(&mSetupSuccesful);
}
@@ -242,6 +243,8 @@
}
void AAudioOutputStreamCallbackTest::SetUp() {
+ mSetupSuccesful = false;
+ if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
mHelper.reset(new OutputStreamBuilderHelper(
std::get<PARAM_SHARING_MODE>(GetParam()),
std::get<PARAM_PERF_MODE>(GetParam())));
@@ -255,7 +258,6 @@
AAudioStreamBuilder_setFramesPerDataCallback(builder(), framesPerDataCallback);
}
- mSetupSuccesful = false;
mHelper->createAndVerifyStream(&mSetupSuccesful);
}
@@ -300,8 +302,11 @@
}
EXPECT_GE(mCbData->minLatency, 1); // Absurdly low
- EXPECT_LE(mCbData->maxLatency, 300); // Absurdly high, should be < 30
- // Note that on some devices it's 200-something
+ // We only issue a warning here because the CDD does not mandate a specific minimum latency
+ if (mCbData->maxLatency > 300) {
+ __android_log_print(ANDROID_LOG_WARN, LOG_TAG,
+ "Suspiciously high callback latency: %d", mCbData->maxLatency);
+ }
//printf("latency: %d, %d\n", mCbData->minLatency, mCbData->maxLatency);
}
diff --git a/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp b/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
index 626422a..225a9c3 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_aaudio_stream_builder.cpp
@@ -24,6 +24,8 @@
#include <gtest/gtest.h>
#include <sys/system_properties.h>
+#include "utils.h"
+
// This was copied from "system/core/libcutils/properties.cpp" because the linker says
// "libnativeaaudiotest (native:ndk:libc++:static) should not link to libcutils (native:platform)"
static int8_t my_property_get_bool(const char *key, int8_t default_value) {
@@ -127,6 +129,7 @@
// Test creating a default stream with everything unspecified.
TEST(test_aaudio, aaudio_stream_unspecified) {
+ if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
AAudioStreamBuilder *aaudioBuilder = nullptr;
create_stream_builder(&aaudioBuilder);
@@ -152,6 +155,7 @@
};
TEST_P(AAudioStreamBuilderSamplingRateTest, openStream) {
+ if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
const int32_t sampleRate = GetParam();
const bool isSampleRateValid = isValidSamplingRate(sampleRate);
// Opening a stream with a high sample rates can fail because the required buffer size
@@ -187,6 +191,7 @@
};
TEST_P(AAudioStreamBuilderChannelCountTest, openStream) {
+ if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
AAudioStreamBuilder *aaudioBuilder = nullptr;
create_stream_builder(&aaudioBuilder);
AAudioStreamBuilder_setChannelCount(aaudioBuilder, GetParam());
@@ -220,6 +225,7 @@
};
TEST_P(AAudioStreamBuilderFormatTest, openStream) {
+ if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
AAudioStreamBuilder *aaudioBuilder = nullptr;
create_stream_builder(&aaudioBuilder);
AAudioStreamBuilder_setFormat(aaudioBuilder, GetParam());
@@ -247,6 +253,7 @@
};
TEST_P(AAudioStreamBuilderSharingModeTest, openStream) {
+ if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
AAudioStreamBuilder *aaudioBuilder = nullptr;
create_stream_builder(&aaudioBuilder);
AAudioStreamBuilder_setFormat(aaudioBuilder, GetParam());
@@ -274,6 +281,10 @@
};
TEST_P(AAudioStreamBuilderDirectionTest, openStream) {
+ if (GetParam() == AAUDIO_DIRECTION_OUTPUT
+ && !deviceSupportsFeature(FEATURE_PLAYBACK)) return;
+ if (GetParam() == AAUDIO_DIRECTION_INPUT
+ && !deviceSupportsFeature(FEATURE_RECORDING)) return;
AAudioStreamBuilder *aaudioBuilder = nullptr;
create_stream_builder(&aaudioBuilder);
AAudioStreamBuilder_setFormat(aaudioBuilder, GetParam());
@@ -303,6 +314,7 @@
};
TEST_P(AAudioStreamBuilderBufferCapacityTest, openStream) {
+ if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
AAudioStreamBuilder *aaudioBuilder = nullptr;
create_stream_builder(&aaudioBuilder);
AAudioStreamBuilder_setBufferCapacityInFrames(aaudioBuilder, GetParam());
@@ -336,6 +348,7 @@
};
TEST_P(AAudioStreamBuilderPerfModeTest, openStream) {
+ if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
AAudioStreamBuilder *aaudioBuilder = nullptr;
create_stream_builder(&aaudioBuilder);
AAudioStreamBuilder_setPerformanceMode(aaudioBuilder, GetParam());
diff --git a/tests/tests/nativemedia/aaudio/jni/test_session_id.cpp b/tests/tests/nativemedia/aaudio/jni/test_session_id.cpp
index fa82f71..dfde6e7 100644
--- a/tests/tests/nativemedia/aaudio/jni/test_session_id.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/test_session_id.cpp
@@ -23,12 +23,14 @@
#include <gtest/gtest.h>
#include "test_aaudio.h"
+#include "utils.h"
constexpr int kNumFrames = 256;
constexpr int kChannelCount = 2;
// Test AAUDIO_SESSION_ID_NONE default
static void checkSessionIdNone(aaudio_performance_mode_t perfMode) {
+ if (!deviceSupportsFeature(FEATURE_PLAYBACK)) return;
float *buffer = new float[kNumFrames * kChannelCount];
@@ -72,6 +74,10 @@
// Test AAUDIO_SESSION_ID_ALLOCATE
static void checkSessionIdAllocate(aaudio_performance_mode_t perfMode,
aaudio_direction_t direction) {
+ // Since this test creates streams in both directions, it can't work
+ // if either of them is not supported by the device.
+ if (!deviceSupportsFeature(FEATURE_RECORDING)
+ || !deviceSupportsFeature(FEATURE_PLAYBACK)) return;
float *buffer = new float[kNumFrames * kChannelCount];
diff --git a/tests/tests/nativemedia/aaudio/jni/utils.cpp b/tests/tests/nativemedia/aaudio/jni/utils.cpp
index 55d1e13..d843614 100644
--- a/tests/tests/nativemedia/aaudio/jni/utils.cpp
+++ b/tests/tests/nativemedia/aaudio/jni/utils.cpp
@@ -16,9 +16,12 @@
#define LOG_TAG "AAudioTest"
+#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
+#include <string>
+
#include <android/log.h>
#include <gtest/gtest.h>
@@ -51,6 +54,28 @@
return "UNKNOWN";
}
+// Runs "pm list features" and attempts to find the specified feature in its output.
+bool deviceSupportsFeature(const char* feature) {
+ bool hasFeature = false;
+ FILE *p = popen("/system/bin/pm list features", "re");
+ if (p) {
+ char* line = NULL;
+ size_t len = 0;
+ while (getline(&line, &len, p) > 0) {
+ if (strstr(line, feature)) {
+ hasFeature = true;
+ break;
+ }
+ }
+ pclose(p);
+ } else {
+ __android_log_print(ANDROID_LOG_FATAL, LOG_TAG, "popen failed: %d", errno);
+ _exit(EXIT_FAILURE);
+ }
+ __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "Feature %s: %ssupported",
+ feature, hasFeature ? "" : "not ");
+ return hasFeature;
+}
// These periods are quite generous. They are not designed to put
// any restrictions on the implementation, but only to ensure sanity.
diff --git a/tests/tests/nativemedia/aaudio/jni/utils.h b/tests/tests/nativemedia/aaudio/jni/utils.h
index 7f38fef..4211410 100644
--- a/tests/tests/nativemedia/aaudio/jni/utils.h
+++ b/tests/tests/nativemedia/aaudio/jni/utils.h
@@ -24,6 +24,11 @@
const char* performanceModeToString(aaudio_performance_mode_t mode);
const char* sharingModeToString(aaudio_sharing_mode_t mode);
+static constexpr const char* FEATURE_PLAYBACK = "android.hardware.audio.output";
+static constexpr const char* FEATURE_RECORDING = "android.hardware.microphone";
+static constexpr const char* FEATURE_LOW_LATENCY = "android.hardware.audio.low_latency";
+bool deviceSupportsFeature(const char* feature);
+
class StreamBuilderHelper {
public:
struct Parameters {
diff --git a/tests/tests/net/src/android/net/cts/UriTest.java b/tests/tests/net/src/android/net/cts/UriTest.java
index 5344f93..40b8fb7 100644
--- a/tests/tests/net/src/android/net/cts/UriTest.java
+++ b/tests/tests/net/src/android/net/cts/UriTest.java
@@ -568,6 +568,15 @@
"ftp://root:love@ftp.android.com:2121/");
}
+ public void testToSafeString_rtsp() {
+ checkToSafeString("rtsp://rtsp.android.com/...", "rtsp://rtsp.android.com/");
+ checkToSafeString("rtsp://rtsp.android.com/...", "rtsp://rtsp.android.com/video.mov");
+ checkToSafeString("rtsp://rtsp.android.com/...", "rtsp://rtsp.android.com/video.mov?param");
+ checkToSafeString("RtsP://rtsp.android.com/...", "RtsP://anonymous@rtsp.android.com/");
+ checkToSafeString("rtsp://rtsp.android.com:2121/...",
+ "rtsp://username:password@rtsp.android.com:2121/");
+ }
+
public void testToSafeString_notSupport() {
checkToSafeString("unsupported://ajkakjah/askdha/secret?secret",
"unsupported://ajkakjah/askdha/secret?secret");
diff --git a/tests/tests/os/TEST_MAPPING b/tests/tests/os/TEST_MAPPING
index af77210..3cbc021 100644
--- a/tests/tests/os/TEST_MAPPING
+++ b/tests/tests/os/TEST_MAPPING
@@ -5,9 +5,6 @@
"options": [
{
"exclude-filter": "android.os.cts.UsbDebuggingTest#testUsbDebugging"
- },
- {
- "exclude-filter": "android.os.cts.DebugTest#testGetAndReset"
}
]
}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStorePendingTest.java b/tests/tests/provider/src/android/provider/cts/MediaStorePendingTest.java
index edb9aa3..c8a805f 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStorePendingTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStorePendingTest.java
@@ -37,7 +37,8 @@
import android.os.FileUtils;
import android.provider.MediaStore;
import android.provider.MediaStore.MediaColumns;
-import android.provider.MediaStore.PendingParams;
+import android.provider.cts.MediaStoreUtils.PendingParams;
+import android.provider.cts.MediaStoreUtils.PendingSession;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -103,10 +104,10 @@
throws Exception {
final String displayName = "cts" + System.nanoTime();
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
+ final PendingParams params = new PendingParams(
insertUri, displayName, "image/png");
- final Uri pendingUri = MediaStore.createPending(mContext, params);
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
final long id = ContentUris.parseId(pendingUri);
// Verify pending status across various queries
@@ -120,7 +121,7 @@
// Write an image into place
final Uri publishUri;
- try (MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri)) {
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
OutputStream out = session.openOutputStream()) {
FileUtils.copy(in, out);
@@ -153,13 +154,13 @@
final String displayName = "cts" + System.nanoTime();
final Uri insertUri = mExternalImages;
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
+ final PendingParams params = new PendingParams(
insertUri, displayName, "image/png");
- final Uri pendingUri = MediaStore.createPending(mContext, params);
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
final File pendingFile;
- try (MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri)) {
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
try (InputStream in = mContext.getResources().openRawResource(R.raw.scenery);
OutputStream out = session.openOutputStream()) {
FileUtils.copy(in, out);
@@ -191,9 +192,9 @@
final String displayName = "cts" + System.nanoTime();
final Uri insertUri = mExternalAudio;
- final MediaStore.PendingParams params1 = new MediaStore.PendingParams(
+ final PendingParams params1 = new PendingParams(
insertUri, displayName, "audio/mpeg");
- final MediaStore.PendingParams params2 = new MediaStore.PendingParams(
+ final PendingParams params2 = new PendingParams(
insertUri, displayName, "audio/mpeg");
final Uri publishUri1 = execPending(params1, R.raw.testmp3);
@@ -402,7 +403,7 @@
}
private void assertCreatePending(PendingParams params) {
- MediaStore.createPending(mContext, params);
+ MediaStoreUtils.createPending(mContext, params);
}
private void assertNotCreatePending(PendingParams params) {
@@ -411,15 +412,15 @@
private void assertNotCreatePending(String message, PendingParams params) {
try {
- MediaStore.createPending(mContext, params);
+ MediaStoreUtils.createPending(mContext, params);
fail(message);
} catch (Exception expected) {
}
}
private Uri execPending(PendingParams params, int resId) throws Exception {
- final Uri pendingUri = MediaStore.createPending(mContext, params);
- try (MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri)) {
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
try (InputStream in = mContext.getResources().openRawResource(resId);
OutputStream out = session.openOutputStream()) {
FileUtils.copy(in, out);
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStoreUtils.java b/tests/tests/provider/src/android/provider/cts/MediaStoreUtils.java
new file mode 100644
index 0000000..b360722
--- /dev/null
+++ b/tests/tests/provider/src/android/provider/cts/MediaStoreUtils.java
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2019 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.provider.cts;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore;
+import android.provider.MediaStore.DownloadColumns;
+import android.provider.MediaStore.Downloads;
+import android.provider.MediaStore.MediaColumns;
+import android.text.format.DateUtils;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.NonNull;
+
+import java.io.FileNotFoundException;
+import java.io.OutputStream;
+import java.util.Objects;
+
+public class MediaStoreUtils {
+ /**
+ * Create a new pending media item using the given parameters. Pending items
+ * are expected to have a short lifetime, and owners should either
+ * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
+ * pending item within a few hours after first creating it.
+ *
+ * @return token which can be passed to {@link #openPending(Context, Uri)}
+ * to work with this pending item.
+ * @see MediaColumns#IS_PENDING
+ * @see MediaStore#setIncludePending(Uri)
+ * @see MediaStore#createPending(Context, PendingParams)
+ * @removed
+ */
+ @Deprecated
+ public static @NonNull Uri createPending(@NonNull Context context,
+ @NonNull PendingParams params) {
+ return context.getContentResolver().insert(params.insertUri, params.insertValues);
+ }
+
+ /**
+ * Open a pending media item to make progress on it. You can open a pending
+ * item multiple times before finally calling either
+ * {@link PendingSession#publish()} or {@link PendingSession#abandon()}.
+ *
+ * @param uri token which was previously returned from
+ * {@link #createPending(Context, PendingParams)}.
+ * @removed
+ */
+ @Deprecated
+ public static @NonNull PendingSession openPending(@NonNull Context context, @NonNull Uri uri) {
+ return new PendingSession(context, uri);
+ }
+
+ /**
+ * Parameters that describe a pending media item.
+ *
+ * @removed
+ */
+ @Deprecated
+ public static class PendingParams {
+ /** {@hide} */
+ public final Uri insertUri;
+ /** {@hide} */
+ public final ContentValues insertValues;
+
+ /**
+ * Create parameters that describe a pending media item.
+ *
+ * @param insertUri the {@code content://} Uri where this pending item
+ * should be inserted when finally published. For example, to
+ * publish an image, use
+ * {@link MediaStore.Images.Media#getContentUri(String)}.
+ */
+ public PendingParams(@NonNull Uri insertUri, @NonNull String displayName,
+ @NonNull String mimeType) {
+ this.insertUri = Objects.requireNonNull(insertUri);
+ final long now = System.currentTimeMillis() / 1000;
+ this.insertValues = new ContentValues();
+ this.insertValues.put(MediaColumns.DISPLAY_NAME, Objects.requireNonNull(displayName));
+ this.insertValues.put(MediaColumns.MIME_TYPE, Objects.requireNonNull(mimeType));
+ this.insertValues.put(MediaColumns.DATE_ADDED, now);
+ this.insertValues.put(MediaColumns.DATE_MODIFIED, now);
+ this.insertValues.put(MediaColumns.IS_PENDING, 1);
+ this.insertValues.put(MediaColumns.DATE_EXPIRES,
+ (System.currentTimeMillis() + DateUtils.DAY_IN_MILLIS) / 1000);
+ }
+
+ /**
+ * Optionally set the primary directory under which this pending item
+ * should be persisted. Only specific well-defined directories from
+ * {@link Environment} are allowed based on the media type being
+ * inserted.
+ * <p>
+ * For example, when creating pending {@link MediaStore.Images.Media}
+ * items, only {@link Environment#DIRECTORY_PICTURES} or
+ * {@link Environment#DIRECTORY_DCIM} are allowed.
+ * <p>
+ * You may leave this value undefined to store the media in a default
+ * location. For example, when this value is left undefined, pending
+ * {@link MediaStore.Audio.Media} items are stored under
+ * {@link Environment#DIRECTORY_MUSIC}.
+ *
+ * @see MediaColumns#PRIMARY_DIRECTORY
+ */
+ public void setPrimaryDirectory(@Nullable String primaryDirectory) {
+ if (primaryDirectory == null) {
+ this.insertValues.remove(MediaColumns.PRIMARY_DIRECTORY);
+ } else {
+ this.insertValues.put(MediaColumns.PRIMARY_DIRECTORY, primaryDirectory);
+ }
+ }
+
+ /**
+ * Optionally set the secondary directory under which this pending item
+ * should be persisted. Any valid directory name is allowed.
+ * <p>
+ * You may leave this value undefined to store the media as a direct
+ * descendant of the {@link #setPrimaryDirectory(String)} location.
+ *
+ * @see MediaColumns#SECONDARY_DIRECTORY
+ */
+ public void setSecondaryDirectory(@Nullable String secondaryDirectory) {
+ if (secondaryDirectory == null) {
+ this.insertValues.remove(MediaColumns.SECONDARY_DIRECTORY);
+ } else {
+ this.insertValues.put(MediaColumns.SECONDARY_DIRECTORY, secondaryDirectory);
+ }
+ }
+
+ /**
+ * Optionally set the Uri from where the file has been downloaded. This is used
+ * for files being added to {@link Downloads} table.
+ *
+ * @see DownloadColumns#DOWNLOAD_URI
+ */
+ public void setDownloadUri(@Nullable Uri downloadUri) {
+ if (downloadUri == null) {
+ this.insertValues.remove(DownloadColumns.DOWNLOAD_URI);
+ } else {
+ this.insertValues.put(DownloadColumns.DOWNLOAD_URI, downloadUri.toString());
+ }
+ }
+
+ /**
+ * Optionally set the Uri indicating HTTP referer of the file. This is used for
+ * files being added to {@link Downloads} table.
+ *
+ * @see DownloadColumns#REFERER_URI
+ */
+ public void setRefererUri(@Nullable Uri refererUri) {
+ if (refererUri == null) {
+ this.insertValues.remove(DownloadColumns.REFERER_URI);
+ } else {
+ this.insertValues.put(DownloadColumns.REFERER_URI, refererUri.toString());
+ }
+ }
+ }
+
+ /**
+ * Session actively working on a pending media item. Pending items are
+ * expected to have a short lifetime, and owners should either
+ * {@link PendingSession#publish()} or {@link PendingSession#abandon()} a
+ * pending item within a few hours after first creating it.
+ *
+ * @removed
+ */
+ @Deprecated
+ public static class PendingSession implements AutoCloseable {
+ /** {@hide} */
+ private final Context mContext;
+ /** {@hide} */
+ private final Uri mUri;
+
+ /** {@hide} */
+ public PendingSession(Context context, Uri uri) {
+ mContext = Objects.requireNonNull(context);
+ mUri = Objects.requireNonNull(uri);
+ }
+
+ /**
+ * Open the underlying file representing this media item. When a media
+ * item is successfully completed, you should
+ * {@link ParcelFileDescriptor#close()} and then {@link #publish()} it.
+ *
+ * @see #notifyProgress(int)
+ */
+ public @NonNull ParcelFileDescriptor open() throws FileNotFoundException {
+ return mContext.getContentResolver().openFileDescriptor(mUri, "rw");
+ }
+
+ /**
+ * Open the underlying file representing this media item. When a media
+ * item is successfully completed, you should
+ * {@link OutputStream#close()} and then {@link #publish()} it.
+ *
+ * @see #notifyProgress(int)
+ */
+ public @NonNull OutputStream openOutputStream() throws FileNotFoundException {
+ return mContext.getContentResolver().openOutputStream(mUri);
+ }
+
+ /**
+ * When this media item is successfully completed, call this method to
+ * publish and make the final item visible to the user.
+ *
+ * @return the final {@code content://} Uri representing the newly
+ * published media.
+ */
+ public @NonNull Uri publish() {
+ final ContentValues values = new ContentValues();
+ values.put(MediaColumns.IS_PENDING, 0);
+ values.putNull(MediaColumns.DATE_EXPIRES);
+ mContext.getContentResolver().update(mUri, values, null, null);
+ return mUri;
+ }
+
+ /**
+ * When this media item has failed to be completed, call this method to
+ * destroy the pending item record and any data related to it.
+ */
+ public void abandon() {
+ mContext.getContentResolver().delete(mUri, null, null);
+ }
+
+ @Override
+ public void close() {
+ // No resources to close, but at least we can inform people that no
+ // progress is being actively made.
+ }
+ }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_DownloadsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_DownloadsTest.java
index b86afbe..57b3a64 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_DownloadsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_DownloadsTest.java
@@ -35,6 +35,8 @@
import android.provider.MediaStore.Downloads;
import android.provider.MediaStore.Files;
import android.provider.MediaStore.Images;
+import android.provider.cts.MediaStoreUtils.PendingParams;
+import android.provider.cts.MediaStoreUtils.PendingSession;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -158,16 +160,16 @@
final Uri downloadUri = Uri.parse("https://developer.android.com/overview.html");
final Uri refererUri = Uri.parse("https://www.android.com");
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
+ final PendingParams params = new PendingParams(
Downloads.EXTERNAL_CONTENT_URI, displayName, mimeType);
params.setDownloadUri(downloadUri);
params.setRefererUri(refererUri);
- final Uri pendingUri = MediaStore.createPending(mContext, params);
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
assertNotNull(pendingUri);
mAddedUris.add(pendingUri);
final Uri publishUri;
- try (MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri)) {
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
try (PrintWriter pw = new PrintWriter(session.openOutputStream())) {
pw.print(content);
}
@@ -205,16 +207,16 @@
@Test
public void testUpdateDownload() throws Exception {
final String displayName = "cts" + System.nanoTime();
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
+ final PendingParams params = new PendingParams(
Downloads.EXTERNAL_CONTENT_URI, displayName, "video/3gp");
final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
params.setDownloadUri(downloadUri);
- final Uri pendingUri = MediaStore.createPending(mContext, params);
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
assertNotNull(pendingUri);
mAddedUris.add(pendingUri);
final Uri publishUri;
- try (MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri)) {
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
OutputStream out = session.openOutputStream()) {
android.os.FileUtils.copy(in, out);
@@ -242,16 +244,16 @@
@Test
public void testDeleteDownload() throws Exception {
final String displayName = "cts" + System.nanoTime();
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
+ final PendingParams params = new PendingParams(
Downloads.EXTERNAL_CONTENT_URI, displayName, "video/3gp");
final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
params.setDownloadUri(downloadUri);
- final Uri pendingUri = MediaStore.createPending(mContext, params);
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
assertNotNull(pendingUri);
mAddedUris.add(pendingUri);
final Uri publishUri;
- try (MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri)) {
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
OutputStream out = session.openOutputStream()) {
android.os.FileUtils.copy(in, out);
@@ -284,15 +286,15 @@
mCountDownLatch = new CountDownLatch(1);
final String displayName = "cts" + System.nanoTime();
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
+ final PendingParams params = new PendingParams(
Downloads.EXTERNAL_CONTENT_URI, displayName, "video/3gp");
final Uri downloadUri = Uri.parse("https://www.android.com/download?file=testvideo.3gp");
params.setDownloadUri(downloadUri);
- final Uri pendingUri = MediaStore.createPending(mContext, params);
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
assertNotNull(pendingUri);
mAddedUris.add(pendingUri);
final Uri publishUri;
- try (MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri)) {
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
try (InputStream in = mContext.getResources().openRawResource(R.raw.testvideo);
OutputStream out = session.openOutputStream()) {
android.os.FileUtils.copy(in, out);
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
index 89f0541..bcc49cc 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_MediaTest.java
@@ -37,6 +37,8 @@
import android.provider.MediaStore;
import android.provider.MediaStore.Images.ImageColumns;
import android.provider.MediaStore.Images.Media;
+import android.provider.cts.MediaStoreUtils.PendingParams;
+import android.provider.cts.MediaStoreUtils.PendingSession;
import android.util.Log;
import android.util.Size;
@@ -320,12 +322,12 @@
Assume.assumeTrue(StorageManager.hasIsolatedStorage());
final String displayName = "cts" + System.nanoTime();
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
+ final PendingParams params = new PendingParams(
mExternalImages, displayName, "image/jpeg");
- final Uri pendingUri = MediaStore.createPending(mContext, params);
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
final Uri publishUri;
- try (MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri)) {
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
try (InputStream in = mContext.getResources().openRawResource(R.raw.volantis);
OutputStream out = session.openOutputStream()) {
android.os.FileUtils.copy(in, out);
@@ -369,12 +371,12 @@
@Test
public void testLocationDeprecated() throws Exception {
final String displayName = "cts" + System.nanoTime();
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
+ final PendingParams params = new PendingParams(
mExternalImages, displayName, "image/jpeg");
- final Uri pendingUri = MediaStore.createPending(mContext, params);
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
final Uri publishUri;
- try (MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri)) {
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
try (InputStream in = mContext.getResources().openRawResource(R.raw.volantis);
OutputStream out = session.openOutputStream()) {
android.os.FileUtils.copy(in, out);
diff --git a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
index ee6b82c..c9e3708 100644
--- a/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MediaStore_Images_ThumbnailsTest.java
@@ -46,6 +46,8 @@
import android.provider.MediaStore.Images.Media;
import android.provider.MediaStore.Images.Thumbnails;
import android.provider.MediaStore.MediaColumns;
+import android.provider.cts.MediaStoreUtils.PendingParams;
+import android.provider.cts.MediaStoreUtils.PendingSession;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.Size;
@@ -412,11 +414,11 @@
@Test
public void testInsertUpdateDelete() throws Exception {
final String displayName = "cts" + System.nanoTime();
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
+ final PendingParams params = new PendingParams(
mExternalImages, displayName, "image/png");
- final Uri pendingUri = MediaStore.createPending(mContext, params);
+ final Uri pendingUri = MediaStoreUtils.createPending(mContext, params);
final Uri finalUri;
- try (MediaStore.PendingSession session = MediaStore.openPending(mContext, pendingUri)) {
+ try (PendingSession session = MediaStoreUtils.openPending(mContext, pendingUri)) {
try (OutputStream out = session.openOutputStream()) {
writeImage(mLargestDimension, mLargestDimension, Color.RED, out);
}
diff --git a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
index e87fb85..aa1d59a 100644
--- a/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
+++ b/tests/tests/provider/src/android/provider/cts/ProviderTestUtils.java
@@ -35,6 +35,8 @@
import android.os.storage.StorageManager;
import android.provider.MediaStore;
import android.provider.MediaStore.MediaColumns;
+import android.provider.cts.MediaStoreUtils.PendingParams;
+import android.provider.cts.MediaStoreUtils.PendingSession;
import android.system.ErrnoException;
import android.system.Os;
import android.system.OsConstants;
@@ -217,10 +219,9 @@
static Uri stageMedia(int resId, Uri collectionUri, String mimeType) throws IOException {
final Context context = InstrumentationRegistry.getTargetContext();
final String displayName = "cts" + System.nanoTime();
- final MediaStore.PendingParams params = new MediaStore.PendingParams(
- collectionUri, displayName, mimeType);
- final Uri pendingUri = MediaStore.createPending(context, params);
- try (MediaStore.PendingSession session = MediaStore.openPending(context, pendingUri)) {
+ final PendingParams params = new PendingParams(collectionUri, displayName, mimeType);
+ final Uri pendingUri = MediaStoreUtils.createPending(context, params);
+ try (PendingSession session = MediaStoreUtils.openPending(context, pendingUri)) {
try (InputStream source = context.getResources().openRawResource(resId);
OutputStream target = session.openOutputStream()) {
FileUtils.copy(source, target);
diff --git a/tests/tests/rcs/Android.mk b/tests/tests/rcs/Android.mk
index f062fec..af2bf4b 100755
--- a/tests/tests/rcs/Android.mk
+++ b/tests/tests/rcs/Android.mk
@@ -26,8 +26,8 @@
LOCAL_PROGUARD_ENABLED := disabled
LOCAL_STATIC_JAVA_LIBRARIES := \
- compatibility-device-util \
- android-support-test \
+ compatibility-device-util-axt \
+ androidx.test.rules \
truth-prebuilt
LOCAL_JAVA_LIBRARIES := android.test.runner.stubs android.test.base.stubs
diff --git a/tests/tests/rcs/AndroidManifest.xml b/tests/tests/rcs/AndroidManifest.xml
index 6584b26..3b46edc 100755
--- a/tests/tests/rcs/AndroidManifest.xml
+++ b/tests/tests/rcs/AndroidManifest.xml
@@ -84,7 +84,7 @@
<!-- self-instrumenting test package. -->
<instrumentation
- android:name="android.support.test.runner.AndroidJUnitRunner"
+ android:name="androidx.test.runner.AndroidJUnitRunner"
android:label="RCS CTS tests"
android:targetPackage="android.telephony.ims.cts" >
</instrumentation>
diff --git a/tests/tests/rcs/AndroidTest.xml b/tests/tests/rcs/AndroidTest.xml
index 906ee88..6b24565 100644
--- a/tests/tests/rcs/AndroidTest.xml
+++ b/tests/tests/rcs/AndroidTest.xml
@@ -16,6 +16,11 @@
<configuration description="Config for RCS test cases">
<option name="test-suite-tag" value="cts" />
<option name="config-descriptor:metadata" key="component" value="framework" />
+
+ <!-- RCS functionality depends on SMS permissions not available to instant apps. -->
+ <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+ <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+
<target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsRcsTestCases.apk" />
diff --git a/tests/tests/rcs/src/android/telephony/ims/cts/DefaultSmsAppHelper.java b/tests/tests/rcs/src/android/telephony/ims/cts/DefaultSmsAppHelper.java
index 0f70073..abecadc 100644
--- a/tests/tests/rcs/src/android/telephony/ims/cts/DefaultSmsAppHelper.java
+++ b/tests/tests/rcs/src/android/telephony/ims/cts/DefaultSmsAppHelper.java
@@ -18,7 +18,7 @@
import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-import android.support.test.InstrumentationRegistry;
+import androidx.test.InstrumentationRegistry;
class DefaultSmsAppHelper {
static void setDefaultSmsApp(boolean setToSmsApp) {
diff --git a/tests/tests/rcs/src/android/telephony/ims/cts/Rcs1To1ThreadTest.java b/tests/tests/rcs/src/android/telephony/ims/cts/Rcs1To1ThreadTest.java
index 5d45a8c..038c925 100644
--- a/tests/tests/rcs/src/android/telephony/ims/cts/Rcs1To1ThreadTest.java
+++ b/tests/tests/rcs/src/android/telephony/ims/cts/Rcs1To1ThreadTest.java
@@ -21,13 +21,14 @@
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
import android.telephony.ims.Rcs1To1Thread;
import android.telephony.ims.RcsManager;
import android.telephony.ims.RcsMessageStore;
import android.telephony.ims.RcsMessageStoreException;
import android.telephony.ims.RcsParticipant;
+import androidx.test.InstrumentationRegistry;
+
import org.junit.AfterClass;
import org.junit.Assume;
import org.junit.Before;
diff --git a/tests/tests/rcs/src/android/telephony/ims/cts/RcsEventTest.java b/tests/tests/rcs/src/android/telephony/ims/cts/RcsEventTest.java
index fddcaa1..375ea4f 100644
--- a/tests/tests/rcs/src/android/telephony/ims/cts/RcsEventTest.java
+++ b/tests/tests/rcs/src/android/telephony/ims/cts/RcsEventTest.java
@@ -23,7 +23,6 @@
import android.content.Context;
import android.net.Uri;
-import android.support.test.InstrumentationRegistry;
import android.telephony.ims.RcsEvent;
import android.telephony.ims.RcsEventQueryParams;
import android.telephony.ims.RcsEventQueryResult;
@@ -39,6 +38,8 @@
import android.telephony.ims.RcsParticipant;
import android.telephony.ims.RcsParticipantAliasChangedEvent;
+import androidx.test.InstrumentationRegistry;
+
import com.google.android.collect.Lists;
import org.junit.AfterClass;
diff --git a/tests/tests/rcs/src/android/telephony/ims/cts/RcsParticipantTest.java b/tests/tests/rcs/src/android/telephony/ims/cts/RcsParticipantTest.java
index d35bb20..6dab877 100644
--- a/tests/tests/rcs/src/android/telephony/ims/cts/RcsParticipantTest.java
+++ b/tests/tests/rcs/src/android/telephony/ims/cts/RcsParticipantTest.java
@@ -22,12 +22,13 @@
import static com.google.common.truth.Truth.assertThat;
import android.content.Context;
-import android.support.test.InstrumentationRegistry;
import android.telephony.ims.RcsManager;
import android.telephony.ims.RcsMessageStore;
import android.telephony.ims.RcsMessageStoreException;
import android.telephony.ims.RcsParticipant;
+import androidx.test.InstrumentationRegistry;
+
import org.junit.AfterClass;
import org.junit.Assume;
import org.junit.Before;
diff --git a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
index 7f096cc..a94a772 100644
--- a/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
+++ b/tests/tests/role/CtsRoleTestApp/AndroidManifest.xml
@@ -26,6 +26,10 @@
android:name=".RequestRoleActivity"
android:exported="true" />
+ <activity
+ android:name=".IsRoleHeldActivity"
+ android:exported="true" />
+
<!-- Dialer -->
<activity android:name=".DialerDialActivity">
<intent-filter>
@@ -71,5 +75,14 @@
<data android:mimeType="application/vnd.wap.mms-message" />
</intent-filter>
</receiver>
+
+ <!-- Music -->
+ <activity android:name=".MusicActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.APP_MUSIC" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </activity>
</application>
</manifest>
diff --git a/tests/tests/role/CtsRoleTestApp/src/android/app/role/cts/app/IsRoleHeldActivity.java b/tests/tests/role/CtsRoleTestApp/src/android/app/role/cts/app/IsRoleHeldActivity.java
new file mode 100644
index 0000000..8e97f9f
--- /dev/null
+++ b/tests/tests/role/CtsRoleTestApp/src/android/app/role/cts/app/IsRoleHeldActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.role.cts.app;
+
+import android.app.Activity;
+import android.app.role.RoleManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.text.TextUtils;
+
+/**
+ * An activity that checks whether a role is held.
+ */
+public class IsRoleHeldActivity extends Activity {
+
+ private static final String EXTRA_IS_ROLE_HELD = "android.app.role.cts.app.extra.IS_ROLE_HELD";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ String roleName = getIntent().getStringExtra(Intent.EXTRA_ROLE_NAME);
+ if (TextUtils.isEmpty(roleName)) {
+ throw new IllegalArgumentException("Role name in extras cannot be null or empty");
+ }
+
+ RoleManager roleManager = getSystemService(RoleManager.class);
+ setResult(RESULT_OK, new Intent()
+ .putExtra(EXTRA_IS_ROLE_HELD, roleManager.isRoleHeld(roleName)));
+ finish();
+ }
+}
diff --git a/tests/tests/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java b/tests/tests/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java
index 46d07a4..a2124f4 100644
--- a/tests/tests/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java
+++ b/tests/tests/role/CtsRoleTestApp/src/android/app/role/cts/app/RequestRoleActivity.java
@@ -27,15 +27,13 @@
*/
public class RequestRoleActivity extends Activity {
- private static final String EXTRA_ROLE_NAME = "android.app.role.cts.app.extra.ROLE_NAME";
-
private static final int REQUEST_CODE_REQUEST_ROLE = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- String roleName = getIntent().getStringExtra(EXTRA_ROLE_NAME);
+ String roleName = getIntent().getStringExtra(Intent.EXTRA_ROLE_NAME);
if (TextUtils.isEmpty(roleName)) {
throw new IllegalArgumentException("Role name in extras cannot be null or empty");
}
diff --git a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
index 407dc7a..9c079a0 100644
--- a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
+++ b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
@@ -21,14 +21,19 @@
import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
import android.app.Activity;
import android.app.AppOpsManager;
import android.app.Instrumentation;
+import android.app.role.OnRoleHoldersChangedListener;
import android.app.role.RoleManager;
import android.app.role.RoleManagerCallback;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.PermissionInfo;
import android.os.Process;
import android.os.UserHandle;
import android.support.test.uiautomator.By;
@@ -45,6 +50,7 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.AppOpsUtils;
+import com.android.compatibility.common.util.ThrowingRunnable;
import org.junit.After;
import org.junit.Before;
@@ -55,10 +61,12 @@
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.util.Collections;
import java.util.List;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Tests {@link RoleManager}.
@@ -70,17 +78,25 @@
private static final long TIMEOUT_MILLIS = 15 * 1000;
- private static final String ROLE_NAME = RoleManager.ROLE_DIALER;
+ private static final long UNEXPECTED_TIMEOUT_MILLIS = 1000;
+
+ private static final String ROLE_NAME = RoleManager.ROLE_MUSIC;
private static final String APP_PACKAGE_NAME = "android.app.role.cts.app";
+ private static final String APP_IS_ROLE_HELD_ACTIVITY_NAME = APP_PACKAGE_NAME
+ + ".IsRoleHeldActivity";
+ private static final String APP_IS_ROLE_HELD_EXTRA_IS_ROLE_HELD = APP_PACKAGE_NAME
+ + ".extra.IS_ROLE_HELD";
private static final String APP_REQUEST_ROLE_ACTIVITY_NAME = APP_PACKAGE_NAME
+ ".RequestRoleActivity";
- private static final String APP_REQUEST_ROLE_EXTRA_ROLE_NAME = APP_PACKAGE_NAME
- + ".extra.ROLE_NAME";
+
+ private static final String PERMISSION_MANAGE_ROLES_FROM_CONTROLLER =
+ "com.android.permissioncontroller.permission.MANAGE_ROLES_FROM_CONTROLLER";
private static final Instrumentation sInstrumentation =
InstrumentationRegistry.getInstrumentation();
private static final Context sContext = InstrumentationRegistry.getTargetContext();
+ private static final PackageManager sPackageManager = sContext.getPackageManager();
private static final RoleManager sRoleManager = sContext.getSystemService(RoleManager.class);
private static final UiDevice sUiDevice = UiDevice.getInstance(sInstrumentation);
@@ -88,30 +104,43 @@
public ActivityTestRule<WaitForResultActivity> mActivityRule =
new ActivityTestRule<>(WaitForResultActivity.class);
- // TODO: STOPSHIP: Remove once we automatically revoke role upon uninstallation.
+ private String mRoleHolder;
+
@Before
+ public void saveRoleHolder() throws Exception {
+ List<String> roleHolders = getRoleHolders(ROLE_NAME);
+ mRoleHolder = !roleHolders.isEmpty() ? roleHolders.get(0) : null;
+
+ if (Objects.equals(mRoleHolder, APP_PACKAGE_NAME)) {
+ removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+ mRoleHolder = null;
+ }
+ }
+
@After
- public void removeRoleHolder() throws Exception {
+ public void restoreRoleHolder() throws Exception {
removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ if (mRoleHolder != null) {
+ addRoleHolder(ROLE_NAME, mRoleHolder);
+ }
+
assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
}
@Test
- public void roleIsAvailable() {
- assertThat(sRoleManager.isRoleAvailable(ROLE_NAME)).isTrue();
+ public void requestRoleIntentHasPermissionControllerPackage() throws Exception {
+ Intent intent = sRoleManager.createRequestRoleIntent(ROLE_NAME);
+
+ assertThat(intent.getPackage()).isEqualTo(
+ sPackageManager.getPermissionControllerPackageName());
}
@Test
- public void addRoleHolderThenIsRoleHolder() throws Exception {
- addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
- assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
- }
+ public void requestRoleIntentHasExtraRoleName() throws Exception {
+ Intent intent = sRoleManager.createRequestRoleIntent(ROLE_NAME);
- @Test
- public void addAndRemoveRoleHolderThenIsNotRoleHolder() throws Exception {
- addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
- removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
- assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+ assertThat(intent.getStringExtra(Intent.EXTRA_ROLE_NAME)).isEqualTo(ROLE_NAME);
}
@FlakyTest
@@ -119,6 +148,7 @@
public void requestRoleAndRejectThenIsNotRoleHolder() throws Exception {
requestRole(ROLE_NAME);
respondToRoleRequest(false);
+
assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
}
@@ -127,27 +157,14 @@
public void requestRoleAndApproveThenIsRoleHolder() throws Exception {
requestRole(ROLE_NAME);
respondToRoleRequest(true);
+
assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
}
- @Test
- public void revokeSingleRoleThenEnsureOtherRolesAppopsIntact() throws Exception {
- addRoleHolder(RoleManager.ROLE_DIALER, APP_PACKAGE_NAME);
- addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
- removeRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
- assertThat(AppOpsUtils.getOpMode(APP_PACKAGE_NAME, AppOpsManager.OPSTR_SEND_SMS))
- .isEqualTo(AppOpsManager.MODE_ALLOWED);
- }
-
- @Test
- public void migratedRoleHoldersNotEmpty() throws Exception {
- assertThat(getRoleHolders(RoleManager.ROLE_SMS)).isNotEmpty();
- }
-
private void requestRole(@NonNull String roleName) {
Intent intent = new Intent()
.setComponent(new ComponentName(APP_PACKAGE_NAME, APP_REQUEST_ROLE_ACTIVITY_NAME))
- .putExtra(APP_REQUEST_ROLE_EXTRA_ROLE_NAME, roleName);
+ .putExtra(Intent.EXTRA_ROLE_NAME, roleName);
mActivityRule.getActivity().startActivityToWaitForResult(intent);
}
@@ -158,12 +175,13 @@
UiObject2 button = sUiDevice.wait(Until.findObject(By.res(buttonId)), TIMEOUT_MILLIS);
if (button == null) {
dumpWindowHierarchy();
- throw new AssertionError("Cannot find button to click");
+ fail("Cannot find button to click");
}
button.click();
Pair<Integer, Intent> result = mActivityRule.getActivity().waitForActivityResult(
TIMEOUT_MILLIS);
int expectedResult = expectResultOk ? Activity.RESULT_OK : Activity.RESULT_CANCELED;
+
assertThat(result.first).isEqualTo(expectedResult);
}
@@ -187,9 +205,225 @@
}
}
+ @Test
+ public void roleIsAvailable() {
+ assertThat(sRoleManager.isRoleAvailable(ROLE_NAME)).isTrue();
+ }
+
+ @Test
+ public void dontAddRoleHolderThenRoleIsNotHeld() throws Exception {
+ assertRoleIsHeld(ROLE_NAME, false);
+ }
+
+ @Test
+ public void addRoleHolderThenRoleIsHeld() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertRoleIsHeld(ROLE_NAME, true);
+ }
+
+ @Test
+ public void addAndRemoveRoleHolderThenRoleIsNotHeld() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+ removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertRoleIsHeld(ROLE_NAME, false);
+ }
+
+ private void assertRoleIsHeld(@NonNull String roleName, boolean isHeld)
+ throws InterruptedException {
+ Intent intent = new Intent()
+ .setComponent(new ComponentName(APP_PACKAGE_NAME, APP_IS_ROLE_HELD_ACTIVITY_NAME))
+ .putExtra(Intent.EXTRA_ROLE_NAME, roleName);
+ WaitForResultActivity activity = mActivityRule.getActivity();
+ activity.startActivityToWaitForResult(intent);
+ Pair<Integer, Intent> result = activity.waitForActivityResult(TIMEOUT_MILLIS);
+
+ assertThat(result.first).isEqualTo(Activity.RESULT_OK);
+ assertThat(result.second).isNotNull();
+ assertThat(result.second.hasExtra(APP_IS_ROLE_HELD_EXTRA_IS_ROLE_HELD)).isTrue();
+ assertThat(result.second.getBooleanExtra(APP_IS_ROLE_HELD_EXTRA_IS_ROLE_HELD, false))
+ .isEqualTo(isHeld);
+ }
+
+ @Test
+ public void dontAddRoleHolderThenIsNotRoleHolder() throws Exception {
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void addRoleHolderThenIsRoleHolder() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
+ }
+
+ @Test
+ public void addAndRemoveRoleHolderThenIsNotRoleHolder() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+ removeRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void addAndClearRoleHoldersThenIsNotRoleHolder() throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+ clearRoleHolders(ROLE_NAME);
+
+ assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
+ }
+
+ @Test
+ public void addOnRoleHoldersChangedListenerAndAddRoleHolderThenIsNotified() throws Exception {
+ assertOnRoleHoldersChangedListenerIsNotified(() -> addRoleHolder(ROLE_NAME,
+ APP_PACKAGE_NAME));
+ }
+
+ @Test
+ public void addOnRoleHoldersChangedListenerAndRemoveRoleHolderThenIsNotified()
+ throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertOnRoleHoldersChangedListenerIsNotified(() -> removeRoleHolder(ROLE_NAME,
+ APP_PACKAGE_NAME));
+ }
+
+ @Test
+ public void addOnRoleHoldersChangedListenerAndClearRoleHoldersThenIsNotified()
+ throws Exception {
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ assertOnRoleHoldersChangedListenerIsNotified(() -> clearRoleHolders(ROLE_NAME));
+ }
+
+ private void assertOnRoleHoldersChangedListenerIsNotified(@NonNull ThrowingRunnable runnable)
+ throws Exception {
+ ListenerFuture future = new ListenerFuture();
+ UserHandle user = Process.myUserHandle();
+ runWithShellPermissionIdentity(() -> sRoleManager.addOnRoleHoldersChangedListenerAsUser(
+ sContext.getMainExecutor(), future, user));
+ Pair<String, UserHandle> result;
+ try {
+ runnable.run();
+ result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } finally {
+ runWithShellPermissionIdentity(() ->
+ sRoleManager.removeOnRoleHoldersChangedListenerAsUser(future, user));
+ }
+
+ assertThat(result.first).isEqualTo(ROLE_NAME);
+ assertThat(result.second).isEqualTo(user);
+ }
+
+ @Test
+ public void addAndRemoveOnRoleHoldersChangedListenerAndAddRoleHolderThenIsNotNotified()
+ throws Exception {
+ ListenerFuture future = new ListenerFuture();
+ UserHandle user = Process.myUserHandle();
+ runWithShellPermissionIdentity(() -> {
+ sRoleManager.addOnRoleHoldersChangedListenerAsUser(sContext.getMainExecutor(), future,
+ user);
+ sRoleManager.removeOnRoleHoldersChangedListenerAsUser(future, user);
+ });
+ addRoleHolder(ROLE_NAME, APP_PACKAGE_NAME);
+
+ try {
+ future.get(UNEXPECTED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ } catch (TimeoutException e) {
+ // Expected
+ return;
+ }
+ throw new AssertionError("OnRoleHoldersChangedListener was notified after removal");
+ }
+
+ @Test
+ public void setRoleNamesFromControllerShouldRequireManageRolesFromControllerPermission() {
+ assertRequiresManageRolesFromControllerPermission(
+ () -> sRoleManager.setRoleNamesFromController(Collections.emptyList()),
+ "setRoleNamesFromController");
+ }
+
+ @Test
+ public void addRoleHolderFromControllerShouldRequireManageRolesFromControllerPermission() {
+ assertRequiresManageRolesFromControllerPermission(
+ () -> sRoleManager.addRoleHolderFromController(ROLE_NAME, APP_PACKAGE_NAME),
+ "addRoleHolderFromController");
+ }
+
+ @Test
+ public void removeRoleHolderFromControllerShouldRequireManageRolesFromControllerPermission() {
+ assertRequiresManageRolesFromControllerPermission(
+ () -> sRoleManager.removeRoleHolderFromController(ROLE_NAME, APP_PACKAGE_NAME),
+ "removeRoleHolderFromController");
+ }
+
+ @Test
+ public void getHeldRolesFromControllerShouldRequireManageRolesFromControllerPermission() {
+ assertRequiresManageRolesFromControllerPermission(
+ () -> sRoleManager.getHeldRolesFromController(APP_PACKAGE_NAME),
+ "getHeldRolesFromController");
+ }
+
+ private void assertRequiresManageRolesFromControllerPermission(@NonNull Runnable runnable,
+ @NonNull String methodName) {
+ try {
+ runnable.run();
+ } catch (SecurityException e) {
+ if (e.getMessage().contains(PERMISSION_MANAGE_ROLES_FROM_CONTROLLER)) {
+ // Expected
+ return;
+ }
+ throw e;
+ }
+ fail("RoleManager." + methodName + "() should require "
+ + PERMISSION_MANAGE_ROLES_FROM_CONTROLLER);
+ }
+
+ @Test
+ public void manageRoleFromsFromControllerPermissionShouldBeDeclaredByPermissionController()
+ throws PackageManager.NameNotFoundException {
+ PermissionInfo permissionInfo = sPackageManager.getPermissionInfo(
+ PERMISSION_MANAGE_ROLES_FROM_CONTROLLER, 0);
+
+ assertThat(permissionInfo.packageName).isEqualTo(
+ sPackageManager.getPermissionControllerPackageName());
+ assertThat(permissionInfo.getProtection()).isEqualTo(PermissionInfo.PROTECTION_SIGNATURE);
+ assertThat(permissionInfo.getProtectionFlags()).isEqualTo(0);
+ }
+
+ @Test
+ public void removeSmsRoleHolderThenDialerRoleAppOpIsNotDenied() throws Exception {
+ if (!(sRoleManager.isRoleAvailable(RoleManager.ROLE_DIALER)
+ && sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS))) {
+ return;
+ }
+
+ addRoleHolder(RoleManager.ROLE_DIALER, APP_PACKAGE_NAME);
+ addRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+ removeRoleHolder(RoleManager.ROLE_SMS, APP_PACKAGE_NAME);
+
+ assertThat(AppOpsUtils.getOpMode(APP_PACKAGE_NAME, AppOpsManager.OPSTR_SEND_SMS))
+ .isEqualTo(AppOpsManager.MODE_ALLOWED);
+ }
+
+ @Test
+ public void smsRoleHasHolder() throws Exception {
+ if (!sRoleManager.isRoleAvailable(RoleManager.ROLE_SMS)) {
+ return;
+ }
+
+ assertThat(getRoleHolders(RoleManager.ROLE_SMS)).isNotEmpty();
+ }
+
+ private List<String> getRoleHolders(@NonNull String roleName) throws Exception {
+ return callWithShellPermissionIdentity(() -> sRoleManager.getRoleHolders(roleName));
+ }
+
private void assertIsRoleHolder(@NonNull String roleName, @NonNull String packageName,
boolean shouldBeRoleHolder) throws Exception {
List<String> packageNames = getRoleHolders(roleName);
+
if (shouldBeRoleHolder) {
assertThat(packageNames).contains(packageName);
} else {
@@ -197,53 +431,49 @@
}
}
- private List<String> getRoleHolders(@NonNull String roleName) throws Exception {
- return callWithShellPermissionIdentity(() -> sRoleManager.getRoleHolders(roleName));
- }
-
private void addRoleHolder(@NonNull String roleName, @NonNull String packageName)
throws Exception {
- UserHandle user = Process.myUserHandle();
- Executor executor = sContext.getMainExecutor();
- boolean[] successful = new boolean[1];
- CountDownLatch latch = new CountDownLatch(1);
+ CallbackFuture future = new CallbackFuture();
runWithShellPermissionIdentity(() -> sRoleManager.addRoleHolderAsUser(roleName,
- packageName, 0, user, executor, new RoleManagerCallback() {
- @Override
- public void onSuccess() {
- successful[0] = true;
- latch.countDown();
- }
- @Override
- public void onFailure() {
- successful[0] = false;
- latch.countDown();
- }
- }));
- latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- assertThat(successful[0]).isTrue();
+ packageName, 0, Process.myUserHandle(), sContext.getMainExecutor(), future));
+ future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
}
private void removeRoleHolder(@NonNull String roleName, @NonNull String packageName)
throws Exception {
- UserHandle user = Process.myUserHandle();
- Executor executor = sContext.getMainExecutor();
- boolean[] successful = new boolean[1];
- CountDownLatch latch = new CountDownLatch(1);
+ CallbackFuture future = new CallbackFuture();
runWithShellPermissionIdentity(() -> sRoleManager.removeRoleHolderAsUser(roleName,
- packageName, 0, user, executor, new RoleManagerCallback() {
- @Override
- public void onSuccess() {
- successful[0] = true;
- latch.countDown();
- }
- @Override
- public void onFailure() {
- successful[0] = false;
- latch.countDown();
- }
- }));
- latch.await(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
- assertThat(successful[0]).isTrue();
+ packageName, 0, Process.myUserHandle(), sContext.getMainExecutor(), future));
+ future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ }
+
+ private void clearRoleHolders(@NonNull String roleName) throws Exception {
+ CallbackFuture future = new CallbackFuture();
+ runWithShellPermissionIdentity(() -> sRoleManager.clearRoleHoldersAsUser(roleName, 0,
+ Process.myUserHandle(), sContext.getMainExecutor(), future));
+ future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+ }
+
+ private static class ListenerFuture extends CompletableFuture<Pair<String, UserHandle>>
+ implements OnRoleHoldersChangedListener {
+
+ @Override
+ public void onRoleHoldersChanged(@NonNull String roleName, @NonNull UserHandle user) {
+ complete(new Pair<>(roleName, user));
+ }
+ }
+
+ private static class CallbackFuture extends CompletableFuture<Void>
+ implements RoleManagerCallback {
+
+ @Override
+ public void onSuccess() {
+ complete(null);
+ }
+
+ @Override
+ public void onFailure() {
+ completeExceptionally(new RuntimeException());
+ }
}
}
diff --git a/tests/tests/role/src/android/app/role/cts/WaitForResultActivity.java b/tests/tests/role/src/android/app/role/cts/WaitForResultActivity.java
index 8f889bf..03944a5 100644
--- a/tests/tests/role/src/android/app/role/cts/WaitForResultActivity.java
+++ b/tests/tests/role/src/android/app/role/cts/WaitForResultActivity.java
@@ -20,6 +20,9 @@
import android.content.Intent;
import android.util.Pair;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -34,11 +37,12 @@
private int mResultCode;
private Intent mData;
- public void startActivityToWaitForResult(Intent intent) {
+ public void startActivityToWaitForResult(@NonNull Intent intent) {
mLatch = new CountDownLatch(1);
startActivityForResult(intent, REQUEST_CODE_WAIT_FOR_RESULT);
}
+ @NonNull
public Pair<Integer, Intent> waitForActivityResult(long timeoutMillis)
throws InterruptedException {
mLatch.await(timeoutMillis, TimeUnit.MILLISECONDS);
@@ -46,7 +50,7 @@
}
@Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
if (requestCode == REQUEST_CODE_WAIT_FOR_RESULT) {
mResultCode = resultCode;
mData = data;
diff --git a/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java b/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
index c960101..ae5bc8e 100644
--- a/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
+++ b/tests/tests/security/src/android/security/cts/AmbiguousBundlesTest.java
@@ -22,6 +22,7 @@
import android.os.BaseBundle;
import android.os.Bundle;
import android.os.Parcel;
+import android.os.Parcelable;
import android.annotation.SuppressLint;
import java.io.InputStream;
@@ -33,6 +34,114 @@
public class AmbiguousBundlesTest extends AndroidTestCase {
+ /*
+ * b/71508348
+ */
+ @SecurityTest(minPatchLevel = "2018-06")
+ public void test_android_CVE_2018_9339() throws Exception {
+
+ Ambiguator ambiguator = new Ambiguator() {
+
+ private final Field parcelledDataField;
+
+ private static final String BASE_PARCELABLE = "android.telephony.CellInfo";
+ private final Parcelable smallerParcelable;
+ private final Parcelable biggerParcelable;
+
+ {
+ parcelledDataField = BaseBundle.class.getDeclaredField("mParcelledData");
+ parcelledDataField.setAccessible(true);
+
+ smallerParcelable = (Parcelable) Class.forName("android.telephony.CellInfoGsm").newInstance();
+ biggerParcelable = (Parcelable) Class.forName("android.telephony.CellInfoLte").newInstance();
+
+ Parcel p = Parcel.obtain();
+ smallerParcelable.writeToParcel(p, 0);
+ int smallerParcelableSize = p.dataPosition();
+ biggerParcelable.writeToParcel(p, 0);
+ int biggerParcelableSize = p.dataPosition() - smallerParcelableSize;
+ p.recycle();
+
+ if (smallerParcelableSize >= biggerParcelableSize) {
+ throw new AssertionError("smallerParcelableSize >= biggerParcelableSize");
+ }
+ }
+
+ @Override
+ public Bundle make(Bundle preReSerialize, Bundle postReSerialize) throws Exception {
+ // Find key that has hash below everything else
+ Random random = new Random(1234);
+ int minHash = 0;
+ for (String s : preReSerialize.keySet()) {
+ minHash = Math.min(minHash, s.hashCode());
+ }
+ for (String s : postReSerialize.keySet()) {
+ minHash = Math.min(minHash, s.hashCode());
+ }
+
+ String key;
+ int keyHash;
+
+ do {
+ key = randomString(random);
+ keyHash = key.hashCode();
+ } while (keyHash >= minHash);
+
+ // Pad bundles
+ padBundle(postReSerialize, preReSerialize.size() + 1, minHash, random);
+ padBundle(preReSerialize, postReSerialize.size() - 1, minHash, random);
+
+ // Write bundle
+ Parcel parcel = Parcel.obtain();
+
+ parcel.writeInt(preReSerialize.size() + 1); // Num key-value pairs
+ parcel.writeString(key); // Key
+
+ parcel.writeInt(VAL_PARCELABLE);
+ parcel.writeString("android.service.autofill.SaveRequest");
+
+ // read/writeTypedArrayList
+ parcel.writeInt(2); // Number of items in typed array list
+ parcel.writeInt(1); // Item present flag
+ parcel.writeString(BASE_PARCELABLE);
+ biggerParcelable.writeToParcel(parcel, 0);
+ parcel.writeInt(1); // Item present flag
+ smallerParcelable.writeToParcel(parcel, 0);
+
+ // read/writeBundle
+ int bundleLengthPosition = parcel.dataPosition();
+ parcel.writeInt(0); // Placeholder, will be replaced
+ parcel.writeInt(BUNDLE_MAGIC);
+ int bundleStart = parcel.dataPosition();
+ for (int i = 0; i < INNER_BUNDLE_PADDING; i++) {
+ parcel.writeInt(414100 + i); // Padding in inner bundle
+ }
+ parcel.writeInt(-1); // Inner bundle length after re-de-serialization (-1 = null Bundle)
+ writeBundleSkippingHeaders(parcel, postReSerialize);
+ int bundleEnd = parcel.dataPosition();
+
+ // Update inner Bundle length
+ parcel.setDataPosition(bundleLengthPosition);
+ parcel.writeInt(bundleEnd - bundleStart);
+ parcel.setDataPosition(bundleEnd);
+
+ // Write original Bundle contents
+ writeBundleSkippingHeaders(parcel, preReSerialize);
+
+ // Package crafted Parcel into Bundle so it can be used in regular Android APIs
+ parcel.setDataPosition(0);
+ Bundle bundle = new Bundle();
+ parcelledDataField.set(bundle, parcel);
+ return bundle;
+ }
+ };
+
+ testAmbiguator(ambiguator);
+ }
+
+ /*
+ * b/62998805
+ */
@SecurityTest(minPatchLevel = "2017-10")
public void test_android_CVE_2017_0806() throws Exception {
Ambiguator ambiguator = new Ambiguator() {
@@ -88,51 +197,14 @@
parcelledDataField.set(bundle, parcel);
return bundle;
}
-
- @Override
- protected String makeStringToInject(Bundle stuffToInject, Random random) {
- Parcel p = Parcel.obtain();
- p.writeInt(0);
- p.writeInt(0);
-
- Parcel p2 = Parcel.obtain();
- stuffToInject.writeToParcel(p2, 0);
- int p2Len = p2.dataPosition() - BUNDLE_SKIP;
-
- for (int i = 0; i < p2Len / 4 + 4; i++) {
- int paddingVal;
- if (i > 3) {
- paddingVal = i;
- } else {
- paddingVal = random.nextInt();
- }
- p.writeInt(paddingVal);
-
- }
-
- p.appendFrom(p2, BUNDLE_SKIP, p2Len);
- p2.recycle();
-
- while (p.dataPosition() % 8 != 0) p.writeInt(0);
- for (int i = 0; i < 2; i++) {
- p.writeInt(0);
- }
-
- int len = p.dataPosition() / 2 - 1;
- p.writeInt(0); p.writeInt(0);
- p.setDataPosition(0);
- p.writeInt(len);
- p.writeInt(len);
- p.setDataPosition(0);
- String result = p.readString();
- p.recycle();
- return result;
- }
};
testAmbiguator(ambiguator);
}
+ /*
+ * b/73252178
+ */
@SecurityTest(minPatchLevel = "2018-05")
public void test_android_CVE_2017_13311() throws Exception {
Ambiguator ambiguator = new Ambiguator() {
@@ -219,16 +291,14 @@
parcelledDataField.set(bundle, parcel);
return bundle;
}
-
- @Override
- protected String makeStringToInject(Bundle stuffToInject, Random random) {
- return null;
- }
};
testAmbiguator(ambiguator);
}
+ /*
+ * b/71714464
+ */
@SecurityTest(minPatchLevel = "2018-04")
public void test_android_CVE_2017_13287() throws Exception {
Ambiguator ambiguator = new Ambiguator() {
@@ -283,46 +353,6 @@
parcelledDataField.set(bundle, parcel);
return bundle;
}
-
- @Override
- protected String makeStringToInject(Bundle stuffToInject, Random random) {
- Parcel p = Parcel.obtain();
- p.writeInt(0);
- p.writeInt(0);
-
- Parcel p2 = Parcel.obtain();
- stuffToInject.writeToParcel(p2, 0);
- int p2Len = p2.dataPosition() - BUNDLE_SKIP;
-
- for (int i = 0; i < p2Len / 4 + 4; i++) {
- int paddingVal;
- if (i > 3) {
- paddingVal = i;
- } else {
- paddingVal = random.nextInt();
- }
- p.writeInt(paddingVal);
-
- }
-
- p.appendFrom(p2, BUNDLE_SKIP, p2Len);
- p2.recycle();
-
- while (p.dataPosition() % 8 != 0) p.writeInt(0);
- for (int i = 0; i < 2; i++) {
- p.writeInt(0);
- }
-
- int len = p.dataPosition() / 2 - 1;
- p.writeInt(0); p.writeInt(0);
- p.setDataPosition(0);
- p.writeInt(len);
- p.writeInt(len);
- p.setDataPosition(0);
- String result = p.readString();
- p.recycle();
- return result;
- }
};
testAmbiguator(ambiguator);
@@ -379,6 +409,9 @@
protected static final int PROCSTATS_SYS_MEM_USAGE_COUNT = 16;
protected static final int PROCSTATS_SPARSE_MAPPING_TABLE_ARRAY_SIZE = 4096;
+ protected static final int BUNDLE_MAGIC = 0x4C444E42;
+ protected static final int INNER_BUNDLE_PADDING = 1;
+
protected final Field parcelledDataField;
public Ambiguator() throws Exception {
@@ -388,7 +421,44 @@
abstract public Bundle make(Bundle preReSerialize, Bundle postReSerialize) throws Exception;
- abstract protected String makeStringToInject(Bundle stuffToInject, Random random);
+ protected String makeStringToInject(Bundle stuffToInject, Random random) {
+ Parcel p = Parcel.obtain();
+ p.writeInt(0);
+ p.writeInt(0);
+
+ Parcel p2 = Parcel.obtain();
+ stuffToInject.writeToParcel(p2, 0);
+ int p2Len = p2.dataPosition() - BUNDLE_SKIP;
+
+ for (int i = 0; i < p2Len / 4 + 4; i++) {
+ int paddingVal;
+ if (i > 3) {
+ paddingVal = i;
+ } else {
+ paddingVal = random.nextInt();
+ }
+ p.writeInt(paddingVal);
+
+ }
+
+ p.appendFrom(p2, BUNDLE_SKIP, p2Len);
+ p2.recycle();
+
+ while (p.dataPosition() % 8 != 0) p.writeInt(0);
+ for (int i = 0; i < 2; i++) {
+ p.writeInt(0);
+ }
+
+ int len = p.dataPosition() / 2 - 1;
+ p.writeInt(0); p.writeInt(0);
+ p.setDataPosition(0);
+ p.writeInt(len);
+ p.writeInt(len);
+ p.setDataPosition(0);
+ String result = p.readString();
+ p.recycle();
+ return result;
+ }
protected static void writeBundleSkippingHeaders(Parcel parcel, Bundle bundle) {
Parcel p2 = Parcel.obtain();
diff --git a/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java b/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
index c0db7fb..04e5b62 100644
--- a/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
+++ b/tests/tests/syncmanager/src/android/content/syncmanager/cts/CtsSyncManagerTest.java
@@ -74,6 +74,8 @@
public static final boolean DEBUG = false;
private static final int TIMEOUT_MS = 10 * 60 * 1000;
+ private static final int STANDBY_BUCKET_NEVER = 50;
+
@Rule
public final OnFailureRule mDumpOnFailureRule = new OnFailureRule(TAG) {
@Override
@@ -232,6 +234,36 @@
});
}
+ @Test
+ public void testInitialSyncInNeverBucket() throws Exception {
+ removeAllAccounts();
+
+ AmUtils.setStandbyBucket(APP1_PACKAGE, STANDBY_BUCKET_NEVER);
+
+ mRpc.invoke(APP1_PACKAGE, rb -> rb.setClearSyncInvocations(
+ ClearSyncInvocations.newBuilder()));
+
+ addAccountAndLetInitialSyncRun(ACCOUNT_1_A, APP1_AUTHORITY);
+
+ // App should be brought out of the NEVER bucket to handle the sync
+ assertEquals(UsageStatsManager.STANDBY_BUCKET_WORKING_SET,
+ AmUtils.getStandbyBucket(APP1_PACKAGE));
+
+ // Check the sync request parameters.
+ Response res = mRpc.invoke(APP1_PACKAGE,
+ rb -> rb.setGetSyncInvocations(GetSyncInvocations.newBuilder()));
+ assertEquals(1, res.getSyncInvocations().getSyncInvocationsCount());
+
+ SyncInvocation si = res.getSyncInvocations().getSyncInvocations(0);
+
+ assertEquals(ACCOUNT_1_A.name, si.getAccountName());
+ assertEquals(ACCOUNT_1_A.type, si.getAccountType());
+ assertEquals(APP1_AUTHORITY, si.getAuthority());
+
+ Bundle extras = ParcelUtils.fromBytes(si.getExtras().toByteArray());
+ assertTrue(extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE));
+ }
+
// WIP This test doesn't work yet.
// @Test
// public void testSoftErrorRetriesFrequentApp() throws Exception {
diff --git a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java
index b4a90db..01c1e60 100644
--- a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java
+++ b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/DefaultSmsAppHelper.java
@@ -21,26 +21,18 @@
import androidx.test.InstrumentationRegistry;
class DefaultSmsAppHelper {
- static void setDefaultSmsApp(boolean setToSmsApp) {
+ static void ensureDefaultSmsApp() {
String packageName =
InstrumentationRegistry.getInstrumentation().getContext().getPackageName();
- setDefaultSmsAppSetting(setToSmsApp, packageName);
+ runShellCommand(
+ String.format("settings put secure sms_default_application %s", packageName));
+
// FIXME: Required because setting default SMS app to a given package adds appops WRITE_SMS
// permissions to the given package, but changing away from a given package seem to remove
// the appops permission from the given package. This is a known issue and should be fixed
// for Q.
- setSmsWritePermission(setToSmsApp, packageName);
- }
-
- private static void setDefaultSmsAppSetting(boolean setToSmsApp, String packageName) {
- runShellCommand(String.format("settings put secure sms_default_application %s",
- setToSmsApp ? packageName : "default"));
- }
-
- private static void setSmsWritePermission(boolean setToSmsApp, String packageName) {
- runShellCommand(String.format("appops set %s WRITE_SMS %s", packageName,
- setToSmsApp ? "allow" : "default"));
+ runShellCommand(String.format("appops set %s WRITE_SMS allow", packageName));
}
}
diff --git a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/MmsPartTest.java b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/MmsPartTest.java
index 7e29dee..26eee31 100644
--- a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/MmsPartTest.java
+++ b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/MmsPartTest.java
@@ -16,8 +16,6 @@
package android.telephonyprovider.cts;
-import static android.telephonyprovider.cts.DefaultSmsAppHelper.setDefaultSmsApp;
-
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
@@ -28,10 +26,9 @@
import android.net.Uri;
import android.provider.Telephony;
-import androidx.test.InstrumentationRegistry;
-
import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import javax.annotation.Nullable;
@@ -45,6 +42,11 @@
*/
private static final String TEST_MESSAGE_ID = "100";
+ @BeforeClass
+ public static void ensureDefaultSmsApp() {
+ DefaultSmsAppHelper.ensureDefaultSmsApp();
+ }
+
@Before
public void setupTestEnvironment() {
cleanup();
@@ -53,18 +55,12 @@
@AfterClass
public static void cleanup() {
- ContentResolver contentResolver =
- InstrumentationRegistry.getInstrumentation().getContext().getContentResolver();
-
- setDefaultSmsApp(true);
+ ContentResolver contentResolver = getInstrumentation().getContext().getContentResolver();
contentResolver.delete(Telephony.Mms.Part.CONTENT_URI, null, null);
- setDefaultSmsApp(false);
}
@Test
public void testMmsPartInsert_cannotInsertPartWithDataColumn() {
- setDefaultSmsApp(true);
-
ContentValues values = new ContentValues();
values.put(Telephony.Mms.Part._DATA, "/dev/urandom");
values.put(Telephony.Mms.Part.NAME, "testMmsPartInsert_cannotInsertPartWithDataColumn");
@@ -75,8 +71,6 @@
@Test
public void testMmsPartInsert_canInsertPartWithoutDataColumn() {
- setDefaultSmsApp(true);
-
String name = "testMmsInsert_canInsertPartWithoutDataColumn";
Uri uri = insertTestMmsPartWithName(name);
@@ -85,8 +79,6 @@
@Test
public void testMmsPart_deletedPartIdsAreNotReused() {
- setDefaultSmsApp(true);
-
long id1 = insertAndVerifyMmsPartReturningId("testMmsPart_deletedPartIdsAreNotReused_1");
deletePartById(id1);
diff --git a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/MmsTest.java b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/MmsTest.java
index 4dc31fe..707e2b2 100644
--- a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/MmsTest.java
+++ b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/MmsTest.java
@@ -16,8 +16,6 @@
package android.telephonyprovider.cts;
-import static android.telephonyprovider.cts.DefaultSmsAppHelper.setDefaultSmsApp;
-
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
@@ -28,11 +26,11 @@
import android.net.Uri;
import android.provider.Telephony;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
import javax.annotation.Nullable;
@@ -41,6 +39,11 @@
public class MmsTest {
private ContentResolver mContentResolver;
+ @BeforeClass
+ public static void ensureDefaultSmsApp() {
+ DefaultSmsAppHelper.ensureDefaultSmsApp();
+ }
+
@Before
public void setupTestEnvironment() {
cleanup();
@@ -49,42 +52,13 @@
@AfterClass
public static void cleanup() {
- ContentResolver contentResolver =
- InstrumentationRegistry.getInstrumentation().getContext().getContentResolver();
-
- setDefaultSmsApp(true);
+ ContentResolver contentResolver = getInstrumentation().getContext().getContentResolver();
contentResolver.delete(Telephony.Mms.CONTENT_URI, null, null);
- setDefaultSmsApp(false);
}
@Test
- public void testMmsInsert_insertFailsWhenNotDefault() {
- setDefaultSmsApp(false);
-
- String testSubject1 = "testMmsInsert_withoutPermission1";
- String testSubject2 = "testMmsInsert_withoutPermission2";
-
- Uri uri1 = insertTestMmsSendReqWithSubject(testSubject1);
- Uri uri2 = insertTestMmsSendReqWithSubject(testSubject2);
-
- // If the URIs are the same, then the inserts failed. This is either due to insert returning
- // null for both, or the appops check on insert returning a dummy string.
- assertThat(uri1).isEqualTo(uri2);
-
- // As this point, we can assume some failure of the insert since each URI points to the same
- // row, which means at least one did not insert properly. We can then double check that the
- // returned URI did not somehow have the correct subject. Since CTS tests should clear the
- // environment, we should be reasonable sure that this assertion will not lead to a false
- // failure.
- assertThatMmsInsertFailed(uri1, testSubject1);
- assertThatMmsInsertFailed(uri2, testSubject2);
- }
-
- @Test
- public void testMmsInsert_insertSendReqSucceedsWhenDefault() {
- setDefaultSmsApp(true);
-
- String expectedSubject = "testMmsInsert_withPermission";
+ public void testMmsInsert_insertSendReqSucceeds() {
+ String expectedSubject = "testMmsInsert_insertSendReqSucceeds";
Uri uri = insertTestMmsSendReqWithSubject(expectedSubject);
@@ -101,8 +75,6 @@
@Test
public void testMmsDelete() {
- setDefaultSmsApp(true);
-
String expectedSubject = "testMmsDelete";
Uri uri = insertTestMmsSendReqWithSubject(expectedSubject);
@@ -119,39 +91,9 @@
}
@Test
- public void testMmsQuery_canViewSendReqMessageIfNotDefault() {
- setDefaultSmsApp(true);
-
- String expectedSubject = "testMmsInsert_withPermission";
-
- Uri uri = insertTestMmsSendReqWithSubject(expectedSubject);
-
- setDefaultSmsApp(false);
-
- assertThatMmsInsertSucceeded(uri, expectedSubject);
- }
-
- @Test
- public void testMmsQuery_cannotViewNotificationIndMessagesIfNotDefault() {
- setDefaultSmsApp(true);
-
+ public void testMmsQuery_canViewNotificationIndMessages() {
int messageType = PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
- String expectedSubject = "testMmsQuery_cannotViewNotificationIndMessagesIfNotDefault";
-
- Uri uri = insertTestMms(expectedSubject, messageType);
-
- setDefaultSmsApp(false);
-
- Cursor cursor = mContentResolver.query(uri, null, null, null);
- assertThat(cursor.getCount()).isEqualTo(0);
- }
-
- @Test
- public void testMmsQuery_canViewNotificationIndMessagesIfDefault() {
- setDefaultSmsApp(true);
-
- int messageType = PduHeaders.MESSAGE_TYPE_NOTIFICATION_IND;
- String expectedSubject = "testMmsQuery_canViewNotificationIndMessagesIfDefault";
+ String expectedSubject = "testMmsQuery_canViewNotificationIndMessages";
Uri uri = insertTestMms(expectedSubject, messageType);
diff --git a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ThreadsTest.java b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ThreadsTest.java
index c92c3a8..e6d8520 100644
--- a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ThreadsTest.java
+++ b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/ThreadsTest.java
@@ -1,7 +1,5 @@
package android.telephonyprovider.cts;
-import static android.telephonyprovider.cts.DefaultSmsAppHelper.setDefaultSmsApp;
-
import static androidx.test.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
@@ -12,11 +10,11 @@
import android.net.Uri;
import android.provider.Telephony;
-import androidx.test.InstrumentationRegistry;
import androidx.test.filters.SmallTest;
import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.BeforeClass;
import org.junit.Test;
@SmallTest
@@ -24,6 +22,11 @@
private Context mContext;
private ContentResolver mContentResolver;
+ @BeforeClass
+ public static void ensureDefaultSmsApp() {
+ DefaultSmsAppHelper.ensureDefaultSmsApp();
+ }
+
@Before
public void setupTestEnvironment() {
cleanup();
@@ -33,18 +36,12 @@
@AfterClass
public static void cleanup() {
- ContentResolver contentResolver =
- InstrumentationRegistry.getInstrumentation().getContext().getContentResolver();
-
- setDefaultSmsApp(true);
+ ContentResolver contentResolver = getInstrumentation().getContext().getContentResolver();
contentResolver.delete(Telephony.Threads.CONTENT_URI, null, null);
- setDefaultSmsApp(false);
}
@Test
public void testThreadDeletion_doNotReuseThreadIdsFromEmptyThreads() {
- setDefaultSmsApp(true);
-
String destination1 = "+19998880001";
String destination2 = "+19998880002";
diff --git a/tests/tests/view/AndroidManifest.xml b/tests/tests/view/AndroidManifest.xml
index 3cc884a..3dc41d0 100644
--- a/tests/tests/view/AndroidManifest.xml
+++ b/tests/tests/view/AndroidManifest.xml
@@ -382,6 +382,12 @@
<action android:name="android.intent.action.MAIN" />
</intent-filter>
</activity>
+
+ <activity android:name="android.view.cts.SystemGestureExclusionActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ </intent-filter>
+ </activity>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/view/res/layout/gesture_exclusion_basic.xml b/tests/tests/view/res/layout/gesture_exclusion_basic.xml
new file mode 100644
index 0000000..f35c4b4
--- /dev/null
+++ b/tests/tests/view/res/layout/gesture_exclusion_basic.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2019 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"
+ android:id="@+id/abslistview_root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" >
+ <View android:id="@+id/animating_view"
+ android:layout_width="5px"
+ android:layout_height="5px"
+ android:layout_gravity="top|left"
+ android:background="#ff00ff00" />
+</FrameLayout>
\ No newline at end of file
diff --git a/tests/tests/view/res/layout/view_layout.xml b/tests/tests/view/res/layout/view_layout.xml
index ab0bd04..38a5177 100644
--- a/tests/tests/view/res/layout/view_layout.xml
+++ b/tests/tests/view/res/layout/view_layout.xml
@@ -26,8 +26,8 @@
<android.view.cts.MockView
android:id="@+id/mock_view"
- android:layout_width="100px"
- android:layout_height="100px"/>
+ android:layout_width="100dp"
+ android:layout_height="100dp"/>
<android.view.cts.MockView
android:id="@+id/scroll_view"
diff --git a/tests/tests/alarmclock/service/src/android/alarmclock/service/SettingsActivity.java b/tests/tests/view/src/android/view/cts/SystemGestureExclusionActivity.java
similarity index 71%
rename from tests/tests/alarmclock/service/src/android/alarmclock/service/SettingsActivity.java
rename to tests/tests/view/src/android/view/cts/SystemGestureExclusionActivity.java
index 0c4d289..a95b611 100644
--- a/tests/tests/alarmclock/service/src/android/alarmclock/service/SettingsActivity.java
+++ b/tests/tests/view/src/android/view/cts/SystemGestureExclusionActivity.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,17 +14,15 @@
* limitations under the License.
*/
-package android.alarmclock.service;
+package android.view.cts;
import android.app.Activity;
import android.os.Bundle;
-/**
- * Stub activity to test out settings selection for voice interactor.
- */
-public class SettingsActivity extends Activity {
+public class SystemGestureExclusionActivity extends Activity {
@Override
- public void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setContentView(R.layout.gesture_exclusion_basic);
}
}
diff --git a/tests/tests/view/src/android/view/cts/SystemGestureExclusionRectsTest.java b/tests/tests/view/src/android/view/cts/SystemGestureExclusionRectsTest.java
new file mode 100644
index 0000000..779efd7
--- /dev/null
+++ b/tests/tests/view/src/android/view/cts/SystemGestureExclusionRectsTest.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2019 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.view.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.app.Activity;
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewTreeObserver;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.google.common.collect.Lists;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.function.Consumer;
+
+/**
+ * Test {@link View#setSystemGestureExclusionRects} and the full-tree reporting of transformed
+ * changes by {@link ViewTreeObserver#addOnSystemGestureExclusionRectsChangedListener(Consumer)}.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class SystemGestureExclusionRectsTest {
+ @Rule
+ public ActivityTestRule<SystemGestureExclusionActivity> mActivityRule =
+ new ActivityTestRule<>(SystemGestureExclusionActivity.class);
+
+ @Test
+ public void staticView() throws Throwable {
+ final Activity activity = mActivityRule.getActivity();
+
+ final CountDownLatch received = new CountDownLatch(1);
+ final List<Rect>[] holder = new List[1];
+ final Rect expected = new Rect();
+
+ mActivityRule.runOnUiThread(() -> {
+ final View v = activity.findViewById(R.id.animating_view);
+ final ViewTreeObserver vto = v.getViewTreeObserver();
+ vto.addOnSystemGestureExclusionRectsChangedListener(rects -> {
+ int[] point = new int[2];
+ v.getLocationInWindow(point);
+ expected.left = point[0];
+ expected.top = point[1];
+ expected.right = expected.left + v.getWidth();
+ expected.bottom = expected.top + v.getHeight();
+
+ holder[0] = rects;
+ received.countDown();
+ });
+ v.setSystemGestureExclusionRects(
+ Lists.newArrayList(new Rect(0, 0, 5, 5)));
+ });
+
+ assertTrue("didn't receive exclusion rects", received.await(3, SECONDS));
+
+ assertEquals("view position reference width " + expected, 5, expected.width());
+ assertEquals("view position reference height", 5, expected.height());
+ assertNotNull("exclusion rects reported", holder[0]);
+ assertEquals("one exclusion rect", 1, holder[0].size());
+ assertEquals("view bounds equals exclusion rect", expected, holder[0].get(0));
+ }
+
+ /**
+ * Animate a view from X=0 to X=30px and verify that the static exclusion rect follows.
+ */
+ @Test
+ public void animatingView() throws Throwable {
+ final Activity activity = mActivityRule.getActivity();
+
+ final List<List<Rect>> results = new ArrayList<>();
+ final CountDownLatch doneAnimating = new CountDownLatch(1);
+
+ final Consumer<List<Rect>> vtoListener = results::add;
+
+ mActivityRule.runOnUiThread(() -> {
+ final View v = activity.findViewById(R.id.animating_view);
+ final ViewTreeObserver vto = v.getViewTreeObserver();
+ vto.addOnSystemGestureExclusionRectsChangedListener(vtoListener);
+
+ v.setSystemGestureExclusionRects(
+ Lists.newArrayList(new Rect(0, 0, 5, 5)));
+ v.animate().x(30).setListener(new AnimationDoneListener(doneAnimating));
+ });
+
+ assertTrue("didn't finish animating", doneAnimating.await(3, SECONDS));
+
+ // Sloppy size range since these rects are transformed and may straddle pixel boundaries
+ List<Integer> sizeRange = Lists.newArrayList(5, 6);
+ Rect prev = null;
+ assertFalse("results empty", results.isEmpty());
+ for (List<Rect> list : results) {
+ assertEquals("one result rect", 1, list.size());
+ final Rect first = list.get(0);
+ if (prev != null) {
+ assertTrue("left edge " + first.left + " >= previous " + prev.left,
+ first.left >= prev.left);
+ }
+ assertTrue("rect had expected width", sizeRange.contains(first.width()));
+ assertTrue("rect had expected height", sizeRange.contains(first.height()));
+ prev = first;
+ }
+
+ assertEquals("reached expected animated destination", prev.right, 35);
+
+ // Make sure we don't get any more callbacks after removing the VTO listener
+ final List<List<Rect>> oldResults = new ArrayList<>(results);
+ final CountDownLatch secondDoneAnimating = new CountDownLatch(1);
+ mActivityRule.runOnUiThread(() -> {
+ final View v = activity.findViewById(R.id.animating_view);
+ final ViewTreeObserver vto = v.getViewTreeObserver();
+ vto.removeOnSystemGestureExclusionRectsChangedListener(vtoListener);
+ v.animate().x(0).setListener(new AnimationDoneListener(secondDoneAnimating));
+ });
+
+ assertTrue("didn't finish second animation", secondDoneAnimating.await(3, SECONDS));
+
+ assertEquals("got unexpected exclusion rects", oldResults, results);
+ }
+
+ private static class AnimationDoneListener extends AnimatorListenerAdapter {
+ private final CountDownLatch mLatch;
+
+ AnimationDoneListener(CountDownLatch latch) {
+ mLatch = latch;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mLatch.countDown();
+ }
+ }
+}
diff --git a/tests/tests/view/src/android/view/cts/ViewTest.java b/tests/tests/view/src/android/view/cts/ViewTest.java
index 8c20fef..245e5c0 100644
--- a/tests/tests/view/src/android/view/cts/ViewTest.java
+++ b/tests/tests/view/src/android/view/cts/ViewTest.java
@@ -2013,24 +2013,30 @@
@Test
public void testMeasure() throws Throwable {
final MockView view = (MockView) mActivity.findViewById(R.id.mock_view);
+
+ int size1 =
+ (int) (100 * view.getContext().getResources().getDisplayMetrics().density + 0.5);
+ int size2 =
+ (int) (75 * view.getContext().getResources().getDisplayMetrics().density + 0.5);
+
assertTrue(view.hasCalledOnMeasure());
- assertEquals(100, view.getMeasuredWidth());
- assertEquals(100, view.getMeasuredHeight());
+ assertEquals(size1, view.getMeasuredWidth());
+ assertEquals(size1, view.getMeasuredHeight());
view.reset();
mActivityRule.runOnUiThread(view::requestLayout);
mInstrumentation.waitForIdleSync();
assertTrue(view.hasCalledOnMeasure());
- assertEquals(100, view.getMeasuredWidth());
- assertEquals(100, view.getMeasuredHeight());
+ assertEquals(size1, view.getMeasuredWidth());
+ assertEquals(size1, view.getMeasuredHeight());
view.reset();
- final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(200, 100);
+ final LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(size2, size1);
mActivityRule.runOnUiThread(() -> view.setLayoutParams(layoutParams));
mInstrumentation.waitForIdleSync();
assertTrue(view.hasCalledOnMeasure());
- assertEquals(200, view.getMeasuredWidth());
- assertEquals(100, view.getMeasuredHeight());
+ assertEquals(size2, view.getMeasuredWidth());
+ assertEquals(size1, view.getMeasuredHeight());
}
@Test(expected=NullPointerException.class)
@@ -2715,11 +2721,13 @@
final View view = mActivity.findViewById(R.id.mock_view);
Rect rect = new Rect();
+ int size = (int) (100 * view.getContext().getResources().getDisplayMetrics().density + 0.5);
+
assertTrue(view.getLocalVisibleRect(rect));
assertEquals(0, rect.left);
assertEquals(0, rect.top);
- assertEquals(100, rect.right);
- assertEquals(100, rect.bottom);
+ assertEquals(size, rect.right);
+ assertEquals(size, rect.bottom);
final LinearLayout.LayoutParams layoutParams1 = new LinearLayout.LayoutParams(0, 300);
mActivityRule.runOnUiThread(() -> view.setLayoutParams(layoutParams1));