Merge "CTS test for Android Security b/212610736" into qt-dev am: a89b9c23f1 am: 7129ddab13 am: eafcee5f84 am: 7357ff6dd8 am: 88d378cd18

Original change: https://googleplex-android-review.googlesource.com/c/platform/cts/+/17397446

Change-Id: Ie92fa67b677c762d2cef3bda9ea57f42aab15f45
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java b/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
index 495c3ef..fe314c42 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/ReportExporter.java
@@ -23,7 +23,6 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.ParcelFileDescriptor;
-import android.util.Log;
 
 import com.android.compatibility.common.util.FileUtil;
 import com.android.compatibility.common.util.IInvocationResult;
@@ -50,8 +49,6 @@
  * Background task to generate a report and save it to external storage.
  */
 class ReportExporter extends AsyncTask<Void, Void, String> {
-    private static final String TAG = ReportExporter.class.getSimpleName();
-    private static final boolean DEBUG = true;
 
     public static final String REPORT_DIRECTORY = "VerifierReports";
     public static final String LOGS_DIRECTORY = "ReportLogFiles";
@@ -79,15 +76,11 @@
     // so that they will get ZIPped into the transmitted file.
     //
     private void copyReportFiles(File tempDir) {
-        if (DEBUG) {
-            Log.d(TAG, "copyReportFiles(" + tempDir.getAbsolutePath() + ")");
-        }
-
         File externalStorageDirectory = Environment.getExternalStorageDirectory();
         File reportLogFolder =
                 new File(Environment.getExternalStorageDirectory().getAbsolutePath()
                         + File.separator
-                        + LOGS_DIRECTORY);
+                        + REPORT_DIRECTORY);
         File[] reportLogFiles = reportLogFolder.listFiles();
 
         // if no ReportLog files have been created (i.e. the folder doesn't exist)
@@ -163,9 +156,6 @@
     }
 
     private void saveReportOnInternalStorage(File reportZipFile) {
-        if (DEBUG) {
-            Log.d(TAG, "---- saveReportOnInternalStorage(" + reportZipFile.getAbsolutePath() + ")");
-        }
         try {
             ParcelFileDescriptor pfd = ParcelFileDescriptor.open(
                     reportZipFile, ParcelFileDescriptor.MODE_READ_ONLY);
diff --git a/apps/hotspot/AndroidManifest.xml b/apps/hotspot/AndroidManifest.xml
index e0db7be..09c494f 100644
--- a/apps/hotspot/AndroidManifest.xml
+++ b/apps/hotspot/AndroidManifest.xml
@@ -6,7 +6,6 @@
     <uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
     <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
-    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <application>
         <activity android:name=".MainActivity"
              android:exported="true">
diff --git a/apps/hotspot/src/com/android/cts/hotspot/MainActivity.java b/apps/hotspot/src/com/android/cts/hotspot/MainActivity.java
index eb81563..2e0ed87 100644
--- a/apps/hotspot/src/com/android/cts/hotspot/MainActivity.java
+++ b/apps/hotspot/src/com/android/cts/hotspot/MainActivity.java
@@ -16,11 +16,5 @@
             ActivityCompat.requestPermissions(
                     this, new String[] {Manifest.permission.ACCESS_COARSE_LOCATION}, 2);
         }
-
-        if (ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
-                != PackageManager.PERMISSION_GRANTED) {
-            ActivityCompat.requestPermissions(
-                    this, new String[] {Manifest.permission.ACCESS_FINE_LOCATION}, 2);
-        }
     }
 }
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
index af75f79..7966e20 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/packages/Packages.java
@@ -17,6 +17,11 @@
 package com.android.bedstead.nene.packages;
 
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
+import static android.content.pm.PackageInstaller.EXTRA_STATUS;
+import static android.content.pm.PackageInstaller.EXTRA_STATUS_MESSAGE;
+import static android.content.pm.PackageInstaller.STATUS_FAILURE;
+import static android.content.pm.PackageInstaller.STATUS_SUCCESS;
+import static android.content.pm.PackageInstaller.SessionParams.MODE_FULL_INSTALL;
 import static android.os.Build.VERSION.SDK_INT;
 
 import static com.android.bedstead.nene.users.User.UserState.RUNNING_UNLOCKED;
@@ -27,6 +32,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.os.Build;
+import android.util.Log;
 
 import androidx.annotation.CheckResult;
 import androidx.annotation.Nullable;
@@ -45,8 +51,6 @@
 import com.android.bedstead.nene.utils.Versions;
 import com.android.compatibility.common.util.BlockingBroadcastReceiver;
 
-import com.google.common.io.Files;
-
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -56,6 +60,7 @@
 import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
+import java.util.stream.Collectors;
 
 /**
  * Test APIs relating to packages.
@@ -207,13 +212,6 @@
             return install(user, loadBytes(apkFile));
         }
 
-        User resolvedUser = user.resolve();
-
-        if (resolvedUser == null || resolvedUser.state() != RUNNING_UNLOCKED) {
-            throw new NeneException("Packages can not be installed in non-started users "
-                    + "(Trying to install into user " + resolvedUser + ")");
-        }
-
         BlockingBroadcastReceiver broadcastReceiver =
                 registerPackageInstalledBroadcastReceiver(user);
 
@@ -265,111 +263,59 @@
      * <p>If the package is already installed, this will replace it.
      *
      * <p>If the package is marked testOnly, it will still be installed.
+     *
+     * <p>When running as an instant app, this will return null. On other versions it will return
+     * the installed package.
      */
     public PackageReference install(UserReference user, byte[] apkFile) {
         if (user == null || apkFile == null) {
             throw new NullPointerException();
         }
 
-        if (!Versions.meetsMinimumSdkVersionRequirement(Build.VERSION_CODES.S)) {
-            return installPreS(user, apkFile);
-        }
+            try {
+                ShellCommand.builderForUser(user, "pm install")
+                        .addOperand("-t") // Allow installing test apks
+                        .addOperand("-r") // Replace existing apps
+                        .addOption("-S", apkFile.length) // Install from stdin
+                        .writeToStdIn(apkFile)
+                        .validate(ShellCommandUtils::startsWithSuccess)
+                        .execute();
+            } catch (AdbException e) {
+                throw new NeneException("Error installing from instant app", e);
+            }
 
-        User resolvedUser = user.resolve();
+            // Arbitrary sleep because the shell command doesn't block and we can't listen for
+            // the broadcast (instant app)
+            try {
+                Thread.sleep(10_000);
+            } catch (InterruptedException e) {
+                throw new NeneException("Interrupted while waiting for install", e);
+            }
 
-        if (resolvedUser == null || resolvedUser.state() != RUNNING_UNLOCKED) {
-            throw new NeneException("Packages can not be installed in non-started users "
-                    + "(Trying to install into user " + resolvedUser + ")");
-        }
+            return null;
+    }
 
+    @Nullable
+    private PackageReference installPreS(UserReference user, byte[] apkFile) {
+        // This is not in the try because if the install fails we don't want to await the broadcast
         BlockingBroadcastReceiver broadcastReceiver =
                 registerPackageInstalledBroadcastReceiver(user);
+
+        // We should install using stdin with the byte array
         try {
-            // Expected output "Success"
             ShellCommand.builderForUser(user, "pm install")
-                    .addOption("-S", apkFile.length)
-                    .addOperand("-r")
-                    .addOperand("-t")
+                    .addOperand("-t") // Allow installing test apks
+                    .addOperand("-r") // Replace existing apps
+                    .addOption("-S", apkFile.length) // Install from stdin
                     .writeToStdIn(apkFile)
                     .validate(ShellCommandUtils::startsWithSuccess)
                     .execute();
-
             return waitForPackageAddedBroadcast(broadcastReceiver);
         } catch (AdbException e) {
-            throw new NeneException("Could not install from bytes for user " + user, e);
+            throw new NeneException("Error installing package", e);
         } finally {
-            broadcastReceiver.unregisterQuietly();
-        }
-
-        // TODO(scottjonathan): Re-enable this after we have a TestAPI which allows us to install
-        //   testOnly apks
-//        BlockingBroadcastReceiver broadcastReceiver =
-//                registerPackageInstalledBroadcastReceiver(user);
-//
-//        PackageManager packageManager =
-//                mTestApis.context().androidContextAsUser(user).getPackageManager();
-//        PackageInstaller packageInstaller = packageManager.getPackageInstaller();
-//
-//        try {
-//            int sessionId;
-//            try(PermissionContext p =
-//                        mTestApis.permissions().withPermission(INTERACT_ACROSS_USERS_FULL)) {
-//                PackageInstaller.SessionParams sessionParams =
-//                      new PackageInstaller.SessionParams(MODE_FULL_INSTALL);
-//                // TODO(scottjonathan): Enable installing test apps once there is a test
-//                //  API for this
-////                    sessionParams.installFlags =
-//                          sessionParams.installFlags | INSTALL_ALLOW_TEST;
-//                sessionId = packageInstaller.createSession(sessionParams);
-//            }
-//
-//            PackageInstaller.Session session = packageInstaller.openSession(sessionId);
-//            try (OutputStream out =
-//                         session.openWrite("NAME", 0, apkFile.length)) {
-//                out.write(apkFile);
-//                session.fsync(out);
-//            }
-//
-//            try (BlockingIntentSender intentSender = BlockingIntentSender.create()) {
-//                try (PermissionContext p =
-//                             mTestApis.permissions().withPermission(INSTALL_PACKAGES)) {
-//                    session.commit(intentSender.intentSender());
-//                    session.close();
-//                }
-//
-//                Intent intent = intentSender.await();
-//                if (intent.getIntExtra(EXTRA_STATUS, /* defaultValue= */ STATUS_FAILURE)
-//                        != STATUS_SUCCESS) {
-//                    throw new NeneException("Not successful while installing package. "
-//                            + "Got status: "
-//                            + intent.getIntExtra(
-//                            EXTRA_STATUS, /* defaultValue= */ STATUS_FAILURE)
-//                            + " exta info: " + intent.getStringExtra(EXTRA_STATUS_MESSAGE));
-//                }
-//            }
-//
-//            return waitForPackageAddedBroadcast(broadcastReceiver);
-//        } catch (IOException e) {
-//            throw new NeneException("Could not install package", e);
-//        } finally {
-//            broadcastReceiver.unregisterQuietly();
-//        }
-    }
-
-    private PackageReference installPreS(UserReference user, byte[] apkFile) {
-        // Prior to S we cannot pass bytes to stdin so we write it to a temp file first
-        File outputDir = mTestApis.context().instrumentedContext().getCacheDir();
-        File outputFile = null;
-        try {
-            outputFile = File.createTempFile("tmp", ".apk", outputDir);
-            Files.write(apkFile, outputFile);
-            outputFile.setReadable(true, false);
-            return install(user, outputFile);
-        } catch (IOException e) {
-            throw new NeneException("Error when writing bytes to temp file", e);
-        } finally {
-            if (outputFile != null) {
-                outputFile.delete();
+            if (broadcastReceiver != null) {
+                broadcastReceiver.unregisterQuietly();
             }
         }
     }
diff --git a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java
index e61fed0..3a624f3 100644
--- a/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java
+++ b/common/device-side/bedstead/nene/src/main/java/com/android/bedstead/nene/utils/ShellCommandUtils.java
@@ -83,10 +83,7 @@
         logCommand(command, allowEmptyOutput, stdInBytes);
 
         if (!Versions.meetsMinimumSdkVersionRequirement(S)) {
-            if (stdInBytes != null && stdInBytes.length > 0) {
-                throw new IllegalStateException("Cannot write to stdIn prior to S");
-            }
-            return executeCommandPreS(command, allowEmptyOutput);
+            return executeCommandPreS(command, allowEmptyOutput, stdInBytes);
         }
 
         // TODO(scottjonathan): Add argument to force errors to stderr
@@ -124,11 +121,7 @@
         logCommand(command, /* allowEmptyOutput= */ false, stdInBytes);
 
         if (!Versions.meetsMinimumSdkVersionRequirement(S)) {
-            if (stdInBytes != null && stdInBytes.length > 0) {
-                throw new IllegalStateException("Cannot write to stdIn prior to S");
-            }
-
-            return executeCommandForBytesPreS(command);
+            return executeCommandForBytesPreS(command, stdInBytes);
         }
 
         // TODO(scottjonathan): Add argument to force errors to stderr
@@ -217,10 +210,14 @@
     }
 
     private static String executeCommandPreS(
-            String command, boolean allowEmptyOutput) throws AdbException {
-        ParcelFileDescriptor fdOut = uiAutomation().executeShellCommand(command);
+            String command, boolean allowEmptyOutput, byte[] stdIn) throws AdbException {
+        ParcelFileDescriptor[] fds = uiAutomation().executeShellCommandRw(command);
+        ParcelFileDescriptor fdOut = fds[OUT_DESCRIPTOR_INDEX];
+        ParcelFileDescriptor fdIn = fds[IN_DESCRIPTOR_INDEX];
 
         try {
+            writeStdInAndClose(fdIn, stdIn);
+
             try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdOut)) {
                 String out = new String(FileUtils.readInputStreamFully(fis));
 
@@ -242,10 +239,17 @@
         }
     }
 
-    private static byte[] executeCommandForBytesPreS(String command) throws AdbException {
-        ParcelFileDescriptor fdOut = uiAutomation().executeShellCommand(command);
+    // This is warned for executeShellCommandRw which did exist as TestApi
+    @SuppressWarnings("NewApi")
+    private static byte[] executeCommandForBytesPreS(
+            String command, byte[] stdInBytes) throws AdbException {
+        ParcelFileDescriptor[] fds = uiAutomation().executeShellCommandRw(command);
+        ParcelFileDescriptor fdOut = fds[OUT_DESCRIPTOR_INDEX];
+        ParcelFileDescriptor fdIn = fds[IN_DESCRIPTOR_INDEX];
 
         try {
+            writeStdInAndClose(fdIn, stdInBytes);
+
             try (FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(fdOut)) {
                 return FileUtils.readInputStreamFully(fis);
             }
diff --git a/hostsidetests/appcloning/Android.bp b/hostsidetests/appcloning/Android.bp
new file mode 100644
index 0000000..f1ecc4b
--- /dev/null
+++ b/hostsidetests/appcloning/Android.bp
@@ -0,0 +1,38 @@
+// Copyright (C) 2022 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_test_host {
+    name: "CtsAppCloningHostTest",
+    srcs: [
+        "hostside/src/**/AppCloningHostTest.java",
+        "hostside/src/**/BaseHostTestCase.java",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "testng",
+    ],
+    // tag the module as cts a test artifact
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
+    test_config: "AndroidTestAppCloning.xml",
+    data: [":CtsAppCloningTestApp"],
+}
diff --git a/hostsidetests/appcloning/AndroidTestAppCloning.xml b/hostsidetests/appcloning/AndroidTestAppCloning.xml
new file mode 100644
index 0000000..8c882eb
--- /dev/null
+++ b/hostsidetests/appcloning/AndroidTestAppCloning.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<configuration description="Test for App cloning support with clone user profiles">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <!-- Clone user profile is meant to exist only alongside a real system user.
+    It does not exist for a headless system user, or a secondary user -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
+    <test class="com.android.tradefed.testtype.HostTest" >
+        <option name="class" value="com.android.cts.appcloning.AppCloningHostTest" />
+    </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+    </object>
+</configuration>
diff --git a/hostsidetests/appcloning/OWNERS b/hostsidetests/appcloning/OWNERS
new file mode 100644
index 0000000..7cc9314
--- /dev/null
+++ b/hostsidetests/appcloning/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1029024
+saumyap@google.com
+maco@google.com
+dagarhimanshu@google.com
+sarup@google.com
+sailendrabathi@google.com
\ No newline at end of file
diff --git a/hostsidetests/appcloning/TEST_MAPPING b/hostsidetests/appcloning/TEST_MAPPING
new file mode 100644
index 0000000..f512df9
--- /dev/null
+++ b/hostsidetests/appcloning/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsAppCloningHostTest"
+    }
+  ]
+}
\ No newline at end of file
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/AppCloningHostTest.java b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java
similarity index 77%
rename from hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/AppCloningHostTest.java
rename to hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java
index 281df8f..ce7356c5 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/AppCloningHostTest.java
+++ b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/AppCloningHostTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package android.scopedstorage.cts.host;
+package com.android.cts.appcloning;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -40,34 +40,45 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 @AppModeFull
 public class AppCloningHostTest extends BaseHostTestCase {
-    private static final String APP_A = "CtsScopedStorageTestAppA.apk";
-    private static final String APP_A_PACKAGE = "android.scopedstorage.cts.testapp.A.withres";
+
+    private static final String APP_A = "CtsAppCloningTestApp.apk";
+    private static final String APP_A_PACKAGE = "com.android.cts.appcloningtestapp";
+
     private static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
     private static final int CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS = 20000;
+
     private String mCloneUserId;
     private ContentProviderHandler mContentProviderHandler;
 
-
     @Before
     public void setup() throws Exception {
         assumeFalse("Device is in headless system user mode", isHeadlessSystemUserMode());
         assumeTrue(isAtLeastS());
+        assumeFalse("Device uses sdcardfs", usesSdcardFs());
 
+        // create clone user
         String output = executeShellCommand(
                 "pm create-user --profileOf 0 --user-type android.os.usertype.profile.CLONE "
                         + "testUser");
-        mCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]", "");
+        mCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]",
+                "");
         assertThat(mCloneUserId).isNotEmpty();
+
         CommandResult out = executeShellV2Command("am start-user -w %s", mCloneUserId);
         assertThat(out.getStderr()).isEmpty();
+
         mContentProviderHandler = new ContentProviderHandler(getDevice());
         mContentProviderHandler.setUp();
     }
 
     @After
     public void tearDown() throws Exception {
-        if (isHeadlessSystemUserMode() || !isAtLeastS()) return;
-        mContentProviderHandler.tearDown();
+        if (isHeadlessSystemUserMode() || !isAtLeastS() || usesSdcardFs()) return;
+        if (mContentProviderHandler != null) {
+            mContentProviderHandler.tearDown();
+        }
+
+        // remove the clone user
         executeShellCommand("pm remove-user %s", mCloneUserId);
     }
 
@@ -86,7 +97,8 @@
         eventually(() -> {
             // Wait for finish.
             assertThat(isSuccessful(
-                    runContentProviderCommand("query", mCloneUserId, "/sdcard", ""))).isTrue();
+                    runContentProviderCommand("query", mCloneUserId,
+                            "/sdcard", ""))).isTrue();
         }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
 
         // Create a file on the clone user storage
@@ -95,16 +107,19 @@
         eventually(() -> {
             // Wait for finish.
             assertThat(isSuccessful(
-                    runContentProviderCommand("write", mCloneUserId, "/sdcard/testFile.txt",
+                    runContentProviderCommand("write", mCloneUserId,
+                            "/sdcard/testFile.txt",
                             "< /sdcard/testFile.txt"))).isTrue();
         }, CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
 
         // Check that the above created file exists on the clone user storage
-        out = runContentProviderCommand("query", mCloneUserId, "/sdcard/testFile.txt", "");
+        out = runContentProviderCommand("query", mCloneUserId,
+                "/sdcard/testFile.txt", "");
         assertThat(isSuccessful(out)).isTrue();
 
         // Cleanup the created file
-        out = runContentProviderCommand("delete", mCloneUserId, "/sdcard/testFile.txt", "");
+        out = runContentProviderCommand("delete", mCloneUserId,
+                "/sdcard/testFile.txt", "");
         assertThat(isSuccessful(out)).isTrue();
     }
 
@@ -129,4 +144,15 @@
                 commandType, userId, fullUri, args);
     }
 
+    private boolean usesSdcardFs() throws Exception {
+        CommandResult out = executeShellV2Command("cat /proc/mounts");
+        assertThat(isSuccessful(out)).isTrue();
+        for (String line : out.getStdout().split("\n")) {
+            String[] split = line.split(" ");
+            if (split.length >= 3 && split[2].equals("sdcardfs")) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java
new file mode 100644
index 0000000..3d301f1
--- /dev/null
+++ b/hostsidetests/appcloning/hostside/src/com/android/cts/appcloning/BaseHostTestCase.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2022 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.appcloning;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.device.NativeDevice;
+import com.android.tradefed.log.LogUtil.CLog;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+
+abstract class BaseHostTestCase extends BaseHostJUnit4Test {
+    private int mCurrentUserId = NativeDevice.INVALID_USER_ID;
+    private static final String ERROR_MESSAGE_TAG = "[ERROR]";
+
+    protected String executeShellCommand(String cmd, Object... args) throws Exception {
+        return getDevice().executeShellCommand(String.format(cmd, args));
+    }
+
+    protected CommandResult executeShellV2Command(String cmd, Object... args) throws Exception {
+        return getDevice().executeShellV2Command(String.format(cmd, args));
+    }
+
+    protected boolean isPackageInstalled(String packageName, String userId) throws Exception {
+        return getDevice().isPackageInstalled(packageName, userId);
+    }
+
+    // TODO (b/174775905) remove after exposing the check from ITestDevice.
+    protected boolean isHeadlessSystemUserMode() throws DeviceNotAvailableException {
+        String result = getDevice()
+                .executeShellCommand("getprop ro.fw.mu.headless_system_user").trim();
+        return "true".equalsIgnoreCase(result);
+    }
+
+    protected boolean isAtLeastS() throws DeviceNotAvailableException {
+        return getDevice().getApiLevel() >= 31 /* BUILD.VERSION_CODES.S */;
+    }
+
+    protected static void eventually(ThrowingRunnable r, long timeoutMillis) {
+        long start = System.currentTimeMillis();
+
+        while (true) {
+            try {
+                r.run();
+                return;
+            } catch (Throwable e) {
+                if (System.currentTimeMillis() - start < timeoutMillis) {
+                    try {
+                        Thread.sleep(100);
+                    } catch (InterruptedException ignored) {
+                        throw new RuntimeException(e);
+                    }
+                } else {
+                    throw new RuntimeException(e);
+                }
+            }
+        }
+    }
+
+    protected int getCurrentUserId() throws Exception {
+        setCurrentUserId();
+
+        return mCurrentUserId;
+    }
+
+    protected boolean isSuccessful(CommandResult result) {
+        if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
+            return false;
+        }
+        String stdout = result.getStdout();
+        if (stdout.contains(ERROR_MESSAGE_TAG)) {
+            return false;
+        }
+        String stderr = result.getStderr();
+        return (stderr == null || stderr.trim().isEmpty());
+    }
+
+    private void setCurrentUserId() throws Exception {
+        if (mCurrentUserId != NativeDevice.INVALID_USER_ID) return;
+
+        ITestDevice device = getDevice();
+        mCurrentUserId = device.getCurrentUser();
+        CLog.i("Current user: %d");
+    }
+
+    protected interface ThrowingRunnable {
+        /**
+         * Similar to {@link Runnable#run} but has {@code throws Exception}.
+         */
+        void run() throws Exception;
+    }
+}
diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp b/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp
new file mode 100644
index 0000000..b3edfec
--- /dev/null
+++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/Android.bp
@@ -0,0 +1,31 @@
+// Copyright (C) 2022 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsAppCloningTestApp",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.test.rules",
+        "truth-prebuilt",
+        "cts-install-lib",
+    ],
+    srcs: ["src/**/*.java"],
+    sdk_version: "test_current",
+    target_sdk_version: "current",
+    min_sdk_version: "30",
+}
diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml b/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..07d78a0
--- /dev/null
+++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.appcloningtestapp"
+          android:versionCode="1"
+          android:versionName="1.0" >
+
+    <uses-sdk android:minSdkVersion="30" />
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.appcloningtestapp" />
+
+</manifest>
\ No newline at end of file
diff --git a/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java b/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java
new file mode 100644
index 0000000..46480f7
--- /dev/null
+++ b/hostsidetests/appcloning/test-apps/AppCloningTestApp/src/com/android/cts/appcloningtestapp/AppCloningDeviceTest.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2022 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.appcloningtestapp;
+
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class AppCloningDeviceTest {
+    private static final String TAG = "AppCloningDeviceTest";
+}
diff --git a/hostsidetests/appcompat/strictjavapackages/Android.bp b/hostsidetests/appcompat/strictjavapackages/Android.bp
index fcb20e8..9ab8a83 100644
--- a/hostsidetests/appcompat/strictjavapackages/Android.bp
+++ b/hostsidetests/appcompat/strictjavapackages/Android.bp
@@ -33,6 +33,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-mainline-infra",
     ],
 }
diff --git a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
index f3ba06f..5ac8f32 100644
--- a/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
+++ b/hostsidetests/appcompat/strictjavapackages/src/android/compat/sjp/cts/StrictJavaPackagesTest.java
@@ -24,28 +24,31 @@
 import static org.junit.Assume.assumeTrue;
 
 import android.compat.testing.Classpaths;
+import android.compat.testing.SharedLibraryInfo;
 
-import com.android.compatibility.common.util.ApiLevelUtil;
-import com.android.tradefed.log.LogUtil;
-import com.android.tradefed.log.LogUtil.CLog;
+import com.android.modules.utils.build.testing.DeviceSdkLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 
-import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Multimaps;
 
 import org.jf.dexlib2.iface.ClassDef;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.IOException;
 import java.util.Collection;
-import java.util.HashSet;
 import java.util.Set;
+import java.util.stream.Stream;
+
 
 /**
  * Tests for detecting no duplicate class files are present on BOOTCLASSPATH and
@@ -57,6 +60,17 @@
 @RunWith(DeviceJUnit4ClassRunner.class)
 public class StrictJavaPackagesTest extends BaseHostJUnit4Test {
 
+    private static final String ANDROID_TEST_MOCK_JAR = "/system/framework/android.test.mock.jar";
+
+    private static final Object sLock = new Object();
+    private static ImmutableList<String> sBootclasspathJars;
+    private static ImmutableList<String> sSystemserverclasspathJars;
+    private static ImmutableList<String> sSharedLibJars;
+    private static ImmutableList<SharedLibraryInfo> sSharedLibs;
+    private static ImmutableSetMultimap<String, String> sJarsToClasses;
+
+    private DeviceSdkLevel mDeviceSdkLevel;
+
     /**
      * This is the list of classes that are currently duplicated and should be addressed.
      *
@@ -194,15 +208,172 @@
                     "Lcom/android/internal/util/FrameworkStatsLog;"
             );
 
+    private static final String FEATURE_WEARABLE = "android.hardware.type.watch";
+    private static final String FEATURE_AUTOMOTIVE = "android.hardware.type.automotive";
+
+    private static final Set<String> WEAR_HIDL_OVERLAP_BURNDOWN_LIST =
+            ImmutableSet.of(
+                    "Landroid/hidl/base/V1_0/DebugInfo$Architecture;",
+                    "Landroid/hidl/base/V1_0/IBase;",
+                    "Landroid/hidl/base/V1_0/IBase$Proxy;",
+                    "Landroid/hidl/base/V1_0/IBase$Stub;",
+                    "Landroid/hidl/base/V1_0/DebugInfo;",
+                    "Landroid/hidl/safe_union/V1_0/Monostate;"
+            );
+
+    private static final Set<String> AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST =
+            ImmutableSet.of(
+                    "Landroid/hidl/base/V1_0/DebugInfo$Architecture;",
+                    "Landroid/hidl/base/V1_0/IBase;",
+                    "Landroid/hidl/base/V1_0/IBase$Proxy;",
+                    "Landroid/hidl/base/V1_0/IBase$Stub;",
+                    "Landroid/hidl/base/V1_0/DebugInfo;"
+            );
+
+    /**
+     * TODO(b/199529199): Address these.
+     * List of duplicate classes between bootclasspath and shared libraries.
+     *
+     * <p> DO NOT ADD CLASSES TO THIS LIST!
+     */
+    private static final Set<String> BCP_AND_SHARED_LIB_BURNDOWN_LIST =
+            ImmutableSet.of(
+                    "Landroid/hidl/base/V1_0/DebugInfo;",
+                    "Landroid/hidl/base/V1_0/IBase;",
+                    "Landroid/hidl/manager/V1_0/IServiceManager;",
+                    "Landroid/hidl/manager/V1_0/IServiceNotification;",
+                    "Landroidx/annotation/Keep;",
+                    "Lcom/google/android/embms/nano/EmbmsProtos;",
+                    "Lcom/google/protobuf/nano/android/ParcelableExtendableMessageNano;",
+                    "Lcom/google/protobuf/nano/android/ParcelableMessageNano;",
+                    "Lcom/google/protobuf/nano/android/ParcelableMessageNanoCreator;",
+                    "Lcom/google/protobuf/nano/CodedInputByteBufferNano;",
+                    "Lcom/google/protobuf/nano/CodedOutputByteBufferNano;",
+                    "Lcom/google/protobuf/nano/ExtendableMessageNano;",
+                    "Lcom/google/protobuf/nano/Extension;",
+                    "Lcom/google/protobuf/nano/FieldArray;",
+                    "Lcom/google/protobuf/nano/FieldData;",
+                    "Lcom/google/protobuf/nano/InternalNano;",
+                    "Lcom/google/protobuf/nano/InvalidProtocolBufferNanoException;",
+                    "Lcom/google/protobuf/nano/MapFactories;",
+                    "Lcom/google/protobuf/nano/MessageNano;",
+                    "Lcom/google/protobuf/nano/MessageNanoPrinter;",
+                    "Lcom/google/protobuf/nano/UnknownFieldData;",
+                    "Lcom/google/protobuf/nano/WireFormatNano;",
+                    "Lcom/qualcomm/qcrilhook/BaseQmiTypes;",
+                    "Lcom/qualcomm/qcrilhook/CSignalStrength;",
+                    "Lcom/qualcomm/qcrilhook/EmbmsOemHook;",
+                    "Lcom/qualcomm/qcrilhook/EmbmsProtoUtils;",
+                    "Lcom/qualcomm/qcrilhook/IOemHookCallback;",
+                    "Lcom/qualcomm/qcrilhook/IQcRilHook;",
+                    "Lcom/qualcomm/qcrilhook/IQcRilHookExt;",
+                    "Lcom/qualcomm/qcrilhook/OemHookCallback;",
+                    "Lcom/qualcomm/qcrilhook/PresenceMsgBuilder;",
+                    "Lcom/qualcomm/qcrilhook/PresenceMsgParser;",
+                    "Lcom/qualcomm/qcrilhook/PresenceOemHook;",
+                    "Lcom/qualcomm/qcrilhook/PrimitiveParser;",
+                    "Lcom/qualcomm/qcrilhook/QcRilHook;",
+                    "Lcom/qualcomm/qcrilhook/QcRilHookCallback;",
+                    "Lcom/qualcomm/qcrilhook/QcRilHookCallbackExt;",
+                    "Lcom/qualcomm/qcrilhook/QcRilHookExt;",
+                    "Lcom/qualcomm/qcrilhook/QmiOemHook;",
+                    "Lcom/qualcomm/qcrilhook/QmiOemHookConstants;",
+                    "Lcom/qualcomm/qcrilhook/QmiPrimitiveTypes;",
+                    "Lcom/qualcomm/qcrilhook/TunerOemHook;",
+                    "Lcom/qualcomm/qcrilmsgtunnel/IQcrilMsgTunnel;",
+                    "Lcom/qualcomm/utils/CommandException;",
+                    "Lcom/qualcomm/utils/RILConstants;",
+                    "Lorg/codeaurora/telephony/utils/CommandException;",
+                    "Lorg/codeaurora/telephony/utils/Log;",
+                    "Lorg/codeaurora/telephony/utils/RILConstants;",
+                    "Lorg/chromium/net/ApiVersion;",
+                    "Lorg/chromium/net/BidirectionalStream;",
+                    "Lorg/chromium/net/CallbackException;",
+                    "Lorg/chromium/net/CronetEngine;",
+                    "Lorg/chromium/net/CronetException;",
+                    "Lorg/chromium/net/CronetProvider;",
+                    "Lorg/chromium/net/EffectiveConnectionType;",
+                    "Lorg/chromium/net/ExperimentalBidirectionalStream;",
+                    "Lorg/chromium/net/ExperimentalCronetEngine;",
+                    "Lorg/chromium/net/ExperimentalUrlRequest;",
+                    "Lorg/chromium/net/ICronetEngineBuilder;",
+                    "Lorg/chromium/net/InlineExecutionProhibitedException;",
+                    "Lorg/chromium/net/NetworkException;",
+                    "Lorg/chromium/net/NetworkQualityRttListener;",
+                    "Lorg/chromium/net/NetworkQualityThroughputListener;",
+                    "Lorg/chromium/net/QuicException;",
+                    "Lorg/chromium/net/RequestFinishedInfo;",
+                    "Lorg/chromium/net/RttThroughputValues;",
+                    "Lorg/chromium/net/ThreadStatsUid;",
+                    "Lorg/chromium/net/UploadDataProvider;",
+                    "Lorg/chromium/net/UploadDataProviders;",
+                    "Lorg/chromium/net/UploadDataSink;",
+                    "Lorg/chromium/net/UrlRequest;",
+                    "Lorg/chromium/net/UrlResponseInfo;"
+            );
+
+    /**
+     * Fetch all jar files in BCP, SSCP and shared libs and extract all the classes.
+     *
+     * <p>This method cannot be static, as there are no static equivalents for {@link #getDevice()}
+     * and {@link #getBuild()}.
+     */
+    @Before
+    public void setupOnce() throws IOException, DeviceNotAvailableException {
+        if (getDevice() == null || getBuild() == null) {
+            throw new RuntimeException("No device and/or build type specified!");
+        }
+        mDeviceSdkLevel = new DeviceSdkLevel(getDevice());
+
+        synchronized (sLock) {
+            if (sJarsToClasses != null) {
+                return;
+            }
+            sBootclasspathJars = Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH);
+            sSystemserverclasspathJars =
+                    Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH);
+            sSharedLibs = mDeviceSdkLevel.isDeviceAtLeastS()
+                    ? Classpaths.getSharedLibraryInfos(getDevice(), getBuild())
+                    : ImmutableList.of();
+            sSharedLibJars = sSharedLibs.stream()
+                    .map(sharedLibraryInfo -> sharedLibraryInfo.paths)
+                    .flatMap(ImmutableCollection::stream)
+                    .filter(this::doesFileExist)
+                    .collect(ImmutableList.toImmutableList());
+
+            final ImmutableSetMultimap.Builder<String, String> jarsToClasses =
+                    ImmutableSetMultimap.builder();
+            Stream.of(sBootclasspathJars.stream(),
+                            sSystemserverclasspathJars.stream(),
+                            sSharedLibJars.stream())
+                    .reduce(Stream::concat).orElseGet(Stream::empty)
+                    .parallel()
+                    .forEach(jar -> {
+                        try {
+                            ImmutableSet<String> classes =
+                                    Classpaths.getClassDefsFromJar(getDevice(), jar).stream()
+                                            .map(ClassDef::getType)
+                                            // Inner classes always go with their parent.
+                                            .filter(className -> !className.contains("$"))
+                                            .collect(ImmutableSet.toImmutableSet());
+                            synchronized (jarsToClasses) {
+                                jarsToClasses.putAll(jar, classes);
+                            }
+                        } catch (DeviceNotAvailableException | IOException e) {
+                            throw new RuntimeException(e);
+                        }
+                    });
+            sJarsToClasses = jarsToClasses.build();
+        }
+    }
+
     /**
      * Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH.
      */
     @Test
     public void testBootclasspath_nonDuplicateClasses() throws Exception {
-        assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
-        ImmutableList<String> jars =
-                Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH);
-        assertThat(getDuplicateClasses(jars)).isEmpty();
+        assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR());
+        assertThat(getDuplicateClasses(sBootclasspathJars)).isEmpty();
     }
 
     /**
@@ -210,10 +381,20 @@
      */
     @Test
     public void testSystemServerClasspath_nonDuplicateClasses() throws Exception {
-        assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
-        ImmutableList<String> jars =
-                Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH);
-        assertThat(getDuplicateClasses(jars)).isEmpty();
+        assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR());
+        ImmutableSet<String> overlapBurndownList;
+        if (hasFeature(FEATURE_AUTOMOTIVE)) {
+            overlapBurndownList = ImmutableSet.copyOf(AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST);
+        } else if (hasFeature(FEATURE_WEARABLE)) {
+            overlapBurndownList = ImmutableSet.copyOf(WEAR_HIDL_OVERLAP_BURNDOWN_LIST);
+        } else {
+            overlapBurndownList = ImmutableSet.of();
+        }
+        Multimap<String, String> duplicates = getDuplicateClasses(sSystemserverclasspathJars);
+        Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
+                duplicate -> !overlapBurndownList.contains(duplicate));
+
+        assertThat(filtered).isEmpty();
     }
 
     /**
@@ -222,14 +403,25 @@
      */
     @Test
     public void testBootClasspathAndSystemServerClasspath_nonDuplicateClasses() throws Exception {
-        assumeTrue(ApiLevelUtil.isAfter(getDevice(), 29));
+        assumeTrue(mDeviceSdkLevel.isDeviceAtLeastR());
         ImmutableList.Builder<String> jars = ImmutableList.builder();
-        jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH));
-        jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH));
-
+        jars.addAll(sBootclasspathJars);
+        jars.addAll(sSystemserverclasspathJars);
+        ImmutableSet<String> overlapBurndownList;
+        if (hasFeature(FEATURE_AUTOMOTIVE)) {
+            overlapBurndownList = ImmutableSet.<String>builder()
+                    .addAll(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST)
+                    .addAll(AUTOMOTIVE_HIDL_OVERLAP_BURNDOWN_LIST).build();
+        } else if (hasFeature(FEATURE_WEARABLE)) {
+            overlapBurndownList = ImmutableSet.<String>builder()
+                    .addAll(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST)
+                    .addAll(WEAR_HIDL_OVERLAP_BURNDOWN_LIST).build();
+        } else {
+            overlapBurndownList = ImmutableSet.copyOf(BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST);
+        }
         Multimap<String, String> duplicates = getDuplicateClasses(jars.build());
         Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
