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