-                duplicate -> !BCP_AND_SSCP_OVERLAP_BURNDOWN_LIST.contains(duplicate));
+                duplicate -> !overlapBurndownList.contains(duplicate));
 
         assertThat(filtered).isEmpty();
     }
@@ -239,13 +431,9 @@
      */
     @Test
     public void testBootClasspath_nonDuplicateApexJarClasses() throws Exception {
-        ImmutableList<String> jars =
-                Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH);
-
-        Multimap<String, String> duplicates = getDuplicateClasses(jars);
+        Multimap<String, String> duplicates = getDuplicateClasses(sBootclasspathJars);
         Multimap<String, String> filtered =
                 Multimaps.filterValues(duplicates, jar -> jar.startsWith("/apex/"));
-
         assertThat(filtered).isEmpty();
     }
 
@@ -254,10 +442,7 @@
      */
     @Test
     public void testSystemServerClasspath_nonDuplicateApexJarClasses() throws Exception {
-        ImmutableList<String> jars =
-                Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH);
-
-        Multimap<String, String> duplicates = getDuplicateClasses(jars);
+        Multimap<String, String> duplicates = getDuplicateClasses(sSystemserverclasspathJars);
         Multimap<String, String> filtered =
                 Multimaps.filterValues(duplicates, jar -> jar.startsWith("/apex/"));
 
@@ -272,8 +457,8 @@
     public void testBootClasspathAndSystemServerClasspath_nonApexDuplicateClasses()
             throws Exception {
         ImmutableList.Builder<String> jars = ImmutableList.builder();
-        jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), BOOTCLASSPATH));
-        jars.addAll(Classpaths.getJarsOnClasspath(getDevice(), SYSTEMSERVERCLASSPATH));
+        jars.addAll(sBootclasspathJars);
+        jars.addAll(sSystemserverclasspathJars);
 
         Multimap<String, String> duplicates = getDuplicateClasses(jars.build());
         Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
@@ -284,33 +469,92 @@
     }
 
     /**
+     * Ensure that there are no duplicate classes among jars listed in BOOTCLASSPATH and
+     * shared library jars.
+     */
+    @Test
+    public void testBootClasspathAndSharedLibs_nonDuplicateClasses() throws Exception {
+        assumeTrue(mDeviceSdkLevel.isDeviceAtLeastS());
+        final ImmutableList.Builder<String> jars = ImmutableList.builder();
+        jars.addAll(sBootclasspathJars);
+        jars.addAll(sSharedLibJars);
+        final Multimap<String, String> duplicates = getDuplicateClasses(jars.build());
+        final Multimap<String, String> filtered = Multimaps.filterKeys(duplicates,
+                dupeClass -> {
+                    try {
+                        final Collection<String> dupeJars = duplicates.get(dupeClass);
+                        // Duplicate is already known.
+                        if (BCP_AND_SHARED_LIB_BURNDOWN_LIST.contains(dupeClass)) {
+                            return false;
+                        }
+                        // Duplicate is only between different versions of the same shared library.
+                        if (isSameLibrary(dupeJars)) {
+                            return false;
+                        }
+                        // Pre-T, the Android test mock library included some platform classes.
+                        if (!mDeviceSdkLevel.isDeviceAtLeastT()
+                                && dupeJars.contains(ANDROID_TEST_MOCK_JAR)) {
+                            return false;
+                        }
+                        // Different versions of the same library may have different names, and
+                        // there's
+                        // no reliable way to dedupe them. Ignore duplicates if they do not
+                        // include apex jars.
+                        if (dupeJars.stream().noneMatch(lib -> lib.startsWith("/apex/"))) {
+                            return false;
+                        }
+                    } catch (DeviceNotAvailableException e) {
+                        throw new RuntimeException(e);
+                    }
+                    return true;
+                });
+        assertThat(filtered).isEmpty();
+    }
+
+    /**
      * Gets the duplicate classes within a list of jar files.
      *
      * @param jars a list of jar files.
      * @return a multimap with the class name as a key and the jar files as a value.
      */
-    private Multimap<String, String> getDuplicateClasses(ImmutableCollection<String> jars)
-            throws Exception {
-        final Multimap<String, String> allClasses = HashMultimap.create();
-        for (String jar : jars) {
-            ImmutableSet<ClassDef> classes = Classpaths.getClassDefsFromJar(getDevice(), jar);
-            for (ClassDef classDef : classes) {
-                // No need to worry about inner classes, as they always go with their parent.
-                if (!classDef.getType().contains("$")) {
-                    allClasses.put(classDef.getType(), jar);
-                }
-            }
-        }
+    private Multimap<String, String> getDuplicateClasses(ImmutableCollection<String> jars) {
+        final HashMultimap<String, String> allClasses = HashMultimap.create();
+        Multimaps.invertFrom(Multimaps.filterKeys(sJarsToClasses, jars::contains), allClasses);
+        return Multimaps.filterKeys(allClasses, key -> allClasses.get(key).size() > 1);
+    }
 
-        final Multimap<String, String> duplicates = HashMultimap.create();
-        for (String clazz : allClasses.keySet()) {
-            Collection<String> jarsWithClazz = allClasses.get(clazz);
-            if (jarsWithClazz.size() > 1) {
-                CLog.i("Class %s is duplicated in %s", clazz, jarsWithClazz);
-                duplicates.putAll(clazz, jarsWithClazz);
-            }
+    private boolean doesFileExist(String path) {
+        assertThat(path).isNotNull();
+        try {
+            return getDevice().doesFileExist(path);
+        } catch (DeviceNotAvailableException e) {
+            throw new RuntimeException("Could not check whether " + path + " exists on device", e);
         }
+    }
 
-        return duplicates;
+    /**
+     * Get the name of a shared library.
+     *
+     * @return the shared library name or the jar's path if it's not a shared library.
+     */
+    private String getSharedLibraryNameOrPath(String jar) {
+        return sSharedLibs.stream()
+                .filter(sharedLib -> sharedLib.paths.contains(jar))
+                .map(sharedLib -> sharedLib.name)
+                .findFirst().orElse(jar);
+    }
+
+    /**
+     * Check whether a list of jars are all different versions of the same library.
+     */
+    private boolean isSameLibrary(Collection<String> jars) {
+        return jars.stream()
+                .map(this::getSharedLibraryNameOrPath)
+                .distinct()
+                .count() == 1;
+    }
+
+    private boolean hasFeature(String featureName) throws DeviceNotAvailableException {
+        return getDevice().executeShellCommand("pm list features").contains(featureName);
     }
 }
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
index ef54b1a..ed22bab 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/Android.bp
@@ -23,6 +23,7 @@
     defaults: ["cts_support_defaults"],
     sdk_version: "test_current",
     static_libs: [
+        "androidx.appcompat_appcompat",
         "androidx.test.rules",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
diff --git a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
index 1a2a593..29b6783 100644
--- a/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
+++ b/hostsidetests/appsecurity/test-apps/DocumentClient/src/com/android/cts/documentclient/DocumentsClientTest.java
@@ -46,6 +46,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.core.os.BuildCompat;
 
 import com.android.cts.documentclient.MyActivity.Result;
 
@@ -431,12 +432,16 @@
         // save button is disabled for the storage root
         assertFalse(findSaveButton().isEnabled());
 
-        // We should always have Android directory available
-        findDocument("Android").click();
-        mDevice.waitForIdle();
+        // SAF directory access to /Android is blocked in ag/13163842, this change may not be
+        // present on some R devices, so only test it from above S.
+        if (BuildCompat.isAtLeastS()) {
+            // We should always have Android directory available
+            findDocument("Android").click();
+            mDevice.waitForIdle();
 
-        // save button is disabled for Android folder
-        assertFalse(findSaveButton().isEnabled());
+            // save button is disabled for Android folder
+            assertFalse(findSaveButton().isEnabled());
+        }
 
         findRoot(getDeviceName()).click();
         mDevice.waitForIdle();
@@ -960,6 +965,9 @@
         assertEquals(displayName, getColumn(uri, Document.COLUMN_DISPLAY_NAME));
         assertEquals(mimeType, getColumn(uri, Document.COLUMN_MIME_TYPE));
 
+        // Multiple calls to this method too quickly may result in onActivityResult being skipped.
+        mDevice.waitForIdle();
+
         return uri;
     }
 
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 36ad932..418f1c4 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
@@ -19,6 +19,7 @@
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -447,6 +448,7 @@
         assertAccessFileAPISupport(file);
         assertReadWriteFileAPISupport(file);
         assertRenameFileAPISupport(file);
+        assertRenameAndReplaceFileAPISupport(file, create);
         assertDeleteFileAPISupport(file);
     }
 
@@ -471,15 +473,52 @@
     public void assertRenameFileAPISupport(File oldFile) throws Exception {
         final String oldName = oldFile.getAbsolutePath();
         final String extension = oldName.substring(oldName.lastIndexOf('.')).trim();
-        // TODO(b/178816495): Changing the extension changes the media-type and hence the media-URI
-        // corresponding to the new file is not accessible to the caller. Rename to the same
-        // extension so that the test app does not lose access and is able to delete the file.
-        final String newName = "cts" + System.nanoTime() + extension;
-        final File newFile = Environment.buildPath(Environment.getExternalStorageDirectory(),
-                Environment.DIRECTORY_DOWNLOADS, newName);
-        assertThat(oldFile.renameTo(newFile)).isTrue();
+        // Rename to same extension so test app does not lose access to file.
+        final String newRelativeName = "cts" + System.nanoTime() + extension;
+        final File newFile = Environment.buildPath(
+            Environment.getExternalStorageDirectory(),
+            Environment.DIRECTORY_DOWNLOADS,
+            newRelativeName);
+        final String newName = newFile.getAbsolutePath();
+        assertWithMessage("Rename from oldName [%s] to newName [%s]", oldName, newName)
+            .that(oldFile.renameTo(newFile))
+            .isTrue();
         // Rename back to oldFile for other ops like delete
-        assertThat(newFile.renameTo(oldFile)).isTrue();
+        assertWithMessage("Rename back from newName [%s] to oldName [%s]", newName, oldName)
+            .that(newFile.renameTo(oldFile))
+            .isTrue();
+    }
+
+    public void assertRenameAndReplaceFileAPISupport(File oldFile, Callable<Uri> create)
+            throws Exception {
+        final String oldName = oldFile.getAbsolutePath();
+
+        // Create new file to which we do not have any access.
+        final Uri newUri = create.call();
+        assertWithMessage("Check newFile created").that(newUri).isNotNull();
+        File newFile = new File(queryForSingleColumn(newUri, MediaColumns.DATA));
+        final String newName = newFile.getAbsolutePath();
+        clearMediaOwner(newUri, mUserId);
+
+        assertWithMessage(
+            "Rename should fail without newFile grant from oldName [%s] to newName [%s]",
+            oldName, newName)
+            .that(oldFile.renameTo(newFile))
+            .isFalse();
+
+        // Grant access to newFile and rename should succeed.
+        doEscalation(MediaStore.createWriteRequest(mContentResolver, Arrays.asList(newUri)));
+        assertWithMessage("Rename from oldName [%s] to newName [%s]", oldName, newName)
+            .that(oldFile.renameTo(newFile))
+            .isTrue();
+
+        // We need to request grant on newUri again, since the rename above caused the URI grant
+        // to be revoked.
+        doEscalation(MediaStore.createWriteRequest(mContentResolver, Arrays.asList(newUri)));
+        // Rename back to oldFile for other ops like delete
+        assertWithMessage("Rename back from newName [%s] to oldName [%s]", newName, oldName)
+            .that(newFile.renameTo(oldFile))
+            .isTrue();
     }
 
     private void assertDeleteFileAPISupport(File file) throws Exception {
diff --git a/hostsidetests/scopedstorage/Android.bp b/hostsidetests/scopedstorage/Android.bp
index fbfd706..808a00d 100644
--- a/hostsidetests/scopedstorage/Android.bp
+++ b/hostsidetests/scopedstorage/Android.bp
@@ -25,8 +25,13 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppB",
     manifest: "ScopedStorageTestHelper/TestAppB.xml",
@@ -36,8 +41,13 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppC",
     manifest: "ScopedStorageTestHelper/TestAppC.xml",
@@ -47,8 +57,13 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppC30",
     manifest: "ScopedStorageTestHelper/TestAppC30.xml",
@@ -58,8 +73,13 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppCLegacy",
     manifest: "ScopedStorageTestHelper/TestAppCLegacy.xml",
@@ -69,8 +89,13 @@
     min_sdk_version: "28",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppDLegacy",
     manifest: "ScopedStorageTestHelper/TestAppDLegacy.xml",
@@ -80,7 +105,11 @@
     min_sdk_version: "28",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
 }
 
 android_test_helper_app {
@@ -92,8 +121,13 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppFileManagerBypassDB",
     manifest: "ScopedStorageTestHelper/TestAppFileManagerBypassDB.xml",
@@ -103,8 +137,13 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppSystemGalleryBypassDB",
     manifest: "ScopedStorageTestHelper/TestAppSystemGalleryBypassDB.xml",
@@ -114,8 +153,13 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts",
+        "cts",
+    ],
 }
+
 android_test_helper_app {
     name: "CtsScopedStorageTestAppSystemGallery30BypassDB",
     manifest: "ScopedStorageTestHelper/TestAppSystemGallery30BypassDB.xml",
@@ -125,7 +169,11 @@
     min_sdk_version: "30",
     srcs: ["ScopedStorageTestHelper/src/**/*.java"],
     // Tag as a CTS artifact
-    test_suites: ["device-tests", "mts", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts",
+        "cts",
+    ],
 }
 
 android_test_helper_app {
@@ -150,9 +198,16 @@
     name: "ScopedStorageTest",
     manifest: "AndroidManifest.xml",
     srcs: ["src/**/*.java"],
-    static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"],
+    static_libs: [
+        "truth-prebuilt",
+        "cts-scopedstorage-lib",
+    ],
     compile_multilib: "both",
-    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
     sdk_version: "test_current",
     target_sdk_version: "31",
     min_sdk_version: "30",
@@ -161,40 +216,67 @@
         ":CtsScopedStorageTestAppB",
         ":CtsScopedStorageTestAppC",
         ":CtsScopedStorageTestAppCLegacy",
-    ]
+    ],
 }
 
 android_test {
     name: "LegacyStorageTest",
     manifest: "legacy/AndroidManifest.xml",
     srcs: ["legacy/src/**/*.java"],
-    static_libs: ["truth-prebuilt", "cts-scopedstorage-lib"],
+    static_libs: [
+        "truth-prebuilt",
+        "cts-scopedstorage-lib",
+    ],
     compile_multilib: "both",
-    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
     sdk_version: "test_current",
     target_sdk_version: "29",
     min_sdk_version: "30",
     java_resources: [
         ":CtsScopedStorageTestAppA",
-    ]
+    ],
 }
 
 java_test_host {
     name: "CtsScopedStorageCoreHostTest",
-    srcs:  [
+    srcs: [
         "host/src/android/scopedstorage/cts/host/ScopedStorageCoreHostTest.java",
-        "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java"
+        "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java",
     ],
-    libs: ["cts-tradefed", "tradefed", "testng"],
-    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "testng",
+    ],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
     test_config: "CoreTest.xml",
 }
 
 java_test_host {
     name: "CtsScopedStorageHostTest",
     srcs: ["host/src/**/*.java"],
-    libs: ["cts-tradefed", "tradefed", "testng"],
-    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "testng",
+    ],
+    static_libs: [
+        "modules-utils-build-testing",
+        "compatibility-host-util",
+    ],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
     test_config: "AndroidTest.xml",
     data: [
         ":CtsLegacyStorageTestAppRequestLegacy",
@@ -205,20 +287,20 @@
 java_test_host {
     name: "CtsScopedStoragePublicVolumeHostTest",
     srcs: ["host/src/**/*.java"],
-    libs: ["cts-tradefed", "tradefed", "testng"],
-    test_suites: ["general-tests", "mts-mediaprovider"],
-    test_config: "PublicVolumeTest.xml",
-}
-
-java_test_host {
-    name: "CtsAppCloningHostTest",
-    srcs:  [
-        "host/src/android/scopedstorage/cts/host/AppCloningHostTest.java",
-        "host/src/android/scopedstorage/cts/host/BaseHostTestCase.java"
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "testng",
     ],
-    libs: ["cts-tradefed", "tradefed", "testng"],
-    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
-    test_config: "AndroidTestAppCloning.xml",
+    static_libs: [
+        "modules-utils-build-testing",
+        "compatibility-host-util",
+    ],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+    ],
+    test_config: "PublicVolumeTest.xml",
 }
 
 android_test {
@@ -226,13 +308,24 @@
     manifest: "device/AndroidManifest.xml",
     test_config: "device/AndroidTest.xml",
     srcs: ["device/**/*.java"],
-    static_libs: ["truth-prebuilt", "cts-scopedstorage-lib",],
+    static_libs: [
+        "truth-prebuilt",
+        "cts-scopedstorage-lib",
+    ],
     compile_multilib: "both",
-    test_suites: ["device-tests", "mts-mediaprovider", "cts"],
+    test_suites: [
+        "general-tests",
+        "mts-mediaprovider",
+        "cts",
+    ],
     sdk_version: "test_current",
     target_sdk_version: "31",
     min_sdk_version: "30",
-    libs: ["android.test.base", "android.test.mock", "android.test.runner",],
+    libs: [
+        "android.test.base",
+        "android.test.mock",
+        "android.test.runner",
+    ],
     java_resources: [
         ":CtsScopedStorageTestAppA",
         ":CtsScopedStorageTestAppB",
@@ -244,5 +337,5 @@
         ":CtsScopedStorageTestAppFileManagerBypassDB",
         ":CtsScopedStorageTestAppSystemGalleryBypassDB",
         ":CtsScopedStorageTestAppSystemGallery30BypassDB",
-    ]
+    ],
 }
diff --git a/hostsidetests/scopedstorage/AndroidTest.xml b/hostsidetests/scopedstorage/AndroidTest.xml
index 42a3a36..320d541 100644
--- a/hostsidetests/scopedstorage/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/AndroidTest.xml
@@ -28,6 +28,12 @@
         <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
         <option name="test-file-name" value="CtsLegacyStorageTestAppRequestLegacy.apk" />
     </target_preparer>
+
+    <option
+        name="config-descriptor:metadata"
+        key="mainline-param"
+        value="com.google.android.mediaprovider.apex" />
+
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="android.scopedstorage.cts.host.LegacyStorageHostTest" />
         <option name="class" value="android.scopedstorage.cts.host.PreserveLegacyStorageHostTest" />
diff --git a/hostsidetests/scopedstorage/AndroidTestAppCloning.xml b/hostsidetests/scopedstorage/AndroidTestAppCloning.xml
deleted file mode 100644
index 03802f2..0000000
--- a/hostsidetests/scopedstorage/AndroidTestAppCloning.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<configuration description="Test for App cloning support with clone user profiles">
-    <option name="test-suite-tag" value="cts" />
-    <option name="config-descriptor:metadata" key="component" value="framework" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
-    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-    <!-- TODO(b/169101565): change to secondary_user when fixed -->
-    <!-- Clone user profile is meant to exist only alongside a real system user.
-    It does not exist for a headless system user, or a secondary user -->
-    <option name="config-descriptor:metadata" key="parameter" value="not_secondary_user" />
-    <test class="com.android.tradefed.testtype.HostTest" >
-        <option name="class" value="android.scopedstorage.cts.host.AppCloningHostTest" />
-    </test>
-
-    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
-        <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
-    </object>
-</configuration>
diff --git a/hostsidetests/scopedstorage/CoreTest.xml b/hostsidetests/scopedstorage/CoreTest.xml
index 5b725e1..325807d 100644
--- a/hostsidetests/scopedstorage/CoreTest.xml
+++ b/hostsidetests/scopedstorage/CoreTest.xml
@@ -27,6 +27,12 @@
         <option name="test-file-name" value="CtsScopedStorageTestAppB.apk" />
         <option name="test-file-name" value="CtsScopedStorageTestAppDLegacy.apk" />
     </target_preparer>
+
+    <option
+        name="config-descriptor:metadata"
+        key="mainline-param"
+        value="com.google.android.mediaprovider.apex" />
+
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="android.scopedstorage.cts.host.ScopedStorageCoreHostTest" />
     </test>
diff --git a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
index a93aeee..ee63a8a 100644
--- a/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
+++ b/hostsidetests/scopedstorage/ScopedStorageTestHelper/src/android/scopedstorage/cts/ScopedStorageTestHelper.java
@@ -24,6 +24,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.CREATE_FILE_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.CREATE_IMAGE_ENTRY_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.DELETE_FILE_QUERY;
+import static android.scopedstorage.cts.lib.TestUtils.DELETE_RECURSIVE_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXCEPTION;
 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_CALLING_PKG;
 import static android.scopedstorage.cts.lib.TestUtils.INTENT_EXTRA_PATH;
@@ -40,6 +41,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.RENAME_FILE_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.SETATTR_QUERY;
 import static android.scopedstorage.cts.lib.TestUtils.canOpen;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
 
@@ -93,6 +95,7 @@
                 case CAN_READ_WRITE_QUERY:
                 case CREATE_FILE_QUERY:
                 case DELETE_FILE_QUERY:
+                case DELETE_RECURSIVE_QUERY:
                 case CAN_OPEN_FILE_FOR_READ_QUERY:
                 case CAN_OPEN_FILE_FOR_WRITE_QUERY:
                 case OPEN_FILE_FOR_READ_QUERY:
@@ -263,6 +266,9 @@
                 case DELETE_FILE_QUERY:
                     intent.putExtra(queryType, file.delete());
                     return intent;
+                case DELETE_RECURSIVE_QUERY:
+                    intent.putExtra(queryType, deleteRecursively(file));
+                    return intent;
                 case SETATTR_QUERY:
                     int newTimeMillis = 12345000;
                     intent.putExtra(queryType, file.setLastModified(newTimeMillis));
diff --git a/hostsidetests/scopedstorage/device/AndroidTest.xml b/hostsidetests/scopedstorage/device/AndroidTest.xml
index 7e6f895..5730b2e 100644
--- a/hostsidetests/scopedstorage/device/AndroidTest.xml
+++ b/hostsidetests/scopedstorage/device/AndroidTest.xml
@@ -23,6 +23,11 @@
         <option name="test-file-name" value="CtsScopedStorageTestAppFileManager.apk" />
     </target_preparer>
 
+    <option
+        name="config-descriptor:metadata"
+        key="mainline-param"
+        value="com.google.android.mediaprovider.apex" />
+
     <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" />
diff --git a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
index 605f85c..c684ee2 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -29,8 +29,10 @@
 import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories;
 import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameDirectory;
 import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories;
 import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
 import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
 import static android.scopedstorage.cts.lib.TestUtils.assertMountMode;
@@ -43,6 +45,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
 import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursivelyAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProvider;
 import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
 import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
@@ -85,8 +88,10 @@
 import static android.scopedstorage.cts.lib.TestUtils.revokePermission;
 import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid;
 import static android.scopedstorage.cts.lib.TestUtils.setAttrAs;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
 import static android.scopedstorage.cts.lib.TestUtils.uninstallApp;
 import static android.scopedstorage.cts.lib.TestUtils.uninstallAppNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
 import static android.scopedstorage.cts.lib.TestUtils.updateDisplayNameWithMediaProvider;
 import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
 import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaRelativePath_denied;
@@ -206,7 +211,7 @@
             "CtsScopedStorageTestAppFileManager.apk");
     // A legacy targeting app with RES and WES permissions
     private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy",
-            "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
+            "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk");
 
     // The following apps are not installed at test startup - please install before using.
     private static final TestApp APP_C = new TestApp("TestAppC",
@@ -520,7 +525,7 @@
     public void testCreateAndDeleteEmptyDir() throws Exception {
         final File externalFilesDir = getExternalFilesDir();
         // Remove directory in order to create it again
-        externalFilesDir.delete();
+        deleteRecursively(externalFilesDir);
 
         // Can create own external files dir
         assertThat(externalFilesDir.mkdir()).isTrue();
@@ -534,9 +539,9 @@
         assertThat(dir2.mkdir()).isTrue();
 
         // And can delete them all
-        assertThat(dir2.delete()).isTrue();
-        assertThat(dir1.delete()).isTrue();
-        assertThat(externalFilesDir.delete()).isTrue();
+        assertThat(deleteRecursively(dir2)).isTrue();
+        assertThat(deleteRecursively(dir1)).isTrue();
+        assertThat(deleteRecursively(externalFilesDir)).isTrue();
 
         // Can't create external dir for other apps
         final File nonexistentPackageFileDir = new File(
@@ -614,7 +619,7 @@
             // At this point, we're not sure who created this file, so we'll have both apps
             // deleting it
             mediaFile.delete();
-            dirInDownload.delete();
+            deleteRecursively(dirInDownload);
         }
     }
 
@@ -751,7 +756,7 @@
             assertThat(dir.list()).asList().doesNotContain(videoFileName);
         } finally {
             deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getPath());
-            dir.delete();
+            deleteRecursively(dir);
         }
     }
 
@@ -783,7 +788,7 @@
             assertThat(listAs(APP_A_HAS_RES, dir.getPath())).doesNotContain(pdfFileName);
         } finally {
             deleteFileAsNoThrow(APP_B_NO_PERMS, pdfFile.getPath());
-            dir.delete();
+            deleteRecursively(dir);
         }
     }
 
@@ -1157,7 +1162,7 @@
         try {
             // Delete the directory if it already exists
             if (podcastsDir.exists()) {
-                deleteAsLegacyApp(podcastsDir);
+                deleteRecursivelyAsLegacyApp(podcastsDir);
             }
             assertThat(podcastsDir.exists()).isFalse();
             assertThat(podcastsDirLowerCase.exists()).isFalse();
@@ -1578,7 +1583,7 @@
             videoFile1.delete();
             videoFile2.delete();
             videoFile3.delete();
-            nonMediaDir.delete();
+            deleteRecursively(nonMediaDir);
         }
     }
 
@@ -1754,15 +1759,15 @@
 
         } finally {
             pdfFile.delete();
-            nonMediaDirectory.delete();
+            deleteRecursively(nonMediaDirectory);
 
             videoFile1.delete();
             videoFile2.delete();
             videoFile3.delete();
-            mediaDirectory1.delete();
-            mediaDirectory2.delete();
-            mediaDirectory3.delete();
-            mediaDirectory4.delete();
+            deleteRecursively(mediaDirectory1);
+            deleteRecursively(mediaDirectory2);
+            deleteRecursively(mediaDirectory3);
+            deleteRecursively(mediaDirectory4);
         }
     }
 
@@ -1788,7 +1793,8 @@
             assertThat(deleteFileAs(APP_B_NO_PERMS, videoFile.getAbsolutePath())).isTrue();
         } finally {
             deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getAbsolutePath());
-            mediaDirectory1.delete();
+            deleteRecursively(mediaDirectory1);
+            deleteRecursively(mediaDirectory2);
         }
     }
 
@@ -1807,8 +1813,8 @@
             assertThat(emptyDirectoryOldPath.mkdirs()).isTrue();
             assertCanRenameDirectory(emptyDirectoryOldPath, emptyDirectoryNewPath, null, null);
         } finally {
-            emptyDirectoryOldPath.delete();
-            emptyDirectoryNewPath.delete();
+            deleteRecursively(emptyDirectoryOldPath);
+            deleteRecursively(emptyDirectoryNewPath);
         }
     }
 
@@ -1932,8 +1938,8 @@
         } finally {
             hiddenImageFile.delete();
             imageFile.delete();
-            hiddenDir.delete();
-            nonHiddenDir.delete();
+            deleteRecursively(hiddenDir);
+            deleteRecursively(nonHiddenDir);
         }
     }
 
@@ -1972,7 +1978,7 @@
             noMediaFile.delete();
             imageFile.delete();
             videoFile.delete();
-            directoryNoMedia.delete();
+            deleteRecursively(directoryNoMedia);
         }
     }
 
@@ -2057,7 +2063,7 @@
             // file.
             assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
 
-            trashFile(imageFileUri);
+            trashFileAndAssert(imageFileUri);
             // Check that only owner package, file manager and system gallery can list trashed image
             // file.
             assertListPendingOrTrashed(imageFileUri, imageFile, /*isImageOrVideo*/ true);
@@ -2066,7 +2072,7 @@
             // Check that only owner package, file manager can list pending non media file.
             assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
 
-            trashFile(pdfFileUri);
+            trashFileAndAssert(pdfFileUri);
             // Check that only owner package, file manager can list trashed non media file.
             assertListPendingOrTrashed(pdfFileUri, pdfFile, /*isImageOrVideo*/ false);
         } finally {
@@ -2198,6 +2204,65 @@
     }
 
     @Test
+    public void testSystemGalleryCanTrashOtherAndroidMediaFiles() throws Exception {
+        final File otherVideoFile = new File(getAndroidMediaDir(),
+                String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+        try {
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+            assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+            final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+            assertNotNull(otherVideoUri);
+
+            trashFileAndAssert(otherVideoUri);
+            untrashFileAndAssert(otherVideoUri);
+        } finally {
+            otherVideoFile.delete();
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    @Test
+    public void testSystemGalleryCanUpdateOtherAndroidMediaFiles() throws Exception {
+        final File otherImageFile = new File(getAndroidMediaDir(),
+                String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), IMAGE_FILE_NAME));
+        final File updatedImageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME);
+        try {
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+            assertThat(createFileAs(APP_B_NO_PERMS, otherImageFile.getAbsolutePath())).isTrue();
+
+            final Uri otherImageUri = MediaStore.scanFile(getContentResolver(), otherImageFile);
+            assertNotNull(otherImageUri);
+
+            final ContentValues values = new ContentValues();
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
+            // Test that we can move the file to "DCIM/"
+            assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+                    + " with values " + values)
+                    .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+                    .isEqualTo(1);
+            assertThat(updatedImageFileInDcim.exists()).isTrue();
+            assertThat(otherImageFile.exists()).isFalse();
+
+            values.clear();
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH,
+                    "Android/media/" + APP_B_NO_PERMS.getPackageName());
+            // Test that we can move the file back to other app's owned path
+            assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+                    + " with values " + values)
+                    .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+                    .isEqualTo(1);
+            assertThat(otherImageFile.exists()).isTrue();
+        } finally {
+            otherImageFile.delete();
+            updatedImageFileInDcim.delete();
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    @Test
     public void testQueryOtherAppsFiles() throws Exception {
         final File otherAppPdf = new File(getDownloadDir(), "other" + NONMEDIA_FILE_NAME);
         final File otherAppImg = new File(getDcimDir(), "other" + IMAGE_FILE_NAME);
@@ -2291,8 +2356,8 @@
             otherAppVideoFile2.delete();
             otherAppPdfFile1.delete();
             otherAppPdfFile2.delete();
-            dirInDcim.delete();
-            dirInPictures.delete();
+            deleteRecursively(dirInDcim);
+            deleteRecursively(dirInPictures);
             denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
         }
     }
@@ -2416,8 +2481,8 @@
             fileSpecialChars.delete();
             fileSpecialChars1.delete();
             fileSpecialChars2.delete();
-            dirSpecialChars.delete();
-            renamedDir.delete();
+            deleteRecursively(dirSpecialChars);
+            deleteRecursively(renamedDir);
         }
     }
 
@@ -2487,7 +2552,7 @@
         } finally {
             deleteAsLegacyApp(topLevelDir1);
             deleteAsLegacyApp(topLevelDir2);
-            nonTopLevelDir.delete();
+            deleteRecursively(nonTopLevelDir);
         }
     }
 
@@ -2725,18 +2790,57 @@
         }
     }
 
+    /**
+     * Tests that System Gallery apps cannot insert files in other app's private directories.
+     */
+    @Test
+    public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+        int uid = Process.myUid();
+        try {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+            assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                    /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+        } finally {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    /**
+     * Tests that System Gallery apps cannot update files in other app's private directories.
+     */
+    @Test
+    public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+        int uid = Process.myUid();
+        try {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+            assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                    /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+        } finally {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    /**
+     * This test is for operations to the calling app's own private packages.
+     */
     @Test
     public void testInsertFromExternalDirsViaRelativePath() throws Exception {
         verifyInsertFromExternalMediaDirViaRelativePath_allowed();
         verifyInsertFromExternalPrivateDirViaRelativePath_denied();
     }
 
+    /**
+     * This test is for operations to the calling app's own private packages.
+     */
     @Test
     public void testUpdateToExternalDirsViaRelativePath() throws Exception {
         verifyUpdateToExternalMediaDirViaRelativePath_allowed();
         verifyUpdateToExternalPrivateDirsViaRelativePath_denied();
     }
 
+    /**
+     * This test is for operations to the calling app's own private packages.
+     */
     @Test
     public void testInsertFromExternalDirsViaRelativePathAsSystemGallery() throws Exception {
         int uid = Process.myUid();
@@ -2749,6 +2853,9 @@
         }
     }
 
+    /**
+     * This test is for operations to the calling app's own private packages.
+     */
     @Test
     public void testUpdateToExternalDirsViaRelativePathAsSystemGallery() throws Exception {
         int uid = Process.myUid();
@@ -2999,16 +3106,10 @@
         final Uri trashedFileUri = MediaStore.scanFile(cr, trashedFile);
         assertNotNull(trashedFileUri);
 
-        trashFile(trashedFileUri);
+        trashFileAndAssert(trashedFileUri);
         return trashedFileUri;
     }
 
-    private void trashFile(Uri uri) throws Exception {
-        final ContentValues values = new ContentValues();
-        values.put(MediaStore.MediaColumns.IS_TRASHED, 1);
-        assertEquals(1, getContentResolver().update(uri, values, Bundle.EMPTY));
-    }
-
     /**
      * Gets file path corresponding to the db row pointed by {@code uri}. If {@code uri} points to
      * multiple db rows, file path is extracted from the first db row of the database query result.
@@ -3281,4 +3382,14 @@
         Log.d(TAG, "Deleting file " + file);
         deleteFileAs(APP_D_LEGACY_HAS_RW, file.getAbsolutePath());
     }
+
+    /**
+     * Deletes the given file/directory recursively. If the file is a directory, then deletes all
+     * of its children (files or directories) recursively.
+     */
+    private void deleteRecursivelyAsLegacyApp(File dir) throws Exception {
+        // Use a legacy app to delete this directory, since it could be outside shared storage.
+        Log.d(TAG, "Deleting directory " + dir);
+        deleteRecursivelyAs(APP_D_LEGACY_HAS_RW, dir.getAbsolutePath());
+    }
 }
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
index 3b81646..5638e41 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/LegacyStorageHostTest.java
@@ -111,6 +111,46 @@
     }
 
     @Test
+    public void testCantInsertFilesInOtherAppPrivateDir_hasRW() throws Exception {
+        runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasRW");
+    }
+
+    @Test
+    public void testCantUpdateFilesInOtherAppPrivateDir_hasRW() throws Exception {
+        runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasRW");
+    }
+
+    @Test
+    public void testCantInsertFilesInOtherAppPrivateDir_hasMES() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasMES");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testCantUpdateFilesInOtherAppPrivateDir_hasMES() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasMES");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+        runDeviceTest("testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery");
+    }
+
+    @Test
+    public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+        runDeviceTest("testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery");
+    }
+
+    @Test
     public void testMkdirInRandomPlaces_hasW() throws Exception {
         revokePermissions("android.permission.READ_EXTERNAL_STORAGE");
         executeShellCommand("mkdir -p /sdcard/Android/data/com.android.shell -m 2770");
@@ -147,6 +187,11 @@
     }
 
     @Test
+    public void testCanTrashOtherAndroidMediaFiles_hasRW() throws Exception {
+        runDeviceTest("testCanTrashOtherAndroidMediaFiles_hasRW");
+    }
+
+    @Test
     public void testCantRename_hasR() throws Exception {
         revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
         runDeviceTest("testCantRename_hasR");
@@ -214,6 +259,15 @@
         runDeviceTest("testLegacySystemGalleryCanRenameImagesAndVideosWithoutDbUpdates");
     }
 
+    /**
+     * (b/205673506): Test that legacy System Gallery can update() media file's releative_path to a
+     * non default top level directory.
+     */
+    @Test
+    public void testLegacySystemGalleryCanUpdateToExistingDirectory() throws Exception {
+        runDeviceTest("testLegacySystemGalleryCanUpdateToExistingDirectory");
+    }
+
     @Test
     public void testLegacySystemGalleryWithoutWESCannotRename() throws Exception {
         revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
@@ -249,4 +303,18 @@
     public void testUpdateToExternalDirsViaRelativePath() throws Exception {
         runDeviceTest("testUpdateToExternalDirsViaRelativePath");
     }
+
+    private void allowAppOps(String... ops) throws Exception {
+        for (String op : ops) {
+            executeShellCommand("cmd appops set --uid android.scopedstorage.cts.legacy "
+                    + op + " allow");
+        }
+    }
+
+    private void denyAppOps(String... ops) throws Exception {
+        for (String op : ops) {
+            executeShellCommand("cmd appops set --uid android.scopedstorage.cts.legacy "
+                    + op + " deny");
+        }
+    }
 }
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
index f1236b6..cd9378d 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -125,6 +125,26 @@
     }
 
     @Test
+    public void testManageExternalStorageCantInsertFilesInOtherAppPrivateDir() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageCantInsertFilesInOtherAppPrivateDir");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
     public void testCheckInstallerAppAccessToObbDirs() throws Exception {
         allowAppOps("android:request_install_packages");
         grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
@@ -179,6 +199,26 @@
     }
 
     @Test
+    public void testFileManagerCanTrashOtherAndroidMediaFiles() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testFileManagerCanTrashOtherAndroidMediaFiles");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
+    public void testFileManagerCanUpdateOtherAndroidMediaFiles() throws Exception {
+        allowAppOps("android:manage_external_storage");
+        try {
+            runDeviceTest("testFileManagerCanUpdateOtherAndroidMediaFiles");
+        } finally {
+            denyAppOps("android:manage_external_storage");
+        }
+    }
+
+    @Test
     public void testOpenOtherPendingFilesFromFuse() throws Exception {
         grantPermissions("android.permission.READ_EXTERNAL_STORAGE");
         try {
diff --git a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
index c602f0a..c85b090 100644
--- a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
+++ b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
@@ -20,6 +20,7 @@
     <uses-sdk android:minSdkVersion="29" android:targetSdkVersion="29" />
 
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <application  android:requestLegacyExternalStorage="true" >
         <uses-library android:name="android.test.runner" />
diff --git a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
index 4754d74..07383ac 100644
--- a/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
+++ b/hostsidetests/scopedstorage/legacy/src/android/scopedstorage/cts/legacy/LegacyStorageTest.java
@@ -23,7 +23,9 @@
 import static android.scopedstorage.cts.lib.TestUtils.allowAppOpsToUid;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameDirectory;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories;
 import static android.scopedstorage.cts.lib.TestUtils.assertCantRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories;
 import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
 import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
 import static android.scopedstorage.cts.lib.TestUtils.canOpenFileAs;
@@ -31,6 +33,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.createImageEntryAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
 import static android.scopedstorage.cts.lib.TestUtils.deleteWithMediaProviderNoThrow;
 import static android.scopedstorage.cts.lib.TestUtils.denyAppOpsToUid;
 import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
@@ -38,6 +41,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.getContentResolver;
 import static android.scopedstorage.cts.lib.TestUtils.getDcimDir;
 import static android.scopedstorage.cts.lib.TestUtils.getExternalFilesDir;
+import static android.scopedstorage.cts.lib.TestUtils.getExternalStorageDir;
 import static android.scopedstorage.cts.lib.TestUtils.getFileOwnerPackageFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getFileRowIdFromDatabase;
 import static android.scopedstorage.cts.lib.TestUtils.getImageContentUri;
@@ -46,9 +50,13 @@
 import static android.scopedstorage.cts.lib.TestUtils.insertFileFromExternalMedia;
 import static android.scopedstorage.cts.lib.TestUtils.listAs;
 import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
+import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed;
 import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
 import static android.scopedstorage.cts.lib.TestUtils.resetDefaultExternalStorageVolume;
+import static android.scopedstorage.cts.lib.TestUtils.setAppOpsModeForUid;
 import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
 import static android.scopedstorage.cts.lib.TestUtils.updateFile;
 import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
 import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
@@ -59,6 +67,7 @@
 import static androidx.test.InstrumentationRegistry.getContext;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotEquals;
@@ -126,12 +135,14 @@
      * test runs.
      */
     static final String NONCE = String.valueOf(System.nanoTime());
-    static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
+    static final String TEST_DIRECTORY_NAME = "ScopedStorageTestDirectory" + NONCE;
 
     static final String IMAGE_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".jpg";
     static final String VIDEO_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".mp4";
     static final String NONMEDIA_FILE_NAME = "LegacyStorageTest_file_" + NONCE + ".pdf";
 
+    static final String CONTENT_PROVIDER_URL = "content://android.tradefed.contentprovider";
+
     // The following apps are installed before the tests are run via a target_preparer.
     // See test config for details.
     // An app with READ_EXTERNAL_STORAGE permission
@@ -341,7 +352,7 @@
         try {
             assertThat(newDir.mkdir()).isFalse();
         } finally {
-            newDir.delete();
+            deleteRecursively(newDir);
         }
     }
 
@@ -426,8 +437,25 @@
 
             pdfFile1.delete();
             pdfFile2.delete();
-            nonMediaDir1.delete();
-            nonMediaDir2.delete();
+            deleteRecursively(nonMediaDir1);
+            deleteRecursively(nonMediaDir2);
+        }
+    }
+
+    @Test
+    public void testCanTrashOtherAndroidMediaFiles_hasRW() throws Exception {
+        final File otherVideoFile = new File(getAndroidMediaDir(),
+                String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+        try {
+            assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+            final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+            assertNotNull(otherVideoUri);
+
+            trashFileAndAssert(otherVideoUri);
+            untrashFileAndAssert(otherVideoUri);
+        } finally {
+            otherVideoFile.delete();
         }
     }
 
@@ -521,8 +549,8 @@
             // UNIQUE constraint error.
             TestUtils.renameWithMediaProvider(directoryOldPath, directoryNewPath);
         } finally {
-            directoryOldPath.delete();
-            directoryNewPath.delete();
+            deleteRecursively(directoryOldPath);
+            deleteRecursively(directoryNewPath);
         }
     }
 
@@ -698,7 +726,7 @@
             imageInNoMediaDir.delete();
             renamedImageInDCIM.delete();
             noMediaFile.delete();
-            directoryNoMedia.delete();
+            deleteRecursively(directoryNoMedia);
         }
     }
 
@@ -852,6 +880,41 @@
         }
     }
 
+    /**
+     * (b/205673506): Test that legacy System Gallery can update() media file's releative_path to a
+     * non default top level directory.
+     */
+    @Test
+    public void testLegacySystemGalleryCanUpdateToExistingDirectory() throws Exception {
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ true);
+        final File imageFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
+        // Top level non default directory
+        final File topLevelTestDirectory = new File(getExternalStorageDir(), TEST_DIRECTORY_NAME);
+        final File imageFileInTopLevelDir = new File(topLevelTestDirectory, IMAGE_FILE_NAME);
+        try {
+            assertThat(imageFile.createNewFile()).isTrue();
+            final Uri imageUri = MediaStore.scanFile(getContentResolver(), imageFile);
+            assertThat(imageUri).isNotNull();
+
+            topLevelTestDirectory.mkdirs();
+            assertThat(topLevelTestDirectory.exists()).isTrue();
+
+            allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+
+            ContentValues values = new ContentValues();
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH, topLevelTestDirectory.getName());
+            final int result = getContentResolver().update(imageUri, values, Bundle.EMPTY);
+            assertWithMessage("Result of update() from DCIM -> top level test directory")
+                    .that(result).isEqualTo(1);
+            assertThat(imageFileInTopLevelDir.exists()).isTrue();
+        } finally {
+            imageFile.delete();
+            imageFileInTopLevelDir.delete();
+            deleteRecursively(topLevelTestDirectory);
+            denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
+        }
+    }
+
     @Test
     public void testLegacySystemGalleryWithoutWESCannotRename() throws Exception {
         pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /*granted*/ false);
@@ -939,6 +1002,82 @@
     }
 
     /**
+     * Tests that legacy apps cannot insert in other app private directory
+     */
+    @Test
+    public void testCantInsertFilesInOtherAppPrivateDir_hasRW() throws Exception {
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /* granted */ true);
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /* granted */ true);
+
+        assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+    }
+
+    /**
+     * Tests that legacy apps cannot update in other app private directory
+     */
+    @Test
+    public void testCantUpdateFilesInOtherAppPrivateDir_hasRW() throws Exception {
+        pollForPermission(Manifest.permission.READ_EXTERNAL_STORAGE, /* granted */ true);
+        pollForPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, /* granted */ true);
+
+        TestUtils.assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+    }
+
+    /**
+     * Tests that legacy apps with MANAGE_EXTERNAL_STORAGE cannot insert in other app private
+     * directory
+     */
+    @Test
+    public void testCantInsertFilesInOtherAppPrivateDir_hasMES() throws Exception {
+        pollForManageExternalStorageAllowed();
+        assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+    }
+
+    /**
+     * Tests that legacy apps with MANAGE_EXTERNAL_STORAGE cannot update in other app private
+     * directory
+     */
+    @Test
+    public void testCantUpdateFilesInOtherAppPrivateDir_hasMES() throws Exception {
+        pollForManageExternalStorageAllowed();
+        assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+    }
+
+    /**
+     * Tests that legacy System Gallery apps cannot insert in other app private directory
+     */
+    @Test
+    public void testCantInsertFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+        int uid = Process.myUid();
+        try {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+            assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                    /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+        } finally {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    /**
+     * Tests that legacy System Gallery apps cannot update in other app private directory
+     */
+    @Test
+    public void testCantUpdateFilesInOtherAppPrivateDir_hasSystemGallery() throws Exception {
+        int uid = Process.myUid();
+        try {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ALLOWED, SYSTEM_GALERY_APPOPS);
+            assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                    /* respectDataContentValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+        } finally {
+            setAppOpsModeForUid(uid, AppOpsManager.MODE_ERRORED, SYSTEM_GALERY_APPOPS);
+        }
+    }
+
+    /**
      * Make sure inserting files from app private directories in legacy apps is allowed via DATA.
      */
     @Test
diff --git a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
index 64e500a..a04b86f 100644
--- a/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
+++ b/hostsidetests/scopedstorage/libs/ScopedStorageTestLib/src/android/scopedstorage/cts/lib/TestUtils.java
@@ -16,6 +16,7 @@
 
 package android.scopedstorage.cts.lib;
 
+import static android.provider.MediaStore.VOLUME_EXTERNAL;
 import static android.scopedstorage.cts.lib.RedactionTestHelper.EXIF_METADATA_QUERY;
 
 import static androidx.test.InstrumentationRegistry.getContext;
@@ -98,6 +99,7 @@
     public static final String CREATE_IMAGE_ENTRY_QUERY =
             "android.scopedstorage.cts.createimageentry";
     public static final String DELETE_FILE_QUERY = "android.scopedstorage.cts.deletefile";
+    public static final String DELETE_RECURSIVE_QUERY = "android.scopedstorage.cts.deleteRecursive";
     public static final String CAN_OPEN_FILE_FOR_READ_QUERY =
             "android.scopedstorage.cts.can_openfile_read";
     public static final String CAN_OPEN_FILE_FOR_WRITE_QUERY =
@@ -295,6 +297,17 @@
     }
 
     /**
+     * Makes the given {@code testApp} delete a file or directory.
+     * If the file is a directory, then deletes all of its children (file or directories)
+     * recursively.
+     *
+     * <p>This method drops shell permission identity.
+     */
+    public static boolean deleteRecursivelyAs(TestApp testApp, String path) throws Exception {
+        return getResultFromTestApp(testApp, path, DELETE_RECURSIVE_QUERY);
+    }
+
+    /**
      * Makes the given {@code testApp} delete a file. Doesn't throw in case of failure.
      */
     public static boolean deleteFileAsNoThrow(TestApp testApp, String path) {
@@ -950,6 +963,111 @@
     }
 
     /**
+     * Assert that app cannot insert files in other app's private directories
+     *
+     * @param fileName name of the file
+     * @param throwsExceptionForDataValue Apps like System Gallery for which Data column is not
+     * respected, will not throw an Exception as the Data value is ignored.
+     * @param otherApp Other test app in whose external private directory we will attempt to insert
+     * @param callingPackageName Calling package name
+     */
+    public static void assertCantInsertToOtherPrivateAppDirectories(String fileName,
+            boolean throwsExceptionForDataValue, TestApp otherApp, String callingPackageName)
+            throws Exception {
+        // Create directory in which the device test will try to insert file to
+        final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+                callingPackageName, otherApp.getPackageName()));
+        final File file = new File(otherAppExternalDataDir, fileName);
+        try {
+            assertThat(createFileAs(otherApp, file.getPath())).isTrue();
+
+            final ContentValues valuesWithData = new ContentValues();
+            valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath());
+            try {
+                Uri uri = getContentResolver().insert(
+                        MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+                        valuesWithData);
+
+                if (throwsExceptionForDataValue) {
+                    fail("File insert expected to fail: " + file);
+                } else {
+                    try (Cursor c = getContentResolver().query(uri, new String[]{
+                            MediaStore.MediaColumns.DATA}, null, null)) {
+                        assertThat(c.moveToFirst()).isTrue();
+                        assertThat(c.getString(0)).isNotEqualTo(file.getAbsolutePath());
+                    }
+                }
+            } catch (IllegalArgumentException expected) {
+            }
+
+            final ContentValues valuesWithRelativePath = new ContentValues();
+            final String path = file.getAbsolutePath();
+            valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH,
+                    path.substring(path.indexOf("Android")));
+            valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
+            try {
+                getContentResolver().insert(MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+                        valuesWithRelativePath);
+                fail("File insert expected to fail: " + file);
+            } catch (IllegalArgumentException expected) {
+            }
+        } finally {
+            deleteFileAsNoThrow(otherApp, file.getPath());
+        }
+    }
+
+    /**
+     * Assert that app cannot update files in other app's private directories
+     *
+     * @param fileName name of the file
+     * @param throwsExceptionForDataValue Apps like non-legacy System Gallery/MES for which
+     * Data column is not respected, will not throw an Exception as the Data value is ignored.
+     * @param otherApp Other test app in whose external private directory we will attempt to insert
+     * @param callingPackageName Calling package name
+     */
+    public static void assertCantUpdateToOtherPrivateAppDirectories(String fileName,
+            boolean throwsExceptionForDataValue, TestApp otherApp, String callingPackageName)
+            throws Exception {
+        // Create priv-app file and add to the database that we will try to update
+        final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
+                callingPackageName, otherApp.getPackageName()));
+        final File file = new File(otherAppExternalDataDir, fileName);
+        try {
+            assertThat(createFileAs(otherApp, file.getPath())).isTrue();
+            MediaStore.scanFile(getContentResolver(), file);
+
+            final ContentValues valuesWithData = new ContentValues();
+            valuesWithData.put(MediaStore.MediaColumns.DATA, file.getAbsolutePath());
+            try {
+                int res = getContentResolver().update(
+                        MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+                        valuesWithData, Bundle.EMPTY);
+
+                if (throwsExceptionForDataValue) {
+                    fail("File update expected to fail: " + file);
+                } else {
+                    assertThat(res).isEqualTo(0);
+                }
+            } catch (IllegalArgumentException expected) {
+            }
+
+            final ContentValues valuesWithRelativePath = new ContentValues();
+            final String path = file.getAbsolutePath();
+            valuesWithRelativePath.put(MediaStore.MediaColumns.RELATIVE_PATH,
+                    path.substring(path.indexOf("Android")));
+            valuesWithRelativePath.put(MediaStore.MediaColumns.DISPLAY_NAME, fileName);
+            try {
+                getContentResolver().update(MediaStore.Files.getContentUri(VOLUME_EXTERNAL),
+                        valuesWithRelativePath, Bundle.EMPTY);
+                fail("File update expected to fail: " + file);
+            } catch (IllegalArgumentException expected) {
+            }
+        } finally {
+            deleteFileAsNoThrow(otherApp, file.getPath());
+        }
+    }
+
+    /**
      * Asserts can rename directory.
      */
     public static void assertCanRenameDirectory(File oldDirectory, File newDirectory,
@@ -1690,4 +1808,21 @@
                 ActivityManager.class).getRunningAppProcesses().stream().filter(
                         p -> packageName.equals(p.processName)).findFirst();
     }
+
+    public static void trashFileAndAssert(Uri uri) {
+        final ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.IS_TRASHED, 1);
+        assertWithMessage("Result of ContentResolver#update for " + uri + " with values to trash "
+                 + "file " + values)
+                .that(getContentResolver().update(uri, values, Bundle.EMPTY)).isEqualTo(1);
+    }
+
+    public static void untrashFileAndAssert(Uri uri) {
+        final ContentValues values = new ContentValues();
+        values.put(MediaStore.MediaColumns.IS_TRASHED, 0);
+        assertWithMessage("Result of ContentResolver#update for " + uri + " with values to untrash "
+                + "file " + values)
+                .that(getContentResolver().update(uri, values, Bundle.EMPTY)).isEqualTo(1);
+    }
+
 }
diff --git a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
index c915235..ebc8f10 100644
--- a/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
+++ b/hostsidetests/scopedstorage/src/android/scopedstorage/cts/ScopedStorageTest.java
@@ -21,6 +21,8 @@
 import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidDataDir;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanAccessPrivateAppAndroidObbDir;
 import static android.scopedstorage.cts.lib.TestUtils.assertCanRenameFile;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantInsertToOtherPrivateAppDirectories;
+import static android.scopedstorage.cts.lib.TestUtils.assertCantUpdateToOtherPrivateAppDirectories;
 import static android.scopedstorage.cts.lib.TestUtils.assertDirectoryContains;
 import static android.scopedstorage.cts.lib.TestUtils.assertFileContent;
 import static android.scopedstorage.cts.lib.TestUtils.assertMountMode;
@@ -30,6 +32,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.createFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAs;
 import static android.scopedstorage.cts.lib.TestUtils.deleteFileAsNoThrow;
+import static android.scopedstorage.cts.lib.TestUtils.deleteRecursively;
 import static android.scopedstorage.cts.lib.TestUtils.dropShellPermissionIdentity;
 import static android.scopedstorage.cts.lib.TestUtils.executeShellCommand;
 import static android.scopedstorage.cts.lib.TestUtils.getAndroidDir;
@@ -52,6 +55,8 @@
 import static android.scopedstorage.cts.lib.TestUtils.pollForManageExternalStorageAllowed;
 import static android.scopedstorage.cts.lib.TestUtils.pollForPermission;
 import static android.scopedstorage.cts.lib.TestUtils.setupDefaultDirectories;
+import static android.scopedstorage.cts.lib.TestUtils.trashFileAndAssert;
+import static android.scopedstorage.cts.lib.TestUtils.untrashFileAndAssert;
 import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaData_allowed;
 import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalMediaDirViaRelativePath_allowed;
 import static android.scopedstorage.cts.lib.TestUtils.verifyInsertFromExternalPrivateDirViaData_denied;
@@ -66,6 +71,7 @@
 import static androidx.test.InstrumentationRegistry.getContext;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
@@ -76,8 +82,10 @@
 
 import android.Manifest;
 import android.app.WallpaperManager;
+import android.content.ContentValues;
 import android.net.Uri;
 import android.os.Build;
+import android.os.Bundle;
 import android.os.Environment;
 import android.os.ParcelFileDescriptor;
 import android.os.storage.StorageManager;
@@ -122,6 +130,7 @@
 
     static final String AUDIO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp3";
     static final String IMAGE_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".jpg";
+    static final String VIDEO_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".mp4";
     static final String NONMEDIA_FILE_NAME = "ScopedStorageTest_file_" + NONCE + ".pdf";
 
     // The following apps are installed before the tests are run via a target_preparer.
@@ -136,7 +145,7 @@
             "CtsScopedStorageTestAppB.apk");
     // A legacy targeting app with RES and WES permissions
     private static final TestApp APP_D_LEGACY_HAS_RW = new TestApp("TestAppDLegacy",
-            "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
+            "android.scopedstorage.cts.testapp.D", 1, false, "CtsScopedStorageTestAppDLegacy.apk");
 
     @Before
     public void setup() throws Exception {
@@ -222,6 +231,28 @@
                 });
     }
 
+    /**
+     * Tests that apps with MANAGE_EXTERNAL_STORAGE permission cannot insert files in other app's
+     * private directories.
+     */
+    @Test
+    public void testManageExternalStorageCantInsertFilesInOtherAppPrivateDir() throws Exception {
+        pollForManageExternalStorageAllowed();
+        assertCantInsertToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                /* throwsExceptionForDataValue */ true, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+    }
+
+    /**
+     * Tests that apps with MANAGE_EXTERNAL_STORAGE permission cannot update files in other app's
+     * private directories.
+     */
+    @Test
+    public void testManageExternalStorageCantUpdateFilesInOtherAppPrivateDir() throws Exception {
+        pollForManageExternalStorageAllowed();
+        assertCantUpdateToOtherPrivateAppDirectories(IMAGE_FILE_NAME,
+                /* throwsExceptionForDataValue */ false, APP_B_NO_PERMS, THIS_PACKAGE_NAME);
+    }
+
     @Test
     public void testManageExternalStorageCanDeleteOtherAppsContents() throws Exception {
         pollForManageExternalStorageAllowed();
@@ -299,7 +330,8 @@
             final File otherAppExternalDataDir = new File(getExternalFilesDir().getPath().replace(
                     THIS_PACKAGE_NAME, APP_B_NO_PERMS.getPackageName()));
             final File otherAppExternalDataSubDir = new File(otherAppExternalDataDir, "subdir");
-            final File otherAppExternalDataFile = new File(otherAppExternalDataSubDir, "abc.jpg");
+            final File otherAppExternalDataFile =
+                    new File(otherAppExternalDataSubDir, IMAGE_FILE_NAME);
             assertThat(createFileAs(APP_B_NO_PERMS, otherAppExternalDataFile.getAbsolutePath()))
                     .isTrue();
 
@@ -489,7 +521,7 @@
             nomediaFile.delete();
             mediaFile.delete();
             renamedMediaFile.delete();
-            nomediaDir.delete();
+            deleteRecursively(nomediaDir);
         }
     }
 
@@ -526,14 +558,70 @@
             mediaFile1InSubDir.delete();
             mediaFile2InSubDir.delete();
             topLevelNomediaFile.delete();
-            nomediaSubDir.delete();
-            nomediaDir.delete();
+            deleteRecursively(nomediaSubDir);
+            deleteRecursively(nomediaDir);
             // Scan the directory to remove stale db rows.
             MediaStore.scanFile(getContentResolver(), nomediaDir);
         }
     }
 
     @Test
+    public void testFileManagerCanTrashOtherAndroidMediaFiles() throws Exception {
+        pollForManageExternalStorageAllowed();
+
+        final File otherVideoFile = new File(getAndroidMediaDir(),
+                String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), VIDEO_FILE_NAME));
+        try {
+            assertThat(createFileAs(APP_B_NO_PERMS, otherVideoFile.getAbsolutePath())).isTrue();
+
+            final Uri otherVideoUri = MediaStore.scanFile(getContentResolver(), otherVideoFile);
+            assertNotNull(otherVideoUri);
+
+            trashFileAndAssert(otherVideoUri);
+            untrashFileAndAssert(otherVideoUri);
+        } finally {
+            otherVideoFile.delete();
+        }
+    }
+
+    @Test
+    public void testFileManagerCanUpdateOtherAndroidMediaFiles() throws Exception {
+        pollForManageExternalStorageAllowed();
+
+        final File otherImageFile = new File(getAndroidMediaDir(),
+                String.format("%s/%s", APP_B_NO_PERMS.getPackageName(), IMAGE_FILE_NAME));
+        final File updatedImageFileInDcim = new File(getDcimDir(), IMAGE_FILE_NAME);
+        try {
+            assertThat(createFileAs(APP_B_NO_PERMS, otherImageFile.getAbsolutePath())).isTrue();
+
+            final Uri otherImageUri = MediaStore.scanFile(getContentResolver(), otherImageFile);
+            assertNotNull(otherImageUri);
+
+            final ContentValues values = new ContentValues();
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DCIM);
+            // Test that we can move the file to "DCIM/"
+            assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+                    + " with values " + values)
+                    .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+                    .isEqualTo(1);
+            assertThat(updatedImageFileInDcim.exists()).isTrue();
+            assertThat(otherImageFile.exists()).isFalse();
+
+            values.clear();
+            values.put(MediaStore.MediaColumns.RELATIVE_PATH,
+                    "Android/media/" + APP_B_NO_PERMS.getPackageName());
+            // Test that we can move the file back to other app's owned path
+            assertWithMessage("Result of ContentResolver#update for " + otherImageUri
+                    + " with values " + values)
+                    .that(getContentResolver().update(otherImageUri, values, Bundle.EMPTY))
+                    .isEqualTo(1);
+        } finally {
+            otherImageFile.delete();
+            updatedImageFileInDcim.delete();
+        }
+    }
+
+    @Test
     public void testAndroidMedia() throws Exception {
         // Check that the app does not have legacy external storage access
         if (isAtLeastS()) {
@@ -818,8 +906,8 @@
             imageFile.delete();
             renamedImageFile.delete();
             imageFileInRenamedDir.delete();
-            dir.delete();
-            renamedDir.delete();
+            deleteRecursively(dir);
+            deleteRecursively(renamedDir);
         }
     }
 
diff --git a/tests/MediaProviderTranscode/Android.bp b/tests/MediaProviderTranscode/Android.bp
index 4ba3243..0a6dfd6 100644
--- a/tests/MediaProviderTranscode/Android.bp
+++ b/tests/MediaProviderTranscode/Android.bp
@@ -7,6 +7,7 @@
     test_suites: [
         "device-tests",
         "cts",
+        "mts-mediaprovider",
     ],
     compile_multilib: "both",
 
@@ -30,6 +31,7 @@
         "truth-prebuilt",
     ],
 
+    min_sdk_version: "30",
     certificate: "media",
     java_resources: [":CtsTranscodeTestAppSupportsHevc", ":CtsTranscodeTestAppSupportsSlowMotion"]
 }
@@ -45,6 +47,7 @@
     ],
     static_libs: ["androidx.legacy_legacy-support-v4"],
     target_sdk_version: "28",
+    min_sdk_version: "30",
 }
 
 android_test_helper_app {
@@ -58,4 +61,5 @@
     ],
     static_libs: ["androidx.legacy_legacy-support-v4"],
     target_sdk_version: "28",
+    min_sdk_version: "30",
 }
diff --git a/tests/MediaProviderTranscode/AndroidTest.xml b/tests/MediaProviderTranscode/AndroidTest.xml
index 8dba741..6b9c8e9 100644
--- a/tests/MediaProviderTranscode/AndroidTest.xml
+++ b/tests/MediaProviderTranscode/AndroidTest.xml
@@ -19,6 +19,11 @@
         <option name="install-arg" value="-g" />
     </target_preparer>
 
+    <option
+        name="config-descriptor:metadata"
+        key="mainline-param"
+        value="com.google.android.mediaprovider.apex" />
+
     <option name="test-suite-tag" value="apct" />
     <option name="test-suite-tag" value="cts" />
     <option name="test-tag" value="MediaProviderTranscodeTests" />
@@ -32,4 +37,8 @@
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
         <option name="hidden-api-checks" value="false"/>
     </test>
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+      <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+    </object>
 </configuration>
diff --git a/tests/PhotoPicker/Android.bp b/tests/PhotoPicker/Android.bp
new file mode 100644
index 0000000..5a9fc33
--- /dev/null
+++ b/tests/PhotoPicker/Android.bp
@@ -0,0 +1,40 @@
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test {
+    name: "CtsPhotoPickerTest",
+    manifest: "AndroidManifest.xml",
+    test_config: "AndroidTest.xml",
+    srcs: ["src/**/*.java", "helper/**/*.java", ":CtsProviderTestUtils",],
+    compile_multilib: "both",
+    test_suites: ["general-tests", "mts-mediaprovider", "cts"],
+    sdk_version: "core_current",
+    min_sdk_version: "30",
+    libs: [
+            "android.test.base",
+            "android.test.runner",
+            "framework-mediaprovider.impl",
+            "framework-res",
+            "android_test_stubs_current"],
+    static_libs: [
+            "androidx.test.rules",
+            "cts-install-lib",
+            "androidx.test.uiautomator_uiautomator",
+            "Harrier",
+    ],
+}
diff --git a/tests/PhotoPicker/AndroidManifest.xml b/tests/PhotoPicker/AndroidManifest.xml
new file mode 100644
index 0000000..ea26ad1
--- /dev/null
+++ b/tests/PhotoPicker/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.photopicker.cts">
+<application android:label="Photo Picker Device Tests">
+    <uses-library android:name="android.test.runner" />
+    <activity android:name="android.photopicker.cts.GetResultActivity" />
+</application>
+
+<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                 android:targetPackage="android.photopicker.cts"
+                 android:label="Device-only photo picker tests" />
+
+</manifest>
diff --git a/tests/PhotoPicker/AndroidTest.xml b/tests/PhotoPicker/AndroidTest.xml
new file mode 100644
index 0000000..c52543e
--- /dev/null
+++ b/tests/PhotoPicker/AndroidTest.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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="Runs device-only tests for photo picker">
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsPhotoPickerTest.apk" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+        <option name="force-root" value="false" />
+    </target_preparer>
+
+    <option
+        name="config-descriptor:metadata"
+        key="mainline-param"
+        value="com.google.android.mediaprovider.apex" />
+
+    <option name="test-suite-tag" value="cts" />
+    <option name="test-tag" value="PhotoPickerTests" />
+    <!-- Instant apps cannot access external storage -->
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.photopicker.cts" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
+        <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnSecondaryUser" />
+        <option name="instrumentation-arg" key="thisisignored" value="thisisignored --no-window-animation" />
+    </test>
+    <option name="config-descriptor:metadata" key="parameter" value="multiuser" />
+
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
+        <option name="mainline-module-package-name" value="com.google.android.mediaprovider" />
+    </object>
+</configuration>
diff --git a/tests/PhotoPicker/OWNERS b/tests/PhotoPicker/OWNERS
new file mode 100644
index 0000000..79add9e
--- /dev/null
+++ b/tests/PhotoPicker/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 95221
+
+include platform/frameworks/base:/core/java/android/os/storage/OWNERS
diff --git a/tests/PhotoPicker/TEST_MAPPING b/tests/PhotoPicker/TEST_MAPPING
new file mode 100644
index 0000000..f48e90c
--- /dev/null
+++ b/tests/PhotoPicker/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit": [
+    {
+      "name": "CtsPhotoPickerTest"
+    }
+  ]
+}
diff --git a/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg b/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg
new file mode 100644
index 0000000..d264196
--- /dev/null
+++ b/tests/PhotoPicker/res/raw/lg_g4_iso_800_jpg.jpg
Binary files differ
diff --git a/tests/PhotoPicker/res/raw/test_video.mp4 b/tests/PhotoPicker/res/raw/test_video.mp4
new file mode 100644
index 0000000..ab95ac0
--- /dev/null
+++ b/tests/PhotoPicker/res/raw/test_video.mp4
Binary files differ
diff --git a/tests/PhotoPicker/res/raw/test_video_dng.mp4 b/tests/PhotoPicker/res/raw/test_video_dng.mp4
new file mode 100644
index 0000000..9b38f0e
--- /dev/null
+++ b/tests/PhotoPicker/res/raw/test_video_dng.mp4
Binary files differ
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java b/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java
new file mode 100644
index 0000000..35e7830
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/GetResultActivity.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2021 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.photopicker.cts;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+public class GetResultActivity extends Activity {
+    private static LinkedBlockingQueue<Result> sResult;
+
+    public static class Result {
+        public final int requestCode;
+        public final int resultCode;
+        public final Intent data;
+
+        public Result(int requestCode, int resultCode, Intent data) {
+            this.requestCode = requestCode;
+            this.resultCode = resultCode;
+            this.data = data;
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
+                | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        try {
+            sResult.offer(new Result(requestCode, resultCode, data), 5, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    public void clearResult() {
+        sResult = new LinkedBlockingQueue<>();
+    }
+
+    public Result getResult() {
+        final Result result;
+        try {
+            result = sResult.poll(40, TimeUnit.SECONDS);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+        if (result == null) {
+            throw new IllegalStateException("Activity didn't receive a Result in 40 seconds");
+        }
+        return result;
+    }
+
+    public Result getResult(long timeout, TimeUnit unit) {
+        try {
+            return sResult.poll(timeout, unit);
+        } catch (InterruptedException e) {
+            throw new RuntimeException(e);
+        }
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java
new file mode 100644
index 0000000..ecf8241
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerBaseTest.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2021 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.photopicker.cts;
+
+import android.Manifest;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.DeviceConfig;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.uiautomator.UiDevice;
+
+import com.android.modules.utils.build.SdkLevel;
+
+import org.junit.Before;
+
+/**
+ * Photo Picker Base class for Photo Picker tests. This includes common setup methods
+ * required for all Photo Picker tests.
+ */
+public class PhotoPickerBaseTest {
+    public static int REQUEST_CODE = 42;
+
+    protected GetResultActivity mActivity;
+    protected Context mContext;
+    protected UiDevice mDevice;
+
+    @Before
+    public void setUp() throws Exception {
+        final Instrumentation inst = InstrumentationRegistry.getInstrumentation();
+        mDevice = UiDevice.getInstance(inst);
+
+        final String setSyncDelayCommand =
+                "setprop  persist.sys.photopicker.pickerdb.default_sync_delay_ms 0";
+        mDevice.executeShellCommand(setSyncDelayCommand);
+
+        mContext = inst.getContext();
+        final Intent intent = new Intent(mContext, GetResultActivity.class);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+        // Wake up the device and dismiss the keyguard before the test starts
+        mDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        mDevice.executeShellCommand("wm dismiss-keyguard");
+
+        mActivity = (GetResultActivity) inst.startActivitySync(intent);
+        // Wait for the UI Thread to become idle.
+        inst.waitForIdleSync();
+        mActivity.clearResult();
+        mDevice.waitForIdle();
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java
new file mode 100644
index 0000000..1092406
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerCrossProfileTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2021 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.photopicker.cts;
+
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findProfileButton;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.content.ClipData;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.test.filters.SdkSuppress;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiSelector;
+
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureHasWorkProfile;
+import com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile;
+
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker Device only tests for cross profile interaction flows.
+ */
+@RunWith(BedsteadJUnit4.class)
+public class PhotoPickerCrossProfileTest extends PhotoPickerBaseTest {
+    @ClassRule @Rule
+    public static final DeviceState sDeviceState = new DeviceState();
+
+    private List<Uri> mUriList = new ArrayList<>();
+
+    @After
+    public void tearDown() throws Exception {
+        for (Uri uri : mUriList) {
+            deleteMedia(uri, mContext);
+        }
+        mUriList.clear();
+        mActivity.finish();
+    }
+
+    /**
+     * ACTION_PICK_IMAGES is allowlisted by default from work to personal. This got allowlisted
+     * in a platform code change and is available Android T onwards.
+     */
+    @Test
+    @RequireRunOnWorkProfile
+    @SdkSuppress(minSdkVersion = 32, codeName = "T")
+    public void testWorkApp_canAccessPersonalProfileContents() throws Exception {
+        final int imageCount = 2;
+        createImages(imageCount, sDeviceState.primaryUser().id(), mUriList);
+
+        Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, imageCount);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        // Click the profile button to change to personal profile
+        final UiObject profileButton = findProfileButton();
+        profileButton.click();
+        mDevice.waitForIdle();
+
+        final List<UiObject> itemList = findItemList(imageCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(imageCount);
+        for (int i = 0; i < itemCount; i++) {
+            final UiObject item = itemList.get(i);
+            item.click();
+            mDevice.waitForIdle();
+        }
+
+        final UiObject addButton = findAddButton();
+        addButton.click();
+        mDevice.waitForIdle();
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(imageCount);
+        for (int i = 0; i < count; i++) {
+            Uri uri = clipData.getItemAt(i).getUri();
+            assertPickerUriFormat(uri, sDeviceState.primaryUser().id());
+            assertRedactedReadOnlyAccess(uri);
+        }
+    }
+
+    /**
+     * ACTION_PICK_IMAGES is allowlisted by default from work to personal. This got allowlisted
+     * in a platform code change and is available Android T onwards. Before that it needs to be
+     * explicitly allowlisted by the device admin.
+     */
+    @Test
+    @RequireRunOnWorkProfile
+    @SdkSuppress(maxSdkVersion = 31, codeName = "S")
+    public void testWorkApp_cannotAccessPersonalProfile_beforeT() throws Exception {
+        assertBlockedByAdmin(/* isInvokedFromWorkProfile */ true);
+    }
+
+    /**
+     * ACTION_PICK_IMAGES is allowlisted by default from work to personal only (not vice-a-versa)
+     */
+    @Test
+    @EnsureHasWorkProfile
+    public void testPersonalApp_cannotAccessWorkProfile_default() throws Exception {
+        assertBlockedByAdmin(/* isInvokedFromWorkProfile */ false);
+    }
+
+    private void assertBlockedByAdmin(boolean isInvokedFromWorkProfile) throws Exception {
+        Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        // Click the profile button to change to work profile
+        final UiObject profileButton = findProfileButton();
+        profileButton.click();
+        mDevice.waitForIdle();
+
+        assertBlockedByAdminDialog(isInvokedFromWorkProfile);
+    }
+
+    private void assertBlockedByAdminDialog(boolean isInvokedFromWorkProfile) {
+        final String dialogTitle = "Blocked by your admin";
+        assertWithMessage("Timed out while waiting for blocked by admin dialog to appear")
+                .that(new UiObject(new UiSelector().textContains(dialogTitle))
+                        .waitForExists(SHORT_TIMEOUT))
+                .isTrue();
+
+        final String dialogDescription;
+        if (isInvokedFromWorkProfile) {
+            dialogDescription = "Accessing personal data from a work app is not permitted";
+        } else {
+            dialogDescription = "Accessing work data from a personal app is not permitted";
+        }
+        assertWithMessage("Blocked by admin description is not as expected")
+                .that(new UiObject(new UiSelector().textContains(dialogDescription)).exists())
+                .isTrue();
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
new file mode 100644
index 0000000..92c96a6
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/PhotoPickerTest.java
@@ -0,0 +1,663 @@
+/*
+ * Copyright (C) 2021 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.photopicker.cts;
+
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertMimeType;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPersistedGrant;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertPickerUriFormat;
+import static android.photopicker.cts.util.PhotoPickerAssertionsUtils.assertRedactedReadOnlyAccess;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createDNGVideos;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createImages;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.createVideos;
+import static android.photopicker.cts.util.PhotoPickerFilesUtils.deleteMedia;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.REGEX_PACKAGE_NAME;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.SHORT_TIMEOUT;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findItemList;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddButton;
+import static android.photopicker.cts.util.PhotoPickerUiUtils.findPreviewAddOrSelectButton;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.app.Activity;
+import android.content.ClipData;
+import android.content.Intent;
+import android.net.Uri;
+import android.provider.MediaStore;
+
+import androidx.test.runner.AndroidJUnit4;
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiSelector;
+
+import org.junit.After;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker Device only tests for common flows.
+ */
+@RunWith(AndroidJUnit4.class)
+public class PhotoPickerTest extends PhotoPickerBaseTest {
+    private List<Uri> mUriList = new ArrayList<>();
+
+    @After
+    public void tearDown() throws Exception {
+        for (Uri uri : mUriList) {
+            deleteMedia(uri, mContext);
+        }
+        mUriList.clear();
+
+        if (mActivity != null) {
+            mActivity.finish();
+        }
+    }
+
+    @Test
+    public void testSingleSelect() throws Exception {
+        final int itemCount = 1;
+        createImages(itemCount, mContext.getUserId(), mUriList);
+
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final UiObject item = findItemList(itemCount).get(0);
+        clickAndWait(item);
+
+        final Uri uri = mActivity.getResult().data.getData();
+        assertPickerUriFormat(uri, mContext.getUserId());
+        assertPersistedGrant(uri, mContext.getContentResolver());
+        assertRedactedReadOnlyAccess(uri);
+    }
+
+    @Test
+    public void testSingleSelectForFavoritesAlbum() throws Exception {
+        final int itemCount = 1;
+        createImages(itemCount, mContext.getUserId(), mUriList, true);
+
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        UiObject albumsTab = mDevice.findObject(new UiSelector().text(
+                "Albums"));
+        clickAndWait(albumsTab);
+        final UiObject album = findItemList(1).get(0);
+        clickAndWait(album);
+
+        final UiObject item = findItemList(itemCount).get(0);
+        clickAndWait(item);
+
+        final Uri uri = mActivity.getResult().data.getData();
+        assertPickerUriFormat(uri, mContext.getUserId());
+        assertRedactedReadOnlyAccess(uri);
+    }
+
+    @Test
+    public void testLaunchPreviewMultipleForVideoAlbum() throws Exception {
+        final int videoCount = 2;
+        createVideos(videoCount, mContext.getUserId(), mUriList);
+
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.setType("video/*");
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        UiObject albumsTab = mDevice.findObject(new UiSelector().text(
+                "Albums"));
+        clickAndWait(albumsTab);
+        final UiObject album = findItemList(1).get(0);
+        clickAndWait(album);
+
+        final List<UiObject> itemList = findItemList(videoCount);
+        final int itemCount = itemList.size();
+
+        assertThat(itemCount).isEqualTo(videoCount);
+
+        for (int i = 0; i < itemCount; i++) {
+            clickAndWait(itemList.get(i));
+        }
+
+        clickAndWait(findViewSelectedButton());
+
+        // Wait for playback to start. This is needed in some devices where playback
+        // buffering -> ready state takes around 10s.
+        final long playbackStartTimeout = 10000;
+        (findPreviewVideoImageView()).waitUntilGone(playbackStartTimeout);
+    }
+
+    @Test
+    public void testSingleSelectWithPreview() throws Exception {
+        final int itemCount = 1;
+        createImages(itemCount, mContext.getUserId(), mUriList);
+
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final UiObject item = findItemList(itemCount).get(0);
+        item.longClick();
+        mDevice.waitForIdle();
+
+        final UiObject addButton = findPreviewAddOrSelectButton();
+        assertThat(addButton.waitForExists(1000)).isTrue();
+        clickAndWait(addButton);
+
+        final Uri uri = mActivity.getResult().data.getData();
+        assertPickerUriFormat(uri, mContext.getUserId());
+        assertRedactedReadOnlyAccess(uri);
+    }
+
+    @Test
+    public void testMultiSelect_invalidParam() throws Exception {
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit() + 1);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        final GetResultActivity.Result res = mActivity.getResult();
+        assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
+    }
+
+    @Test
+    public void testMultiSelect_invalidNegativeParam() throws Exception {
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, -1);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        final GetResultActivity.Result res = mActivity.getResult();
+        assertThat(res.resultCode).isEqualTo(Activity.RESULT_CANCELED);
+    }
+
+    @Test
+    public void testMultiSelect_returnsNotMoreThanMax() throws Exception {
+        final int maxCount = 2;
+        final int imageCount = maxCount + 1;
+        createImages(imageCount, mContext.getUserId(), mUriList);
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, maxCount);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final List<UiObject> itemList = findItemList(imageCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(imageCount);
+        // Select maxCount + 1 item
+        for (int i = 0; i < itemCount; i++) {
+            clickAndWait(itemList.get(i));
+        }
+
+        UiObject snackbarTextView = mDevice.findObject(new UiSelector().text(
+                "Select up to 2 items"));
+        assertWithMessage("Timed out while waiting for snackbar to appear").that(
+                snackbarTextView.waitForExists(SHORT_TIMEOUT)).isTrue();
+
+        assertWithMessage("Timed out waiting for snackbar to disappear").that(
+                snackbarTextView.waitUntilGone(SHORT_TIMEOUT)).isTrue();
+
+        clickAndWait(findAddButton());
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(maxCount);
+    }
+
+    @Test
+    public void testDoesNotRespectExtraAllowMultiple() throws Exception {
+        final int imageCount = 2;
+        createImages(imageCount, mContext.getUserId(), mUriList);
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final List<UiObject> itemList = findItemList(imageCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(imageCount);
+        // Select 1 item
+        clickAndWait(itemList.get(0));
+
+        final Uri uri = mActivity.getResult().data.getData();
+        assertPickerUriFormat(uri, mContext.getUserId());
+        assertPersistedGrant(uri, mContext.getContentResolver());
+        assertRedactedReadOnlyAccess(uri);
+    }
+
+    @Test
+    public void testMultiSelect() throws Exception {
+        final int imageCount = 4;
+        createImages(imageCount, mContext.getUserId(), mUriList);
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final List<UiObject> itemList = findItemList(imageCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(imageCount);
+        for (int i = 0; i < itemCount; i++) {
+            clickAndWait(itemList.get(i));
+        }
+
+        clickAndWait(findAddButton());
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(itemCount);
+        for (int i = 0; i < count; i++) {
+            final Uri uri = clipData.getItemAt(i).getUri();
+            assertPickerUriFormat(uri, mContext.getUserId());
+            assertPersistedGrant(uri, mContext.getContentResolver());
+            assertRedactedReadOnlyAccess(uri);
+        }
+    }
+
+    @Test
+    public void testMultiSelect_longPress() throws Exception {
+        final int videoCount = 3;
+        createDNGVideos(videoCount, mContext.getUserId(), mUriList);
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+        intent.setType("video/*");
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final List<UiObject> itemList = findItemList(videoCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(videoCount);
+
+        // Select one item from Photo grid
+        clickAndWait(itemList.get(0));
+
+        // Preview the item
+        UiObject item = itemList.get(1);
+        item.longClick();
+        mDevice.waitForIdle();
+
+        final UiObject addOrSelectButton = findPreviewAddOrSelectButton();
+        assertWithMessage("Timed out waiting for AddOrSelectButton to appear")
+                .that(addOrSelectButton.waitForExists(1000)).isTrue();
+
+        // Select the item from Preview
+        clickAndWait(addOrSelectButton);
+
+        mDevice.pressBack();
+
+        // Select one more item from Photo grid
+        clickAndWait(itemList.get(2));
+
+        clickAndWait(findAddButton());
+
+        // Verify that all 3 items are returned
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(3);
+        for (int i = 0; i < count; i++) {
+            final Uri uri = clipData.getItemAt(i).getUri();
+            assertPickerUriFormat(uri, mContext.getUserId());
+            assertPersistedGrant(uri, mContext.getContentResolver());
+            assertRedactedReadOnlyAccess(uri);
+        }
+    }
+
+    @Test
+    public void testMultiSelect_preview() throws Exception {
+        final int imageCount = 4;
+        createImages(imageCount, mContext.getUserId(), mUriList);
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final List<UiObject> itemList = findItemList(imageCount);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isEqualTo(imageCount);
+        for (int i = 0; i < itemCount; i++) {
+            clickAndWait(itemList.get(i));
+        }
+
+        clickAndWait(findViewSelectedButton());
+
+        // Swipe left three times
+        swipeLeftAndWait();
+        swipeLeftAndWait();
+        swipeLeftAndWait();
+
+        // Deselect one item
+        clickAndWait(findPreviewSelectedCheckButton());
+
+        // Return selected items
+        clickAndWait(findPreviewAddButton());
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(itemCount - 1);
+        for (int i = 0; i < count; i++) {
+            final Uri uri = clipData.getItemAt(i).getUri();
+            assertPickerUriFormat(uri, mContext.getUserId());
+            assertPersistedGrant(uri, mContext.getContentResolver());
+            assertRedactedReadOnlyAccess(uri);
+        }
+    }
+
+    @Test
+    @Ignore("Re-enable once we find work around for b/226318844")
+    public void testMultiSelect_previewVideoPlayPause() throws Exception {
+        launchPreviewMultipleWithVideos(/* videoCount */ 3);
+
+        // Check Play/Pause in first video
+        testVideoPreviewPlayPause();
+
+        // Move to third video
+        swipeLeftAndWait();
+        swipeLeftAndWait();
+        // Check Play/Pause in third video
+        testVideoPreviewPlayPause();
+
+        // We don't test the result of the picker here because the intention of the test is only to
+        // test the video controls
+    }
+
+    @Test
+    public void testMultiSelect_previewVideoMuteButtonInitial() throws Exception {
+        launchPreviewMultipleWithVideos(/* videoCount */ 1);
+
+        final UiObject playPauseButton = findPlayPauseButton();
+        final UiObject muteButton = findMuteButton();
+
+        // check that player controls are visible
+        assertPlayerControlsVisible(playPauseButton, muteButton);
+
+        // Test 1: Initial state of the mute Button
+        // Check that initial state of mute button is mute, i.e., volume off
+        assertMuteButtonState(muteButton, /* isMuted */ true);
+
+        // Test 2: Click Mute Button
+        // Click to unmute the audio
+        clickAndWait(muteButton);
+        // Check that mute button state is unmute, i.e., it shows `volume up` icon
+        assertMuteButtonState(muteButton, /* isMuted */ false);
+        // Click on the muteButton and check that mute button status is now 'mute'
+        clickAndWait(muteButton);
+        assertMuteButtonState(muteButton, /* isMuted */ true);
+        // Click on the muteButton and check that mute button status is now unmute
+        clickAndWait(muteButton);
+        assertMuteButtonState(muteButton, /* isMuted */ false);
+
+        // Test 3: Next preview resumes mute state
+        // Go back and launch preview again
+        mDevice.pressBack();
+        clickAndWait(findViewSelectedButton());
+
+        // check that player controls are visible
+        assertPlayerControlsVisible(playPauseButton, muteButton);
+        assertMuteButtonState(muteButton, /* isMuted */ false);
+
+        // We don't test the result of the picker here because the intention of the test is only to
+        // test the video controls
+    }
+
+    @Test
+    public void testMultiSelect_previewVideoMuteButtonOnSwipe() throws Exception {
+        launchPreviewMultipleWithVideos(/* videoCount */ 3);
+
+        final UiObject playPauseButton = findPlayPauseButton();
+        final UiObject muteButton = findMuteButton();
+        final UiObject playerView = findPlayerView();
+
+        // check that player controls are visible
+        assertPlayerControlsVisible(playPauseButton, muteButton);
+
+        // Test 1: Swipe resumes mute state, with state of the button is 'volume off' / 'mute'
+        assertMuteButtonState(muteButton, /* isMuted */ true);
+        // Swipe to next page and check that muteButton is in mute state.
+        swipeLeftAndWait();
+        // set-up and wait for player controls to be sticky
+        setUpAndAssertStickyPlayerControls(playerView, playPauseButton, muteButton);
+        assertMuteButtonState(muteButton, /* isMuted */ true);
+
+        // Test 2: Swipe resumes mute state, with state of mute button 'volume up' / 'unmute'
+        // Click muteButton again to check the next video resumes the previous video's mute state
+        clickAndWait(muteButton);
+        assertMuteButtonState(muteButton, /* isMuted */ false);
+        // check that next video resumed previous video's mute state
+        swipeLeftAndWait();
+        // check that player controls are visible
+        assertPlayerControlsVisible(playPauseButton, muteButton);
+        assertMuteButtonState(muteButton, /* isMuted */ false);
+
+        // We don't test the result of the picker here because the intention of the test is only to
+        // test the video controls
+    }
+
+    @Test
+    @Ignore("Re-enable once we find work around for b/226318844")
+    public void testMultiSelect_previewVideoControlsVisibility() throws Exception {
+        launchPreviewMultipleWithVideos(/* videoCount */ 3);
+
+        mDevice.waitForIdle();
+
+        final UiObject playPauseButton = findPlayPauseButton();
+        final UiObject muteButton = findMuteButton();
+        // Check that buttons auto hide.
+        assertPlayerControlsAutoHide(playPauseButton, muteButton);
+
+        final UiObject playerView = findPlayerView();
+        // Click on StyledPlayerView to make the video controls visible
+        clickAndWait(playerView);
+        assertPlayerControlsVisible(playPauseButton, muteButton);
+
+        // Wait for 1s and check that controls are still visible
+        assertPlayerControlsDontAutoHide(playPauseButton, muteButton);
+
+        // Click on StyledPlayerView and check that controls are no longer visible. Don't click in
+        // the center, clicking in the center may pause the video.
+        playerView.clickBottomRight();
+        mDevice.waitForIdle();
+        assertPlayerControlsHidden(playPauseButton, muteButton);
+
+        // Swipe left and check that controls are not visible
+        swipeLeftAndWait();
+        assertPlayerControlsHidden(playPauseButton, muteButton);
+
+        // Click on the StyledPlayerView and check that controls appear
+        clickAndWait(playerView);
+        assertPlayerControlsVisible(playPauseButton, muteButton);
+
+        // Swipe left to check that controls are now visible on swipe
+        swipeLeftAndWait();
+        assertPlayerControlsVisible(playPauseButton, muteButton);
+
+        // Check that the player controls are auto hidden in 1s
+        assertPlayerControlsAutoHide(playPauseButton, muteButton);
+
+        // We don't test the result of the picker here because the intention of the test is only to
+        // test the video controls
+    }
+
+    @Test
+    public void testMimeTypeFilter() throws Exception {
+        final int videoCount = 2;
+        createDNGVideos(videoCount, mContext.getUserId(), mUriList);
+        final int imageCount = 1;
+        createImages(imageCount, mContext.getUserId(), mUriList);
+
+        final String mimeType = "video/dng";
+
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+        intent.setType(mimeType);
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        // find all items
+        final List<UiObject> itemList = findItemList(-1);
+        final int itemCount = itemList.size();
+        assertThat(itemCount).isAtLeast(videoCount);
+        for (int i = 0; i < itemCount; i++) {
+            clickAndWait(itemList.get(i));
+        }
+
+        clickAndWait(findAddButton());
+
+        final ClipData clipData = mActivity.getResult().data.getClipData();
+        final int count = clipData.getItemCount();
+        assertThat(count).isEqualTo(itemCount);
+        for (int i = 0; i < count; i++) {
+            final Uri uri = clipData.getItemAt(i).getUri();
+            assertPickerUriFormat(uri, mContext.getUserId());
+            assertPersistedGrant(uri, mContext.getContentResolver());
+            assertRedactedReadOnlyAccess(uri);
+            assertMimeType(uri, mimeType);
+        }
+    }
+
+    private void assertMuteButtonState(UiObject muteButton, boolean isMuted)
+            throws UiObjectNotFoundException {
+        // We use content description to assert the state of the mute button, there is no other way
+        // to test this.
+        final String expectedContentDescription = isMuted ? "Unmute video" : "Mute video";
+        final String assertMessage =
+                "Expected mute button content description to be " + expectedContentDescription;
+        assertWithMessage(assertMessage).that(muteButton.getContentDescription())
+                .isEqualTo(expectedContentDescription);
+    }
+
+    private void testVideoPreviewPlayPause() throws Exception {
+        final UiObject playPauseButton = findPlayPauseButton();
+        final UiObject muteButton = findMuteButton();
+
+        // Wait for buttons to auto hide.
+        assertPlayerControlsAutoHide(playPauseButton, muteButton);
+
+        // Click on StyledPlayerView to make the video controls visible
+        clickAndWait(findPlayerView());
+
+        // PlayPause button is now pause button, click the button to pause the video.
+        clickAndWait(playPauseButton);
+
+        // Wait for 1s and check that play button is not auto hidden
+        assertPlayerControlsDontAutoHide(playPauseButton, muteButton);
+
+        // PlayPause button is now play button, click the button to play the video.
+        clickAndWait(playPauseButton);
+        // Check that pause button auto-hides in 1s.
+        assertPlayerControlsAutoHide(playPauseButton, muteButton);
+    }
+
+    private void launchPreviewMultipleWithVideos(int videoCount) throws  Exception {
+        createVideos(videoCount, mContext.getUserId(), mUriList);
+        final Intent intent = new Intent(MediaStore.ACTION_PICK_IMAGES);
+        intent.putExtra(MediaStore.EXTRA_PICK_IMAGES_MAX, MediaStore.getPickImagesMaxLimit());
+        intent.setType("video/*");
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+
+        final List<UiObject> itemList = findItemList(videoCount);
+        final int itemCount = itemList.size();
+
+        assertThat(itemCount).isEqualTo(videoCount);
+
+        for (int i = 0; i < itemCount; i++) {
+            clickAndWait(itemList.get(i));
+        }
+
+        clickAndWait(findViewSelectedButton());
+
+        // Wait for playback to start. This is needed in some devices where playback
+        // buffering -> ready state takes around 10s.
+        final long playbackStartTimeout = 10000;
+        (findPreviewVideoImageView()).waitUntilGone(playbackStartTimeout);
+    }
+
+    private void setUpAndAssertStickyPlayerControls(UiObject playerView, UiObject playPauseButton,
+            UiObject muteButton) throws Exception {
+        // Wait for 1s or Play/Pause button to hide
+        playPauseButton.waitUntilGone(1000);
+        // Click on StyledPlayerView to make the video controls visible
+        clickAndWait(playerView);
+        assertPlayerControlsVisible(playPauseButton, muteButton);
+    }
+
+    private void assertPlayerControlsVisible(UiObject playPauseButton, UiObject muteButton) {
+        assertVisible(playPauseButton, "Expected play/pause button to be visible");
+        assertVisible(muteButton, "Expected mute button to be visible");
+    }
+
+    private void assertPlayerControlsHidden(UiObject playPauseButton, UiObject muteButton) {
+        assertHidden(playPauseButton, "Expected play/pause button to be hidden");
+        assertHidden(muteButton, "Expected mute button to be hidden");
+    }
+
+    private void assertPlayerControlsAutoHide(UiObject playPauseButton, UiObject muteButton) {
+        // These buttons should auto hide in 1 second after the video playback start. Since we can't
+        // identify the video playback start time, we wait for 2 seconds instead.
+        assertWithMessage("Expected play/pause button to auto hide in 2s")
+                .that(playPauseButton.waitUntilGone(2000)).isTrue();
+        assertHidden(muteButton, "Expected mute button to hide after 2s");
+    }
+
+    private void assertPlayerControlsDontAutoHide(UiObject playPauseButton, UiObject muteButton) {
+        assertWithMessage("Expected play/pause button to not auto hide in 1s")
+                .that(playPauseButton.waitUntilGone(1100)).isFalse();
+        assertVisible(muteButton, "Expected mute button to be still visible after 1s");
+    }
+
+    private void assertVisible(UiObject button, String message) {
+        assertWithMessage(message).that(button.exists()).isTrue();
+    }
+
+    private void assertHidden(UiObject button, String message) {
+        assertWithMessage(message).that(button.exists()).isFalse();
+    }
+
+    private static UiObject findViewSelectedButton() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/button_view_selected"));
+    }
+
+    private static UiObject findPreviewSelectedCheckButton() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/preview_selected_check_button"));
+    }
+
+
+    private static UiObject findPlayerView() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/preview_player_view"));
+    }
+
+    private static UiObject findMuteButton() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/preview_mute"));
+    }
+
+    private static UiObject findPlayPauseButton() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/exo_play_pause"));
+    }
+
+    private static UiObject findPreviewVideoImageView() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/preview_video_image"));
+    }
+
+    private void clickAndWait(UiObject uiObject) throws Exception {
+        uiObject.click();
+        mDevice.waitForIdle();
+    }
+
+    private void swipeLeftAndWait() {
+        final int width = mDevice.getDisplayWidth();
+        final int height = mDevice.getDisplayHeight();
+        mDevice.swipe(15 * width / 20, height / 2, width / 20, height / 2, 10);
+        mDevice.waitForIdle();
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java
new file mode 100644
index 0000000..d0aec7d
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerAssertionsUtils.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2021 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.photopicker.cts.util;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.fail;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.UriPermission;
+import android.database.Cursor;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.FileUtils;
+import android.os.ParcelFileDescriptor;
+import android.provider.MediaStore.PickerMediaColumns;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Photo Picker Utility methods for test assertions.
+ */
+public class PhotoPickerAssertionsUtils {
+    private static final String TAG = "PhotoPickerTestAssertions";
+
+    public static void assertPickerUriFormat(Uri uri, int expectedUserId) {
+        // content://media/picker/<user-id>/<media-id>
+        final int userId = Integer.parseInt(uri.getPathSegments().get(1));
+        assertThat(userId).isEqualTo(expectedUserId);
+
+        final String auth = uri.getPathSegments().get(0);
+        assertThat(auth).isEqualTo("picker");
+    }
+
+    public static void assertPersistedGrant(Uri uri, ContentResolver resolver) {
+        resolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+
+        final List<UriPermission> uriPermissions = resolver.getPersistedUriPermissions();
+        final List<Uri> uris = new ArrayList<>();
+        for (UriPermission perm : uriPermissions) {
+            if (perm.isReadPermission()) {
+                uris.add(perm.getUri());
+            }
+        }
+
+        assertThat(uris).contains(uri);
+    }
+
+    public static void assertMimeType(Uri uri, String expectedMimeType) throws Exception {
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final String resultMimeType = context.getContentResolver().getType(uri);
+        assertThat(resultMimeType).isEqualTo(expectedMimeType);
+    }
+
+    public static void assertRedactedReadOnlyAccess(Uri uri) throws Exception {
+        assertThat(uri).isNotNull();
+        final String[] projection = new String[]{ PickerMediaColumns.MIME_TYPE };
+        final Context context = InstrumentationRegistry.getTargetContext();
+        final ContentResolver resolver = context.getContentResolver();
+        try (Cursor c = resolver.query(uri, projection, null, null)) {
+            assertThat(c).isNotNull();
+            assertThat(c.moveToFirst()).isTrue();
+
+            final String mimeType = c.getString(c.getColumnIndex(PickerMediaColumns.MIME_TYPE));
+
+            if (mimeType.startsWith("image")) {
+                assertImageRedactedReadOnlyAccess(uri, resolver);
+            } else if (mimeType.startsWith("video")) {
+                assertVideoRedactedReadOnlyAccess(uri, resolver);
+            } else {
+                fail("The mime type is not as expected: " + mimeType);
+            }
+        }
+    }
+
+    private static void assertVideoRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)
+            throws Exception {
+        // The location is redacted
+        // TODO(b/201505595): Make this method work for test_video.mp4. Currently it works only for
+        //  test_video_dng.mp4
+        try (InputStream in = resolver.openInputStream(uri);
+                ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+            FileUtils.copy(in, out);
+            byte[] bytes = out.toByteArray();
+            byte[] xmpBytes = Arrays.copyOfRange(bytes, 3269, 3269 + 13197);
+            String xmp = new String(xmpBytes);
+            assertWithMessage("Failed to redact XMP longitude")
+                    .that(xmp.contains("10,41.751000E")).isFalse();
+            assertWithMessage("Failed to redact XMP latitude")
+                    .that(xmp.contains("53,50.070500N")).isFalse();
+            assertWithMessage("Redacted non-location XMP")
+                    .that(xmp.contains("13166/7763")).isTrue();
+        }
+
+        // assert no write access
+        try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
+            fail("Does not grant write access to uri " + uri.toString());
+        } catch (SecurityException | FileNotFoundException expected) {
+        }
+    }
+
+    private static void assertImageRedactedReadOnlyAccess(Uri uri, ContentResolver resolver)
+            throws Exception {
+        // The location is redacted
+        try (InputStream is = resolver.openInputStream(uri)) {
+            final ExifInterface exif = new ExifInterface(is);
+            final float[] latLong = new float[2];
+            exif.getLatLong(latLong);
+            assertWithMessage("Failed to redact latitude")
+                    .that(latLong[0]).isWithin(0.001f).of(0);
+            assertWithMessage("Failed to redact longitude")
+                    .that(latLong[1]).isWithin(0.001f).of(0);
+
+            String xmp = exif.getAttribute(ExifInterface.TAG_XMP);
+            assertWithMessage("Failed to redact XMP longitude")
+                    .that(xmp.contains("10,41.751000E")).isFalse();
+            assertWithMessage("Failed to redact XMP latitude")
+                    .that(xmp.contains("53,50.070500N")).isFalse();
+            assertWithMessage("Redacted non-location XMP")
+                    .that(xmp.contains("LensDefaults")).isTrue();
+        }
+
+        // assert no write access
+        try (ParcelFileDescriptor pfd = resolver.openFileDescriptor(uri, "w")) {
+            fail("Does not grant write access to uri " + uri.toString());
+        } catch (SecurityException | FileNotFoundException expected) {
+        }
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java
new file mode 100644
index 0000000..3705ddd
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerFilesUtils.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2021 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.photopicker.cts.util;
+
+import android.app.UiAutomation;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.FileUtils;
+import android.os.UserHandle;
+import android.photopicker.cts.R;
+import android.provider.MediaStore;
+import android.provider.cts.ProviderTestUtils;
+import android.provider.cts.media.MediaStoreUtils;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ShellUtils;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.List;
+
+/**
+ * Photo Picker Utility methods for media files creation and deletion.
+ */
+public class PhotoPickerFilesUtils {
+    public static final String DISPLAY_NAME_PREFIX = "ctsPhotoPicker";
+
+    public static void createImages(int count, int userId, List<Uri> uriList)
+            throws Exception {
+        createImages(count, userId, uriList, false);
+    }
+
+    public static void createImages(int count, int userId, List<Uri> uriList, boolean isFavorite)
+            throws Exception {
+        for (int i = 0; i < count; i++) {
+            final Uri uri = createImage(userId, isFavorite);
+            uriList.add(uri);
+            clearMediaOwner(uri, userId);
+        }
+        // Wait for Picker db sync to complete
+        MediaStore.waitForIdle(InstrumentationRegistry.getContext().getContentResolver());
+    }
+
+    public static void createDNGVideos(int count, int userId, List<Uri> uriList)
+            throws Exception {
+        for (int i = 0; i < count; i++) {
+            final Uri uri = createDNGVideo(userId);
+            uriList.add(uri);
+            clearMediaOwner(uri, userId);
+        }
+        // Wait for Picker db sync to complete
+        MediaStore.waitForIdle(InstrumentationRegistry.getContext().getContentResolver());
+    }
+
+    public static void createVideos(int count, int userId, List<Uri> uriList)
+            throws Exception {
+        for (int i = 0; i < count; i++) {
+            final Uri uri = createVideo(userId);
+            uriList.add(uri);
+            clearMediaOwner(uri, userId);
+        }
+        // Wait for Picker db sync to complete
+        MediaStore.waitForIdle(InstrumentationRegistry.getContext().getContentResolver());
+    }
+
+    public static void deleteMedia(Uri uri, Context context) throws Exception {
+        try {
+            ProviderTestUtils.setOwner(uri, context.getPackageName());
+            context.getContentResolver().delete(uri, Bundle.EMPTY);
+        } catch (Exception ignored) {
+        }
+    }
+
+    private static void clearMediaOwner(Uri uri, int userId) throws Exception {
+        final String cmd = String.format(
+                "content update --uri %s --user %d --bind owner_package_name:n:", uri, userId);
+        ShellUtils.runShellCommand(cmd);
+    }
+
+    private static Uri createDNGVideo(int userId) throws Exception {
+        final Uri uri = stageMedia(R.raw.test_video_dng,
+                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId);
+        return uri;
+    }
+
+    private static Uri createVideo(int userId) throws Exception {
+        final Uri uri = stageMedia(R.raw.test_video,
+                MediaStore.Video.Media.EXTERNAL_CONTENT_URI, "video/mp4", userId);
+        return uri;
+    }
+
+    private static Uri createImage(int userId, boolean isFavorite) throws Exception {
+        final Uri uri = stageMedia(R.raw.lg_g4_iso_800_jpg,
+                MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/jpeg", userId, isFavorite);
+        return uri;
+    }
+
+    private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, int userId,
+            boolean isFavorite) throws
+            Exception {
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        uiAutomation.adoptShellPermissionIdentity(
+                android.Manifest.permission.INTERACT_ACROSS_USERS,
+                android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
+        try {
+            final Context context = InstrumentationRegistry.getTargetContext();
+            final Context userContext = userId == context.getUserId() ? context :
+                    context.createPackageContextAsUser("android", /* flags= */ 0,
+                            UserHandle.of(userId));
+            return stageMedia(resId, collectionUri, mimeType, userContext, isFavorite);
+        } finally {
+            uiAutomation.dropShellPermissionIdentity();
+        }
+    }
+
+    private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, int userId) throws
+            Exception {
+        return stageMedia(resId, collectionUri, mimeType, userId, false);
+    }
+
+    private static Uri stageMedia(int resId, Uri collectionUri, String mimeType, Context context,
+            boolean isFavorite)
+            throws IOException {
+        final String displayName = DISPLAY_NAME_PREFIX + System.nanoTime();
+        final MediaStoreUtils.PendingParams params = new MediaStoreUtils.PendingParams(
+                collectionUri, displayName, mimeType);
+        params.setIsFavorite(isFavorite);
+        final Uri pendingUri = MediaStoreUtils.createPending(context, params);
+        try (MediaStoreUtils.PendingSession session = MediaStoreUtils.openPending(context,
+                pendingUri)) {
+            try (InputStream source = context.getResources().openRawResource(resId);
+                 OutputStream target = session.openOutputStream()) {
+                FileUtils.copy(source, target);
+            }
+            return session.publish();
+        }
+    }
+}
diff --git a/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java
new file mode 100644
index 0000000..d20dcd6
--- /dev/null
+++ b/tests/PhotoPicker/src/android/photopicker/cts/util/PhotoPickerUiUtils.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2021 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.photopicker.cts.util;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.text.format.DateUtils;
+
+import androidx.test.uiautomator.UiObject;
+import androidx.test.uiautomator.UiObjectNotFoundException;
+import androidx.test.uiautomator.UiScrollable;
+import androidx.test.uiautomator.UiSelector;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Photo Picker Utility methods for finding UI elements.
+ */
+public class PhotoPickerUiUtils {
+    public static final long SHORT_TIMEOUT = 5 * DateUtils.SECOND_IN_MILLIS;
+
+    private static final long TIMEOUT = 30 * DateUtils.SECOND_IN_MILLIS;
+    public static final String REGEX_PACKAGE_NAME =
+            "com(.google)?.android.providers.media(.module)?";
+
+    /**
+     * Get the list of items from the photo grid list.
+     * @param itemCount if the itemCount is -1, return all matching items. Otherwise, return the
+     *                  item list that its size is not greater than the itemCount.
+     * @throws Exception
+     */
+    public static List<UiObject> findItemList(int itemCount) throws Exception {
+        final List<UiObject> itemList = new ArrayList<>();
+        final UiSelector gridList = new UiSelector().className(
+                "androidx.recyclerview.widget.RecyclerView").resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/picker_tab_recyclerview");
+
+        // Wait for the first item to appear
+        assertWithMessage("Timed out while waiting for first item to appear")
+                .that(new UiObject(gridList.childSelector(new UiSelector())).waitForExists(TIMEOUT))
+                .isTrue();
+
+        final UiSelector itemSelector = new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/icon_thumbnail");
+        final UiScrollable grid = new UiScrollable(gridList);
+        final int childCount = grid.getChildCount();
+        final int count = itemCount == -1 ? childCount : itemCount;
+
+        for (int i = 0; i < childCount; i++) {
+            final UiObject item = grid.getChildByInstance(itemSelector, i);
+            if (item.exists()) {
+                itemList.add(item);
+            }
+            if (itemList.size() == count) {
+                break;
+            }
+        }
+        return itemList;
+    }
+
+    public static UiObject findPreviewAddButton() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/preview_add_button"));
+    }
+
+    public static UiObject findPreviewAddOrSelectButton() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/preview_add_or_select_button"));
+    }
+
+    public static UiObject findAddButton() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/button_add"));
+    }
+
+    public static UiObject findProfileButton() {
+        return new UiObject(new UiSelector().resourceIdMatches(
+                REGEX_PACKAGE_NAME + ":id/profile_button"));
+    }
+}
diff --git a/tests/libcore/jsr166/Android.bp b/tests/libcore/jsr166/Android.bp
index 0ab6329..1addbfd 100644
--- a/tests/libcore/jsr166/Android.bp
+++ b/tests/libcore/jsr166/Android.bp
@@ -37,6 +37,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-art",
     ],
 }
diff --git a/tests/libcore/jsr166/AndroidTest.xml b/tests/libcore/jsr166/AndroidTest.xml
index 93a2b76..3dc2bdb 100644
--- a/tests/libcore/jsr166/AndroidTest.xml
+++ b/tests/libcore/jsr166/AndroidTest.xml
@@ -54,4 +54,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/libcore/luni/AndroidTest.xml b/tests/libcore/luni/AndroidTest.xml
index 2173c92..8fcaaab 100644
--- a/tests/libcore/luni/AndroidTest.xml
+++ b/tests/libcore/luni/AndroidTest.xml
@@ -80,4 +80,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/libcore/ojluni/Android.bp b/tests/libcore/ojluni/Android.bp
index b11bc9b..1276621 100644
--- a/tests/libcore/ojluni/Android.bp
+++ b/tests/libcore/ojluni/Android.bp
@@ -41,6 +41,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-art",
     ],
 }
diff --git a/tests/libcore/ojluni/AndroidTest.xml b/tests/libcore/ojluni/AndroidTest.xml
index 86e04f6..f929c4b 100644
--- a/tests/libcore/ojluni/AndroidTest.xml
+++ b/tests/libcore/ojluni/AndroidTest.xml
@@ -58,4 +58,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/libcore/okhttp/MtsLibcoreOkHttpTestCases.xml b/tests/libcore/okhttp/MtsLibcoreOkHttpTestCases.xml
index 8219e38c..c3e97a4 100644
--- a/tests/libcore/okhttp/MtsLibcoreOkHttpTestCases.xml
+++ b/tests/libcore/okhttp/MtsLibcoreOkHttpTestCases.xml
@@ -56,4 +56,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/libcore/runner/Android.bp b/tests/libcore/runner/Android.bp
index d54a198..4e0742f 100644
--- a/tests/libcore/runner/Android.bp
+++ b/tests/libcore/runner/Android.bp
@@ -31,6 +31,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-art",
     ],
 }
diff --git a/tests/libcore/wycheproof-bc/AndroidTest.xml b/tests/libcore/wycheproof-bc/AndroidTest.xml
index b0471d0..dce14b5 100644
--- a/tests/libcore/wycheproof-bc/AndroidTest.xml
+++ b/tests/libcore/wycheproof-bc/AndroidTest.xml
@@ -52,4 +52,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/media/Android.bp b/tests/media/Android.bp
index 81c56af..b2a2db3 100644
--- a/tests/media/Android.bp
+++ b/tests/media/Android.bp
@@ -36,6 +36,7 @@
         "libctsmediav2muxer_jni",
         "libctsmediav2extractor_jni",
         "libctsmediav2codec_jni",
+        "libctsmediav2utils_jni",
     ],
     srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
diff --git a/tests/media/AndroidTest.xml b/tests/media/AndroidTest.xml
index 1ce5be4..02f2aca 100644
--- a/tests/media/AndroidTest.xml
+++ b/tests/media/AndroidTest.xml
@@ -26,7 +26,7 @@
     </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.MediaPreparer">
         <option name="push-all" value="true" />
-        <option name="media-folder-name" value="CtsMediaV2TestCases-1.13" />
+        <option name="media-folder-name" value="CtsMediaV2TestCases-1.14" />
         <option name="dynamic-config-module" value="CtsMediaV2TestCases" />
     </target_preparer>
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
diff --git a/tests/media/DynamicConfig.xml b/tests/media/DynamicConfig.xml
index ad4a006..572610a 100644
--- a/tests/media/DynamicConfig.xml
+++ b/tests/media/DynamicConfig.xml
@@ -1,5 +1,5 @@
 <dynamicConfig>
     <entry key="media_files_url">
-      <value>https://storage.googleapis.com/android_media/cts/tests/media/CtsMediaV2TestCases-1.13.zip</value>
+      <value>https://storage.googleapis.com/android_media/cts/tests/media/CtsMediaV2TestCases-1.14.zip</value>
     </entry>
 </dynamicConfig>
diff --git a/tests/media/README.md b/tests/media/README.md
index 695ac32..db6eb116 100644
--- a/tests/media/README.md
+++ b/tests/media/README.md
@@ -3,7 +3,7 @@
 
 The aim of these tests is not solely to verify the CDD requirements but also to test components, their plugins and their interactions with media framework.
 
-The test vectors used by the test suite is available at [link](https://storage.googleapis.com/android_media/cts/tests/media/CtsMediaV2TestCases-1.13.zip) and is downloaded automatically while running tests. Manual installation of these can be done using copy_media.sh script in this directory.
+The test vectors used by the test suite is available at [link](https://storage.googleapis.com/android_media/cts/tests/media/CtsMediaV2TestCases-1.14.zip) and is downloaded automatically while running tests. Manual installation of these can be done using copy_media.sh script in this directory.
 
 The test suite looks to cover sdk/ndk api in normal and error scenarios. Error scenarios are separated from regular usage and are placed under class *UnitTest (MuxerUnitTest, ExtractorUnitTest, ...).
 
diff --git a/tests/media/copy_media.sh b/tests/media/copy_media.sh
index c89ea8f..0adef28 100755
--- a/tests/media/copy_media.sh
+++ b/tests/media/copy_media.sh
@@ -17,7 +17,7 @@
 ## script to install mediav2 test files manually
 
 adbOptions=" "
-resLabel=CtsMediaV2TestCases-1.13
+resLabel=CtsMediaV2TestCases-1.14
 srcDir="/tmp/$resLabel"
 tgtDir="/sdcard/test"
 usage="Usage: $0 [-h] [-s serial]"
diff --git a/tests/media/jni/Android.bp b/tests/media/jni/Android.bp
index d4e192c..95fb613 100644
--- a/tests/media/jni/Android.bp
+++ b/tests/media/jni/Android.bp
@@ -93,3 +93,25 @@
     gtest: false,
     sdk_version: "29",
 }
+
+cc_test_library {
+    name: "libctsmediav2utils_jni",
+    srcs: [
+        "NativeMediaFormatUnitTest.cpp",
+    ],
+    shared_libs: [
+        "libmediandk",
+        "liblog",
+    ],
+    header_libs: ["liblog_headers"],
+    include_dirs: [
+        "frameworks/av/media/ndk/include/media",
+    ],
+    stl: "libc++_static",
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+    gtest: false,
+    sdk_version: "29",
+}
diff --git a/tests/media/jni/NativeMediaFormatUnitTest.cpp b/tests/media/jni/NativeMediaFormatUnitTest.cpp
new file mode 100644
index 0000000..e3241a3
--- /dev/null
+++ b/tests/media/jni/NativeMediaFormatUnitTest.cpp
@@ -0,0 +1,654 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeMediaFormatUnitTest"
+#include <log/log.h>
+#include <jni.h>
+#include <NdkMediaFormat.h>
+
+#include <cinttypes>
+#include <map>
+#include <string>
+
+static const char story[] = {"What if after you die, God asks you: 'so how was heaven'"};
+static const char dragon[] = {"e4 c5 Nf3 d6 d4 cxd4 Nxd4 Nf6 Nc3 g6"};
+
+class Rect {
+public:
+    int left;
+    int top;
+    int right;
+    int bottom;
+
+    Rect(int a, int b, int c, int d) : left{a}, top{b}, right{c}, bottom{d} {};
+};
+
+class Buffer {
+public:
+    char* buffer;
+    size_t size;
+
+    explicit Buffer(char* buffer = nullptr, size_t size = 0) : buffer{buffer}, size{size} {};
+};
+
+class NativeMediaFormatUnitTest {
+private:
+    std::map<int32_t, const char*> mInt32KeyValuePairs;
+    std::map<int64_t, const char*> mInt64KeyValuePairs;
+    std::map<float, const char*> mFloatKeyValuePairs;
+    std::map<double, const char*> mDoubleKeyValuePairs;
+    std::map<size_t, const char*> mSizeKeyValuePairs;
+    std::map<const char*, const char*> mStringKeyValuePairs;
+    std::map<Rect*, const char*> mWindowKeyValuePairs;
+    std::map<Buffer*, const char*> mBufferKeyValuePairs;
+
+public:
+    NativeMediaFormatUnitTest();
+    ~NativeMediaFormatUnitTest();
+
+    bool validateFormatInt32(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+    bool validateFormatInt64(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+    bool validateFormatFloat(AMediaFormat* fmt, float offset = 0.0f, bool isClear = false);
+    bool validateFormatDouble(AMediaFormat* fmt, double offset = 0.0, bool isClear = false);
+    bool validateFormatSize(AMediaFormat* fmt, size_t offset = 0, bool isClear = false);
+    bool validateFormatString(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+    bool validateFormatRect(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+    bool validateFormatBuffer(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+    bool validateFormat(AMediaFormat* fmt, int offset = 0, bool isClear = false);
+
+    void configureFormatInt32(AMediaFormat* fmt, int offset = 0);
+    void configureFormatInt64(AMediaFormat* fmt, int offset = 0);
+    void configureFormatFloat(AMediaFormat* fmt, float offset = 0.0f);
+    void configureFormatDouble(AMediaFormat* fmt, double offset = 0.0);
+    void configureFormatSize(AMediaFormat* fmt, size_t offset = 0);
+    void configureFormatString(AMediaFormat* fmt, int offset = 0);
+    void configureFormatRect(AMediaFormat* fmt, int offset = 0);
+    void configureFormatBuffer(AMediaFormat* fmt, int offset = 0);
+    void configureFormat(AMediaFormat* fmt, int offset = 0);
+};
+
+NativeMediaFormatUnitTest::NativeMediaFormatUnitTest() {
+    mInt32KeyValuePairs.insert({118, "elements in periodic table"});
+    mInt32KeyValuePairs.insert({5778, "surface temp. of sun in kelvin"});
+    mInt32KeyValuePairs.insert({8611, "k2 peak in mts"});
+    mInt32KeyValuePairs.insert({72, "heart rate in bpm"});
+    mInt64KeyValuePairs.insert({299792458L, "vel. of em wave in free space m/s"});
+    mInt64KeyValuePairs.insert({86400L, "number of seconds in a day"});
+    mInt64KeyValuePairs.insert({1520200000L, "distance of earth from the sun in km"});
+    mInt64KeyValuePairs.insert({39000000L, "forest area of the world km^2"});
+    mFloatKeyValuePairs.insert({22.0f / 7.0f, "pi"});
+    mFloatKeyValuePairs.insert({3.6f, "not great, not terrible"});
+    mFloatKeyValuePairs.insert({15.999f, "atomic weight of oxygen 8"});
+    mFloatKeyValuePairs.insert({2.7182f, "Euler's number"});
+    mDoubleKeyValuePairs.insert({44.0 / 7, "tau"});
+    mDoubleKeyValuePairs.insert({9.80665, "g on earth m/sec^2"});
+    mSizeKeyValuePairs.insert({sizeof(int64_t), "size of int64_t"});
+    mSizeKeyValuePairs.insert({sizeof(wchar_t), "size of wide char"});
+    mSizeKeyValuePairs.insert({sizeof(intptr_t), "size of pointer variable"});
+    mSizeKeyValuePairs.insert({sizeof *this, "size of class NativeMediaFormatUnitTest"});
+    mStringKeyValuePairs.insert(
+            {"Discovered radium and polonium, and made huge contribution to finding treatments "
+             "for cancer", "Marie Curie"});
+    mStringKeyValuePairs.insert({"Sun rises in the east has zero entropy", "Shannon"});
+    mWindowKeyValuePairs.insert({new Rect{12, 15, 12, 21}, "trapezoid"});
+    mWindowKeyValuePairs.insert({new Rect{12, 12, 12, 12}, "rhombus"});
+    mWindowKeyValuePairs.insert({new Rect{12, 15, 12, 15}, "rectangle"});
+    mWindowKeyValuePairs.insert({new Rect{12, 15, 18, 21}, "quadrilateral"});
+    mBufferKeyValuePairs.insert({new Buffer(), "empty buffer"});
+    size_t sz = strlen(story) + 1;
+    auto* quote = new Buffer{new char[sz], sz};
+    memcpy(quote->buffer, story, sz);
+    mBufferKeyValuePairs.insert({quote, "one line story"});
+    sz = strlen(dragon) + 1;
+    auto* chess = new Buffer(new char[sz], sz);
+    memcpy(chess->buffer, dragon, sz);
+    mBufferKeyValuePairs.insert({chess, "sicilian dragon"});
+}
+
+NativeMediaFormatUnitTest::~NativeMediaFormatUnitTest() {
+    for (auto it : mWindowKeyValuePairs) {
+        delete it.first;
+    }
+    for (auto it : mBufferKeyValuePairs) {
+        delete[] it.first->buffer;
+        delete it.first;
+    }
+}
+
+bool NativeMediaFormatUnitTest::validateFormatInt32(AMediaFormat* fmt, int offset, bool isClear) {
+    bool status = true;
+    int32_t val;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mInt32KeyValuePairs) {
+        bool result = AMediaFormat_getInt32(fmt, it.second, &val);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (val != it.first + offset) {
+                ALOGE("MediaFormat Value for Key %s is not %d but %d", it.second, it.first + offset,
+                      val);
+                status &= false;
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first + offset).c_str());
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getInt32(fmt, "hello world", &val)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatInt64(AMediaFormat* fmt, int offset, bool isClear) {
+    bool status = true;
+    int64_t val;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mInt64KeyValuePairs) {
+        bool result = AMediaFormat_getInt64(fmt, it.second, &val);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (val != it.first + offset) {
+                ALOGE("MediaFormat Value for Key %s is not %" PRId64 "but %" PRId64, it.second,
+                      it.first + offset, val);
+                status &= false;
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first + offset).c_str());
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getInt64(fmt, "hello world", &val)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatFloat(AMediaFormat* fmt, float offset, bool isClear) {
+    bool status = true;
+    float val;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mFloatKeyValuePairs) {
+        bool result = AMediaFormat_getFloat(fmt, it.second, &val);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (val != it.first + offset) {
+                ALOGE("MediaFormat Value for Key %s is not %f but %f", it.second, it.first + offset,
+                      val);
+                status &= false;
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first + offset).c_str());
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getFloat(fmt, "hello world", &val)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatDouble(AMediaFormat* fmt, double offset,
+                                                     bool isClear) {
+    bool status = true;
+    double val;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mDoubleKeyValuePairs) {
+        bool result = AMediaFormat_getDouble(fmt, it.second, &val);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (val != it.first + offset) {
+                ALOGE("MediaFormat Value for Key %s is not %f but %f", it.second, it.first + offset,
+                      val);
+                status &= false;
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first + offset).c_str());
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getDouble(fmt, "hello world", &val)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatSize(AMediaFormat* fmt, size_t offset, bool isClear) {
+    bool status = true;
+    size_t val;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mSizeKeyValuePairs) {
+        bool result = AMediaFormat_getSize(fmt, it.second, &val);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (val != it.first + offset) {
+                ALOGE("MediaFormat Value for Key %s is not %zu but %zu", it.second,
+                      it.first + offset, val);
+                status &= false;
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first + offset).c_str());
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getSize(fmt, "hello world", &val)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatString(AMediaFormat* fmt, int offset, bool isClear) {
+    bool status = true;
+    const char* val;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mStringKeyValuePairs) {
+        bool result = AMediaFormat_getString(fmt, it.second, &val);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            std::string s = it.first + std::to_string(offset);
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (s != val) {
+                ALOGE("MediaFormat Value for Key %s is not %s but %s", it.second, s.c_str(), val);
+                status &= false;
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+            if (strstr(toString, s.c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, s.c_str());
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getString(fmt, "hello world", &val)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatRect(AMediaFormat* fmt, int offset, bool isClear) {
+    bool status = true;
+    int left, top, right, bottom;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mWindowKeyValuePairs) {
+        bool result = AMediaFormat_getRect(fmt, it.second, &left, &top, &right, &bottom);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (left != it.first->left + offset || top != it.first->top + offset ||
+                       right != it.first->right + offset || bottom != it.first->bottom + offset) {
+                ALOGE("MediaFormat Value for Key %s is not (%d, %d, %d, %d)) but (%d, %d, %d, %d)",
+                      it.second, it.first->left, it.first->top, it.first->right, it.first->bottom,
+                      left, top, right, bottom);
+                status &= false;
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first->left + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first->left + offset).c_str());
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first->top + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first->top + offset).c_str());
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first->right + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first->right + offset).c_str());
+                status &= false;
+            }
+            if (strstr(toString, std::to_string(it.first->bottom + offset).c_str()) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString,
+                      std::to_string(it.first->bottom + offset).c_str());
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getRect(fmt, "hello world", &left, &top, &right, &bottom)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormatBuffer(AMediaFormat* fmt, int offset, bool isClear) {
+    bool status = true;
+    void* data;
+    size_t size;
+    const char* toString = AMediaFormat_toString(fmt);
+    for (auto it : mBufferKeyValuePairs) {
+        bool result = AMediaFormat_getBuffer(fmt, it.second, &data, &size);
+        if (isClear) {
+            if (result) {
+                ALOGE("MediaFormat is not expected to contain Key %s", it.second);
+                status &= false;
+            }
+        } else {
+            if (!result) {
+                ALOGE("MediaFormat doesn't contain key %s", it.second);
+                status &= false;
+            } else if (size != (offset == 0 ? it.first->size : it.first->size / 2)) {
+                ALOGE("MediaFormat Value for Key %s is not %zu but %zu", it.second,
+                      (offset == 0 ? it.first->size : it.first->size / 2), size);
+                status &= false;
+            } else {
+                if (it.first->buffer != nullptr &&
+                    memcmp(data, it.first->buffer + it.first->size - size, size) != 0) {
+                    ALOGE("MediaFormat Value for Key %s is not %s but %s {%zu}", it.second,
+                          it.first->buffer + it.first->size - size, (char*)data, size);
+                    status &= false;
+                }
+            }
+            if (strstr(toString, it.second) == nullptr) {
+                ALOGE("AMediaFormat_toString() of fmt %s doesn't contains %s", toString, it.second);
+                status &= false;
+            }
+        }
+    }
+    if (AMediaFormat_getBuffer(fmt, "hello world", &data, &size)) {
+        ALOGE("MediaFormat has value for key 'hello world' ");
+        status &= false;
+    }
+    return status;
+}
+
+bool NativeMediaFormatUnitTest::validateFormat(AMediaFormat* fmt, int offset, bool isClear) {
+    bool status = validateFormatInt32(fmt, offset, isClear);
+    status &= validateFormatInt64(fmt, offset, isClear);
+    status &= validateFormatFloat(fmt, offset, isClear);
+    status &= validateFormatDouble(fmt, offset, isClear);
+    status &= validateFormatSize(fmt, offset, isClear);
+    status &= validateFormatString(fmt, offset, isClear);
+    status &= validateFormatRect(fmt, offset, isClear);
+    status &= validateFormatBuffer(fmt, offset, isClear);
+    return status;
+}
+
+void NativeMediaFormatUnitTest::configureFormatInt32(AMediaFormat* fmt, int offset) {
+    for (auto it : mInt32KeyValuePairs) {
+        AMediaFormat_setInt32(fmt, it.second, it.first + offset);
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormatInt64(AMediaFormat* fmt, int offset) {
+    for (auto it : mInt64KeyValuePairs) {
+        AMediaFormat_setInt64(fmt, it.second, it.first + offset);
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormatFloat(AMediaFormat* fmt, float offset) {
+    for (auto it : mFloatKeyValuePairs) {
+        AMediaFormat_setFloat(fmt, it.second, it.first + offset);
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormatDouble(AMediaFormat* fmt, double offset) {
+    for (auto it : mDoubleKeyValuePairs) {
+        AMediaFormat_setDouble(fmt, it.second, it.first + offset);
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormatSize(AMediaFormat* fmt, size_t offset) {
+    for (auto it : mSizeKeyValuePairs) {
+        AMediaFormat_setSize(fmt, it.second, it.first + offset);
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormatString(AMediaFormat* fmt, int offset) {
+    for (auto it : mStringKeyValuePairs) {
+        std::string s1 = it.first + std::to_string(offset);
+        AMediaFormat_setString(fmt, it.second, s1.c_str());
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormatRect(AMediaFormat* fmt, int offset) {
+    for (auto it : mWindowKeyValuePairs) {
+        AMediaFormat_setRect(fmt, it.second, it.first->left + offset, it.first->top + offset,
+                             it.first->right + offset, it.first->bottom + offset);
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormatBuffer(AMediaFormat* fmt, int offset) {
+    for (auto it : mBufferKeyValuePairs) {
+        int sz = offset == 0 ? it.first->size : it.first->size / 2;
+        AMediaFormat_setBuffer(fmt, it.second, it.first->buffer + it.first->size - sz, sz);
+    }
+}
+
+void NativeMediaFormatUnitTest::configureFormat(AMediaFormat* fmt, int offset) {
+    configureFormatInt32(fmt, offset);
+    configureFormatInt64(fmt, offset);
+    configureFormatFloat(fmt, offset);
+    configureFormatDouble(fmt, offset);
+    configureFormatSize(fmt, offset);
+    configureFormatString(fmt, offset);
+    configureFormatRect(fmt, offset);
+    configureFormatBuffer(fmt, offset);
+}
+
+// 1. configure format with default values and validate the same
+// 2. copy configured format to an empty format and validate the copied format
+// 3. overwrite copied format with default + offset values and validate the updated format
+// 4. overwrite updated format with default values using AMediaFormat_copy API and validate the same
+// 5. clear mediaformat and validate if keys are not present
+static bool testMediaFormatAllNative() {
+    auto* nmf = new NativeMediaFormatUnitTest();
+    AMediaFormat* fmtOrig = AMediaFormat_new();
+    AMediaFormat* fmtDup = AMediaFormat_new();
+    const int offset = 123;
+
+    nmf->configureFormat(fmtOrig);
+    bool status = nmf->validateFormat(fmtOrig);
+
+    AMediaFormat_copy(fmtDup, fmtOrig);
+    status &= nmf->validateFormat(fmtDup);
+
+    nmf->configureFormat(fmtDup, offset);
+    status &= nmf->validateFormat(fmtDup, offset);
+
+    AMediaFormat_copy(fmtDup, fmtOrig);
+    status &= nmf->validateFormat(fmtDup);
+
+    AMediaFormat_clear(fmtDup);
+    status &= nmf->validateFormat(fmtDup, offset, true);
+
+    AMediaFormat_delete(fmtOrig);
+    AMediaFormat_delete(fmtDup);
+    delete nmf;
+
+    return status;
+}
+
+// 1. configure format with default values and validate the same
+// 2. copy configured format to an empty format and validate the copied format
+// 3. overwrite copied format with default + offset values and validate the updated format
+// 4. overwrite updated format with default values using AMediaFormat_copy API and validate the same
+#define testMediaFormatfuncNative(func)                            \
+    static bool testMediaFormat##func##Native() {                  \
+        auto* nmf = new NativeMediaFormatUnitTest();               \
+        AMediaFormat* fmtOrig = AMediaFormat_new();                \
+        AMediaFormat* fmtDup = AMediaFormat_new();                 \
+        const int offset = 12345;                                  \
+                                                                   \
+        nmf->configureFormat##func(fmtOrig);                       \
+        bool status = nmf->validateFormat##func(fmtOrig);          \
+                                                                   \
+        AMediaFormat_copy(fmtDup, fmtOrig);                        \
+        status &= nmf->validateFormat##func(fmtDup);               \
+                                                                   \
+        nmf->configureFormat##func(fmtDup, offset);                \
+        status &= nmf->validateFormat##func(fmtDup, offset);       \
+                                                                   \
+        AMediaFormat_copy(fmtDup, fmtOrig);                        \
+        status &= nmf->validateFormat##func(fmtDup);               \
+                                                                   \
+        AMediaFormat_clear(fmtDup);                                \
+        status &= nmf->validateFormat##func(fmtDup, offset, true); \
+        AMediaFormat_delete(fmtOrig);                              \
+        AMediaFormat_delete(fmtDup);                               \
+        delete nmf;                                                \
+        return status;                                             \
+    }
+
+testMediaFormatfuncNative(Int32)
+
+testMediaFormatfuncNative(Int64)
+
+testMediaFormatfuncNative(Float)
+
+testMediaFormatfuncNative(Double)
+
+testMediaFormatfuncNative(Size)
+
+testMediaFormatfuncNative(String)
+
+testMediaFormatfuncNative(Rect)
+
+testMediaFormatfuncNative(Buffer)
+
+#define nativeTestMediaFormatfunc(func)                                \
+    static jboolean nativeTestMediaFormat##func(JNIEnv*, jobject) {    \
+        return static_cast<jboolean>(testMediaFormat##func##Native()); \
+    }
+
+nativeTestMediaFormatfunc(Int32)
+
+nativeTestMediaFormatfunc(Int64)
+
+nativeTestMediaFormatfunc(Float)
+
+nativeTestMediaFormatfunc(Double)
+
+nativeTestMediaFormatfunc(Size)
+
+nativeTestMediaFormatfunc(String)
+
+nativeTestMediaFormatfunc(Rect)
+
+nativeTestMediaFormatfunc(Buffer)
+
+nativeTestMediaFormatfunc(All)
+
+int registerAndroidMediaV2CtsMediaFormatUnitTest(JNIEnv* env) {
+    const JNINativeMethod methodTable[] = {
+            {"nativeTestMediaFormatInt32", "()Z", (void*)nativeTestMediaFormatInt32},
+            {"nativeTestMediaFormatInt64", "()Z", (void*)nativeTestMediaFormatInt64},
+            {"nativeTestMediaFormatFloat", "()Z", (void*)nativeTestMediaFormatFloat},
+            {"nativeTestMediaFormatDouble", "()Z", (void*)nativeTestMediaFormatDouble},
+            {"nativeTestMediaFormatSize", "()Z", (void*)nativeTestMediaFormatSize},
+            {"nativeTestMediaFormatString", "()Z", (void*)nativeTestMediaFormatString},
+            {"nativeTestMediaFormatRect", "()Z", (void*)nativeTestMediaFormatRect},
+            {"nativeTestMediaFormatBuffer", "()Z", (void*)nativeTestMediaFormatBuffer},
+            {"nativeTestMediaFormatAll", "()Z", (void*)nativeTestMediaFormatAll},
+    };
+    jclass c = env->FindClass("android/mediav2/cts/MediaFormatUnitTest");
+    return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
+}
+
+extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
+    JNIEnv* env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR;
+    if (registerAndroidMediaV2CtsMediaFormatUnitTest(env) != JNI_OK) return JNI_ERR;
+    return JNI_VERSION_1_6;
+}
diff --git a/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java b/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java
index e803863..ae9054f 100644
--- a/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java
+++ b/tests/media/src/android/mediav2/cts/AdaptivePlaybackTest.java
@@ -219,6 +219,7 @@
             queueEOS();
             waitForAllOutputs();
             mCodec.reset();
+            mCodec.release();
         }
         tearDownSurface();
     }
diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
index 832127d..457ebf7 100644
--- a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
@@ -706,7 +706,6 @@
     private native boolean nativeTestSetForceSyncFrame(String encoder, String file, String mime,
             int[] list0, int[] list1, int[] list2, int colorFormat);
 
-    @Ignore("TODO(b/) = test sometimes timesout")
     @LargeTest
     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
     public void testSetForceSyncFrameNative() throws IOException {
@@ -742,8 +741,6 @@
         mOutputBuff = new OutputManager();
         mSaveToMem = true;
         {
-            /* TODO(b/147574800) */
-            if (mCodecName.equals("c2.android.hevc.encoder")) return;
             mCodec = MediaCodec.createByCodecName(mCodecName);
             format.removeKey(MediaFormat.KEY_BITRATE_MODE);
             MediaCodecInfo.EncoderCapabilities cap =
@@ -803,7 +800,6 @@
     private native boolean nativeTestAdaptiveBitRate(String encoder, String file, String mime,
             int[] list0, int[] list1, int[] list2, int colorFormat);
 
-    @Ignore("TODO(b/) = test sometimes timesout")
     @LargeTest
     @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
     public void testAdaptiveBitRateNative() throws IOException {
@@ -811,8 +807,6 @@
             mAdaptiveBitrateMimeList.contains(mMime));
         int colorFormat = -1;
         {
-            /* TODO(b/147574800) */
-            if (mCodecName.equals("c2.android.hevc.encoder")) return;
             if (!mIsAudio) {
                 colorFormat = findByteBufferColorFormat(mCodecName, mMime);
                 assertTrue("no valid color formats received", colorFormat != -1);
diff --git a/tests/media/src/android/mediav2/cts/CodecTestBase.java b/tests/media/src/android/mediav2/cts/CodecTestBase.java
index ea542d6..575eed7 100644
--- a/tests/media/src/android/mediav2/cts/CodecTestBase.java
+++ b/tests/media/src/android/mediav2/cts/CodecTestBase.java
@@ -16,9 +16,11 @@
 
 package android.mediav2.cts;
 
+import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.ImageFormat;
 import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
 import android.media.Image;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
@@ -29,6 +31,7 @@
 import android.os.PersistableBundle;
 import android.util.Log;
 import android.util.Pair;
+import android.view.Display;
 import android.view.Surface;
 
 import androidx.annotation.NonNull;
@@ -60,6 +63,7 @@
 
 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface;
 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
+import static android.media.MediaCodecInfo.CodecProfileLevel.*;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -524,8 +528,8 @@
     static final int RETRY_LIMIT = 100; // max poll counter before test aborts and returns error
     static final String INVALID_CODEC = "unknown.codec_";
     static final String mInpPrefix = WorkDir.getMediaDirString();
-    static final PackageManager pm =
-            InstrumentationRegistry.getInstrumentation().getContext().getPackageManager();
+    static final Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+    static final PackageManager pm = mContext.getPackageManager();
     static String mimeSelKeys;
     static String codecPrefix;
 
@@ -629,6 +633,47 @@
         return isSupported;
     }
 
+    static boolean doesAnyFormatHaveHDRProfile(String mime, ArrayList<MediaFormat> formats) {
+        boolean isHDR = false;
+        for (MediaFormat format : formats) {
+            assertEquals(mime, format.getString(MediaFormat.KEY_MIME));
+            if (mime.equals(MediaFormat.MIMETYPE_VIDEO_AVC)) {
+                int profile = format.getInteger(MediaFormat.KEY_PROFILE);
+                if (profile == AVCProfileHigh10 || profile == AVCProfileHigh422 ||
+                        profile == AVCProfileHigh444) {
+                    isHDR = true;
+                    break;
+                }
+            } else if (mime.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+                int profile = format.getInteger(MediaFormat.KEY_PROFILE, VP9Profile0);
+                if (profile == VP9Profile2HDR || profile == VP9Profile3HDR ||
+                        profile == VP9Profile2HDR10Plus || profile == VP9Profile3HDR10Plus) {
+                    isHDR = true;
+                    break;
+                }
+            } else if (mime.equals(MediaFormat.MIMETYPE_VIDEO_HEVC)) {
+                int profile = format.getInteger(MediaFormat.KEY_PROFILE, HEVCProfileMain);
+                if (profile == HEVCProfileMain10HDR10 || profile == HEVCProfileMain10HDR10Plus) {
+                    isHDR = true;
+                    break;
+                }
+            } else if (mime.equals(MediaFormat.MIMETYPE_VIDEO_AV1)) {
+                int profile = format.getInteger(MediaFormat.KEY_PROFILE, AV1ProfileMain8);
+                if (profile == AV1ProfileMain10HDR10 || profile == AV1ProfileMain10HDR10Plus) {
+                    isHDR = true;
+                    break;
+                }
+            }
+        }
+        return isHDR;
+    }
+
+    static boolean canDisplaySupportHDRContent() {
+        DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+        return displayManager.getDisplay(Display.DEFAULT_DISPLAY).getHdrCapabilities()
+                .getSupportedHdrTypes().length != 0;
+    }
+
     static boolean areFormatsSupported(String name, String mime, ArrayList<MediaFormat> formats)
             throws IOException {
         MediaCodec codec = MediaCodec.createByCodecName(name);
diff --git a/tests/media/src/android/mediav2/cts/CodecUnitTest.java b/tests/media/src/android/mediav2/cts/CodecUnitTest.java
index 3901b88..803b144 100644
--- a/tests/media/src/android/mediav2/cts/CodecUnitTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecUnitTest.java
@@ -23,6 +23,7 @@
 import android.os.Bundle;
 import android.util.Pair;
 
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
 
 import org.junit.After;
@@ -49,6 +50,10 @@
     static final long STALL_TIME_MS = 1000;
 
     @SmallTest
+    // Following tests were added in Android R and are not limited to c2.android.* codecs.
+    // Hence limit the tests to Android R and above and also annotate as NonMediaMainlineTest
+    @SdkSuppress(minSdkVersion = 30)
+    @NonMediaMainlineTest
     public static class TestApi extends CodecTestBase {
         @Rule
         public Timeout timeout = new Timeout(PER_TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
@@ -1964,6 +1969,10 @@
     }
 
     @SmallTest
+    // Following tests were added in Android R and are not limited to c2.android.* codecs.
+    // Hence limit the tests to Android R and above and also annotate as NonMediaMainlineTest
+    @SdkSuppress(minSdkVersion = 30)
+    @NonMediaMainlineTest
     public static class TestApiNative {
         @Rule
         public Timeout timeout = new Timeout(PER_TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
diff --git a/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java b/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java
index ce36bd3..15f5e65 100644
--- a/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java
+++ b/tests/media/src/android/mediav2/cts/DecoderColorAspectsTest.java
@@ -51,10 +51,10 @@
         mCheckESList = new ArrayList<>();
         mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AVC);
         mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_HEVC);
-        /* TODO (b/165492703) Mpeg2 and (b/165787556) AV1 has problems in signalling color
+        /* TODO (b/165492703) Mpeg2 has problems in signalling color
             aspects information via elementary stream. */
         // mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_MPEG2);
-        // mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AV1);
+        mCheckESList.add(MediaFormat.MIMETYPE_VIDEO_AV1);
         mCanIgnoreColorBox = canIgnoreColorBox;
     }
 
@@ -246,6 +246,9 @@
         ArrayList<MediaFormat> formats = new ArrayList<>();
         formats.add(format);
         Assume.assumeTrue(areFormatsSupported(mCodecName, mMime, formats));
+        if (doesAnyFormatHaveHDRProfile(mMime, formats)) {
+            Assume.assumeTrue(canDisplaySupportHDRContent());
+        }
         CodecTestActivity activity = mActivityRule.getActivity();
         setUpSurface(activity);
         activity.setScreenParams(getWidth(format), getHeight(format), true);
diff --git a/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java b/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
index f24a4b1..51a10fc 100644
--- a/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
+++ b/tests/media/src/android/mediav2/cts/EncoderProfileLevelTest.java
@@ -155,8 +155,7 @@
                 {MediaFormat.MIMETYPE_VIDEO_H263, 16384000, 720, 480, 60},
                 {MediaFormat.MIMETYPE_VIDEO_H263, 16384000, 720, 576, 50},
 
-                // TODO (b/151429828)
-                //{MediaFormat.MIMETYPE_VIDEO_HEVC, 128000, 176, 144, 15},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, 128000, 176, 144, 15},
                 {MediaFormat.MIMETYPE_VIDEO_HEVC, 1500000, 352, 288, 30},
                 // TODO (b/152576008) - Limit HEVC Encoder test to 512x512
                 {MediaFormat.MIMETYPE_VIDEO_HEVC, 3000000, 512, 512, 30},
diff --git a/tests/media/src/android/mediav2/cts/MediaFormatUnitTest.java b/tests/media/src/android/mediav2/cts/MediaFormatUnitTest.java
new file mode 100644
index 0000000..6047f30
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/MediaFormatUnitTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2021 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.mediav2.cts;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class MediaFormatUnitTest {
+    static final int PER_TEST_TIMEOUT_MS = 10000;
+
+    static {
+        System.loadLibrary("ctsmediav2utils_jni");
+    }
+
+    @Rule
+    public Timeout timeout = new Timeout(PER_TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+    @Test
+    public void testMediaFormatNativeInt32() {
+        assertTrue(nativeTestMediaFormatInt32());
+    }
+
+    private native boolean nativeTestMediaFormatInt32();
+
+    @Test
+    public void testMediaFormatNativeInt64() {
+        assertTrue(nativeTestMediaFormatInt64());
+    }
+
+    private native boolean nativeTestMediaFormatInt64();
+
+    @Test
+    public void testMediaFormatNativeFloat() {
+        assertTrue(nativeTestMediaFormatFloat());
+    }
+
+    private native boolean nativeTestMediaFormatFloat();
+
+    @Test
+    public void testMediaFormatNativeDouble() {
+        assertTrue(nativeTestMediaFormatDouble());
+    }
+
+    private native boolean nativeTestMediaFormatDouble();
+
+    @Test
+    public void testMediaFormatNativeSize() {
+        assertTrue(nativeTestMediaFormatSize());
+    }
+
+    private native boolean nativeTestMediaFormatSize();
+
+    @Test
+    public void testMediaFormatNativeString() {
+        assertTrue(nativeTestMediaFormatString());
+    }
+
+    private native boolean nativeTestMediaFormatString();
+
+    @Test
+    public void testMediaFormatNativeRect() {
+        assertTrue(nativeTestMediaFormatRect());
+    }
+
+    private native boolean nativeTestMediaFormatRect();
+
+    @Test
+    public void testMediaFormatNativeBuffer() {
+        assertTrue(nativeTestMediaFormatBuffer());
+    }
+
+    private native boolean nativeTestMediaFormatBuffer();
+
+    @Test
+    public void testMediaFormatNativeAll() {
+        assertTrue(nativeTestMediaFormatAll());
+    }
+
+    private native boolean nativeTestMediaFormatAll();
+}
diff --git a/tests/media/src/android/mediav2/cts/WorkDir.java b/tests/media/src/android/mediav2/cts/WorkDir.java
index 9490d69..698eb6b 100644
--- a/tests/media/src/android/mediav2/cts/WorkDir.java
+++ b/tests/media/src/android/mediav2/cts/WorkDir.java
@@ -40,7 +40,7 @@
             // user has specified the mediaDirString via instrumentation-arg
             return mediaDirString + ((mediaDirString.endsWith("/")) ? "" : "/");
         } else {
-            return (getTopDirString() + "test/CtsMediaV2TestCases-1.13/");
+            return (getTopDirString() + "test/CtsMediaV2TestCases-1.14/");
         }
     }
 }
diff --git a/tests/net/Android.bp b/tests/net/Android.bp
new file mode 100644
index 0000000..2a4153a
--- /dev/null
+++ b/tests/net/Android.bp
@@ -0,0 +1,24 @@
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_library {
+    name: "CtsNetTestsNonUpdatableLib",
+    srcs: ["src/**/*.java"],
+    static_libs: ["androidx.test.rules"],
+    platform_apis: true,
+}
diff --git a/tests/net/OWNERS b/tests/net/OWNERS
new file mode 100644
index 0000000..67e4fc9
--- /dev/null
+++ b/tests/net/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 31808
+set noparent
+file:platform/packages/modules/Connectivity:master:/OWNERS_core_networking_xts
\ No newline at end of file
diff --git a/tests/net/TEST_MAPPING b/tests/net/TEST_MAPPING
new file mode 100644
index 0000000..a6a02d5
--- /dev/null
+++ b/tests/net/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "imports": [
+    {
+      "path": "packages/modules/Connectivity"
+    }
+  ]
+}
diff --git a/tests/net/src/android/net/cts/LocalSocketTest.java b/tests/net/src/android/net/cts/LocalSocketTest.java
new file mode 100644
index 0000000..969f706
--- /dev/null
+++ b/tests/net/src/android/net/cts/LocalSocketTest.java
@@ -0,0 +1,471 @@
+/*
+ * Copyright (C) 2008 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.net.cts;
+
+import android.net.Credentials;
+import android.net.LocalServerSocket;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
+import android.os.ParcelFileDescriptor;
+import android.system.Os;
+import android.system.OsConstants;
+
+import junit.framework.TestCase;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.Callable;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+
+public class LocalSocketTest extends TestCase {
+    private final static String ADDRESS_PREFIX = "com.android.net.LocalSocketTest";
+
+    public void testLocalConnections() throws IOException {
+        String address = ADDRESS_PREFIX + "_testLocalConnections";
+        // create client and server socket
+        LocalServerSocket localServerSocket = new LocalServerSocket(address);
+        LocalSocket clientSocket = new LocalSocket();
+
+        // establish connection between client and server
+        LocalSocketAddress locSockAddr = new LocalSocketAddress(address);
+        assertFalse(clientSocket.isConnected());
+        clientSocket.connect(locSockAddr);
+        assertTrue(clientSocket.isConnected());
+
+        LocalSocket serverSocket = localServerSocket.accept();
+        assertTrue(serverSocket.isConnected());
+        assertTrue(serverSocket.isBound());
+        try {
+            serverSocket.bind(localServerSocket.getLocalSocketAddress());
+            fail("Cannot bind a LocalSocket from accept()");
+        } catch (IOException expected) {
+        }
+        try {
+            serverSocket.connect(locSockAddr);
+            fail("Cannot connect a LocalSocket from accept()");
+        } catch (IOException expected) {
+        }
+
+        Credentials credent = clientSocket.getPeerCredentials();
+        assertTrue(0 != credent.getPid());
+
+        // send data from client to server
+        OutputStream clientOutStream = clientSocket.getOutputStream();
+        clientOutStream.write(12);
+        InputStream serverInStream = serverSocket.getInputStream();
+        assertEquals(12, serverInStream.read());
+
+        //send data from server to client
+        OutputStream serverOutStream = serverSocket.getOutputStream();
+        serverOutStream.write(3);
+        InputStream clientInStream = clientSocket.getInputStream();
+        assertEquals(3, clientInStream.read());
+
+        // Test sending and receiving file descriptors
+        clientSocket.setFileDescriptorsForSend(new FileDescriptor[]{FileDescriptor.in});
+        clientOutStream.write(32);
+        assertEquals(32, serverInStream.read());
+
+        FileDescriptor[] out = serverSocket.getAncillaryFileDescriptors();
+        assertEquals(1, out.length);
+        FileDescriptor fd = clientSocket.getFileDescriptor();
+        assertTrue(fd.valid());
+
+        //shutdown input stream of client
+        clientSocket.shutdownInput();
+        assertEquals(-1, clientInStream.read());
+
+        //shutdown output stream of client
+        clientSocket.shutdownOutput();
+        try {
+            clientOutStream.write(10);
+            fail("testLocalSocket shouldn't come to here");
+        } catch (IOException e) {
+            // expected
+        }
+
+        //shutdown input stream of server
+        serverSocket.shutdownInput();
+        assertEquals(-1, serverInStream.read());
+
+        //shutdown output stream of server
+        serverSocket.shutdownOutput();
+        try {
+            serverOutStream.write(10);
+            fail("testLocalSocket shouldn't come to here");
+        } catch (IOException e) {
+            // expected
+        }
+
+        //close client socket
+        clientSocket.close();
+        try {
+            clientInStream.read();
+            fail("testLocalSocket shouldn't come to here");
+        } catch (IOException e) {
+            // expected
+        }
+
+        //close server socket
+        serverSocket.close();
+        try {
+            serverInStream.read();
+            fail("testLocalSocket shouldn't come to here");
+        } catch (IOException e) {
+            // expected
+        }
+    }
+
+    public void testAccessors() throws IOException {
+        String address = ADDRESS_PREFIX + "_testAccessors";
+        LocalSocket socket = new LocalSocket();
+        LocalSocketAddress addr = new LocalSocketAddress(address);
+
+        assertFalse(socket.isBound());
+        socket.bind(addr);
+        assertTrue(socket.isBound());
+        assertEquals(addr, socket.getLocalSocketAddress());
+
+        String str = socket.toString();
+        assertTrue(str.contains("impl:android.net.LocalSocketImpl"));
+
+        socket.setReceiveBufferSize(1999);
+        assertEquals(1999 << 1, socket.getReceiveBufferSize());
+
+        socket.setSendBufferSize(3998);
+        assertEquals(3998 << 1, socket.getSendBufferSize());
+
+        assertEquals(0, socket.getSoTimeout());
+        socket.setSoTimeout(1996);
+        assertTrue(socket.getSoTimeout() > 0);
+
+        try {
+            socket.getRemoteSocketAddress();
+            fail("testLocalSocketSecondary shouldn't come to here");
+        } catch (UnsupportedOperationException e) {
+            // expected
+        }
+
+        try {
+            socket.isClosed();
+            fail("testLocalSocketSecondary shouldn't come to here");
+        } catch (UnsupportedOperationException e) {
+            // expected
+        }
+
+        try {
+            socket.isInputShutdown();
+            fail("testLocalSocketSecondary shouldn't come to here");
+        } catch (UnsupportedOperationException e) {
+            // expected
+        }
+
+        try {
+            socket.isOutputShutdown();
+            fail("testLocalSocketSecondary shouldn't come to here");
+        } catch (UnsupportedOperationException e) {
+            // expected
+        }
+
+        try {
+            socket.connect(addr, 2005);
+            fail("testLocalSocketSecondary shouldn't come to here");
+        } catch (UnsupportedOperationException e) {
+            // expected
+        }
+
+        socket.close();
+    }
+
+    // http://b/31205169
+    public void testSetSoTimeout_readTimeout() throws Exception {
+        String address = ADDRESS_PREFIX + "_testSetSoTimeout_readTimeout";
+
+        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+            final LocalSocket clientSocket = socketPair.clientSocket;
+
+            // Set the timeout in millis.
+            int timeoutMillis = 1000;
+            clientSocket.setSoTimeout(timeoutMillis);
+
+            // Avoid blocking the test run if timeout doesn't happen by using a separate thread.
+            Callable<Result> reader = () -> {
+                try {
+                    clientSocket.getInputStream().read();
+                    return Result.noException("Did not block");
+                } catch (IOException e) {
+                    return Result.exception(e);
+                }
+            };
+            // Allow the configured timeout, plus some slop.
+            int allowedTime = timeoutMillis + 2000;
+            Result result = runInSeparateThread(allowedTime, reader);
+
+            // Check the message was a timeout, it's all we have to go on.
+            String expectedMessage = Os.strerror(OsConstants.EAGAIN);
+            result.assertThrewIOException(expectedMessage);
+        }
+    }
+
+    // http://b/31205169
+    public void testSetSoTimeout_writeTimeout() throws Exception {
+        String address = ADDRESS_PREFIX + "_testSetSoTimeout_writeTimeout";
+
+        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+            final LocalSocket clientSocket = socketPair.clientSocket;
+
+            // Set the timeout in millis.
+            int timeoutMillis = 1000;
+            clientSocket.setSoTimeout(timeoutMillis);
+
+            // Set a small buffer size so we know we can flood it.
+            clientSocket.setSendBufferSize(100);
+            final int bufferSize = clientSocket.getSendBufferSize();
+
+            // Avoid blocking the test run if timeout doesn't happen by using a separate thread.
+            Callable<Result> writer = () -> {
+                try {
+                    byte[] toWrite = new byte[bufferSize * 2];
+                    clientSocket.getOutputStream().write(toWrite);
+                    return Result.noException("Did not block");
+                } catch (IOException e) {
+                    return Result.exception(e);
+                }
+            };
+            // Allow the configured timeout, plus some slop.
+            int allowedTime = timeoutMillis + 2000;
+
+            Result result = runInSeparateThread(allowedTime, writer);
+
+            // Check the message was a timeout, it's all we have to go on.
+            String expectedMessage = Os.strerror(OsConstants.EAGAIN);
+            result.assertThrewIOException(expectedMessage);
+        }
+    }
+
+    public void testAvailable() throws Exception {
+        String address = ADDRESS_PREFIX + "_testAvailable";
+
+        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+            LocalSocket clientSocket = socketPair.clientSocket;
+            LocalSocket serverSocket = socketPair.serverSocket.accept();
+
+            OutputStream clientOutputStream = clientSocket.getOutputStream();
+            InputStream serverInputStream = serverSocket.getInputStream();
+            assertEquals(0, serverInputStream.available());
+
+            byte[] buffer = new byte[50];
+            clientOutputStream.write(buffer);
+            assertEquals(50, serverInputStream.available());
+
+            InputStream clientInputStream = clientSocket.getInputStream();
+            OutputStream serverOutputStream = serverSocket.getOutputStream();
+            assertEquals(0, clientInputStream.available());
+            serverOutputStream.write(buffer);
+            assertEquals(50, serverInputStream.available());
+
+            serverSocket.close();
+        }
+    }
+
+    // http://b/34095140
+    public void testLocalSocketCreatedFromFileDescriptor() throws Exception {
+        String address = ADDRESS_PREFIX + "_testLocalSocketCreatedFromFileDescriptor";
+
+        // Establish connection between a local client and server to get a valid client socket file
+        // descriptor.
+        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+            // Extract the client FileDescriptor we can use.
+            FileDescriptor fileDescriptor = socketPair.clientSocket.getFileDescriptor();
+            assertTrue(fileDescriptor.valid());
+
+            // Create the LocalSocket we want to test.
+            LocalSocket clientSocketCreatedFromFileDescriptor =
+                    LocalSocket.createConnectedLocalSocket(fileDescriptor);
+            assertTrue(clientSocketCreatedFromFileDescriptor.isConnected());
+            assertTrue(clientSocketCreatedFromFileDescriptor.isBound());
+
+            // Test the LocalSocket can be used for communication.
+            LocalSocket serverSocket = socketPair.serverSocket.accept();
+            OutputStream clientOutputStream =
+                    clientSocketCreatedFromFileDescriptor.getOutputStream();
+            InputStream serverInputStream = serverSocket.getInputStream();
+
+            clientOutputStream.write(12);
+            assertEquals(12, serverInputStream.read());
+
+            // Closing clientSocketCreatedFromFileDescriptor does not close the file descriptor.
+            clientSocketCreatedFromFileDescriptor.close();
+            assertTrue(fileDescriptor.valid());
+
+            // .. while closing the LocalSocket that owned the file descriptor does.
+            socketPair.clientSocket.close();
+            assertFalse(fileDescriptor.valid());
+        }
+    }
+
+    public void testFlush() throws Exception {
+        String address = ADDRESS_PREFIX + "_testFlush";
+
+        try (LocalSocketPair socketPair = LocalSocketPair.createConnectedSocketPair(address)) {
+            LocalSocket clientSocket = socketPair.clientSocket;
+            LocalSocket serverSocket = socketPair.serverSocket.accept();
+
+            OutputStream clientOutputStream = clientSocket.getOutputStream();
+            InputStream serverInputStream = serverSocket.getInputStream();
+            testFlushWorks(clientOutputStream, serverInputStream);
+
+            OutputStream serverOutputStream = serverSocket.getOutputStream();
+            InputStream clientInputStream = clientSocket.getInputStream();
+            testFlushWorks(serverOutputStream, clientInputStream);
+
+            serverSocket.close();
+        }
+    }
+
+    private void testFlushWorks(OutputStream outputStream, InputStream inputStream)
+            throws Exception {
+        final int bytesToTransfer = 50;
+        StreamReader inputStreamReader = new StreamReader(inputStream, bytesToTransfer);
+
+        byte[] buffer = new byte[bytesToTransfer];
+        outputStream.write(buffer);
+        assertEquals(bytesToTransfer, inputStream.available());
+
+        // Start consuming the data.
+        inputStreamReader.start();
+
+        // This doesn't actually flush any buffers, it just polls until the reader has read all the
+        // bytes.
+        outputStream.flush();
+
+        inputStreamReader.waitForCompletion(5000);
+        inputStreamReader.assertBytesRead(bytesToTransfer);
+        assertEquals(0, inputStream.available());
+    }
+
+    private static class StreamReader extends Thread {
+        private final InputStream is;
+        private final int expectedByteCount;
+        private final CountDownLatch completeLatch = new CountDownLatch(1);
+
+        private volatile Exception exception;
+        private int bytesRead;
+
+        private StreamReader(InputStream is, int expectedByteCount) {
+            this.is = is;
+            this.expectedByteCount = expectedByteCount;
+        }
+
+        @Override
+        public void run() {
+            try {
+                byte[] buffer = new byte[10];
+                int readCount;
+                while ((readCount = is.read(buffer)) >= 0) {
+                    bytesRead += readCount;
+                    if (bytesRead >= expectedByteCount) {
+                        break;
+                    }
+                }
+            } catch (IOException e) {
+                exception = e;
+            } finally {
+                completeLatch.countDown();
+            }
+        }
+
+        public void waitForCompletion(long waitMillis) throws Exception {
+            if (!completeLatch.await(waitMillis, TimeUnit.MILLISECONDS)) {
+                fail("Timeout waiting for completion");
+            }
+            if (exception != null) {
+                throw new Exception("Read failed", exception);
+            }
+        }
+
+        public void assertBytesRead(int expected) {
+            assertEquals(expected, bytesRead);
+        }
+    }
+
+    private static class Result {
+        private final String type;
+        private final Exception e;
+
+        private Result(String type, Exception e) {
+            this.type = type;
+            this.e = e;
+        }
+
+        static Result noException(String description) {
+            return new Result(description, null);
+        }
+
+        static Result exception(Exception e) {
+            return new Result(e.getClass().getName(), e);
+        }
+
+        void assertThrewIOException(String expectedMessage) {
+            assertEquals("Unexpected result type", IOException.class.getName(), type);
+            assertEquals("Unexpected exception message", expectedMessage, e.getMessage());
+        }
+    }
+
+    private static Result runInSeparateThread(int allowedTime, final Callable<Result> callable)
+            throws Exception {
+        ExecutorService service = Executors.newSingleThreadScheduledExecutor();
+        Future<Result> future = service.submit(callable);
+        Result result = future.get(allowedTime, TimeUnit.MILLISECONDS);
+        if (!future.isDone()) {
+            fail("Worker thread appears blocked");
+        }
+        return result;
+    }
+
+    private static class LocalSocketPair implements AutoCloseable {
+        static LocalSocketPair createConnectedSocketPair(String address) throws Exception {
+            LocalServerSocket localServerSocket = new LocalServerSocket(address);
+            final LocalSocket clientSocket = new LocalSocket();
+
+            // Establish connection between client and server
+            LocalSocketAddress locSockAddr = new LocalSocketAddress(address);
+            clientSocket.connect(locSockAddr);
+            assertTrue(clientSocket.isConnected());
+            return new LocalSocketPair(localServerSocket, clientSocket);
+        }
+
+        final LocalServerSocket serverSocket;
+        final LocalSocket clientSocket;
+
+        LocalSocketPair(LocalServerSocket serverSocket, LocalSocket clientSocket) {
+            this.serverSocket = serverSocket;
+            this.clientSocket = clientSocket;
+        }
+
+        public void close() throws Exception {
+            serverSocket.close();
+            clientSocket.close();
+        }
+    }
+}
diff --git a/tests/providerui/AndroidManifest.xml b/tests/providerui/AndroidManifest.xml
index 2f1f791..a14df70 100644
--- a/tests/providerui/AndroidManifest.xml
+++ b/tests/providerui/AndroidManifest.xml
@@ -23,9 +23,7 @@
     <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
-    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
-    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_SETTINGS" />
 
     <!--
@@ -42,7 +40,7 @@
         </intent>
     </queries>
 
-    <application android:requestLegacyExternalStorage = "true">
+    <application>
         <uses-library android:name="android.test.runner"/>
         <activity android:name="android.providerui.cts.GetResultActivity" />
 
diff --git a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
index 4342810..542b3aa 100644
--- a/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
+++ b/tests/providerui/src/android/providerui/cts/MediaStoreUiTest.java
@@ -16,6 +16,8 @@
 
 package android.providerui.cts;
 
+import static android.provider.cts.ProviderTestUtils.resolveVolumeName;
+
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -26,12 +28,12 @@
 import android.app.Instrumentation;
 import android.app.UiAutomation;
 import android.content.ContentResolver;
+import android.content.ContentUris;
 import android.content.Context;
 import android.content.Intent;
 import android.content.UriPermission;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.res.AssetFileDescriptor;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Environment;
@@ -39,7 +41,6 @@
 import android.os.ParcelFileDescriptor;
 import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
-import android.os.UserManager;
 import android.provider.DocumentsContract;
 import android.provider.MediaStore;
 import android.provider.cts.ProviderTestUtils;
@@ -50,12 +51,12 @@
 import android.support.test.uiautomator.UiObject;
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.support.test.uiautomator.UiScrollable;
 import android.support.test.uiautomator.UiSelector;
 import android.support.test.uiautomator.Until;
 import android.system.Os;
 import android.text.format.DateUtils;
 import android.util.Log;
+import android.util.Pair;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -68,6 +69,7 @@
 import org.junit.runners.Parameterized.Parameters;
 
 import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
@@ -122,6 +124,7 @@
         mActivity = (GetResultActivity) mInstrumentation.startActivitySync(intent);
         mInstrumentation.waitForIdleSync();
         mActivity.clearResult();
+        mDevice.wakeUp();
     }
 
     @After
@@ -147,6 +150,7 @@
         if (!supportsHardware()) return;
 
         prepareFile();
+        clearDocumentsUi();
 
         final Uri treeUri = acquireAccess(mFile, Environment.DIRECTORY_DOCUMENTS);
         assertNotNull(treeUri);
@@ -171,10 +175,11 @@
     }
 
     @Test
-    public void testGetDocumentUri_ThrowsWithoutPermission() throws Exception {
+    public void testGetDocumentUri_throwsWithoutPermission() throws Exception {
         if (!supportsHardware()) return;
 
         prepareFile();
+        clearDocumentsUi();
 
         try {
             MediaStore.getDocumentUri(mActivity, mMediaStoreUri);
@@ -185,10 +190,11 @@
     }
 
     @Test
-    public void testGetDocumentUri_Symmetry_ExternalStorageProvider() throws Exception {
+    public void testGetDocumentUri_symmetry_externalStorageProvider() throws Exception {
         if (!supportsHardware()) return;
 
         prepareFile();
+        clearDocumentsUi();
 
         final Uri treeUri = acquireAccess(mFile, Environment.DIRECTORY_DOCUMENTS);
         Log.v(TAG, "Tree " + treeUri);
@@ -207,10 +213,10 @@
     }
 
     @Test
-    public void testGetMediaUriAccess_MediaDocumentsProvider() throws Exception {
+    public void testGetMediaUriAccess_mediaDocumentsProvider() throws Exception {
         if (!supportsHardware()) return;
 
-        prepareFile();
+        prepareFile("TEST");
         clearDocumentsUi();
         final Intent intent = new Intent();
         intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
@@ -228,6 +234,104 @@
         assertAccessToMediaUri(mediaUri, mFile);
     }
 
+    @Test
+    public void testOpenFile_onMediaDocumentsProvider_success() throws Exception {
+        if (!supportsHardware()) return;
+
+        final String rawText = "TEST";
+        // Stage a text file which contains raw text "TEST"
+        prepareFile(rawText);
+        clearDocumentsUi();
+        final Intent intent = new Intent();
+        intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+        intent.setType("*/*");
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        mDevice.waitForIdle();
+
+        findDocument(mFile.getName()).click();
+        final Result result = mActivity.getResult();
+        final Uri uri = result.data.getData();
+        assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY, uri.getAuthority());
+
+        // Test reading
+        final byte[] expected = rawText.getBytes();
+        final byte[] actual = new byte[4];
+        try (ParcelFileDescriptor fd = mContext.getContentResolver()
+                .openFileDescriptor(uri, "r")) {
+            Os.read(fd.getFileDescriptor(), actual, 0, actual.length);
+            assertArrayEquals(expected, actual);
+        }
+
+        // Test write and read after it
+        final byte[] writtenText = "Hello World".getBytes();
+        final byte[] readText = new byte[11];
+        try (ParcelFileDescriptor fd = mContext.getContentResolver()
+                .openFileDescriptor(uri, "wt")) {
+            Os.write(fd.getFileDescriptor(), writtenText, 0, writtenText.length);
+        }
+        try (ParcelFileDescriptor fd = mContext.getContentResolver()
+                .openFileDescriptor(uri, "r")) {
+            Os.read(fd.getFileDescriptor(), readText, 0, readText.length);
+            assertArrayEquals(writtenText, readText);
+        }
+    }
+
+    @Test
+    public void testOpenFile_onMediaDocumentsProvider_failsWithoutAccess() throws Exception {
+        if (!supportsHardware()) return;
+
+        clearDocumentsUi();
+        final Intent intent = new Intent();
+        intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
+        intent.addCategory(Intent.CATEGORY_OPENABLE);
+        intent.setType("*/*");
+        mActivity.startActivityForResult(intent, REQUEST_CODE);
+        mDevice.waitForIdle();
+
+        String rawText = "TEST";
+        // Read and write grants will be provided to the file associated with this pair.
+        // Stages a text file which contains raw text "TEST"
+        Pair<Uri, File> uriFilePairWithGrants =  prepareFileAndFetchDetails(rawText);
+        // Read and write grants will not be provided to the file associated with this pair
+        // Stages a text file which contains raw text "TEST"
+        Pair<Uri, File> uriFilePairWithoutGrants =  prepareFileAndFetchDetails(rawText);
+        // Get access grants
+        findDocument(uriFilePairWithGrants.second.getName()).click();
+        final Result result = mActivity.getResult();
+        final Uri docUriOfFileWithAccess = result.data.getData();
+        // Creating doc URI for file by string replacement
+        Uri docUriOfFileWithoutAccess = Uri.parse(docUriOfFileWithAccess.toSafeString().replaceAll(
+                String.valueOf(ContentUris.parseId(uriFilePairWithGrants.first)),
+                String.valueOf(ContentUris.parseId(uriFilePairWithoutGrants.first))));
+
+        try {
+            assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY, docUriOfFileWithAccess.getAuthority());
+            assertEquals(MEDIA_DOCUMENTS_PROVIDER_AUTHORITY,
+                    docUriOfFileWithoutAccess.getAuthority());
+            // Test reading
+            try (ParcelFileDescriptor fd = mContext.getContentResolver().openFileDescriptor(
+                    docUriOfFileWithoutAccess, "r")) {
+                fail("Expecting security exception as file does not have read grants which "
+                        + "are provided through ACTION_OPEN_DOCUMENT intent.");
+            } catch (SecurityException expected) {
+                // Expected security exception as file does not have read grants
+            }
+            // Test writing
+            try (ParcelFileDescriptor fd = mContext.getContentResolver().openFileDescriptor(
+                    docUriOfFileWithoutAccess, "wt")) {
+                fail("Expecting security exception as file does not have write grants which "
+                        + "are provided through ACTION_OPEN_DOCUMENT intent.");
+            } catch (SecurityException expected) {
+                // Expected security exception as file does not have write grants
+            }
+        } finally {
+            // Deleting files
+            uriFilePairWithGrants.second.delete();
+            uriFilePairWithoutGrants.second.delete();
+        }
+    }
+
     private void assertAccessToMediaUri(Uri mediaUri, File file) {
         final String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};
         try (Cursor c = mContext.getContentResolver().query(
@@ -310,7 +414,7 @@
     }
 
     private void prepareFile() throws Exception {
-        final File dir = new File(getVolumePath(mVolumeName),
+        final File dir = new File(getVolumePath(resolveVolumeName(mVolumeName)),
                 Environment.DIRECTORY_DOCUMENTS);
         final File file = new File(dir, "cts" + System.nanoTime() + ".txt");
 
@@ -320,6 +424,29 @@
         Log.v(TAG, "Staged " + mFile + " as " + mMediaStoreUri);
     }
 
+    private void prepareFile(String rawText) throws Exception {
+        final File dir = new File(getVolumePath(resolveVolumeName(mVolumeName)),
+                Environment.DIRECTORY_DOCUMENTS);
+        final File file = new File(dir, "cts" + System.nanoTime() + ".txt");
+
+        mFile = stageFileWithRawText(rawText, file);
+        mMediaStoreUri = MediaStore.scanFile(mContext.getContentResolver(), mFile);
+
+        Log.v(TAG, "Staged " + mFile + " as " + mMediaStoreUri);
+    }
+
+    private Pair<Uri, File> prepareFileAndFetchDetails(String rawText) throws Exception {
+        final File dir = new File(getVolumePath(resolveVolumeName(mVolumeName)),
+                Environment.DIRECTORY_DOCUMENTS);
+        final File file = new File(dir, "cts" + System.nanoTime() + ".txt");
+
+        File stagedFile = stageFileWithRawText(rawText, file);
+
+        Uri uri = MediaStore.scanFile(mContext.getContentResolver(), stagedFile);
+        Log.v(TAG, "Staged " + stagedFile + " as " + uri);
+        return Pair.create(uri, stagedFile);
+    }
+
     private void assertToolbarTitleEquals(String targetPackageName, String label)
             throws UiObjectNotFoundException {
         final UiSelector toolbarUiSelector = new UiSelector().resourceId(
@@ -427,32 +554,28 @@
         // The caller may be trying to stage into a location only available to
         // the shell user, so we need to perform the entire copy as the shell
         final Context context = InstrumentationRegistry.getTargetContext();
-        UserManager userManager = context.getSystemService(UserManager.class);
-        if (userManager.isSystemUser() &&
-                 FileUtils.contains(Environment.getStorageDirectory(), file)) {
-            executeShellCommand("mkdir -p " + file.getParent());
+        final File dir = file.getParentFile();
+        dir.mkdirs();
+        if (!dir.exists()) {
+            throw new FileNotFoundException("Failed to create parent for " + file);
+        }
+        try (InputStream source = context.getResources().openRawResource(resId);
+             OutputStream target = new FileOutputStream(file)) {
+            FileUtils.copy(source, target);
+        }
+        return file;
+    }
 
-            try (AssetFileDescriptor afd = context.getResources().openRawResourceFd(resId)) {
-                final File source = ParcelFileDescriptor.getFile(afd.getFileDescriptor());
-                final long skip = afd.getStartOffset();
-                final long count = afd.getLength();
-
-                executeShellCommand(String.format("dd bs=1 if=%s skip=%d count=%d of=%s",
-                        source.getAbsolutePath(), skip, count, file.getAbsolutePath()));
-
-                // Force sync to try updating other views
-                executeShellCommand("sync");
-            }
-        } else {
-            final File dir = file.getParentFile();
-            dir.mkdirs();
-            if (!dir.exists()) {
-                throw new FileNotFoundException("Failed to create parent for " + file);
-            }
-            try (InputStream source = context.getResources().openRawResource(resId);
-                    OutputStream target = new FileOutputStream(file)) {
-                FileUtils.copy(source, target);
-            }
+    static File stageFileWithRawText(String rawText, File file) throws IOException {
+        final File dir = file.getParentFile();
+        dir.mkdirs();
+        if (!dir.exists()) {
+            throw new FileNotFoundException("Failed to create parent for " + file);
+        }
+        try (InputStream source = new ByteArrayInputStream(
+                rawText.getBytes(StandardCharsets.UTF_8));
+             OutputStream target = new FileOutputStream(file)) {
+            FileUtils.copy(source, target);
         }
         return file;
     }
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index a20a8f9..c56103c 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
@@ -259,6 +259,19 @@
 
     @AppModeFull(reason = "No usage events access in instant apps")
     @Test
+    public void testLastTimeVisible_launchActivityShouldBeDetected() throws Exception {
+        mUiDevice.wakeUp();
+        dismissKeyguard(); // also want to start out with the keyguard dismissed.
+
+        final long startTime = System.currentTimeMillis();
+        launchSubActivity(Activities.ActivityOne.class);
+        final long endTime = System.currentTimeMillis();
+
+        verifyLastTimeVisibleWithinRange(startTime, endTime, mTargetPackage);
+    }
+
+    @AppModeFull(reason = "No usage events access in instant apps")
+    @Test
     public void testLastTimeAnyComponentUsed_launchActivityShouldBeDetected() throws Exception {
         mUiDevice.wakeUp();
         dismissKeyguard(); // also want to start out with the keyguard dismissed.
@@ -311,6 +324,17 @@
         verifyLastTimeAnyComponentUsedWithinRange(startTime, endTime, TEST_APP_PKG);
     }
 
+    private void verifyLastTimeVisibleWithinRange(
+            long startTime, long endTime, String targetPackage) {
+        final Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats(
+                startTime, endTime);
+        final UsageStats stats = map.get(targetPackage);
+        assertNotNull(stats);
+        final long lastTimeVisible = stats.getLastTimeVisible();
+        assertLessThanOrEqual(startTime, lastTimeVisible);
+        assertLessThanOrEqual(lastTimeVisible, endTime);
+    }
+
     private void verifyLastTimeAnyComponentUsedWithinRange(
             long startTime, long endTime, String targetPackage) {
         final Map<String, UsageStats> map = mUsageStatsManager.queryAndAggregateUsageStats(
diff --git a/tests/tests/appop/Android.bp b/tests/tests/appop/Android.bp
index 33e53a5..bd34b7d 100644
--- a/tests/tests/appop/Android.bp
+++ b/tests/tests/appop/Android.bp
@@ -82,7 +82,7 @@
         "libbacktrace",
         "libbase",
         "libbinder",
-        "libbpf",
+        "libbpf_bcc",
         "libbpf_android",
         "libc++",
         "libcgrouprc",
diff --git a/tests/tests/content/AndroidTest.xml b/tests/tests/content/AndroidTest.xml
index c03dd80..e39bf36 100644
--- a/tests/tests/content/AndroidTest.xml
+++ b/tests/tests/content/AndroidTest.xml
@@ -39,7 +39,6 @@
         <option name="push" value="CtsContentLongSharedUserIdTestApp.apk->/data/local/tmp/cts/content/CtsContentLongSharedUserIdTestApp.apk" />
         <option name="push" value="CtsContentMaxPackageNameTestApp.apk->/data/local/tmp/cts/content/CtsContentMaxPackageNameTestApp.apk" />
         <option name="push" value="CtsContentMaxSharedUserIdTestApp.apk->/data/local/tmp/cts/content/CtsContentMaxSharedUserIdTestApp.apk" />
-        <option name="push" value="CtsContentLongLabelNameTestApp.apk->/data/local/tmp/cts/content/CtsContentLongLabelNameTestApp.apk" />
     </target_preparer>
 
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
diff --git a/tests/tests/content/BinderPermissionTestService/Android.bp b/tests/tests/content/BinderPermissionTestService/Android.bp
index 928e532..8ba2432 100644
--- a/tests/tests/content/BinderPermissionTestService/Android.bp
+++ b/tests/tests/content/BinderPermissionTestService/Android.bp
@@ -27,6 +27,7 @@
         "aidl/**/I*.aidl",
     ],
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
diff --git a/tests/tests/content/DirectBootUnawareTestApp/Android.bp b/tests/tests/content/DirectBootUnawareTestApp/Android.bp
index 4a1a9bb..8c35eb4 100644
--- a/tests/tests/content/DirectBootUnawareTestApp/Android.bp
+++ b/tests/tests/content/DirectBootUnawareTestApp/Android.bp
@@ -22,6 +22,7 @@
     sdk_version: "current",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
diff --git a/tests/tests/content/HelloWorldApp/Android.bp b/tests/tests/content/HelloWorldApp/Android.bp
index e3c2de5..cb45323 100644
--- a/tests/tests/content/HelloWorldApp/Android.bp
+++ b/tests/tests/content/HelloWorldApp/Android.bp
@@ -42,6 +42,7 @@
     srcs: ["src5/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
@@ -56,6 +57,7 @@
     manifest: "AndroidManifestProfileable.xml",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "vts10",
         "general-tests",
@@ -70,6 +72,7 @@
     srcs: ["src7/**/*.java"],
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
@@ -105,6 +108,7 @@
     ],
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
@@ -120,6 +124,7 @@
     manifest: "AndroidManifestShell.xml",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "vts10",
         "general-tests",
diff --git a/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp b/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
index c24d69a..c211c0c 100644
--- a/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
+++ b/tests/tests/content/PartiallyDirectBootAwareTestApp/Android.bp
@@ -22,6 +22,7 @@
     sdk_version: "current",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
diff --git a/tests/tests/content/SyncAccountAccessStubs/Android.bp b/tests/tests/content/SyncAccountAccessStubs/Android.bp
index e8904d7..9f1e9ab 100644
--- a/tests/tests/content/SyncAccountAccessStubs/Android.bp
+++ b/tests/tests/content/SyncAccountAccessStubs/Android.bp
@@ -25,6 +25,7 @@
     srcs: ["src/**/*.java"],
     sdk_version: "current",
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
diff --git a/tests/tests/content/emptytestapp/Android.bp b/tests/tests/content/emptytestapp/Android.bp
index f66ccdf..70c87fe 100644
--- a/tests/tests/content/emptytestapp/Android.bp
+++ b/tests/tests/content/emptytestapp/Android.bp
@@ -22,6 +22,7 @@
     sdk_version: "current",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
@@ -35,6 +36,7 @@
     manifest: "AndroidManifestLongPackageName.xml",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
@@ -48,6 +50,7 @@
     manifest: "AndroidManifestLongSharedUserId.xml",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
@@ -61,6 +64,7 @@
     manifest: "AndroidManifestMaxPackageName.xml",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
@@ -73,20 +77,8 @@
     manifest: "AndroidManifestMaxSharedUserId.xml",
     // tag this module as a cts test artifact
     test_suites: [
+        "mts",
         "cts",
         "general-tests",
     ],
 }
-
-android_test_helper_app {
-    name: "CtsContentLongLabelNameTestApp",
-    defaults: ["cts_defaults"],
-    sdk_version: "current",
-    manifest: "AndroidManifestLongLabelName.xml",
-    // tag this module as a cts test artifact
-    test_suites: [
-        "cts",
-        "general-tests",
-    ],
-    min_sdk_version : "29"
-}
diff --git a/tests/tests/content/emptytestapp/AndroidManifestLongLabelName.xml b/tests/tests/content/emptytestapp/AndroidManifestLongLabelName.xml
deleted file mode 100644
index eaa69b1..0000000
--- a/tests/tests/content/emptytestapp/AndroidManifestLongLabelName.xml
+++ /dev/null
@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 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.
--->
-<!-- Test that the maximum length of the label returned from the load label api should be 1000 -->
-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-        package="android.content.cts.emptytestapp" >
-    <application android:hasCode="false" android:label="@string/long_app_name">
-        <activity android:name=".MockActivity" android:label="@string/long_app_name"
-                  android:exported="true" android:enabled="true" />
-    </application>
-</manifest>
diff --git a/tests/tests/content/emptytestapp/res/values/strings.xml b/tests/tests/content/emptytestapp/res/values/strings.xml
deleted file mode 100644
index 8c06d18..0000000
--- a/tests/tests/content/emptytestapp/res/values/strings.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources>
-    <string name="long_app_name">
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-EmptyTestApp123456789012345678901234567890123456789012345678901234567890123456789012345678901234567
-    </string>
-</resources>
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
index df40ea9..3d967d7 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
@@ -28,25 +28,12 @@
 import android.app.UiAutomation;
 import android.content.ComponentName;
 import android.content.Context;
-import android.content.IIntentReceiver;
-import android.content.IIntentSender;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.content.IntentSender;
-import android.content.pm.ApkChecksum;
-import android.content.pm.ApplicationInfo;
 import android.content.pm.DataLoaderParams;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageInstaller.SessionParams;
 import android.content.pm.PackageManager;
 import android.content.pm.cts.util.AbandonAllPackageSessionsRule;
-import android.os.Bundle;
-import android.os.ConditionVariable;
-import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
-import android.os.Process;
-import android.os.RemoteException;
-import android.os.UserHandle;
 import android.platform.test.annotations.AppModeFull;
 
 import androidx.test.InstrumentationRegistry;
@@ -70,10 +57,6 @@
 import java.util.Arrays;
 import java.util.Optional;
 import java.util.Random;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 
 @RunWith(Parameterized.class)
@@ -171,15 +154,6 @@
         }
     }
 
-    private static void writeFileToSession(PackageInstaller.Session session, String name,
-            String apk) throws IOException {
-        File file = new File(createApkPath(apk));
-        try (OutputStream os = session.openWrite(name, 0, file.length());
-             InputStream is = new FileInputStream(file)) {
-            writeFullStream(is, os, file.length());
-        }
-    }
-
     @Before
     public void onBefore() throws Exception {
         // Check if Incremental is allowed and revert to non-dataloader installation.
@@ -508,78 +482,6 @@
     }
 
     @Test
-    public void testDontKillWithSplit() throws Exception {
-        installPackage(TEST_HW5);
-
-        getUiAutomation().adoptShellPermissionIdentity();
-        try {
-            final PackageInstaller installer = getPackageInstaller();
-            final SessionParams params = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
-            params.setAppPackageName(TEST_APP_PACKAGE);
-            params.setDontKillApp(true);
-
-            final int sessionId = installer.createSession(params);
-            PackageInstaller.Session session = installer.openSession(sessionId);
-            assertTrue((session.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) != 0);
-
-            writeFileToSession(session, "hw5_split0", TEST_HW5_SPLIT0);
-
-            final CompletableFuture<Boolean> result = new CompletableFuture<>();
-            session.commit(new IntentSender((IIntentSender) new IIntentSender.Stub() {
-                @Override
-                public void send(int code, Intent intent, String resolvedType,
-                        IBinder whitelistToken, IIntentReceiver finishedReceiver,
-                        String requiredPermission, Bundle options) throws RemoteException {
-                    boolean dontKillApp =
-                            (session.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) != 0;
-                    result.complete(dontKillApp);
-                }
-            }));
-
-            // We are adding split. OK to have the flag.
-            assertTrue(result.get());
-        } finally {
-            getUiAutomation().dropShellPermissionIdentity();
-        }
-    }
-
-    @Test
-    public void testDontKillRemovedWithBaseApk() throws Exception {
-        installPackage(TEST_HW5);
-
-        getUiAutomation().adoptShellPermissionIdentity();
-        try {
-            final PackageInstaller installer = getPackageInstaller();
-            final SessionParams params = new SessionParams(SessionParams.MODE_INHERIT_EXISTING);
-            params.setAppPackageName(TEST_APP_PACKAGE);
-            params.setDontKillApp(true);
-
-            final int sessionId = installer.createSession(params);
-            PackageInstaller.Session session = installer.openSession(sessionId);
-            assertTrue((session.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) != 0);
-
-            writeFileToSession(session, "hw7", TEST_HW7);
-
-            final CompletableFuture<Boolean> result = new CompletableFuture<>();
-            session.commit(new IntentSender((IIntentSender) new IIntentSender.Stub() {
-                @Override
-                public void send(int code, Intent intent, String resolvedType,
-                        IBinder whitelistToken, IIntentReceiver finishedReceiver,
-                        String requiredPermission, Bundle options) throws RemoteException {
-                    boolean dontKillApp =
-                            (session.getInstallFlags() & PackageManager.INSTALL_DONT_KILL_APP) != 0;
-                    result.complete(dontKillApp);
-                }
-            }));
-
-            // We are updating base.apk. Flag to be removed.
-            assertFalse(result.get());
-        } finally {
-            getUiAutomation().dropShellPermissionIdentity();
-        }
-    }
-
-    @Test
     public void testDataLoaderParamsApiV1() throws Exception {
         if (!mStreaming) {
             return;
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
index 262c687..71648b3 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -154,8 +154,6 @@
             + "CtsContentMaxPackageNameTestApp.apk";
     private static final String MAX_SHARED_USER_ID_APK = SAMPLE_APK_BASE
             + "CtsContentMaxSharedUserIdTestApp.apk";
-    private static final String LONG_LABEL_NAME_APK = SAMPLE_APK_BASE
-            + "CtsContentLongLabelNameTestApp.apk";
     private static final String EMPTY_APP_PACKAGE_NAME = "android.content.cts.emptytestapp";
     private static final String EMPTY_APP_MAX_PACKAGE_NAME = "android.content.cts.emptytestapp27j"
             + "EBRNRG3ozwBsGr1sVIM9U0bVTI2TdyIyeRkZgW4JrJefwNIBAmCg4AzqXiCvG6JjqA0uTCWSFu2YqAVxVd"
@@ -165,8 +163,6 @@
     private static final String HELLO_WORLD_PACKAGE_NAME = "com.example.helloworld";
     private static final String HELLO_WORLD_APK = SAMPLE_APK_BASE + "HelloWorld5.apk";
 
-    private static final int MAX_SAFE_LABEL_LENGTH = 1000;
-
     @Before
     public void setup() throws Exception {
         mContext = InstrumentationRegistry.getContext();
@@ -1485,33 +1481,4 @@
     private void uninstallPackage(String packageName) {
         SystemUtil.runShellCommand("pm uninstall " + packageName);
     }
-
-    @Test
-    public void loadApplicationLabel_withLongLabelName_truncated() throws Exception {
-        assertThat(installPackage(LONG_LABEL_NAME_APK)).isTrue();
-        final ApplicationInfo info = mPackageManager.getApplicationInfo(
-                EMPTY_APP_PACKAGE_NAME, 0 /* flags */);
-        final CharSequence resLabel = mPackageManager.getText(
-                EMPTY_APP_PACKAGE_NAME, info.labelRes, info);
-
-        assertThat(resLabel.length()).isGreaterThan(MAX_SAFE_LABEL_LENGTH);
-        assertThat(info.loadLabel(mPackageManager).length()).isEqualTo(MAX_SAFE_LABEL_LENGTH);
-    }
-
-    @Test
-    public void loadComponentLabel_withLongLabelName_truncated() throws Exception {
-        assertThat(installPackage(LONG_LABEL_NAME_APK)).isTrue();
-        final ComponentName componentName = ComponentName.createRelative(
-                EMPTY_APP_PACKAGE_NAME, ".MockActivity");
-        final ApplicationInfo appInfo = mPackageManager.getApplicationInfo(
-                EMPTY_APP_PACKAGE_NAME, 0 /* flags */);
-        final ActivityInfo activityInfo = mPackageManager.getActivityInfo(
-                componentName, 0 /* flags */);
-        final CharSequence resLabel = mPackageManager.getText(
-                EMPTY_APP_PACKAGE_NAME, activityInfo.labelRes, appInfo);
-
-        assertThat(resLabel.length()).isGreaterThan(MAX_SAFE_LABEL_LENGTH);
-        assertThat(activityInfo.loadLabel(mPackageManager).length())
-                .isEqualTo(MAX_SAFE_LABEL_LENGTH);
-    }
 }
diff --git a/tests/tests/icu/Android.bp b/tests/tests/icu/Android.bp
index c5a5141..26c195c 100644
--- a/tests/tests/icu/Android.bp
+++ b/tests/tests/icu/Android.bp
@@ -30,7 +30,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
     ],
     platform_apis: true,
 }
diff --git a/tests/tests/libcoreapievolution/Android.bp b/tests/tests/libcoreapievolution/Android.bp
index 891ebc3..eed4fc3 100644
--- a/tests/tests/libcoreapievolution/Android.bp
+++ b/tests/tests/libcoreapievolution/Android.bp
@@ -30,6 +30,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-art",
     ],
 }
diff --git a/tests/tests/libcoreapievolution/AndroidTest.xml b/tests/tests/libcoreapievolution/AndroidTest.xml
index 08f47fd..79aa4b2 100644
--- a/tests/tests/libcoreapievolution/AndroidTest.xml
+++ b/tests/tests/libcoreapievolution/AndroidTest.xml
@@ -42,4 +42,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/tests/libcorefileio/Android.bp b/tests/tests/libcorefileio/Android.bp
index 58c388c..3febb32 100644
--- a/tests/tests/libcorefileio/Android.bp
+++ b/tests/tests/libcorefileio/Android.bp
@@ -30,6 +30,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-art",
     ],
 }
diff --git a/tests/tests/libcorefileio/AndroidTest.xml b/tests/tests/libcorefileio/AndroidTest.xml
index c90b702..2220973 100644
--- a/tests/tests/libcorefileio/AndroidTest.xml
+++ b/tests/tests/libcorefileio/AndroidTest.xml
@@ -40,4 +40,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/tests/libcorelegacy22/Android.bp b/tests/tests/libcorelegacy22/Android.bp
index 44a997a..98684b2 100644
--- a/tests/tests/libcorelegacy22/Android.bp
+++ b/tests/tests/libcorelegacy22/Android.bp
@@ -26,6 +26,6 @@
     test_suites: [
         "cts",
         "general-tests",
-        "mts",
+        "mts-art",
     ],
 }
diff --git a/tests/tests/libcorelegacy22/AndroidTest.xml b/tests/tests/libcorelegacy22/AndroidTest.xml
index 94c1134..670a3af 100644
--- a/tests/tests/libcorelegacy22/AndroidTest.xml
+++ b/tests/tests/libcorelegacy22/AndroidTest.xml
@@ -42,4 +42,7 @@
         <!-- ART Mainline Module (external (AOSP) version). -->
         <option name="mainline-module-package-name" value="com.android.art" />
     </object>
+
+    <!--- Only run tests if the device under test is SDK version 31 (Android 12) or above. -->
+    <object type="module_controller" class="com.android.tradefed.testtype.suite.module.Sdk31ModuleController" />
 </configuration>
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index 20d0f50..7106a9a 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -387,13 +387,6 @@
     }
 
     public void testID3v240ExtHeader() {
-        if(!ApiLevelUtil.isAtLeast(Build.VERSION_CODES.R)) {
-            // The fix for b/154357105 was released in mainline release 30.09.007.01
-            // See https://android-build.googleplex.com/builds/treetop/googleplex-android-review/11174063
-            if (TestUtils.skipTestIfMainlineLessThan("com.google.android.media", 300900701)) {
-                return;
-            }
-        }
         setDataSourceFd("sinesweepid3v24ext.mp3");
         assertEquals("Mime type was other than expected",
                 "audio/mpeg",
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
index d5d01c8..0df3b1b 100644
--- a/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
+++ b/tests/tests/mediastress/src/android/mediastress/cts/MediaPlayerStressTest.java
@@ -162,6 +162,8 @@
             }
         }
 
+        Preconditions.assertTestFileExists(mediaName);
+
         File playbackOutput = new File(WorkDir.getTopDir(), "PlaybackTestResult.txt");
         Writer output = new BufferedWriter(new FileWriter(playbackOutput, true));
 
diff --git a/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java b/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java
new file mode 100644
index 0000000..6fc2b8a
--- /dev/null
+++ b/tests/tests/mediastress/src/android/mediastress/cts/Preconditions.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2021 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.mediastress.cts;
+
+import java.io.File;
+
+import junit.framework.Assert;
+
+/**
+ * Static methods used to validate preconditions in the media CTS suite
+ * to simplify failure diagnosis.
+ */
+
+public final class Preconditions {
+    private static final String TAG = "Preconditions";
+
+    public static void assertTestFileExists(String pathName) {
+        File testFile = new File(pathName);
+        Assert.assertTrue("Test Setup Error, missing file: " + pathName, testFile.exists());
+    }
+
+    private Preconditions() {}
+}
diff --git a/tests/tests/mediatranscoding/Android.bp b/tests/tests/mediatranscoding/Android.bp
index 0b889ae..6a220a3 100644
--- a/tests/tests/mediatranscoding/Android.bp
+++ b/tests/tests/mediatranscoding/Android.bp
@@ -19,10 +19,12 @@
 android_test {
     name: "CtsMediaTranscodingTestCases",
     defaults: ["CtsMediaTranscodingTestCasesDefaults", "cts_defaults"],
-    min_sdk_version: "31",
+    // part of MTS, so we need compatibility back to Q/29
+    min_sdk_version: "29",
     test_suites: [
         "cts",
         "general-tests",
+        "mts-media",
         "mts",
     ],
     static_libs: [
@@ -43,5 +45,4 @@
         "android.test.base",
         "android.test.runner",
     ],
-    sdk_version: "test_current",
 }
diff --git a/tests/tests/mediatranscoding/AndroidManifest.xml b/tests/tests/mediatranscoding/AndroidManifest.xml
index 1618b00..c69754a 100644
--- a/tests/tests/mediatranscoding/AndroidManifest.xml
+++ b/tests/tests/mediatranscoding/AndroidManifest.xml
@@ -25,6 +25,12 @@
         <uses-library android:name="android.test.runner" />
     </application>
 
+    <!-- included in mainline testing, so must run back on 29.
+         Transcoding was introduced in 31 -->
+    <uses-sdk android:minSdkVersion="29"
+         android:targetSdkVersion="31"/>
+
+
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
             android:targetPackage="android.media.mediatranscoding.cts"
             android:label="Tests for MediaTranscoding.">
diff --git a/tests/tests/mediatranscoding/OWNERS b/tests/tests/mediatranscoding/OWNERS
index e653979..a4393a7 100644
--- a/tests/tests/mediatranscoding/OWNERS
+++ b/tests/tests/mediatranscoding/OWNERS
@@ -1,4 +1,4 @@
 # Bug component: 761430
-hkuang@google.com
-chz@google.com
-lnilsson@google.com
+
+# go/android-fwk-media-solutions for info on areas of ownership.
+include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_480p_30Frames.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_480p_30Frames.mp4
new file mode 100644
index 0000000..41f6c22
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_480p_30Frames.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/res/raw/Video_HEVC_720p_30Frames.mp4 b/tests/tests/mediatranscoding/res/raw/Video_HEVC_720p_30Frames.mp4
new file mode 100644
index 0000000..7211fec1
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/raw/Video_HEVC_720p_30Frames.mp4
Binary files differ
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
index ba40ca5..6e63624 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
@@ -27,6 +27,8 @@
 import android.test.AndroidTestCase;
 import android.util.Xml;
 
+import androidx.test.filters.SdkSuppress;
+
 import org.junit.Test;
 import org.xmlpull.v1.XmlPullParser;
 
@@ -36,6 +38,7 @@
 
 @Presubmit
 @AppModeFull(reason = "Instant apps cannot access the SD card")
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
 public class ApplicationMediaCapabilitiesTest extends AndroidTestCase {
     private static final String TAG = "ApplicationMediaCapabilitiesTest";
 
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
index c9b2b16..a88f81c 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodingManagerTest.java
@@ -33,6 +33,7 @@
 import android.media.MediaTranscodingManager.TranscodingSession;
 import android.media.MediaTranscodingManager.VideoTranscodingRequest;
 import android.net.Uri;
+// import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.FileUtils;
@@ -44,6 +45,7 @@
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.MediaUtils;
@@ -70,6 +72,7 @@
 @Presubmit
 @RequiresDevice
 @AppModeFull(reason = "Instant apps cannot access the SD card")
+@SdkSuppress(minSdkVersion = 31, codeName = "S")
 public class MediaTranscodingManagerTest extends AndroidTestCase {
     private static final String TAG = "MediaTranscodingManagerTest";
     /** The time to wait for the transcode operation to complete before failing the test. */
@@ -88,9 +91,9 @@
 
     // Default setting for transcoding to H.264.
     private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
-    private static final int BIT_RATE = 20000000;            // 20Mbps
-    private static final int WIDTH = 1920;
-    private static final int HEIGHT = 1080;
+    private static final int BIT_RATE = 4000000;            // 4Mbps
+    private static final int WIDTH = 720;
+    private static final int HEIGHT = 480;
 
     // Threshold for the psnr to make sure the transcoded video is valid.
     private static final int PSNR_THRESHOLD = 20;
@@ -148,9 +151,9 @@
         androidx.test.InstrumentationRegistry.registerInstance(
                 InstrumentationRegistry.getInstrumentation(), new Bundle());
 
-        // Setup source HEVC file uri.
-        mSourceHEVCVideoUri = resourceToUri(mContext, R.raw.Video_HEVC_30Frames,
-                "Video_HEVC_30Frames.mp4");
+        // Setup default source HEVC 480p file uri.
+        mSourceHEVCVideoUri = resourceToUri(mContext, R.raw.Video_HEVC_480p_30Frames,
+                "Video_HEVC_480p_30Frames.mp4");
 
         // Setup source AVC file uri.
         mSourceAVCVideoUri = resourceToUri(mContext, R.raw.Video_AVC_30Frames,
@@ -169,6 +172,7 @@
 
     // Skip the test for TV, Car and Watch devices.
     private boolean shouldSkip() {
+
         PackageManager pm =
                 InstrumentationRegistry.getInstrumentation().getTargetContext().getPackageManager();
         return pm.hasSystemFeature(pm.FEATURE_LEANBACK) || pm.hasSystemFeature(pm.FEATURE_WATCH)
@@ -340,7 +344,7 @@
     // Tests transcoding to a uri in res folder and expects failure as test could not write to res
     // folder.
     public void testTranscodingToResFolder() throws Exception {
-        if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+        if (shouldSkip()) {
             return;
         }
         // Create a file Uri:  android.resource://android.media.cts/temp.mp4
@@ -354,7 +358,7 @@
 
     // Tests transcoding to a uri in internal cache folder and expects success.
     public void testTranscodingToCacheDir() throws Exception {
-        if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+        if (shouldSkip()) {
             return;
         }
         // Create a file Uri: file:///data/user/0/android.media.cts/cache/temp.mp4
@@ -368,7 +372,7 @@
 
     // Tests transcoding to a uri in internal files directory and expects success.
     public void testTranscodingToInternalFilesDir() throws Exception {
-        if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+        if (shouldSkip()) {
             return;
         }
         // Create a file Uri: file:///data/user/0/android.media.cts/files/temp.mp4
@@ -379,6 +383,14 @@
                 TranscodingSession.RESULT_SUCCESS);
     }
 
+    public void testHevcTranscoding720PVideo30FramesWithoutAudio() throws Exception {
+        if (shouldSkip()) {
+            return;
+        }
+        transcodeFile(resourceToUri(mContext, R.raw.Video_HEVC_720p_30Frames,
+                "Video_HEVC_720p_30Frames.mp4"), false /* testFileDescriptor */);
+    }
+
     public void testAvcTranscoding1080PVideo30FramesWithoutAudio() throws Exception {
         if (shouldSkip()) {
             return;
@@ -562,7 +574,7 @@
     }
 
     public void testCancelTranscoding() throws Exception {
-        if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+        if (shouldSkip()) {
             return;
         }
         Log.d(TAG, "Starting: testCancelTranscoding");
@@ -653,7 +665,7 @@
     }*/
 
     public void testTranscodingProgressUpdate() throws Exception {
-        if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+        if (shouldSkip()) {
             return;
         }
         Log.d(TAG, "Starting: testTranscodingProgressUpdate");
@@ -705,7 +717,7 @@
     }
 
     public void testAddingClientUids() throws Exception {
-        if (shouldSkip() || !isVideoTranscodingSupported(mSourceHEVCVideoUri)) {
+        if (shouldSkip()) {
             return;
         }
         Log.d(TAG, "Starting: testTranscodingProgressUpdate");
@@ -782,27 +794,6 @@
         return videoFormat;
     }
 
-    private boolean isVideoTranscodingSupported(Uri fileUri) throws IOException {
-        MediaFormat sourceFormat = getVideoTrackFormat(fileUri);
-        if (sourceFormat != null) {
-            // Since destination format is not available, we assume width, height and
-            // frame rate same as source format, and mime as AVC for destination format.
-            MediaFormat destinationFormat = new MediaFormat();
-            destinationFormat.setString(MediaFormat.KEY_MIME, MIME_TYPE);
-            destinationFormat.setInteger(MediaFormat.KEY_WIDTH,
-                    sourceFormat.getInteger(MediaFormat.KEY_WIDTH));
-            destinationFormat.setInteger(MediaFormat.KEY_HEIGHT,
-                    sourceFormat.getInteger(MediaFormat.KEY_HEIGHT));
-            if (sourceFormat.containsKey(MediaFormat.KEY_FRAME_RATE)) {
-                destinationFormat.setInteger(MediaFormat.KEY_FRAME_RATE,
-                        sourceFormat.getInteger(MediaFormat.KEY_FRAME_RATE));
-            }
-            return isFormatSupported(sourceFormat, false)
-                    && isFormatSupported(destinationFormat, true);
-        }
-        return false;
-    }
-
     private boolean isFormatSupported(MediaFormat format, boolean isEncoder) {
         String mime = format.getString(MediaFormat.KEY_MIME);
         MediaCodec codec = null;
diff --git a/tests/tests/neuralnetworks/Android.mk b/tests/tests/neuralnetworks/Android.mk
index 7ac51ea..d8b6ee3 100644
--- a/tests/tests/neuralnetworks/Android.mk
+++ b/tests/tests/neuralnetworks/Android.mk
@@ -43,4 +43,3 @@
 include $(BUILD_CTS_EXECUTABLE)
 
 include $(nnapi_cts_dir)/benchmark/Android.mk
-include $(nnapi_cts_dir)/tflite_delegate/Android.mk
diff --git a/tests/tests/neuralnetworks/tflite_delegate/Android.bp b/tests/tests/neuralnetworks/tflite_delegate/Android.bp
new file mode 100644
index 0000000..6acce1d
--- /dev/null
+++ b/tests/tests/neuralnetworks/tflite_delegate/Android.bp
@@ -0,0 +1,58 @@
+// 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 {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// The "CtsTfliteNnapiDelegateTests_static" has been moved to
+// external/tensorflow/Android.bp, due to the fact that the srcs files
+// are not in the current directory.
+
+// Build the actual CTS module with the static lib above.
+// This is necessary for the build system to pickup the AndroidTest.xml.
+
+cc_test {
+    name: "CtsTfliteNnapiDelegateTestCases",
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    whole_static_libs: ["TfliteNnapiDelegateTests_static"],
+    shared_libs: [
+        "libandroid",
+        "liblog",
+        "libneuralnetworks",
+    ],
+    static_libs: [
+        "libgtest_ndk_c++",
+        "libgmock_ndk",
+        "libtflite_static",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "mts",
+        "mts-neuralnetworks",
+        "general-tests",
+    ],
+    sdk_version: "current",
+    stl: "c++_static",
+}
diff --git a/tests/tests/neuralnetworks/tflite_delegate/Android.mk b/tests/tests/neuralnetworks/tflite_delegate/Android.mk
deleted file mode 100644
index 785f7bd..0000000
--- a/tests/tests/neuralnetworks/tflite_delegate/Android.mk
+++ /dev/null
@@ -1,80 +0,0 @@
-# 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.
-
-my_dir := $(call my-dir)
-
-LOCAL_PATH:= external/tensorflow/
-include $(CLEAR_VARS)
-LOCAL_MODULE := CtsTfliteNnapiDelegateTests_static
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_SRC_FILES := \
-    tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc \
-    tensorflow/lite/kernels/test_util.cc \
-    tensorflow/core/platform/default/logging.cc \
-    tensorflow/core/platform/default/env_time.cc \
-    tensorflow/lite/kernels/acceleration_test_util.cc \
-    tensorflow/lite/kernels/acceleration_test_util_internal.cc \
-    tensorflow/lite/delegates/nnapi/acceleration_test_list.cc \
-    tensorflow/lite/delegates/nnapi/acceleration_test_util.cc
-LOCAL_CPP_EXTENSION := .cc
-
-LOCAL_C_INCLUDES += external/flatbuffers/include
-LOCAL_C_INCLUDES += external/tensorflow
-LOCAL_C_INCLUDES += external/ruy
-
-LOCAL_CFLAGS :=  \
-    -DPLATFORM_POSIX_ANDROID \
-    -Wall \
-    -Werror \
-    -Wextra \
-    -Wno-extern-c-compat \
-    -Wno-sign-compare \
-    -Wno-unused-parameter \
-    -Wno-unused-private-field \
-
-LOCAL_SHARED_LIBRARIES := libandroid liblog libneuralnetworks
-LOCAL_STATIC_LIBRARIES := libgtest_ndk_c++ libgmock_ndk libtflite_static
-LOCAL_HEADER_LIBRARIES := libeigen gemmlowp_headers libtflite_schema_headers
-LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_static
-include $(BUILD_STATIC_LIBRARY)
-
-
-# Build the actual CTS module with the static lib above.
-# This is necessary for the build system to pickup the AndroidTest.xml.
-LOCAL_PATH:= $(my_dir)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE := CtsTfliteNnapiDelegateTestCases
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_MODULE_PATH := $(TARGET_OUT_DATA)/nativetest
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-
-LOCAL_WHOLE_STATIC_LIBRARIES := CtsTfliteNnapiDelegateTests_static
-
-LOCAL_SHARED_LIBRARIES := libandroid liblog libneuralnetworks
-LOCAL_STATIC_LIBRARIES := libgtest_ndk_c++ libgmock_ndk libtflite_static
-LOCAL_CTS_TEST_PACKAGE := android.neuralnetworks
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts mts mts-neuralnetworks general-tests
-
-LOCAL_SDK_VERSION := current
-LOCAL_NDK_STL_VARIANT := c++_static
-
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/tests/tests/os/src/android/os/cts/StrictModeTest.java b/tests/tests/os/src/android/os/cts/StrictModeTest.java
index 0b5ed0d..54caf6b 100644
--- a/tests/tests/os/src/android/os/cts/StrictModeTest.java
+++ b/tests/tests/os/src/android/os/cts/StrictModeTest.java
@@ -77,6 +77,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
 import java.io.FileInputStream;
@@ -147,6 +148,63 @@
     }
 
     @Test
+    public void testThreadBuilder_detectUnbufferedIo() throws Exception {
+        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
+            .penaltyLog()
+            .detectUnbufferedIo()
+            .build();
+        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(policy).build());
+
+        final File test = File.createTempFile("foo", "bar");
+        inspectViolation(
+            () -> {
+                writeUnbuffered(test);
+            },
+            info -> {
+                assertThat(info.getViolationDetails()).isNull();
+                assertThat(info.getStackTrace()).contains("UnbufferedIoViolation");
+            });
+    }
+
+    @Test
+    public void testThreadBuilder_permitUnbufferedIo() throws Exception {
+        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
+            .penaltyLog()
+            .permitUnbufferedIo()
+            .build();
+        StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder(policy).build());
+
+        final File test = File.createTempFile("foo", "bar");
+        inspectViolation(
+            () -> {
+                writeUnbuffered(test);
+            },
+            info -> {
+                assertThat(info).isNull();
+            });
+    }
+
+    private void writeUnbuffered(File file) throws Exception {
+        if (file.exists()) {
+            file.delete();
+        }
+
+        try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
+            for (int i = 0; i < 11; i++) {
+                out.write(1);
+                out.write(2);
+                out.write(3);
+                out.write(4);
+                out.flush();
+            }
+        } finally {
+            if (file.exists()) {
+                file.delete();
+            }
+        }
+    }
+
+    @Test
     public void testUnclosedCloseable() throws Exception {
         //clean before test
         System.gc();
diff --git a/tests/tests/permission3/Android.bp b/tests/tests/permission3/Android.bp
index 3d91b81..e249fe3 100644
--- a/tests/tests/permission3/Android.bp
+++ b/tests/tests/permission3/Android.bp
@@ -47,11 +47,13 @@
         ":CtsUsePermissionApp30",
         ":CtsUsePermissionApp30WithBackground",
         ":CtsUsePermissionApp30WithBluetooth",
+        ":CtsUsePermissionApp31",
         ":CtsUsePermissionAppLatest",
         ":CtsUsePermissionAppLatestNone",
         ":CtsUsePermissionAppWithOverlay",
         ":CtsAccessMicrophoneApp",
         ":CtsAccessMicrophoneApp2",
+        ":CtsAccessMicrophoneAppLocationProvider",
     ],
     test_suites: [
         "cts",
diff --git a/tests/tests/permission3/AndroidTest.xml b/tests/tests/permission3/AndroidTest.xml
index d708d76..2342b6a 100644
--- a/tests/tests/permission3/AndroidTest.xml
+++ b/tests/tests/permission3/AndroidTest.xml
@@ -41,6 +41,7 @@
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
         <option name="push" value="CtsAccessMicrophoneApp.apk->/data/local/tmp/cts/permission3/CtsAccessMicrophoneApp.apk" />
         <option name="push" value="CtsAccessMicrophoneApp2.apk->/data/local/tmp/cts/permission3/CtsAccessMicrophoneApp2.apk" />
+        <option name="push" value="CtsAccessMicrophoneAppLocationProvider.apk->/data/local/tmp/cts/permission3/CtsAccessMicrophoneAppLocationProvider.apk" />
         <option name="push" value="CtsPermissionPolicyApp25.apk->/data/local/tmp/cts/permission3/CtsPermissionPolicyApp25.apk" />
         <option name="push" value="CtsUsePermissionApp22.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp22.apk" />
         <option name="push" value="CtsUsePermissionApp22CalendarOnly.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp22CalendarOnly.apk" />
@@ -53,6 +54,7 @@
         <option name="push" value="CtsUsePermissionApp30.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30.apk" />
         <option name="push" value="CtsUsePermissionApp30WithBackground.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBackground.apk" />
         <option name="push" value="CtsUsePermissionApp30WithBluetooth.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp30WithBluetooth.apk" />
+        <option name="push" value="CtsUsePermissionApp31.apk->/data/local/tmp/cts/permission3/CtsUsePermissionApp31.apk" />
         <option name="push" value="CtsUsePermissionAppLatest.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatest.apk" />
         <option name="push" value="CtsUsePermissionAppLatestNone.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppLatestNone.apk" />
         <option name="push" value="CtsUsePermissionAppWithOverlay.apk->/data/local/tmp/cts/permission3/CtsUsePermissionAppWithOverlay.apk" />
diff --git a/tests/tests/permission3/UsePermissionApp31/Android.bp b/tests/tests/permission3/UsePermissionApp31/Android.bp
new file mode 100644
index 0000000..48a2d4f
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp31/Android.bp
@@ -0,0 +1,32 @@
+//
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsUsePermissionApp31",
+    srcs: [
+        ":CtsUsePermissionAppSrc",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+    certificate: ":cts-testkey2",
+    target_sdk_version: "31",
+    min_sdk_version: "31",
+}
diff --git a/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml b/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml
new file mode 100644
index 0000000..f6d9b29
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionApp31/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2021 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.permission3.cts.usepermission">
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+    <uses-permission android:name="android.permission.CAMERA" />
+    <application>
+        <activity android:name=".CheckCalendarAccessActivity" android:exported="true" />
+        <activity android:name=".FinishOnCreateActivity" android:exported="true" />
+        <activity android:name=".RequestPermissionsActivity" android:exported="true" />
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/Android.bp b/tests/tests/permission3/UsePermissionAppLocationProvider/Android.bp
new file mode 100644
index 0000000..a2cd88d
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/Android.bp
@@ -0,0 +1,31 @@
+//
+// Copyright (C) 2021 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 {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_test_helper_app {
+    name: "CtsAccessMicrophoneAppLocationProvider",
+    defaults: ["mts-target-sdk-version-current"],
+    min_sdk_version: "31",
+    srcs: [
+        "src/**/*.kt",
+    ],
+    static_libs: [
+        "kotlin-stdlib",
+    ],
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/AndroidManifest.xml b/tests/tests/permission3/UsePermissionAppLocationProvider/AndroidManifest.xml
new file mode 100644
index 0000000..56afc09
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2021 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.permission3.cts.accessmicrophoneapplocationprovider">
+
+    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <uses-permission android:name="android.permission.INTERNET" />
+    <uses-permission android:name="android.permission.RECORD_AUDIO" />
+
+    <attribution
+        android:label="@string/attribution_label"
+        android:tag="test.tag" />
+
+    <application
+        android:attributionsAreUserVisible="true"
+        android:label="LocationProviderWithMicApp">
+        <activity android:name=".AddLocationProviderActivity" android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity android:name=".UseMicrophoneActivity" android:exported="true"/>
+    </application>
+</manifest>
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/res/values/strings.xml b/tests/tests/permission3/UsePermissionAppLocationProvider/res/values/strings.xml
new file mode 100644
index 0000000..9682d7f
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<!--
+  ~ Copyright (C) 2021 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources>
+    <string name="attribution_label">Attribution Label</string>
+</resources>
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt
new file mode 100644
index 0000000..3b3355cc
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/AddLocationProviderActivity.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 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.permission3.cts.accessmicrophoneapplocationprovider
+
+import android.app.Activity
+import android.location.Criteria
+import android.location.LocationManager
+import android.os.Bundle
+
+/**
+ * An activity that adds this package as a test location provider and uses microphone.
+ */
+class AddLocationProviderActivity : Activity() {
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        val attrContext = createAttributionContext("test.tag")
+        val locationManager = attrContext.getSystemService(LocationManager::class.java)
+        locationManager.addTestProvider(
+            packageName, false, false, false, false, false, false, false, Criteria.POWER_LOW,
+            Criteria.ACCURACY_COARSE
+        )
+
+        setResult(RESULT_OK)
+        finish()
+    }
+}
diff --git a/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt
new file mode 100644
index 0000000..0d8374f
--- /dev/null
+++ b/tests/tests/permission3/UsePermissionAppLocationProvider/src/android/permission3/cts/accessmicrophoneapplocationprovider/UseMicrophoneActivity.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2021 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.permission3.cts.accessmicrophoneapplocationprovider
+
+import android.app.Activity
+import android.content.Context
+import android.media.AudioFormat
+import android.media.AudioRecord
+import android.media.MediaRecorder
+import android.os.Bundle
+import android.os.Handler
+
+private const val USE_DURATION_MS = 10000L
+private const val SAMPLE_RATE_HZ = 44100
+
+/**
+ * An activity that uses microphone.
+ */
+class UseMicrophoneActivity : Activity() {
+    private var recorder: AudioRecord? = null
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        val attrContext = createAttributionContext("test.tag")
+        useMic(attrContext)
+        setResult(RESULT_OK)
+        finish()
+    }
+
+    override fun finish() {
+        recorder?.stop()
+        recorder = null
+        super.finish()
+    }
+
+    override fun onStop() {
+        super.onStop()
+        finish()
+    }
+
+    private fun useMic(context: Context) {
+        recorder = AudioRecord.Builder()
+            .setAudioSource(MediaRecorder.AudioSource.MIC)
+            .setAudioFormat(AudioFormat.Builder()
+                .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
+                .setSampleRate(SAMPLE_RATE_HZ)
+                .setChannelMask(AudioFormat.CHANNEL_IN_MONO)
+                .build())
+            .setContext(context)
+            .build()
+        recorder?.startRecording()
+        Handler().postDelayed({ finish() }, USE_DURATION_MS)
+    }
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/BasePermissionHubTest.kt b/tests/tests/permission3/src/android/permission3/cts/BasePermissionHubTest.kt
new file mode 100644
index 0000000..014a4f3
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/BasePermissionHubTest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2021 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.permission3.cts
+
+import android.content.Intent
+import com.android.compatibility.common.util.SystemUtil
+
+/** Base class for the permission hub tests. */
+abstract class BasePermissionHubTest : BasePermissionTest() {
+
+    protected fun openMicrophoneTimeline() {
+        SystemUtil.runWithShellPermissionIdentity {
+            context.startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_HISTORY).apply {
+                putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME,
+                    android.Manifest.permission_group.MICROPHONE)
+                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+            })
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
index d968dce..49646f4 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BasePermissionTest.kt
@@ -59,6 +59,10 @@
     private val mPermissionControllerResources: Resources = context.createPackageContext(
             context.packageManager.permissionControllerPackageName, 0).resources
 
+    protected val isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
+    protected val isWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
+    protected val isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
+
     @get:Rule
     val disableAnimationRule = DisableAnimationRule()
 
@@ -154,6 +158,10 @@
         waitForIdle()
     }
 
+    protected fun clickPermissionControllerUi(selector: BySelector, timeoutMillis: Long = 20_000) {
+        click(selector.pkg(context.packageManager.permissionControllerPackageName), timeoutMillis)
+    }
+
     protected fun pressBack() {
         uiDevice.pressBack()
         waitForIdle()
diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
index ab4a05a..6c69a65 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
@@ -52,6 +52,8 @@
         const val APP_APK_PATH_28 = "$APK_DIRECTORY/CtsUsePermissionApp28.apk"
         const val APP_APK_PATH_29 = "$APK_DIRECTORY/CtsUsePermissionApp29.apk"
         const val APP_APK_PATH_30 = "$APK_DIRECTORY/CtsUsePermissionApp30.apk"
+        const val APP_APK_PATH_31 = "$APK_DIRECTORY/CtsUsePermissionApp31.apk"
+
         const val APP_APK_PATH_30_WITH_BACKGROUND =
                 "$APK_DIRECTORY/CtsUsePermissionApp30WithBackground.apk"
         const val APP_APK_PATH_30_WITH_BLUETOOTH =
@@ -74,6 +76,8 @@
                 "com.android.permissioncontroller:" +
                         "id/permission_no_upgrade_and_dont_ask_again_button"
 
+        const val ALLOW_ALWAYS_RADIO_BUTTON =
+                "com.android.permissioncontroller:id/allow_always_radio_button"
         const val ALLOW_RADIO_BUTTON = "com.android.permissioncontroller:id/allow_radio_button"
         const val ALLOW_FOREGROUND_RADIO_BUTTON =
                 "com.android.permissioncontroller:id/allow_foreground_only_radio_button"
@@ -97,10 +101,6 @@
         DENIED_WITH_PREJUDICE
     }
 
-    protected val isTv = packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK)
-    protected val isWatch = packageManager.hasSystemFeature(PackageManager.FEATURE_WATCH)
-    protected val isAutomotive = packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)
-
     private val platformResources = context.createPackageContext("android", 0).resources
     private val permissionToLabelResNameMap = mapOf(
             // Contacts
@@ -130,6 +130,8 @@
                     to "@android:string/permgrouplab_location",
             android.Manifest.permission.ACCESS_COARSE_LOCATION
                     to "@android:string/permgrouplab_location",
+            android.Manifest.permission.ACCESS_BACKGROUND_LOCATION
+                    to "@android:string/permgrouplab_location",
             // Phone
             android.Manifest.permission.READ_PHONE_STATE
                     to "@android:string/permgrouplab_phone",
@@ -449,7 +451,7 @@
             if (isWatch) {
                 click(By.text(permissionLabel), 40_000)
             } else {
-                click(By.text(permissionLabel))
+                clickPermissionControllerUi(By.text(permissionLabel))
             }
 
             val wasGranted = if (isAutomotive) {
@@ -512,6 +514,8 @@
                         PermissionState.ALLOWED ->
                             if (showsForegroundOnlyButton(permission)) {
                                 By.res(ALLOW_FOREGROUND_RADIO_BUTTON)
+                            } else if (showsAlwaysButton(permission)) {
+                                By.res(ALLOW_ALWAYS_RADIO_BUTTON)
                             } else if (isMediaStorageButton(permission, targetSdk)) {
                                 // Uses "allow_foreground_only_radio_button" as id
                                 byTextRes(R.string.allow_media_storage)
@@ -581,6 +585,12 @@
             else -> false
         }
 
+    private fun showsAlwaysButton(permission: String): Boolean =
+        when (permission) {
+            android.Manifest.permission.ACCESS_BACKGROUND_LOCATION -> true
+            else -> false
+        }
+
     private fun isMediaStorageButton(permission: String, targetSdk: Int): Boolean =
             if (isTv || isWatch) {
                 false
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt
new file mode 100644
index 0000000..e88572d
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionAttributionTest.kt
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2021 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.permission3.cts
+
+import android.app.Activity
+import android.app.AppOpsManager
+import android.content.ComponentName
+import android.content.Intent
+import android.location.LocationManager
+import android.os.Build
+import android.provider.DeviceConfig
+import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.AppOpsUtils.setOpMode
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.modules.utils.build.SdkLevel
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertTrue
+import org.junit.Assume.assumeFalse
+import org.junit.Before
+import org.junit.Test
+import java.util.concurrent.TimeUnit
+
+/**
+ * Tests permission attribution for location providers.
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+class PermissionAttributionTest : BasePermissionHubTest() {
+    private val micLabel = packageManager.getPermissionGroupInfo(
+        android.Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
+    val locationManager = context.getSystemService(LocationManager::class.java)!!
+    private var wasEnabled = false
+
+    // Permission history is not available on Auto devices running S or below.
+    @Before
+    fun assumeNotAutoBelowT() {
+        assumeFalse(isAutomotive && !SdkLevel.isAtLeastT())
+    }
+
+    @Before
+    fun installAppLocationProviderAndAllowMockLocation() {
+        installPackage(APP_APK_PATH, grantRuntimePermissions = true)
+        // The package name of a mock location provider is the caller adding it, so we have to let
+        // the test app add itself.
+        setOpMode(APP_PACKAGE_NAME, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpsManager.MODE_ALLOWED)
+    }
+
+    @Before
+    fun setup() {
+        // Allow ourselves to reliably remove the test location provider.
+        setOpMode(
+            context.packageName, AppOpsManager.OPSTR_MOCK_LOCATION, AppOpsManager.MODE_ALLOWED
+        )
+        wasEnabled = setSubattributionEnabledStateIfNeeded(true)
+    }
+
+    @After
+    fun teardown() {
+        locationManager.removeTestProvider(APP_PACKAGE_NAME)
+        if (!wasEnabled) {
+            setSubattributionEnabledStateIfNeeded(false)
+        }
+    }
+
+    @Test
+    fun testLocationProviderAttributionForMicrophone() {
+        enableAppAsLocationProvider()
+        useMicrophone()
+        openMicrophoneTimeline()
+
+        waitFindObject(By.descContains(micLabel))
+        waitFindObject(By.textContains(APP_LABEL))
+        waitFindObject(By.textContains(ATTRIBUTION_LABEL))
+    }
+
+    private fun enableAppAsLocationProvider() {
+        // Add the test app as location provider.
+        val future = startActivityForFuture(
+            Intent().apply {
+                component = ComponentName(
+                    APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.AddLocationProviderActivity"
+                )
+            }
+        )
+        val result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+        assertEquals(Activity.RESULT_OK, result.resultCode)
+        assertTrue(
+            callWithShellPermissionIdentity {
+                locationManager.isProviderPackage(APP_PACKAGE_NAME)
+            }
+        )
+    }
+
+    private fun useMicrophone() {
+        val future = startActivityForFuture(
+            Intent().apply {
+                component = ComponentName(
+                    APP_PACKAGE_NAME, "$APP_PACKAGE_NAME.UseMicrophoneActivity"
+                )
+            }
+        )
+        val result = future.get(TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)
+        assertEquals(Activity.RESULT_OK, result.resultCode)
+    }
+
+    private fun setSubattributionEnabledStateIfNeeded(shouldBeEnabled: Boolean): Boolean {
+        var currentlyEnabled = false
+        runWithShellPermissionIdentity {
+            currentlyEnabled = DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_PRIVACY,
+                FLAG_SUBATTRIBUTION, false)
+            if (currentlyEnabled != shouldBeEnabled) {
+                DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY, FLAG_SUBATTRIBUTION,
+                    shouldBeEnabled.toString(), false)
+            }
+        }
+        return currentlyEnabled
+    }
+
+    companion object {
+        const val APP_APK_PATH = "$APK_DIRECTORY/CtsAccessMicrophoneAppLocationProvider.apk"
+        const val APP_PACKAGE_NAME = "android.permission3.cts.accessmicrophoneapplocationprovider"
+        const val APP_LABEL = "LocationProviderWithMicApp"
+        const val ATTRIBUTION_LABEL = "Attribution Label"
+        const val FLAG_SUBATTRIBUTION = "permissions_hub_subattribution_enabled"
+    }
+}
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
index 794a731..db0b2b4 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionHistoryTest.kt
@@ -18,12 +18,11 @@
 
 import android.Manifest
 import android.content.Intent
-import android.content.pm.PackageManager.FEATURE_LEANBACK
-import android.content.pm.PackageManager.FEATURE_AUTOMOTIVE
 import android.os.Build
 import android.support.test.uiautomator.By
 import androidx.test.filters.SdkSuppress
 import com.android.compatibility.common.util.SystemUtil
+import com.android.modules.utils.build.SdkLevel
 import org.junit.After
 import org.junit.Assume.assumeFalse
 import org.junit.Before
@@ -40,19 +39,19 @@
 private const val MORE_OPTIONS = "More options"
 
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
-class PermissionHistoryTest : BasePermissionTest() {
+class PermissionHistoryTest : BasePermissionHubTest() {
     private val micLabel = packageManager.getPermissionGroupInfo(
-            Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
+        Manifest.permission_group.MICROPHONE, 0).loadLabel(packageManager).toString()
 
     // Permission history is not available on TV devices.
     @Before
-    fun assumeNotTv() =
-            assumeFalse(packageManager.hasSystemFeature(FEATURE_LEANBACK))
+    fun assumeNotTv() = assumeFalse(isTv)
 
-    // Permission history is not available on Auto devices.
+    // Permission history is not available on Auto devices running S or below.
     @Before
-    fun assumeNotAuto() =
-            assumeFalse(packageManager.hasSystemFeature(FEATURE_AUTOMOTIVE))
+    fun assumeNotAutoBelowT() {
+        assumeFalse(isAutomotive && !SdkLevel.isAtLeastT())
+    }
 
     @Before
     fun installApps() {
@@ -135,15 +134,6 @@
         })
     }
 
-    private fun openMicrophoneTimeline() {
-        SystemUtil.runWithShellPermissionIdentity {
-            context.startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_HISTORY).apply {
-                putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, Manifest.permission_group.MICROPHONE)
-                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
-            })
-        }
-    }
-
     private fun openPermissionDashboard() {
         SystemUtil.runWithShellPermissionIdentity {
             context.startActivity(Intent(Intent.ACTION_REVIEW_PERMISSION_USAGE).apply {
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt
index 8925088..b26e536 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTapjackingTest.kt
@@ -20,7 +20,9 @@
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.graphics.Point
+import android.os.Build
 import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
 import com.android.compatibility.common.util.SystemUtil
 import org.junit.Assume.assumeFalse
 import org.junit.Before
@@ -55,6 +57,7 @@
         tryClicking(buttonCenter)
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
     @Test
     fun testTapjackGrantDialog_partialOverlay() {
         // PermissionController for television uses a floating window.
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
index 26fe615..0a2c373 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
@@ -16,8 +16,10 @@
 
 package android.permission3.cts
 
+import android.content.pm.PackageManager
 import androidx.test.filters.FlakyTest
-import com.android.modules.utils.build.SdkLevel
+import com.android.compatibility.common.util.SystemUtil
+import org.junit.Assert
 import org.junit.Assume
 import org.junit.Before
 import org.junit.Test
@@ -182,11 +184,7 @@
         // Request the permission and allow it
         // Make sure the permission is granted
         requestAppPermissionsAndAssertResult(android.Manifest.permission.CAMERA to true) {
-            if (SdkLevel.isAtLeastS()) {
-                clickPermissionRequestAllowForegroundButton()
-            } else {
-                clickPermissionRequestAllowButton()
-            }
+            clickPermissionRequestAllowForegroundButton()
         }
     }
 
@@ -290,11 +288,7 @@
             null to false,
             android.Manifest.permission.RECORD_AUDIO to true
         ) {
-            if (SdkLevel.isAtLeastS()) {
-                clickPermissionRequestAllowForegroundButton()
-            } else {
-                clickPermissionRequestAllowButton()
-            }
+            clickPermissionRequestAllowForegroundButton()
             clickPermissionRequestAllowButton()
         }
     }
@@ -306,6 +300,34 @@
         requestAppPermissionsAndAssertResult(INVALID_PERMISSION to false) {}
     }
 
+    @Test
+    fun testAskButtonSetsFlags() {
+        Assume.assumeFalse("other form factors might not support the ask button",
+                isTv || isAutomotive || isWatch)
+
+        grantAppPermissions(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, targetSdk = 23)
+        assertAppHasPermission(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, true)
+        assertAppHasPermission(android.Manifest.permission.ACCESS_FINE_LOCATION, true)
+        assertAppHasPermission(android.Manifest.permission.ACCESS_COARSE_LOCATION, true)
+
+        revokeAppPermissions(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION, targetSdk = 23)
+        SystemUtil.runWithShellPermissionIdentity {
+            val perms = listOf(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION,
+                    android.Manifest.permission.ACCESS_FINE_LOCATION,
+                    android.Manifest.permission.ACCESS_COARSE_LOCATION)
+            for (perm in perms) {
+                var flags = packageManager.getPermissionFlags(perm, APP_PACKAGE_NAME, context.user)
+                Assert.assertEquals("USER_SET should not be set for $perm", 0,
+                        flags and PackageManager.FLAG_PERMISSION_USER_SET)
+                Assert.assertEquals("USER_FIXED should not be set for $perm", 0,
+                        flags and PackageManager.FLAG_PERMISSION_USER_FIXED)
+                Assert.assertEquals("ONE_TIME should be set for $perm",
+                        PackageManager.FLAG_PERMISSION_ONE_TIME,
+                        flags and PackageManager.FLAG_PERMISSION_ONE_TIME)
+            }
+        }
+    }
+
     private fun denyPermissionRequestWithPrejudice() {
         if (isTv || isWatch) {
             clickPermissionRequestDontAskAgainButton()
diff --git a/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt b/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt
new file mode 100644
index 0000000..d39fb9c
--- /dev/null
+++ b/tests/tests/permission3/src/android/permission3/cts/SensorBlockedBannerTest.kt
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2021 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.permission3.cts
+
+import android.content.Intent
+import android.hardware.SensorPrivacyManager
+import android.hardware.SensorPrivacyManager.Sensors.CAMERA
+import android.hardware.SensorPrivacyManager.Sensors.MICROPHONE
+import android.location.LocationManager
+import android.Manifest.permission_group.CAMERA as CAMERA_PERMISSION_GROUP
+import android.Manifest.permission_group.LOCATION as LOCATION_PERMISSION_GROUP
+import android.Manifest.permission_group.MICROPHONE as MICROPHONE_PERMISSION_GROUP
+import android.os.Build
+import android.provider.DeviceConfig
+import android.support.test.uiautomator.By
+import androidx.test.filters.SdkSuppress
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+
+/**
+ * Banner card display tests on sensors being blocked
+ */
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+class SensorBlockedBannerTest : BaseUsePermissionTest() {
+    companion object {
+        const val LOCATION = -1
+        const val WARNING_BANNER_ENABLED = "warning_banner_enabled"
+    }
+
+    val sensorPrivacyManager = context.getSystemService(SensorPrivacyManager::class.java)!!
+    val locationManager = context.getSystemService(LocationManager::class.java)!!
+    private val originalEnabledValue = callWithShellPermissionIdentity {
+        DeviceConfig.getString(DeviceConfig.NAMESPACE_PRIVACY,
+                WARNING_BANNER_ENABLED, false.toString())
+    }
+
+    private val sensorToPermissionGroup = mapOf(CAMERA to CAMERA_PERMISSION_GROUP,
+            MICROPHONE to MICROPHONE_PERMISSION_GROUP,
+            LOCATION to LOCATION_PERMISSION_GROUP)
+
+    private val permToTitle = mapOf(CAMERA to "blocked_camera_title",
+            MICROPHONE to "blocked_microphone_title",
+            LOCATION to "blocked_location_title")
+
+    @Before
+    fun setup() {
+        Assume.assumeFalse(isTv)
+        // TODO(b/203784852) Auto will eventually support the blocked sensor banner, but there won't
+        // be support in T or below
+        Assume.assumeFalse(isAutomotive)
+        installPackage(APP_APK_PATH_31)
+        runWithShellPermissionIdentity {
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+                WARNING_BANNER_ENABLED, true.toString(), false)
+        }
+    }
+
+    @After
+    fun restoreWarningBannerState() {
+        runWithShellPermissionIdentity {
+            DeviceConfig.setProperty(DeviceConfig.NAMESPACE_PRIVACY,
+                    WARNING_BANNER_ENABLED, originalEnabledValue, false)
+        }
+    }
+
+    private fun navigateAndTest(sensor: Int) {
+        val permissionGroup = sensorToPermissionGroup.getOrDefault(sensor, "Break")
+        val intent = Intent(Intent.ACTION_MANAGE_PERMISSION_APPS)
+                .putExtra(Intent.EXTRA_PERMISSION_GROUP_NAME, permissionGroup)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+        runWithShellPermissionIdentity {
+            context.startActivity(intent)
+        }
+        val bannerTitle = permToTitle.getOrDefault(sensor, "Break")
+        waitFindObject(By.text(getPermissionControllerString(bannerTitle)))
+        pressBack()
+    }
+
+    private fun runSensorTest(sensor: Int) {
+        val blocked = isSensorPrivacyEnabled(sensor)
+        if (!blocked) {
+            setSensor(sensor, true)
+        }
+        navigateAndTest(sensor)
+        if (!blocked) {
+            setSensor(sensor, false)
+        }
+    }
+
+    @Test
+    fun testCameraCardDisplayed() {
+        Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(CAMERA))
+        runSensorTest(CAMERA)
+    }
+
+    @Test
+    fun testMicCardDisplayed() {
+        Assume.assumeTrue(sensorPrivacyManager.supportsSensorToggle(MICROPHONE))
+        runSensorTest(MICROPHONE)
+    }
+
+    @Test
+    fun testLocationCardDisplayed() {
+        runSensorTest(LOCATION)
+    }
+
+    private fun setSensor(sensor: Int, enable: Boolean) {
+        if (sensor == LOCATION) {
+            runWithShellPermissionIdentity {
+                locationManager.setLocationEnabledForUser(!enable,
+                    android.os.Process.myUserHandle())
+                if (enable) {
+                    try {
+                        waitFindObjectOrNull(By.text("CLOSE"))?.click()
+                    } catch (e: Exception) {
+                        // Do nothing, warning didn't show up so test can proceed
+                    }
+                }
+            }
+        } else {
+            runWithShellPermissionIdentity {
+                sensorPrivacyManager.setSensorPrivacy(SensorPrivacyManager.Sources.OTHER,
+                    sensor, enable)
+            }
+        }
+    }
+
+    private fun isSensorPrivacyEnabled(sensor: Int): Boolean {
+        return if (sensor == LOCATION) {
+            callWithShellPermissionIdentity {
+                !locationManager.isLocationEnabled()
+            }
+        } else {
+            callWithShellPermissionIdentity {
+                sensorPrivacyManager.isSensorPrivacyEnabled(sensor)
+            }
+        }
+    }
+}
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
index ff5a1c8..c0e100c 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreAudioTestHelper.java
@@ -24,6 +24,7 @@
 import android.provider.MediaStore.Audio.Media;
 import android.provider.cts.ProviderTestUtils;
 
+import androidx.annotation.RequiresApi;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -91,7 +92,6 @@
         public static final int IS_RINGTONE = 0;
         public static final int IS_NOTIFICATION = 0;
         public static final int IS_ALARM = 0;
-        public static final int IS_RECORDING = 0;
         public static final int IS_MUSIC = 1;
         public static final int YEAR = 1992;
         public static final int TRACK = 1;
@@ -135,7 +135,6 @@
             values.put(Media.IS_MUSIC, IS_MUSIC);
             values.put(Media.IS_ALARM, IS_ALARM);
             values.put(Media.IS_NOTIFICATION, IS_NOTIFICATION);
-            values.put(Media.IS_RECORDING, IS_RECORDING);
             values.put(Media.IS_RINGTONE, IS_RINGTONE);
             return values;
         }
@@ -278,6 +277,28 @@
         }
     }
 
+    @RequiresApi(Build.VERSION_CODES.S)
+    public static class Audio7 extends Audio1 {
+        public static final int IS_RECORDING = 0;
+
+        private Audio7() {
+        }
+
+        private static Audio7 sInstance = new Audio7();
+
+        public static Audio7 getInstance() {
+            return sInstance;
+        }
+
+        @Override
+        public ContentValues getContentValues(String volumeName) {
+            ContentValues values = super.getContentValues(volumeName);
+            values.put(Media.DATA, values.getAsString(Media.DATA) + ".recording.mp3");
+            values.put(Media.IS_RECORDING, IS_RECORDING);
+            return values;
+        }
+    }
+
     @Test
     public void testStub() {
         // No-op test here to keep atest happy
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
index aed220d..c6768ab 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStorePlacementTest.java
@@ -221,8 +221,9 @@
                 Optional.of("Android/media/android.provider.cts/foo"), null));
         assertFalse(updatePlacement(uri,
                 Optional.of("Android/media/com.example/foo"), null));
-        assertFalse(updatePlacement(uri,
+        assertTrue(updatePlacement(uri,
                 Optional.of("DCIM"), null));
+        assertFalse(updatePlacement(uri, Optional.of("Android/media"), null));
     }
 
     @Test
@@ -232,12 +233,13 @@
         final Uri uri = ProviderTestUtils.stageMedia(R.drawable.scenery,
                 mExternalImages, "image/jpeg");
 
-        assertFalse(updatePlacement(uri,
+        assertTrue(updatePlacement(uri,
                 Optional.of("Android/media/android.provider.cts/foo"), null));
         assertFalse(updatePlacement(uri,
                 Optional.of("Android/media/com.example/foo"), null));
         assertTrue(updatePlacement(uri,
                 Optional.of("DCIM"), null));
+        assertFalse(updatePlacement(uri, Optional.of("Android/media"), null));
     }
 
     @Test
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java b/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java
index 49a33ad..da7f0fb 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStoreUtils.java
@@ -27,16 +27,16 @@
 import android.provider.MediaStore.MediaColumns;
 import android.text.format.DateUtils;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.SdkSuppress;
+
 import org.junit.Test;
 
 import java.io.FileNotFoundException;
 import java.io.OutputStream;
 import java.util.Objects;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.SdkSuppress;
-
 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
 public class MediaStoreUtils {
     @Test
@@ -118,6 +118,10 @@
             }
         }
 
+        public void setIsFavorite(@Nullable Boolean isFavorite) {
+            this.insertValues.put(MediaColumns.IS_FAVORITE, isFavorite);
+        }
+
         /**
          * Optionally set the Uri from where the file has been downloaded. This is used
          * for files being added to {@link Downloads} table.
diff --git a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
index a196c5a..6a4c199 100644
--- a/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
+++ b/tests/tests/provider/src/android/provider/cts/media/MediaStore_Audio_MediaTest.java
@@ -36,10 +36,10 @@
 import android.provider.MediaStore.Audio;
 import android.provider.MediaStore.Audio.Media;
 import android.provider.MediaStore.Files.FileColumns;
-import android.provider.MediaStore.MediaColumns;
 import android.provider.cts.ProviderTestUtils;
 import android.provider.cts.R;
 import android.provider.cts.media.MediaStoreAudioTestHelper.Audio1;
+import android.provider.cts.media.MediaStoreAudioTestHelper.Audio7;
 import android.provider.cts.media.MediaStoreUtils.PendingParams;
 import android.provider.cts.media.MediaStoreUtils.PendingSession;
 import android.util.Log;
@@ -157,7 +157,6 @@
             assertEquals(Audio1.IS_MUSIC, c.getInt(c.getColumnIndex(Media.IS_MUSIC)));
             assertEquals(Audio1.IS_NOTIFICATION, c.getInt(c.getColumnIndex(Media.IS_NOTIFICATION)));
             assertEquals(Audio1.IS_RINGTONE, c.getInt(c.getColumnIndex(Media.IS_RINGTONE)));
-            assertEquals(Audio1.IS_RECORDING, c.getInt(c.getColumnIndex(Media.IS_RECORDING)));
             assertEquals(Audio1.TRACK, c.getInt(c.getColumnIndex(Media.TRACK)));
             assertEquals(Audio1.YEAR, c.getInt(c.getColumnIndex(Media.YEAR)));
             String titleKey = c.getString(c.getColumnIndex(Media.TITLE_KEY));
@@ -186,6 +185,41 @@
         }
     }
 
+    /**
+     * The test case checks below behaviors:
+     * 1. The IS_RECORDING column is in Audio table in MediaStore's database since S OS. Insert with
+     *    IS_RECORDING column doesn't fail and we can query the result with IS_RECORDING column.
+     * 2. Validate IS_RECORDING is correctly set for database row.
+     */
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S)
+    public void testStoreNotAudioRecordingMedia() {
+        Audio7 audio7 = Audio7.getInstance();
+        ContentValues values = audio7.getContentValues(mVolumeName);
+        //insert
+        Uri mediaUri = Media.getContentUri(mVolumeName);
+        Uri uri = mContentResolver.insert(mediaUri, values);
+        assertNotNull(uri);
+
+        try (Cursor c = mContentResolver.query(uri, null, null, null, null)) {
+            assertEquals(1, c.getCount());
+            c.moveToFirst();
+            long id = c.getLong(c.getColumnIndex(Media._ID));
+            assertTrue(id > 0);
+            String expected = audio7.getContentValues(mVolumeName).getAsString(Media.DATA);
+            assertEquals(expected, c.getString(c.getColumnIndex(Media.DATA)));
+            assertEquals(Audio7.MIME_TYPE, c.getString(c.getColumnIndex(Media.MIME_TYPE)));
+            assertEquals(Audio7.IS_ALARM, c.getInt(c.getColumnIndex(Media.IS_ALARM)));
+            assertEquals(Audio7.IS_MUSIC, c.getInt(c.getColumnIndex(Media.IS_MUSIC)));
+            assertEquals(Audio7.IS_NOTIFICATION, c.getInt(c.getColumnIndex(Media.IS_NOTIFICATION)));
+            assertEquals(Audio7.IS_RINGTONE, c.getInt(c.getColumnIndex(Media.IS_RINGTONE)));
+            assertEquals(Audio7.IS_RECORDING, c.getInt(c.getColumnIndex(Media.IS_RECORDING)));
+        } finally {
+            // delete
+            mContentResolver.delete(uri, null, null);
+        }
+    }
+
     @Test(timeout = 60000)
     public void testCanonicalize() throws Exception {
         // Remove all audio left over from other tests
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/SelectionEventTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/SelectionEventTest.java
index 72550c4..d86ffb1 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/SelectionEventTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/SelectionEventTest.java
@@ -16,6 +16,11 @@
 
 package android.view.textclassifier.cts;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.textclassifier.SelectionEvent;
+import android.view.textclassifier.TextClassifier;
+
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -27,7 +32,23 @@
 public class SelectionEventTest {
 
     @Test
-    public void testSelectionEvent_placeholder() {
-        // TODO: add tests for SelectionEvent
+    public void testSelectionEvent() {
+        SelectionEvent event = SelectionEvent.createSelectionActionEvent(0, 1,
+                SelectionEvent.ACTION_COPY);
+        assertThat(event.getEventType()).isEqualTo(SelectionEvent.ACTION_COPY);
+        assertThat(event.getStart()).isEqualTo(0);
+        assertThat(event.getEnd()).isEqualTo(0);
+        assertThat(event.getInvocationMethod()).isEqualTo(SelectionEvent.INVOCATION_UNKNOWN);
+        assertThat(event.getEntityType()).isEqualTo(TextClassifier.TYPE_UNKNOWN);
+        assertThat(event.getEventIndex()).isEqualTo(0);
+        assertThat(event.getPackageName()).isEqualTo("");
+        assertThat(event.getSmartStart()).isEqualTo(0);
+        assertThat(event.getSmartEnd()).isEqualTo(0);
+        assertThat(event.getWidgetType()).isEqualTo(TextClassifier.WIDGET_TYPE_UNKNOWN);
+        assertThat(event.getWidgetVersion()).isNull();
+        assertThat(event.getResultId()).isEqualTo("");
+        assertThat(event.getEventTime()).isEqualTo(0);
+        assertThat(event.getDurationSinceSessionStart()).isEqualTo(0);
+        assertThat(event.getDurationSincePreviousEvent()).isEqualTo(0);
     }
 }
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLinksTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLinksTest.java
index 1414ed7..3592c96 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLinksTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextLinksTest.java
@@ -231,6 +231,7 @@
         assertNull(request.getDefaultLocales());
         assertTrue(request.getExtras().isEmpty());
         assertNull(request.getEntityConfig());
+        assertNull(request.getCallingPackageName());
     }
 
     @Test
@@ -253,6 +254,7 @@
                 TextClassifier.HINT_TEXT_IS_EDITABLE,
                 request.getEntityConfig().getHints().iterator().next());
         assertEquals(referenceTime, request.getReferenceTime());
+        assertNull(request.getCallingPackageName());
     }
 
 }
diff --git a/tests/tests/wifi/AndroidTest.xml b/tests/tests/wifi/AndroidTest.xml
index 421a9f7..5da40ea 100644
--- a/tests/tests/wifi/AndroidTest.xml
+++ b/tests/tests/wifi/AndroidTest.xml
@@ -43,6 +43,5 @@
 
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
         <option name="mainline-module-package-name" value="com.google.android.wifi" />
-        <option name="mainline-module-package-name" value="com.google.android.tethering" />
     </object>
 </configuration>
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
index 7130051..88457ca 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/MultiStaConcurrencyWifiNetworkSpecifierTest.java
@@ -259,7 +259,9 @@
      * Tests the concurrent connection flow.
      * 1. Connect to a network using peer to peer API.
      * 2. Connect to a network using internet connectivity API.
-     * 3. Verify that both connections are active.
+     * 3. Verify that both connections are active only the network for peer-to-peer and network
+     *    for internet have different SSIDs. If they have the same SSID, verify there's exactly one
+     *    connection.
      */
     @Test
     public void testConnectToInternetNetworkWhenConnectedToPeerPeerNetwork() throws Exception {
@@ -275,7 +277,9 @@
                 mTestNetworkForInternetConnection);
 
         // Ensure that there are 2 wifi connections available for apps.
-        assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(2);
+        assertThat(mTestHelper.getNumWifiConnections()).isEqualTo(
+                mTestNetworkForPeerToPeer.SSID.equals(mTestNetworkForInternetConnection.SSID)
+                        ? 1 : 2);
     }
 
     /**
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
index 98ab11d..c845138 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/TestHelper.java
@@ -146,7 +146,7 @@
                 wifiManager.unregisterScanResultsCallback(scanResultsCallback);
             }
             List<ScanResult> scanResults = wifiManager.getScanResults();
-            if (scanResults == null || scanResults.isEmpty()) fail("No scan results available");
+            if (scanResults == null || scanResults.isEmpty()) continue;
             for (ScanResult scanResult : scanResults) {
                 WifiConfiguration matchingNetwork = savedNetworks.stream()
                         .filter(network -> TextUtils.equals(
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
index 81129ed..a4f3ff5 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -1332,14 +1332,15 @@
             // Skip the test if wifi module version is older than S.
             return;
         }
-        List<WifiConfiguration> testConfigs = new ArrayList<>();
-        testConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OPEN));
-        testConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OWE));
-        testConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_PSK));
-        testConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_SAE));
-        testConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
+        List<WifiConfiguration> baseConfigs = new ArrayList<>();
+        baseConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OPEN));
+        baseConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_PSK));
+        baseConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
                 WifiConfiguration.SECURITY_TYPE_EAP));
-        testConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
+        List<WifiConfiguration> upgradeConfigs = new ArrayList<>();
+        upgradeConfigs.add(createConfig("test-open-owe-jdur", WifiConfiguration.SECURITY_TYPE_OWE));
+        upgradeConfigs.add(createConfig("test-psk-sae-ijfe", WifiConfiguration.SECURITY_TYPE_SAE));
+        upgradeConfigs.add(createConfig("test-wpa2e-wpa3e-plki",
                 WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE));
         UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         try {
@@ -1349,46 +1350,51 @@
                     mWifiManager.getPrivilegedConfiguredNetworks().size();
             final int originalCallerConfiguredNetworksNumber =
                 mWifiManager.getCallerConfiguredNetworks().size();
-            for (WifiConfiguration c: testConfigs) {
+            for (WifiConfiguration c: baseConfigs) {
                 WifiManager.AddNetworkResult result = mWifiManager.addNetworkPrivileged(c);
                 assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode);
                 assertTrue(result.networkId >= 0);
                 c.networkId = result.networkId;
             }
-            List<WifiConfiguration> expectedConfigs = testConfigs;
+            for (WifiConfiguration c: upgradeConfigs) {
+                WifiManager.AddNetworkResult result = mWifiManager.addNetworkPrivileged(c);
+                assertEquals(WifiManager.AddNetworkResult.STATUS_SUCCESS, result.statusCode);
+                assertTrue(result.networkId >= 0);
+                c.networkId = result.networkId;
+            }
+            // open/owe, psk/sae, and wpa2e/wpa3e should be merged
+            // so they should have the same network ID.
+            for (int i = 0; i < baseConfigs.size(); i++) {
+                assertEquals(baseConfigs.get(i).networkId, upgradeConfigs.get(i).networkId);
+            }
+
+            int numAddedConfigs = baseConfigs.size();
+            List<WifiConfiguration> expectedConfigs = new ArrayList<>(baseConfigs);
             if (SdkLevel.isAtLeastS()) {
-                // open/owe, psk/sae, and wpa2e/wpa3e should be merged
-                // so they should have the same network ID.
-                assertEquals(testConfigs.get(0).networkId, testConfigs.get(1).networkId);
-                assertEquals(testConfigs.get(2).networkId, testConfigs.get(3).networkId);
-                assertEquals(testConfigs.get(4).networkId, testConfigs.get(5).networkId);
-            } else {
-                // Network IDs for different security types should be unique for R
-                assertNotEquals(testConfigs.get(0).networkId, testConfigs.get(1).networkId);
-                assertNotEquals(testConfigs.get(2).networkId, testConfigs.get(3).networkId);
-                assertNotEquals(testConfigs.get(4).networkId, testConfigs.get(5).networkId);
-                // WPA3-Enterprise is omitted when WPA2-Enterprise is present for R
-                expectedConfigs = testConfigs.subList(0, 5);
+                // S devices and above will return one additional config per each security type
+                // added, so we include the number of both base and upgrade configs.
+                numAddedConfigs += upgradeConfigs.size();
+                expectedConfigs.addAll(upgradeConfigs);
             }
             List<WifiConfiguration> configuredNetworks = mWifiManager.getConfiguredNetworks();
-            assertEquals(originalConfiguredNetworksNumber + expectedConfigs.size(),
+            assertEquals(originalConfiguredNetworksNumber + numAddedConfigs,
                     configuredNetworks.size());
             assertConfigsAreFound(expectedConfigs, configuredNetworks);
 
             List<WifiConfiguration> privilegedConfiguredNetworks =
                     mWifiManager.getPrivilegedConfiguredNetworks();
-            assertEquals(originalPrivilegedConfiguredNetworksNumber + expectedConfigs.size(),
+            assertEquals(originalPrivilegedConfiguredNetworksNumber + numAddedConfigs,
                     privilegedConfiguredNetworks.size());
             assertConfigsAreFound(expectedConfigs, privilegedConfiguredNetworks);
 
             List<WifiConfiguration> callerConfiguredNetworks =
                     mWifiManager.getCallerConfiguredNetworks();
-            assertEquals(originalCallerConfiguredNetworksNumber + expectedConfigs.size(),
+            assertEquals(originalCallerConfiguredNetworksNumber + numAddedConfigs,
                     callerConfiguredNetworks.size());
             assertConfigsAreFound(expectedConfigs, callerConfiguredNetworks);
 
         } finally {
-            for (WifiConfiguration c: testConfigs) {
+            for (WifiConfiguration c: baseConfigs) {
                 if (c.networkId >= 0) {
                     mWifiManager.removeNetwork(c.networkId);
                 }
@@ -2276,6 +2282,7 @@
      * configuration.
      * @throws Exception
      */
+    @VirtualDeviceNotSupported
     public void testSetGetSoftApConfigurationAndSoftApCapabilityCallback() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
@@ -2365,6 +2372,7 @@
      * Verify that startTetheredHotspot with specific channel config.
      * @throws Exception
      */
+    @VirtualDeviceNotSupported
     public void testStartTetheredHotspotWithChannelConfigAndSoftApStateAndInfoCallback()
             throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
@@ -2540,17 +2548,8 @@
             assertTrue(actionListener.onSuccessCalled);
             // Wait for connection to complete & ensure we are connected to the saved network.
             waitForConnection();
-            if (SdkLevel.isAtLeastS()) {
-                assertEquals(savedNetworkToConnect.networkId,
-                        mWifiManager.getConnectionInfo().getNetworkId());
-            } else {
-                // In R, auto-upgraded network IDs may be different from the original saved network.
-                // Since we may end up selecting the auto-upgraded network ID for connection and end
-                // up connected to the original saved network with a different network ID, we should
-                // instead match by SSID.
-                assertEquals(savedNetworkToConnect.SSID,
-                        mWifiManager.getConnectionInfo().getSSID());
-            }
+            assertEquals(savedNetworkToConnect.networkId,
+                    mWifiManager.getConnectionInfo().getNetworkId());
         } finally {
             // Re-enable all saved networks before exiting.
             if (savedNetworks != null) {
diff --git a/tools/cts-dynamic-config/Android.mk b/tools/cts-dynamic-config/Android.mk
index fae519c..7a3b3c6 100644
--- a/tools/cts-dynamic-config/Android.mk
+++ b/tools/cts-dynamic-config/Android.mk
@@ -22,7 +22,7 @@
 LOCAL_MODULE_CLASS := FAKE
 LOCAL_IS_HOST_MODULE := true
 
-LOCAL_COMPATIBILITY_SUITE := cts general-tests mts
+LOCAL_COMPATIBILITY_SUITE := cts general-tests mts mts-media
 
 # my_test_config_file := DynamicConfig.xml
 # TODO (sbasi): Update to use BUILD_HOST_TEST_CONFIG when it's primary install