Merge "Throw AssumptionViolatedException if not live_tv"
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
index fd7a783..f258dff 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/IsolatedSplitsTests.java
@@ -44,6 +44,7 @@
     private static final String APK_FEATURE_B_pl = "CtsIsolatedSplitAppFeatureB_pl.apk";
     private static final String APK_FEATURE_C = "CtsIsolatedSplitAppFeatureC.apk";
     private static final String APK_FEATURE_C_pl = "CtsIsolatedSplitAppFeatureC_pl.apk";
+    private static final String APK_FEATURE_A_DiffRev = "CtsIsolatedSplitAppFeatureADiffRev.apk";
 
     @Before
     public void setUp() throws Exception {
@@ -246,4 +247,50 @@
         Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG, TEST_CLASS,
                 "shouldLoadFeatureCDefault");
     }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testSplitsInheritInstall_full() throws Exception {
+        testSplitsInheritInstall(false);
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testSplitsInheritInstall_instant() throws Exception {
+        testSplitsInheritInstall(true);
+    }
+
+    private void testSplitsInheritInstall(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK_BASE).addFile(APK_FEATURE_A).addFile(APK_FEATURE_B)
+                .addFile(APK_FEATURE_C).run();
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG, TEST_CLASS,
+                "shouldLoadFeatureADefault");
+
+        new InstallMultiple(instant).inheritFrom(PKG).addFile(APK_FEATURE_A_DiffRev).run();
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG, TEST_CLASS,
+                "shouldLoadFeatureADiffRevision");
+    }
+
+    @Test
+    @AppModeFull(reason = "'full' portion of the hostside test")
+    public void testSplitsRemoved_full() throws Exception {
+        testSplitsRemoved(false);
+    }
+
+    @Test
+    @AppModeInstant(reason = "'instant' portion of the hostside test")
+    public void testSplitsRemoved_instant() throws Exception {
+        testSplitsRemoved(true);
+    }
+
+    private void testSplitsRemoved(boolean instant) throws Exception {
+        new InstallMultiple(instant).addFile(APK_BASE).addFile(APK_FEATURE_A).addFile(APK_FEATURE_B)
+                .addFile(APK_FEATURE_C).run();
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG, TEST_CLASS,
+                "shouldLoadFeatureCDefault");
+
+        new InstallMultiple(instant).inheritFrom(PKG).removeSplit("feature_c").run();
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), PKG, TEST_CLASS,
+                "shouldNotFoundFeatureC");
+    }
 }
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/SessionReferrerUriTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/SessionReferrerUriTest.java
index 695ed26..7fa2618 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/SessionReferrerUriTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/SessionReferrerUriTest.java
@@ -54,9 +54,10 @@
     @Test
     @AppModeFull(reason = "Only full apps may install")
     public void testSessionReferrerUriVisibleToOwner() throws DeviceNotAvailableException {
-        Utils.runDeviceTests(getDevice(), SESSION_INSPECTOR_PKG_A,
+        // Device test launches an Activity, so it should run under current user.
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), SESSION_INSPECTOR_PKG_A,
                 "com.android.cts.sessioninspector.SessionInspectorTest", "testOnlyOwnerCanSee");
-        Utils.runDeviceTests(getDevice(), SESSION_INSPECTOR_PKG_B,
+        Utils.runDeviceTestsAsCurrentUser(getDevice(), SESSION_INSPECTOR_PKG_B,
                 "com.android.cts.sessioninspector.SessionInspectorTest", "testOnlyOwnerCanSee");
     }
 }
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp
index 796b0b0..6472461 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/Android.bp
@@ -29,6 +29,7 @@
     static_libs: [
         "ctstestrunner-axt",
         "androidx.test.rules",
+        "truth-prebuilt",
     ],
     srcs: ["src/**/*.java"],
     // Generate a locale split.
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml
index 57320b3..521a96b 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/AndroidManifest.xml
@@ -21,7 +21,7 @@
 
     <application android:label="IsolatedSplitApp">
 
-        <activity android:name=".BaseActivity"
+        <activity android:name=".BaseActivity" android:theme="@style/Theme_Base"
              android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/Android.bp b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/Android.bp
index 07d7fa6..cc2ea8c 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/Android.bp
@@ -27,6 +27,7 @@
     // Make sure our test locale polish is not stripped.
     aapt_include_all_resources: true,
     srcs: ["**/*.java"],
+    resource_dirs: ["res"],
     // Generate a locale split.
     package_splits: ["pl"],
     libs: ["CtsIsolatedSplitApp"],
@@ -39,3 +40,31 @@
         "--package-id 0x80",
     ],
 }
+
+android_test_helper_app {
+    name: "CtsIsolatedSplitAppFeatureADiffRev",
+    defaults: ["cts_support_defaults"],
+    sdk_version: "current",
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+    // Feature splits are dependent on this split, so it must be exported.
+    export_package_resources: true,
+    // Make sure our test locale polish is not stripped.
+    aapt_include_all_resources: true,
+    srcs: ["**/*.java"],
+    resource_dirs: ["res_diffrev"],
+    // Generate a locale split.
+    package_splits: ["pl"],
+    libs: ["CtsIsolatedSplitApp"],
+    // Although feature splits use unique resource package names, they must all
+    // have the same manifest package name to be considered one app.
+    aaptflags: [
+        "--rename-manifest-package com.android.cts.isolatedsplitapp",
+        // Assign a unique package ID to this feature split. Since these are
+        // isolated splits, it must only be unique across a dependency chain.
+        "--package-id 0x80",
+        "--revision-code 10"
+    ],
+}
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
index 7e2e1da..e7c2016 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/AndroidManifest.xml
@@ -20,7 +20,7 @@
      android:targetSandboxVersion="2">
 
     <application>
-        <activity android:name=".FeatureAActivity"
+        <activity android:name=".FeatureAActivity" android:theme="@style/Theme_Feature_A"
              android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/drawable/feature_a_color_drawable.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/drawable/feature_a_color_drawable.xml
new file mode 100644
index 0000000..cba22a6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/drawable/feature_a_color_drawable.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Copyright (C) 2020 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.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/themePrimaryColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/layout/feature_a_textview.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/layout/feature_a_textview.xml
new file mode 100644
index 0000000..fdc22fb
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/layout/feature_a_textview.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2020 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
+
+      https://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.
+  -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/feature_a_text"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:text="@string/feature_a_string"
+    android:background="?android:attr/colorBackground">
+</TextView>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values-pl/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values-pl/colors.xml
new file mode 100644
index 0000000..9fb73b9
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values-pl/colors.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <color name="theme_base_color">@color/yellow</color>
+    <color name="theme_primary_color">@color/dark_gray</color>
+    <color name="theme_color_background">@color/gray</color>
+    <color name="navigation_bar_color">@color/light_gray</color>
+    <color name="status_bar_color">@color/blue</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values-pl/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values-pl/values.xml
index 2929156..aef6469 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values-pl/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values-pl/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="feature_a_string">Feature A String Polish</string>
+    <string name="theme_name">Feature A Theme Polish</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/attrs.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/attrs.xml
new file mode 100644
index 0000000..efd04b8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/attrs.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <attr name="themePrimaryColor" format="color|reference"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/colors.xml
new file mode 100644
index 0000000..dd4e007
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/colors.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <color name="theme_base_color">@color/blue</color>
+    <color name="theme_primary_color">@color/gray</color>
+    <color name="theme_color_background">@color/light_gray</color>
+    <color name="navigation_bar_color">@color/dark_gray</color>
+    <color name="status_bar_color">@color/yellow</color>
+
+    <color name="blue">#ff0000ff</color>
+    <color name="gray">#ff888888</color>
+    <color name="light_gray">#ff444444</color>
+    <color name="dark_gray">#ffcccccc</color>
+    <color name="yellow">#ffffff00</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/styles.xml
new file mode 100644
index 0000000..f1d6f91
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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 xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:base="http://schemas.android.com/apk/res/com.android.cts.isolatedsplitapp">
+    <style name="Theme_Feature_A" parent="@base:style/Theme_Base">
+        <item name="base:themeName">@string/theme_name</item>
+        <item name="base:themeBaseColor">@color/theme_base_color</item>
+        <item name="themePrimaryColor">@color/theme_primary_color</item>
+        <item name="android:colorBackground">@color/theme_color_background</item>
+        <item name="android:windowBackground">@drawable/feature_a_color_drawable</item>
+        <item name="android:navigationBarColor">@color/navigation_bar_color</item>
+        <item name="android:statusBarColor">@color/status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/values.xml
index dc1289c..6310ae1 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res/values/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="feature_a_string">Feature A String Default</string>
+    <string name="theme_name">Feature A Theme</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/drawable/feature_a_color_drawable.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/drawable/feature_a_color_drawable.xml
new file mode 100644
index 0000000..cba22a6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/drawable/feature_a_color_drawable.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Copyright (C) 2020 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.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/themePrimaryColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/layout/feature_a_textview.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/layout/feature_a_textview.xml
new file mode 100644
index 0000000..fdc22fb
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/layout/feature_a_textview.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2020 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
+
+      https://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.
+  -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/feature_a_text"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:text="@string/feature_a_string"
+    android:background="?android:attr/colorBackground">
+</TextView>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values-pl/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values-pl/colors.xml
new file mode 100644
index 0000000..c071bb7
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values-pl/colors.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <color name="theme_base_color">@color/blue</color>
+    <color name="theme_primary_color">@color/yellow</color>
+    <color name="theme_color_background">@color/dark_gray</color>
+    <color name="navigation_bar_color">@color/gray</color>
+    <color name="status_bar_color">@color/light_gray</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values-pl/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values-pl/values.xml
new file mode 100644
index 0000000..1caedf6
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values-pl/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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="feature_a_string">Feature A String Polish Diff Revision</string>
+    <string name="theme_name">Feature A Theme Polish Diff Revision</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/attrs.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/attrs.xml
new file mode 100644
index 0000000..efd04b8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/attrs.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <attr name="themePrimaryColor" format="color|reference"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/colors.xml
new file mode 100644
index 0000000..70756b1
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/colors.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <color name="theme_base_color">@color/yellow</color>
+    <color name="theme_primary_color">@color/blue</color>
+    <color name="theme_color_background">@color/gray</color>
+    <color name="navigation_bar_color">@color/light_gray</color>
+    <color name="status_bar_color">@color/dark_gray</color>
+
+    <color name="blue">#ff0000ff</color>
+    <color name="gray">#ff888888</color>
+    <color name="light_gray">#ff444444</color>
+    <color name="dark_gray">#ffcccccc</color>
+    <color name="yellow">#ffffff00</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/styles.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/styles.xml
new file mode 100644
index 0000000..f1d6f91
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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 xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:base="http://schemas.android.com/apk/res/com.android.cts.isolatedsplitapp">
+    <style name="Theme_Feature_A" parent="@base:style/Theme_Base">
+        <item name="base:themeName">@string/theme_name</item>
+        <item name="base:themeBaseColor">@color/theme_base_color</item>
+        <item name="themePrimaryColor">@color/theme_primary_color</item>
+        <item name="android:colorBackground">@color/theme_color_background</item>
+        <item name="android:windowBackground">@drawable/feature_a_color_drawable</item>
+        <item name="android:navigationBarColor">@color/navigation_bar_color</item>
+        <item name="android:statusBarColor">@color/status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/values.xml
new file mode 100644
index 0000000..a6bdeaa
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/res_diffrev/values/values.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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="feature_a_string">Feature A String Diff Revision</string>
+    <string name="theme_name">Feature A Theme Diff Revision</string>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/src/com/android/cts/isolatedsplitapp/feature_a/FeatureAActivity.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/src/com/android/cts/isolatedsplitapp/feature_a/FeatureAActivity.java
index df06bd9..3a0e961 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/src/com/android/cts/isolatedsplitapp/feature_a/FeatureAActivity.java
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_a/src/com/android/cts/isolatedsplitapp/feature_a/FeatureAActivity.java
@@ -15,8 +15,18 @@
  */
 package com.android.cts.isolatedsplitapp.feature_a;
 
+import android.os.Bundle;
+import android.widget.LinearLayout;
+
 import com.android.cts.isolatedsplitapp.BaseActivity;
 
 public class FeatureAActivity extends BaseActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
 
+        final LinearLayout linearLayout = findViewById(
+                com.android.cts.isolatedsplitapp.R.id.base_layout);
+        getLayoutInflater().inflate(R.layout.feature_a_textview, linearLayout);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
index 2b616cc..35e924f 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/AndroidManifest.xml
@@ -22,7 +22,7 @@
     <uses-split android:name="feature_a"/>
 
     <application>
-        <activity android:name=".FeatureBActivity"
+        <activity android:name=".FeatureBActivity" android:theme="@style/Theme_Feature_B"
              android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/drawable/feature_b_color_drawable.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/drawable/feature_b_color_drawable.xml
new file mode 100644
index 0000000..ce4a7a4
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/drawable/feature_b_color_drawable.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Copyright (C) 2020 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.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/themeSecondaryColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/layout/feature_b_textview.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/layout/feature_b_textview.xml
new file mode 100644
index 0000000..42f9207
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/layout/feature_b_textview.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2020 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
+
+      https://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.
+  -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/feature_b_text"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:text="@string/feature_b_string"
+    android:background="?android:attr/colorBackground">
+</TextView>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values-pl/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values-pl/colors.xml
new file mode 100644
index 0000000..2ee94b9
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values-pl/colors.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <color name="theme_base_color">@color/mintcream</color>
+    <color name="theme_secondary_color">@color/linen</color>
+    <color name="theme_color_background">@color/pink</color>
+    <color name="navigation_bar_color">@color/orange</color>
+    <color name="status_bar_color">@color/purple</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values-pl/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values-pl/values.xml
index fc46307..52d0bd6 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values-pl/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values-pl/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="feature_b_string">Feature B String Polish</string>
+    <string name="theme_name">Feature B Theme Polish</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/attrs.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/attrs.xml
new file mode 100644
index 0000000..c9eae8e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/attrs.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <attr name="themeSecondaryColor" format="color|reference"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/colors.xml
new file mode 100644
index 0000000..3f774d8
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/colors.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <color name="theme_base_color">@color/purple</color>
+    <color name="theme_secondary_color">@color/pink</color>
+    <color name="theme_color_background">@color/orange</color>
+    <color name="navigation_bar_color">@color/linen</color>
+    <color name="status_bar_color">@color/mintcream</color>
+
+    <color name="purple">#ff800080</color>
+    <color name="pink">#ffffc0cb</color>
+    <color name="orange">#ffffa500</color>
+    <color name="linen">#fffaf0e6</color>
+    <color name="mintcream">#fff5fffa</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/styles.xml
new file mode 100644
index 0000000..230f0eb
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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 xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:base="http://schemas.android.com/apk/res/com.android.cts.isolatedsplitapp">
+    <style name="Theme_Feature_B" parent="@base:style/Theme_Base">
+        <item name="base:themeName">@string/theme_name</item>
+        <item name="base:themeBaseColor">@color/theme_base_color</item>
+        <item name="themeSecondaryColor">@color/theme_secondary_color</item>
+        <item name="android:colorBackground">@color/theme_color_background</item>
+        <item name="android:windowBackground">@drawable/feature_b_color_drawable</item>
+        <item name="android:navigationBarColor">@color/navigation_bar_color</item>
+        <item name="android:statusBarColor">@color/status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/values.xml
index 421ce55..ceef1dd 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/res/values/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="feature_b_string">Feature B String Default</string>
+    <string name="theme_name">Feature B Theme</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/src/com/android/cts/isolatedsplitapp/feature_b/FeatureBActivity.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/src/com/android/cts/isolatedsplitapp/feature_b/FeatureBActivity.java
index 038555d..b5c3ad0 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/src/com/android/cts/isolatedsplitapp/feature_b/FeatureBActivity.java
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_b/src/com/android/cts/isolatedsplitapp/feature_b/FeatureBActivity.java
@@ -15,8 +15,18 @@
  */
 package com.android.cts.isolatedsplitapp.feature_b;
 
+import android.os.Bundle;
+import android.widget.LinearLayout;
+
 import com.android.cts.isolatedsplitapp.feature_a.FeatureAActivity;
 
 public class FeatureBActivity extends FeatureAActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
 
+        final LinearLayout linearLayout = findViewById(
+                com.android.cts.isolatedsplitapp.R.id.base_layout);
+        getLayoutInflater().inflate(R.layout.feature_b_textview, linearLayout);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
index 3e65a43..4bbc9ce 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/AndroidManifest.xml
@@ -20,7 +20,7 @@
      android:targetSandboxVersion="2">
 
     <application>
-        <activity android:name=".FeatureCActivity"
+        <activity android:name=".FeatureCActivity" android:theme="@style/Theme_Feature_C"
              android:exported="true">
             <intent-filter>
                 <action android:name="android.intent.action.MAIN"/>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/drawable/feature_c_color_drawable.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/drawable/feature_c_color_drawable.xml
new file mode 100644
index 0000000..1bc19ff
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/drawable/feature_c_color_drawable.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Copyright (C) 2020 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.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/themeTertiaryColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/layout/feature_c_textview.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/layout/feature_c_textview.xml
new file mode 100644
index 0000000..a69eceb
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/layout/feature_c_textview.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2020 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
+
+      https://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.
+  -->
+
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/feature_c_text"
+    android:layout_height="wrap_content"
+    android:layout_width="wrap_content"
+    android:text="@string/feature_c_string"
+    android:background="?android:attr/colorBackground">
+</TextView>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values-pl/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values-pl/colors.xml
new file mode 100644
index 0000000..086261e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values-pl/colors.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <color name="theme_base_color">@color/olive</color>
+    <color name="theme_tertiary_color">@color/navy</color>
+    <color name="theme_color_background">@color/magenta</color>
+    <color name="navigation_bar_color">@color/maroon</color>
+    <color name="status_bar_color">@color/cyan</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values-pl/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values-pl/values.xml
index 2ab54a8..e8c2850 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values-pl/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values-pl/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="feature_c_string">Feature C String Polish</string>
+    <string name="theme_name">Feature C Theme Polish</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/attrs.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/attrs.xml
new file mode 100644
index 0000000..007706d
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/attrs.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <attr name="themeTertiaryColor" format="color|reference"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/colors.xml
new file mode 100644
index 0000000..3bad5b3
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/colors.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <color name="theme_base_color">@color/cyan</color>
+    <color name="theme_tertiary_color">@color/magenta</color>
+    <color name="theme_color_background">@color/maroon</color>
+    <color name="navigation_bar_color">@color/navy</color>
+    <color name="status_bar_color">@color/olive</color>
+
+    <color name="cyan">#ff00ffff</color>
+    <color name="magenta">#ffff00ff</color>
+    <color name="maroon">#ff800000</color>
+    <color name="navy">#ff000080</color>
+    <color name="olive">#ff808000</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/styles.xml
new file mode 100644
index 0000000..2dc1a3e
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/styles.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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 xmlns:android="http://schemas.android.com/apk/res/android"
+           xmlns:base="http://schemas.android.com/apk/res/com.android.cts.isolatedsplitapp">
+    <style name="Theme_Feature_C" parent="@base:style/Theme_Base">
+        <item name="base:themeName">@string/theme_name</item>
+        <item name="base:themeBaseColor">@color/theme_base_color</item>
+        <item name="themeTertiaryColor">@color/theme_tertiary_color</item>
+        <item name="android:colorBackground">@color/theme_color_background</item>
+        <item name="android:windowBackground">@drawable/feature_c_color_drawable</item>
+        <item name="android:navigationBarColor">@color/navigation_bar_color</item>
+        <item name="android:statusBarColor">@color/status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/values.xml
index 09f48ad..248e53b 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/res/values/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="feature_c_string">Feature C String Default</string>
+    <string name="theme_name">Feature C Theme</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/src/com/android/cts/isolatedsplitapp/feature_c/FeatureCActivity.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/src/com/android/cts/isolatedsplitapp/feature_c/FeatureCActivity.java
index dab09a8..e97fd89 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/src/com/android/cts/isolatedsplitapp/feature_c/FeatureCActivity.java
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/feature_c/src/com/android/cts/isolatedsplitapp/feature_c/FeatureCActivity.java
@@ -15,8 +15,18 @@
  */
 package com.android.cts.isolatedsplitapp.feature_c;
 
+import android.os.Bundle;
+import android.widget.LinearLayout;
+
 import com.android.cts.isolatedsplitapp.BaseActivity;
 
 public class FeatureCActivity extends BaseActivity {
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
 
+        final LinearLayout linearLayout = findViewById(
+                com.android.cts.isolatedsplitapp.R.id.base_layout);
+        getLayoutInflater().inflate(R.layout.feature_c_textview, linearLayout);
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/drawable/base_color_drawable.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/drawable/base_color_drawable.xml
new file mode 100644
index 0000000..b9382ae
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/drawable/base_color_drawable.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+/*
+ * Copyright (C) 2020 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.
+ */
+-->
+<color xmlns:android="http://schemas.android.com/apk/res/android"
+       android:color="?attr/themeBaseColor"/>
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/layout/base_linearlayout.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/layout/base_linearlayout.xml
new file mode 100644
index 0000000..191266a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/layout/base_linearlayout.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2020 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
+
+      https://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.
+  -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:id="@+id/base_layout"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="wrap_content"
+              android:background="?attr/themeBaseColor">
+</LinearLayout>
+
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values-pl/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values-pl/colors.xml
new file mode 100644
index 0000000..e2a6512
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values-pl/colors.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <color name="theme_base_color">@color/green</color>
+    <color name="navigation_bar_color">@color/black</color>
+    <color name="status_bar_color">@color/red</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values-pl/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values-pl/values.xml
index a2b389d..457709d 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values-pl/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values-pl/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="base_string">Base String Polish</string>
+    <string name="theme_name">Base Theme Polish</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/attrs.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/attrs.xml
new file mode 100644
index 0000000..d657844
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/attrs.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <attr name="themeName" format="string"/>
+    <attr name="themeBaseColor" format="color|reference"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/colors.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/colors.xml
new file mode 100644
index 0000000..3e19b55
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/colors.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <color name="theme_base_color">@color/black</color>
+    <color name="navigation_bar_color">@color/red</color>
+    <color name="status_bar_color">@color/green</color>
+
+    <color name="black">#ff000000</color>
+    <color name="red">#ffff0000</color>
+    <color name="green">#ff00ff00</color>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/public.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/public.xml
new file mode 100644
index 0000000..0b43b0a
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/public.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 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>
+    <public name="Theme_Base" type="style"/>
+    <public name="themeName" type="attr"/>
+    <public name="themeBaseColor" type="attr"/>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/styles.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/styles.xml
new file mode 100644
index 0000000..305edf5
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/styles.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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>
+    <style name="Theme_Base" parent="@android:style/Theme.Material">
+        <item name="themeName">@string/theme_name</item>
+        <item name="themeBaseColor">@color/theme_base_color</item>
+        <item name="android:windowBackground">@drawable/base_color_drawable</item>
+        <item name="android:navigationBarColor">@color/navigation_bar_color</item>
+        <item name="android:statusBarColor">@color/status_bar_color</item>
+    </style>
+</resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/values.xml b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/values.xml
index da33f0b..ec07804 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/values.xml
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/res/values/values.xml
@@ -16,4 +16,5 @@
 
 <resources>
     <string name="base_string">Base String Default</string>
+    <string name="theme_name">Base Theme</string>
 </resources>
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/BaseActivity.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/BaseActivity.java
index e0fafc6..421fab0 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/BaseActivity.java
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/BaseActivity.java
@@ -17,6 +17,28 @@
 package com.android.cts.isolatedsplitapp;
 
 import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Bundle;
 
 public class BaseActivity extends Activity {
+    private static Configuration sOverrideConfiguration;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.base_linearlayout);
+    }
+
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        super.attachBaseContext(newBase);
+        if (sOverrideConfiguration != null) {
+            applyOverrideConfiguration(sOverrideConfiguration);
+        }
+    }
+
+    public static void setOverrideConfiguration(Configuration overrideConfiguration) {
+        sOverrideConfiguration = overrideConfiguration;
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
index 300f978..abc6257 100644
--- a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/SplitAppTest.java
@@ -26,12 +26,17 @@
 import android.content.Intent;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
 import android.os.Bundle;
+import android.view.View;
+import android.widget.LinearLayout;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -47,6 +52,8 @@
 public class SplitAppTest {
     private static final String PACKAGE = "com.android.cts.isolatedsplitapp";
 
+    private static final ComponentName BASE_ACTIVITY =
+            ComponentName.createRelative(PACKAGE, ".BaseActivity");
     private static final ComponentName FEATURE_A_ACTIVITY =
             ComponentName.createRelative(PACKAGE, ".feature_a.FeatureAActivity");
     private static final ComponentName FEATURE_B_ACTIVITY =
@@ -60,84 +67,123 @@
             "com.android.cts.isolatedsplitapp.feature_b:string/feature_b_string";
     private static final String FEATURE_C_STRING =
             "com.android.cts.isolatedsplitapp.feature_c:string/feature_c_string";
+    private static final String FEATURE_A_TEXTVIEW_ID =
+            "com.android.cts.isolatedsplitapp.feature_a:id/feature_a_text";
+    private static final String FEATURE_B_TEXTVIEW_ID =
+            "com.android.cts.isolatedsplitapp.feature_b:id/feature_b_text";
+    private static final String FEATURE_C_TEXTVIEW_ID =
+            "com.android.cts.isolatedsplitapp.feature_c:id/feature_c_text";
 
     private static final Configuration PL = new Configuration();
     static {
         PL.setLocale(Locale.forLanguageTag("pl"));
     }
 
-    @Rule
-    public ActivityTestRule<BaseActivity> mBaseActivityRule =
-            new ActivityTestRule<>(BaseActivity.class);
-
-    // Do not launch this activity lazily. We use this rule to launch all feature Activities,
+    // Do not launch this activity lazily. We use this rule to launch all Activities,
     // so we use #launchActivity() with the correct Intent.
     @Rule
-    public ActivityTestRule<Activity> mFeatureActivityRule =
+    public ActivityTestRule<Activity> mActivityRule =
             new ActivityTestRule<>(Activity.class, true /*initialTouchMode*/,
                     false /*launchActivity*/);
 
     @Rule
     public AppContextTestRule mAppContextTestRule = new AppContextTestRule();
 
+    @Before
+    public void setUp() {
+        BaseActivity.setOverrideConfiguration(null);
+    }
+
     @Test
     public void shouldLoadDefault() throws Exception {
-        final Context context = mBaseActivityRule.getActivity();
-        final Resources resources = context.getResources();
+        final Activity activity = mActivityRule.launchActivity(
+                new Intent().setComponent(BASE_ACTIVITY));
+        final TestTheme testTheme = new TestTheme(activity, R.style.Theme_Base);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Default"));
+        testTheme.assertThemeBaseValues();
+
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
 
         // The base does not depend on any splits so no splits should be accessible.
-        assertActivitiesDoNotExist(context, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY,
+        assertActivitiesDoNotExist(activity, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY,
                 FEATURE_C_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_A_STRING, FEATURE_B_STRING, FEATURE_C_STRING);
+        assertResourcesDoNotExist(activity, FEATURE_A_STRING, FEATURE_B_STRING, FEATURE_C_STRING,
+                TestTheme.THEME_FEATURE_A, TestTheme.THEME_FEATURE_B, TestTheme.THEME_FEATURE_C);
     }
 
     @Test
     public void shouldLoadPolishLocale() throws Exception {
-        final Context context = mBaseActivityRule.getActivity().createConfigurationContext(PL);
-        final Resources resources = context.getResources();
+        BaseActivity.setOverrideConfiguration(PL);
+        final Activity activity = mActivityRule.launchActivity(
+                new Intent().setComponent(BASE_ACTIVITY));
+        final TestTheme testTheme = new TestTheme(activity, R.style.Theme_Base);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Polish"));
+        testTheme.assertThemeBaseValues_pl();
+
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
 
         // The base does not depend on any splits so no splits should be accessible.
-        assertActivitiesDoNotExist(context, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY,
+        assertActivitiesDoNotExist(activity, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY,
                 FEATURE_C_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_A_STRING, FEATURE_B_STRING, FEATURE_C_STRING);
+        assertResourcesDoNotExist(activity, FEATURE_A_STRING, FEATURE_B_STRING, FEATURE_C_STRING,
+                TestTheme.THEME_FEATURE_A, TestTheme.THEME_FEATURE_B, TestTheme.THEME_FEATURE_C);
     }
 
     @Test
     public void shouldLoadFeatureADefault() throws Exception {
-        final Context context = mFeatureActivityRule.launchActivity(
+        final Activity activity = mActivityRule.launchActivity(
                 new Intent().setComponent(FEATURE_A_ACTIVITY));
-        final Resources resources = context.getResources();
+        final TestTheme testTheme = new TestTheme(activity, TestTheme.THEME_FEATURE_A);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Default"));
+        new TestTheme(activity, R.style.Theme_Base).assertThemeBaseValues();
 
         int resourceId = resources.getIdentifier(FEATURE_A_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature A String Default"));
+        testTheme.assertThemeFeatureAValues();
 
-        assertActivitiesDoNotExist(context, FEATURE_B_ACTIVITY, FEATURE_C_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_B_STRING, FEATURE_C_STRING);
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
+        assertTextViewBGColor(activity, FEATURE_A_TEXTVIEW_ID, testTheme.mColorBackground);
+
+        assertActivitiesDoNotExist(activity, FEATURE_B_ACTIVITY, FEATURE_C_ACTIVITY);
+        assertResourcesDoNotExist(activity, FEATURE_B_STRING, FEATURE_C_STRING,
+                TestTheme.THEME_FEATURE_B, TestTheme.THEME_FEATURE_C);
     }
 
     @Test
     public void shouldLoadFeatureAPolishLocale() throws Exception {
-        final Context context = mFeatureActivityRule.launchActivity(
-                new Intent().setComponent(FEATURE_A_ACTIVITY)).createConfigurationContext(PL);
-        final Resources resources = context.getResources();
+        BaseActivity.setOverrideConfiguration(PL);
+        final Activity activity = mActivityRule.launchActivity(
+                new Intent().setComponent(FEATURE_A_ACTIVITY));
+        final TestTheme testTheme = new TestTheme(activity, TestTheme.THEME_FEATURE_A);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Polish"));
+        new TestTheme(activity, R.style.Theme_Base).assertThemeBaseValues_pl();
 
         int resourceId = resources.getIdentifier(FEATURE_A_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature A String Polish"));
+        testTheme.assertThemeFeatureAValues_pl();
 
-        assertActivitiesDoNotExist(context, FEATURE_B_ACTIVITY, FEATURE_C_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_B_STRING, FEATURE_C_STRING);
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
+        assertTextViewBGColor(activity, FEATURE_A_TEXTVIEW_ID, testTheme.mColorBackground);
+
+        assertActivitiesDoNotExist(activity, FEATURE_B_ACTIVITY, FEATURE_C_ACTIVITY);
+        assertResourcesDoNotExist(activity, FEATURE_B_STRING, FEATURE_C_STRING,
+                TestTheme.THEME_FEATURE_B, TestTheme.THEME_FEATURE_C);
     }
 
     @Test
@@ -154,40 +200,57 @@
     @Test
     public void shouldLoadFeatureBDefault() throws Exception {
         // Feature B depends on A, so we expect both to be available.
-        final Context context = mFeatureActivityRule.launchActivity(
+        final Activity activity = mActivityRule.launchActivity(
                 new Intent().setComponent(FEATURE_B_ACTIVITY));
-        final Resources resources = context.getResources();
+        final TestTheme testTheme = new TestTheme(activity, TestTheme.THEME_FEATURE_B);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Default"));
+        new TestTheme(activity, R.style.Theme_Base).assertThemeBaseValues();
 
         int resourceId = resources.getIdentifier(FEATURE_A_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature A String Default"));
+        new TestTheme(activity, TestTheme.THEME_FEATURE_A).assertThemeFeatureAValues();
 
         resourceId = resources.getIdentifier(FEATURE_B_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature B String Default"));
+        testTheme.assertThemeFeatureBValues();
 
-        assertActivitiesDoNotExist(context, FEATURE_C_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_C_STRING);
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
+        assertTextViewBGColor(activity, FEATURE_B_TEXTVIEW_ID, testTheme.mColorBackground);
+
+        assertActivitiesDoNotExist(activity, FEATURE_C_ACTIVITY);
+        assertResourcesDoNotExist(activity, FEATURE_C_STRING, TestTheme.THEME_FEATURE_C);
     }
 
     @Test
     public void shouldLoadFeatureBPolishLocale() throws Exception {
-        final Context context = mFeatureActivityRule.launchActivity(
-                new Intent().setComponent(FEATURE_B_ACTIVITY)).createConfigurationContext(PL);
-        final Resources resources = context.getResources();
+        BaseActivity.setOverrideConfiguration(PL);
+        final Activity activity = mActivityRule.launchActivity(
+                new Intent().setComponent(FEATURE_B_ACTIVITY));
+        final TestTheme testTheme = new TestTheme(activity, TestTheme.THEME_FEATURE_B);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Polish"));
+        new TestTheme(activity, R.style.Theme_Base).assertThemeBaseValues_pl();
 
         int resourceId = resources.getIdentifier(FEATURE_A_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature A String Polish"));
+        new TestTheme(activity, TestTheme.THEME_FEATURE_A).assertThemeFeatureAValues_pl();
 
         resourceId = resources.getIdentifier(FEATURE_B_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature B String Polish"));
+        testTheme.assertThemeFeatureBValues_pl();
 
-        assertActivitiesDoNotExist(context, FEATURE_C_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_C_STRING);
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
+        assertTextViewBGColor(activity, FEATURE_B_TEXTVIEW_ID, testTheme.mColorBackground);
+
+        assertActivitiesDoNotExist(activity, FEATURE_C_ACTIVITY);
+        assertResourcesDoNotExist(activity, FEATURE_C_STRING, TestTheme.THEME_FEATURE_C);
     }
 
     @Test
@@ -203,34 +266,75 @@
 
     @Test
     public void shouldLoadFeatureCDefault() throws Exception {
-        final Context context = mFeatureActivityRule.launchActivity(
+        final Activity activity = mActivityRule.launchActivity(
                 new Intent().setComponent(FEATURE_C_ACTIVITY));
-        final Resources resources = context.getResources();
+        final TestTheme testTheme = new TestTheme(activity, TestTheme.THEME_FEATURE_C);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Default"));
+        new TestTheme(activity, R.style.Theme_Base).assertThemeBaseValues();
 
         int resourceId = resources.getIdentifier(FEATURE_C_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature C String Default"));
+        testTheme.assertThemeFeatureCValues();
 
-        assertActivitiesDoNotExist(context, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_A_STRING, FEATURE_B_STRING);
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
+        assertTextViewBGColor(activity, FEATURE_C_TEXTVIEW_ID, testTheme.mColorBackground);
+
+        assertActivitiesDoNotExist(activity, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY);
+        assertResourcesDoNotExist(activity, FEATURE_A_STRING, FEATURE_B_STRING,
+                TestTheme.THEME_FEATURE_A, TestTheme.THEME_FEATURE_B);
     }
 
     @Test
     public void shouldLoadFeatureCPolishLocale() throws Exception {
-        final Context context = mFeatureActivityRule.launchActivity(
-                new Intent().setComponent(FEATURE_C_ACTIVITY)).createConfigurationContext(PL);
-        final Resources resources = context.getResources();
+        BaseActivity.setOverrideConfiguration(PL);
+        final Activity activity = mActivityRule.launchActivity(
+                new Intent().setComponent(FEATURE_C_ACTIVITY));
+        final TestTheme testTheme = new TestTheme(activity, TestTheme.THEME_FEATURE_C);
+        final Resources resources = activity.getResources();
         assertThat(resources, notNullValue());
 
         assertThat(resources.getString(R.string.base_string), equalTo("Base String Polish"));
+        new TestTheme(activity, R.style.Theme_Base).assertThemeBaseValues_pl();
 
         int resourceId = resources.getIdentifier(FEATURE_C_STRING, null, null);
         assertThat(resources.getString(resourceId), equalTo("Feature C String Polish"));
+        testTheme.assertThemeFeatureCValues_pl();
 
-        assertActivitiesDoNotExist(context, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY);
-        assertResourcesDoNotExist(context, FEATURE_A_STRING, FEATURE_B_STRING);
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
+        assertTextViewBGColor(activity, FEATURE_C_TEXTVIEW_ID, testTheme.mColorBackground);
+
+        assertActivitiesDoNotExist(activity, FEATURE_A_ACTIVITY, FEATURE_B_ACTIVITY);
+        assertResourcesDoNotExist(activity, FEATURE_A_STRING, FEATURE_B_STRING,
+                TestTheme.THEME_FEATURE_A, TestTheme.THEME_FEATURE_B);
+    }
+
+    @Test
+    public void shouldLoadFeatureADiffRevision() throws Exception {
+        final Activity activity = mActivityRule.launchActivity(
+                new Intent().setComponent(FEATURE_A_ACTIVITY));
+        final TestTheme testTheme = new TestTheme(activity, TestTheme.THEME_FEATURE_A);
+        final Resources resources = activity.getResources();
+        assertThat(resources, notNullValue());
+
+        assertThat(resources.getString(R.string.base_string), equalTo("Base String Default"));
+        new TestTheme(activity, R.style.Theme_Base).assertThemeBaseValues();
+
+        int resourceId = resources.getIdentifier(FEATURE_A_STRING, null, null);
+        assertThat(resources.getString(resourceId), equalTo("Feature A String Diff Revision"));
+        testTheme.assertThemeFeatureAValuesDiffRev();
+
+        // Test the theme applied to the activity correctly
+        assertActivityThemeApplied(activity, testTheme);
+        assertTextViewBGColor(activity, FEATURE_A_TEXTVIEW_ID, testTheme.mColorBackground);
+
+        assertActivitiesDoNotExist(activity, FEATURE_B_ACTIVITY, FEATURE_C_ACTIVITY);
+        assertResourcesDoNotExist(activity, FEATURE_B_STRING, FEATURE_C_STRING,
+                TestTheme.THEME_FEATURE_B, TestTheme.THEME_FEATURE_C);
     }
 
     @Test
@@ -244,6 +348,20 @@
         assertThat(results.getString("feature_c"), equalTo("Feature C String Default"));
     }
 
+    @Test
+    public void shouldNotFoundFeatureC() throws Exception {
+        assertActivityDoNotExist(FEATURE_C_ACTIVITY);
+    }
+
+    private void assertActivityDoNotExist(ComponentName activity) {
+        try {
+            mActivityRule.launchActivity(new Intent().setComponent(activity));
+            fail("Activity " + activity + " is accessible");
+        } catch (RuntimeException e) {
+            // Pass.
+        }
+    }
+
     private static void assertActivitiesDoNotExist(Context context, ComponentName... activities) {
         for (ComponentName activity : activities) {
             try {
@@ -265,6 +383,41 @@
         }
     }
 
+    private static void assertActivityThemeApplied(Activity activity, TestTheme testTheme) {
+        assertBaseLayoutBGColor(activity, testTheme.mBaseColor);
+        assertThat(activity.getWindow().getStatusBarColor(), equalTo(testTheme.mStatusBarColor));
+        assertThat(activity.getWindow().getNavigationBarColor(),
+                equalTo(testTheme.mNavigationBarColor));
+        assertDrawableColor(activity.getWindow().getDecorView().getBackground(),
+                testTheme.mWindowBackground);
+    }
+
+    private static void assertBaseLayoutBGColor(Activity activity, int expected) {
+        final LinearLayout layout = activity.findViewById(R.id.base_layout);
+        final Drawable background = layout.getBackground();
+        assertDrawableColor(background, expected);
+    }
+
+    private static void assertTextViewBGColor(Activity activity, String nameOfIdentifier,
+            int expected) {
+        final int viewId = activity.getResources().getIdentifier(nameOfIdentifier, null, null);
+        assertThat(viewId, not(equalTo(0)));
+
+        final View view = activity.findViewById(viewId);
+        final Drawable background = view.getBackground();
+        assertDrawableColor(background, expected);
+    }
+
+    private static void assertDrawableColor(Drawable drawable, int expected) {
+        int color = 0;
+        if (drawable instanceof ColorDrawable) {
+            color = ((ColorDrawable) drawable).getColor();
+        } else {
+            fail("Can't get drawable color");
+        }
+        assertThat(color, equalTo(expected));
+    }
+
     private static class ExtrasResultReceiver extends BroadcastReceiver {
         private final CompletableFuture<Bundle> mResult = new CompletableFuture<>();
 
diff --git a/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/TestTheme.java b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/TestTheme.java
new file mode 100644
index 0000000..5909a26
--- /dev/null
+++ b/hostsidetests/appsecurity/test-apps/IsolatedSplitApp/src/com/android/cts/isolatedsplitapp/TestTheme.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2020 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.isolatedsplitapp;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.view.ContextThemeWrapper;
+
+/**
+ * A helper class to retrieve theme values of Theme_Base, Theme_Feature_A, Theme_Feature_B or
+ * Theme_Feature_C.
+ */
+public class TestTheme {
+
+    public static final String THEME_FEATURE_A =
+            "com.android.cts.isolatedsplitapp.feature_a:style/Theme_Feature_A";
+    public static final String THEME_FEATURE_B =
+            "com.android.cts.isolatedsplitapp.feature_b:style/Theme_Feature_B";
+    public static final String THEME_FEATURE_C =
+            "com.android.cts.isolatedsplitapp.feature_c:style/Theme_Feature_C";
+
+    public static final int COLOR_BLACK = 0xFF000000;
+    public static final int COLOR_RED = 0xFFFF0000;
+    public static final int COLOR_GREEN = 0xFF00FF00;
+    public static final int COLOR_BLUE = 0xFF0000FF;
+    public static final int COLOR_GRAY = 0xFF888888;
+    public static final int COLOR_LTGRAY = 0xFF444444;
+    public static final int COLOR_DKGRAY = 0xFFCCCCCC;
+    public static final int COLOR_YELLOW = 0xFFFFFF00;
+    public static final int COLOR_PURPLE = 0xFF800080;
+    public static final int COLOR_PINK = 0xFFFFC0CB;
+    public static final int COLOR_ORANGE = 0xFFFFA500;
+    public static final int COLOR_LINEN = 0xFFFAF0E6;
+    public static final int COLOR_MINTCREAM = 0xFFF5FFFA;
+    public static final int COLOR_CYAN = 0xFF00FFFF;
+    public static final int COLOR_MAGENTA = 0xFFFF00FF;
+    public static final int COLOR_MAROON = 0xFF800000;
+    public static final int COLOR_NAVY = 0xFF000080;
+    public static final int COLOR_OLIVE = 0xFF808000;
+
+    private static final String ATTR_THEME_PRIMARY_COLOR =
+            "com.android.cts.isolatedsplitapp.feature_a:attr/themePrimaryColor";
+    private static final String ATTR_THEME_SECONDARY_COLOR =
+            "com.android.cts.isolatedsplitapp.feature_b:attr/themeSecondaryColor";
+    private static final String ATTR_THEME_TERTIARY_COLOR =
+            "com.android.cts.isolatedsplitapp.feature_c:attr/themeTertiaryColor";
+
+    /** {@link R.attr.themeName} */
+    public String mName;
+
+    /** {@link R.attr.themeBaseColor} */
+    public int mBaseColor;
+
+    /** {@link #ATTR_THEME_PRIMARY_COLOR} */
+    public int mPrimaryColor;
+
+    /** {@link #ATTR_THEME_SECONDARY_COLOR} */
+    public int mSecondaryColor;
+
+    /** {@link #ATTR_THEME_TERTIARY_COLOR} */
+    public int mTertiaryColor;
+
+    /** {#link android.R.attr.colorBackground} */
+    public int mColorBackground;
+
+    /** {#link android.R.attr.navigationBarColor} */
+    public int mNavigationBarColor;
+
+    /** {#link android.R.attr.statusBarColor} */
+    public int mStatusBarColor;
+
+    /** {#link android.R.attr.windowBackground} */
+    public int mWindowBackground;
+
+    public TestTheme(Context context, String nameOfIdentifier) {
+        setTheme(context, nameOfIdentifier);
+    }
+
+    public TestTheme(Context context, int themeId) {
+        setTheme(context, themeId);
+    }
+
+    public void assertThemeBaseValues() {
+        assertThat(mName).isEqualTo("Base Theme");
+        assertThat(mBaseColor).isEqualTo(COLOR_BLACK);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_RED);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_GREEN);
+        assertThat(mWindowBackground).isEqualTo(mBaseColor);
+    }
+
+    public void assertThemeBaseValues_pl() {
+        assertThat(mName).isEqualTo("Base Theme Polish");
+        assertThat(mBaseColor).isEqualTo(COLOR_GREEN);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_BLACK);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_RED);
+        assertThat(mWindowBackground).isEqualTo(mBaseColor);
+    }
+
+    public void assertThemeFeatureAValues() {
+        assertThat(mName).isEqualTo("Feature A Theme");
+        assertThat(mBaseColor).isEqualTo(COLOR_BLUE);
+        assertThat(mPrimaryColor).isEqualTo(COLOR_GRAY);
+        assertThat(mColorBackground).isEqualTo(COLOR_LTGRAY);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_DKGRAY);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_YELLOW);
+        assertThat(mWindowBackground).isEqualTo(mPrimaryColor);
+    }
+
+    public void assertThemeFeatureAValues_pl() {
+        assertThat(mName).isEqualTo("Feature A Theme Polish");
+        assertThat(mBaseColor).isEqualTo(COLOR_YELLOW);
+        assertThat(mPrimaryColor).isEqualTo(COLOR_DKGRAY);
+        assertThat(mColorBackground).isEqualTo(COLOR_GRAY);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_LTGRAY);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_BLUE);
+        assertThat(mWindowBackground).isEqualTo(mPrimaryColor);
+    }
+
+    public void assertThemeFeatureAValuesDiffRev() {
+        assertThat(mName).isEqualTo("Feature A Theme Diff Revision");
+        assertThat(mBaseColor).isEqualTo(COLOR_YELLOW);
+        assertThat(mPrimaryColor).isEqualTo(COLOR_BLUE);
+        assertThat(mColorBackground).isEqualTo(COLOR_GRAY);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_LTGRAY);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_DKGRAY);
+        assertThat(mWindowBackground).isEqualTo(mPrimaryColor);
+    }
+
+    public void assertThemeFeatureBValues() {
+        assertThat(mName).isEqualTo("Feature B Theme");
+        assertThat(mBaseColor).isEqualTo(COLOR_PURPLE);
+        assertThat(mSecondaryColor).isEqualTo(COLOR_PINK);
+        assertThat(mColorBackground).isEqualTo(COLOR_ORANGE);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_LINEN);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_MINTCREAM);
+        assertThat(mWindowBackground).isEqualTo(mSecondaryColor);
+    }
+
+    public void assertThemeFeatureBValues_pl() {
+        assertThat(mName).isEqualTo("Feature B Theme Polish");
+        assertThat(mBaseColor).isEqualTo(COLOR_MINTCREAM);
+        assertThat(mSecondaryColor).isEqualTo(COLOR_LINEN);
+        assertThat(mColorBackground).isEqualTo(COLOR_PINK);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_ORANGE);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_PURPLE);
+        assertThat(mWindowBackground).isEqualTo(mSecondaryColor);
+    }
+
+    public void assertThemeFeatureCValues() {
+        assertThat(mName).isEqualTo("Feature C Theme");
+        assertThat(mBaseColor).isEqualTo(COLOR_CYAN);
+        assertThat(mTertiaryColor).isEqualTo(COLOR_MAGENTA);
+        assertThat(mColorBackground).isEqualTo(COLOR_MAROON);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_NAVY);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_OLIVE);
+        assertThat(mWindowBackground).isEqualTo(mTertiaryColor);
+    }
+
+    public void assertThemeFeatureCValues_pl() {
+        assertThat(mName).isEqualTo("Feature C Theme Polish");
+        assertThat(mBaseColor).isEqualTo(COLOR_OLIVE);
+        assertThat(mTertiaryColor).isEqualTo(COLOR_NAVY);
+        assertThat(mColorBackground).isEqualTo(COLOR_MAGENTA);
+        assertThat(mNavigationBarColor).isEqualTo(COLOR_MAROON);
+        assertThat(mStatusBarColor).isEqualTo(COLOR_CYAN);
+        assertThat(mWindowBackground).isEqualTo(mTertiaryColor);
+    }
+
+    private void setTheme(Context context, String nameOfIdentifier) {
+        final int themeId = resolveResourceId(context , nameOfIdentifier);
+        if (themeId == 0) {
+            throw new IllegalArgumentException("Failed to a resource identifier for the "
+                    + nameOfIdentifier);
+        }
+        setTheme(context, themeId);
+    }
+
+    private void setTheme(Context context, int themeId) {
+        final Resources.Theme theme = new ContextThemeWrapper(context, themeId).getTheme();
+        mName = getString(theme, R.attr.themeName);
+        mBaseColor = getColor(theme, R.attr.themeBaseColor);
+        mPrimaryColor = getColor(theme, resolveResourceId(context, ATTR_THEME_PRIMARY_COLOR));
+        mSecondaryColor = getColor(theme, resolveResourceId(context, ATTR_THEME_SECONDARY_COLOR));
+        mTertiaryColor = getColor(theme, resolveResourceId(context, ATTR_THEME_TERTIARY_COLOR));
+        mColorBackground = getColor(theme, android.R.attr.colorBackground);
+        mNavigationBarColor = getColor(theme, android.R.attr.navigationBarColor);
+        mStatusBarColor = getColor(theme, android.R.attr.statusBarColor);
+        mWindowBackground = getDrawableColor(theme, android.R.attr.windowBackground);
+    }
+
+    private int resolveResourceId(Context context, String nameOfIdentifier) {
+        return context.getResources().getIdentifier(nameOfIdentifier, null, null);
+    }
+
+    private String getString(Resources.Theme theme, int resourceId) {
+        final TypedArray ta = theme.obtainStyledAttributes(new int[] {resourceId});
+        final String string = ta.getString(0);
+        ta.recycle();
+        return string;
+    }
+
+    private int getColor(Resources.Theme theme, int resourceId) {
+        if (resourceId == 0) {
+            return 0;
+        }
+        final TypedArray ta = theme.obtainStyledAttributes(new int[] {resourceId});
+        final int color = ta.getColor(0, 0);
+        ta.recycle();
+        return color;
+    }
+
+    private int getDrawableColor(Resources.Theme theme, int resourceId) {
+        final TypedArray ta = theme.obtainStyledAttributes(new int[] {resourceId});
+        final Drawable color = ta.getDrawable(0);
+        ta.recycle();
+        if (!(color instanceof ColorDrawable)) {
+            return 0;
+        }
+        return ((ColorDrawable) color).getColor();
+    }
+}
diff --git a/hostsidetests/install/app/AndroidManifest.xml b/hostsidetests/install/app/AndroidManifest.xml
index 01130f2..449d24f 100644
--- a/hostsidetests/install/app/AndroidManifest.xml
+++ b/hostsidetests/install/app/AndroidManifest.xml
@@ -18,8 +18,6 @@
      package="android.cts.install">
     <application>
         <uses-library android:name="android.test.runner"/>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-             android:exported="true"/>
         <!-- This activity is necessary to register the test app as the default home activity (i.e.
                          to receive SESSION_COMMITTED broadcasts.) -->
         <activity android:name=".LauncherActivity"
diff --git a/hostsidetests/os/app/src/android/os/app/TestFgService.java b/hostsidetests/os/app/src/android/os/app/TestFgService.java
index 3548105..e751d47 100644
--- a/hostsidetests/os/app/src/android/os/app/TestFgService.java
+++ b/hostsidetests/os/app/src/android/os/app/TestFgService.java
@@ -42,6 +42,7 @@
                 .setContentTitle("Foreground service")
                 .setContentText("Ongoing test app foreground service is live")
                 .setSmallIcon(NOTIFICATION_ID)
+                .setShowForegroundImmediately(true)
                 .build();
 
         Log.i(TAG, "TestFgService starting foreground: pid=" + Process.myPid());
diff --git a/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml b/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml
index 361d21a..51589ea 100755
--- a/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml
+++ b/hostsidetests/os/test-apps/StaticSharedLibTestApp/AndroidManifest.xml
@@ -19,8 +19,6 @@
     package="android.os.lib.app">
 
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/hostsidetests/rollback/app/AndroidManifest.xml b/hostsidetests/rollback/app/AndroidManifest.xml
index 71cf5c1..76d239d 100644
--- a/hostsidetests/rollback/app/AndroidManifest.xml
+++ b/hostsidetests/rollback/app/AndroidManifest.xml
@@ -23,8 +23,6 @@
     </queries>
 
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/hostsidetests/rollback/app2/AndroidManifest.xml b/hostsidetests/rollback/app2/AndroidManifest.xml
index be6d483..7e90f85 100644
--- a/hostsidetests/rollback/app2/AndroidManifest.xml
+++ b/hostsidetests/rollback/app2/AndroidManifest.xml
@@ -18,8 +18,6 @@
           package="com.android.cts.rollback.host.app2" >
 
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/hostsidetests/scopedstorage/AndroidManifest.xml b/hostsidetests/scopedstorage/AndroidManifest.xml
index 0394f4b..da158c3 100644
--- a/hostsidetests/scopedstorage/AndroidManifest.xml
+++ b/hostsidetests/scopedstorage/AndroidManifest.xml
@@ -21,8 +21,6 @@
     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/hostsidetests/scopedstorage/device/AndroidManifest.xml b/hostsidetests/scopedstorage/device/AndroidManifest.xml
index 0e91f9a..6f716fb 100644
--- a/hostsidetests/scopedstorage/device/AndroidManifest.xml
+++ b/hostsidetests/scopedstorage/device/AndroidManifest.xml
@@ -18,8 +18,6 @@
           package="android.scopedstorage.cts.device">
 <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 <application android:label="Scoped Storage Device Tests">
-    <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-              android:exported="true" />
     <uses-library android:name="android.test.runner" />
 </application>
 
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 faf334b..f88bfe9 100644
--- a/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
+++ b/hostsidetests/scopedstorage/device/src/android/scopedstorage/cts/device/ScopedStorageDeviceTest.java
@@ -69,6 +69,7 @@
 import static android.scopedstorage.cts.lib.TestUtils.grantPermission;
 import static android.scopedstorage.cts.lib.TestUtils.installApp;
 import static android.scopedstorage.cts.lib.TestUtils.installAppWithStoragePermissions;
+import static android.scopedstorage.cts.lib.TestUtils.isAppInstalled;
 import static android.scopedstorage.cts.lib.TestUtils.listAs;
 import static android.scopedstorage.cts.lib.TestUtils.openWithMediaProvider;
 import static android.scopedstorage.cts.lib.TestUtils.pollForExternalStorageState;
@@ -132,6 +133,7 @@
 import com.google.common.io.Files;
 
 import org.junit.After;
+import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -179,13 +181,17 @@
 
     static final String FILE_CREATION_ERROR_MESSAGE = "No such file or directory";
 
-    private static final TestApp TEST_APP_A = new TestApp("TestAppA",
+    // This app is installed with READ_EXTERNAL_STORAGE permission before tests are run
+    private static final TestApp APP_A_HAS_RES = new TestApp("TestAppA",
             "android.scopedstorage.cts.testapp.A", 1, false, "CtsScopedStorageTestAppA.apk");
-    private static final TestApp TEST_APP_B = new TestApp("TestAppB",
+    // This app is installed without any permissions before tests are run
+    private static final TestApp APP_B_NO_PERMS = new TestApp("TestAppB",
             "android.scopedstorage.cts.testapp.B", 1, false, "CtsScopedStorageTestAppB.apk");
-    private static final TestApp TEST_APP_C = new TestApp("TestAppC",
+    // This app is not installed at test startup - please install before using.
+    private static final TestApp APP_C = new TestApp("TestAppC",
             "android.scopedstorage.cts.testapp.C", 1, false, "CtsScopedStorageTestAppC.apk");
-    private static final TestApp TEST_APP_C_LEGACY = new TestApp("TestAppCLegacy",
+    // This app is not installed at test startup - please install before using.
+    private static final TestApp APP_C_LEGACY = new TestApp("TestAppCLegacy",
             "android.scopedstorage.cts.testapp.C", 1, false, "CtsScopedStorageTestAppCLegacy.apk");
     private static final String[] SYSTEM_GALERY_APPOPS = {
             AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO};
@@ -208,6 +214,12 @@
         }
     }
 
+    @BeforeClass
+    public static void installApps() throws Exception {
+        installAppWithStoragePermissions(APP_A_HAS_RES);
+        installApp(APP_B_NO_PERMS);
+    }
+
     @Before
     public void setup() throws Exception {
         // skips all test cases if FUSE is not active.
@@ -224,6 +236,12 @@
         executeShellCommand("rm -r /sdcard/Android/data/com.android.shell");
     }
 
+    @AfterClass
+    public static void uninstallApps() {
+        uninstallAppNoThrow(APP_B_NO_PERMS);
+        uninstallAppNoThrow(APP_A_HAS_RES);
+    }
+
     @Before
     public void setupExternalStorage() {
         setupDefaultDirectories();
@@ -404,15 +422,12 @@
                 assertThat(videoFile.createNewFile()).isTrue();
             }
 
-            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
-            installAppWithStoragePermissions(TEST_APP_A);
-
-            // TEST_APP_A should not be able to read/write to other app's external files directory.
-            assertThat(canOpenFileAs(TEST_APP_A, videoFile, false /* forWrite */)).isFalse();
-            assertThat(canOpenFileAs(TEST_APP_A, videoFile, true /* forWrite */)).isFalse();
-            // TEST_APP_A should not be able to delete files in other app's external files
+            // App A should not be able to read/write to other app's external files directory.
+            assertThat(canOpenFileAs(APP_A_HAS_RES, videoFile, false /* forWrite */)).isFalse();
+            assertThat(canOpenFileAs(APP_A_HAS_RES, videoFile, true /* forWrite */)).isFalse();
+            // App A should not be able to delete files in other app's external files
             // directory.
-            assertThat(deleteFileAs(TEST_APP_A, videoFile.getPath())).isFalse();
+            assertThat(deleteFileAs(APP_A_HAS_RES, videoFile.getPath())).isFalse();
 
             // Apps should have read/write access in their own app's external files directory.
             assertThat(canOpen(videoFile, false /* forWrite */)).isTrue();
@@ -421,7 +436,6 @@
             assertThat(videoFile.delete()).isTrue();
         } finally {
             videoFile.delete();
-            uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
@@ -498,10 +512,8 @@
         final File mediaFile = new File(getPicturesDir(), IMAGE_FILE_NAME);
         final File nonMediaFile = new File(getDownloadDir(), NONMEDIA_FILE_NAME);
         try {
-            installApp(TEST_APP_A);
-
-            assertThat(createFileAs(TEST_APP_A, mediaFile.getPath())).isTrue();
-            assertThat(createFileAs(TEST_APP_A, nonMediaFile.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, mediaFile.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, nonMediaFile.getPath())).isTrue();
 
             // We can still see that the files exist
             assertThat(mediaFile.exists()).isTrue();
@@ -513,9 +525,8 @@
             assertThat(canOpen(mediaFile, /* forWrite */ false)).isFalse();
             assertThat(canOpen(nonMediaFile, /* forWrite */ true)).isFalse();
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, nonMediaFile.getPath());
-            deleteFileAsNoThrow(TEST_APP_A, mediaFile.getPath());
-            uninstallAppNoThrow(TEST_APP_A);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, nonMediaFile.getPath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, mediaFile.getPath());
         }
     }
 
@@ -525,10 +536,9 @@
         final File mediaFile = new File(dirInDownload, IMAGE_FILE_NAME);
         final File nonMediaFile = new File(dirInDownload, NONMEDIA_FILE_NAME);
         try {
-            installApp(TEST_APP_A);
             assertThat(dirInDownload.mkdir()).isTrue();
             // Have another app create a media file in the directory
-            assertThat(createFileAs(TEST_APP_A, mediaFile.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, mediaFile.getPath())).isTrue();
 
             // Can't delete the directory since it contains another app's content
             assertThat(dirInDownload.delete()).isFalse();
@@ -536,7 +546,7 @@
             assertThat(deleteRecursively(dirInDownload)).isFalse();
 
             // Have another app create a non-media file in the directory
-            assertThat(createFileAs(TEST_APP_A, nonMediaFile.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, nonMediaFile.getPath())).isTrue();
 
             // Can't delete the directory since it contains another app's content
             assertThat(dirInDownload.delete()).isFalse();
@@ -544,26 +554,25 @@
             assertThat(deleteRecursively(dirInDownload)).isFalse();
 
             // Delete only the media file and keep the non-media file
-            assertThat(deleteFileAs(TEST_APP_A, mediaFile.getPath())).isTrue();
+            assertThat(deleteFileAs(APP_B_NO_PERMS, mediaFile.getPath())).isTrue();
             // Directory now has only the non-media file contributed by another app, so we still
             // can't delete it nor its content
             assertThat(dirInDownload.delete()).isFalse();
             assertThat(deleteRecursively(dirInDownload)).isFalse();
 
             // Delete the last file belonging to another app
-            assertThat(deleteFileAs(TEST_APP_A, nonMediaFile.getPath())).isTrue();
+            assertThat(deleteFileAs(APP_B_NO_PERMS, nonMediaFile.getPath())).isTrue();
             // Create our own file
             assertThat(nonMediaFile.createNewFile()).isTrue();
 
             // Now that the directory only has content that was contributed by us, we can delete it
             assertThat(deleteRecursively(dirInDownload)).isTrue();
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, nonMediaFile.getPath());
-            deleteFileAsNoThrow(TEST_APP_A, mediaFile.getPath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, nonMediaFile.getPath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, mediaFile.getPath());
             // At this point, we're not sure who created this file, so we'll have both apps
             // deleting it
             mediaFile.delete();
-            uninstallAppNoThrow(TEST_APP_A);
             dirInDownload.delete();
         }
     }
@@ -685,32 +694,23 @@
                 assertThat(dir.mkdir()).isTrue();
             }
 
-            // Install TEST_APP_A and create media file in the new directory.
-            installApp(TEST_APP_A);
-            assertThat(createFileAs(TEST_APP_A, videoFile.getPath())).isTrue();
-            // TEST_APP_A should see TEST_DIRECTORY in DCIM and new file in TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_A, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME);
-            assertThat(listAs(TEST_APP_A, dir.getPath())).containsExactly(videoFileName);
+            assertThat(createFileAs(APP_B_NO_PERMS, videoFile.getPath())).isTrue();
+            // App B should see TEST_DIRECTORY in DCIM and new file in TEST_DIRECTORY.
+            assertThat(listAs(APP_B_NO_PERMS, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME);
+            assertThat(listAs(APP_B_NO_PERMS, dir.getPath())).containsExactly(videoFileName);
 
-            // Install TEST_APP_B with storage permission.
-            installAppWithStoragePermissions(TEST_APP_B);
-            // TEST_APP_B with storage permission should see TEST_DIRECTORY in DCIM and new file
+            // App A has storage permission, so should see TEST_DIRECTORY in DCIM and new file
             // in TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_B, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME);
-            assertThat(listAs(TEST_APP_B, dir.getPath())).containsExactly(videoFileName);
+            assertThat(listAs(APP_A_HAS_RES, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME);
+            assertThat(listAs(APP_A_HAS_RES, dir.getPath())).containsExactly(videoFileName);
 
-            // Revoke storage permission for TEST_APP_B
-            revokePermission(
-                    TEST_APP_B.getPackageName(), Manifest.permission.READ_EXTERNAL_STORAGE);
-            // TEST_APP_B without storage permission should see TEST_DIRECTORY in DCIM and should
-            // not see new file in new TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_B, dcimDir.getPath())).contains(TEST_DIRECTORY_NAME);
-            assertThat(listAs(TEST_APP_B, dir.getPath())).doesNotContain(videoFileName);
+            // We are an app without storage permission; should see TEST_DIRECTORY in DCIM and
+            // should not see new file in new TEST_DIRECTORY.
+            assertThat(dcimDir.list()).asList().contains(TEST_DIRECTORY_NAME);
+            assertThat(dir.list()).asList().doesNotContain(videoFileName);
         } finally {
-            uninstallAppNoThrow(TEST_APP_B);
-            deleteFileAsNoThrow(TEST_APP_A, videoFile.getPath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getPath());
             dir.delete();
-            uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
@@ -728,26 +728,21 @@
                 assertThat(dir.mkdir()).isTrue();
             }
 
-            // Install TEST_APP_A and create non media file in the new directory.
-            installApp(TEST_APP_A);
-            assertThat(createFileAs(TEST_APP_A, pdfFile.getPath())).isTrue();
+            // Have App B create non media file in the new directory.
+            assertThat(createFileAs(APP_B_NO_PERMS, pdfFile.getPath())).isTrue();
 
-            // TEST_APP_A should see TEST_DIRECTORY in downloadDir and new non media file in
+            // App B should see TEST_DIRECTORY in downloadDir and new non media file in
             // TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_A, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME);
-            assertThat(listAs(TEST_APP_A, dir.getPath())).containsExactly(pdfFileName);
+            assertThat(listAs(APP_B_NO_PERMS, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME);
+            assertThat(listAs(APP_B_NO_PERMS, dir.getPath())).containsExactly(pdfFileName);
 
-            // Install TEST_APP_B with storage permission.
-            installAppWithStoragePermissions(TEST_APP_B);
-            // TEST_APP_B with storage permission should see TEST_DIRECTORY in downloadDir
-            // and should not see new non media file in TEST_DIRECTORY.
-            assertThat(listAs(TEST_APP_B, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME);
-            assertThat(listAs(TEST_APP_B, dir.getPath())).doesNotContain(pdfFileName);
+            // APP B with storage permission should see TEST_DIRECTORY in downloadDir
+            // and should not see non media file in TEST_DIRECTORY.
+            assertThat(listAs(APP_A_HAS_RES, downloadDir.getPath())).contains(TEST_DIRECTORY_NAME);
+            assertThat(listAs(APP_A_HAS_RES, dir.getPath())).doesNotContain(pdfFileName);
         } finally {
-            uninstallAppNoThrow(TEST_APP_B);
-            deleteFileAsNoThrow(TEST_APP_A, pdfFile.getPath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, pdfFile.getPath());
             dir.delete();
-            uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
@@ -768,17 +763,13 @@
             // files and directories in its external directory.
             assertDirectoryContains(nonmediaFile.getParentFile(), nonmediaFile);
 
-            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
-            // TEST_APP_A should not see other app's external files directory.
-            installAppWithStoragePermissions(TEST_APP_A);
-
+            // App A should not see other app's external files directory despite RES.
             assertThrows(IOException.class,
-                    () -> listAs(TEST_APP_A, getAndroidDataDir().getPath()));
+                    () -> listAs(APP_A_HAS_RES, getAndroidDataDir().getPath()));
             assertThrows(IOException.class,
-                    () -> listAs(TEST_APP_A, getExternalFilesDir().getPath()));
+                    () -> listAs(APP_A_HAS_RES, getExternalFilesDir().getPath()));
         } finally {
             nonmediaFile.delete();
-            uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
@@ -799,18 +790,15 @@
             // files.
             assertDirectoryContains(videoFile.getParentFile(), videoFile);
 
-            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
-            // TEST_APP_A with storage permission should see other app's external media directory.
-            installAppWithStoragePermissions(TEST_APP_A);
+            // App A with storage permission should see other app's external media directory.
             // Apps with READ_EXTERNAL_STORAGE can list files in other app's external media
             // directory.
-            assertThat(listAs(TEST_APP_A, getAndroidMediaDir().getPath()))
+            assertThat(listAs(APP_A_HAS_RES, getAndroidMediaDir().getPath()))
                     .contains(THIS_PACKAGE_NAME);
-            assertThat(listAs(TEST_APP_A, getExternalMediaDir().getPath()))
+            assertThat(listAs(APP_A_HAS_RES, getExternalMediaDir().getPath()))
                     .containsExactly(videoFile.getName());
         } finally {
             videoFile.delete();
-            uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
@@ -834,19 +822,17 @@
                 HashMap<String, String> exif = getExifMetadata(jpgFile);
                 assertExifMetadataMatch(exif, originalExif);
 
-                installAppWithStoragePermissions(TEST_APP_A);
                 HashMap<String, String> exifFromTestApp =
-                        readExifMetadataFromTestApp(TEST_APP_A, jpgFile.getPath());
+                        readExifMetadataFromTestApp(APP_A_HAS_RES, jpgFile.getPath());
                 // Other apps shouldn't have access to the same metadata without explicit permission
                 assertExifMetadataMismatch(exifFromTestApp, originalExif);
 
-                // TODO(b/146346138): Test that if we give TEST_APP_A write URI permission,
+                // TODO(b/146346138): Test that if we give APP_A write URI permission,
                 //  it would be able to access the metadata.
             } // Intentionally keep the original streams open during the test so bytes are more
             // likely to be in the VFS cache from both file opens
         } finally {
             jpgFile.delete();
-            uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
@@ -1129,33 +1115,33 @@
 
     @Test
     public void testReadStorageInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C, new File(getDcimDir(), "read_storage.jpg"),
+        testAppOpInvalidation(APP_B_NO_PERMS, new File(getDcimDir(), "read_storage.jpg"),
                 Manifest.permission.READ_EXTERNAL_STORAGE,
                 AppOpsManager.OPSTR_READ_EXTERNAL_STORAGE, /* forWrite */ false);
     }
 
     @Test
     public void testWriteStorageInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C_LEGACY, new File(getDcimDir(), "write_storage.jpg"),
+        testAppOpInvalidation(APP_C_LEGACY, new File(getDcimDir(), "write_storage.jpg"),
                 Manifest.permission.WRITE_EXTERNAL_STORAGE,
                 AppOpsManager.OPSTR_WRITE_EXTERNAL_STORAGE, /* forWrite */ true);
     }
 
     @Test
     public void testManageStorageInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C, new File(getDownloadDir(), "manage_storage.pdf"),
+        testAppOpInvalidation(APP_C, new File(getDownloadDir(), "manage_storage.pdf"),
                 /* permission */ null, OPSTR_MANAGE_EXTERNAL_STORAGE, /* forWrite */ true);
     }
 
     @Test
     public void testWriteImagesInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C, new File(getDcimDir(), "write_images.jpg"),
+        testAppOpInvalidation(APP_B_NO_PERMS, new File(getDcimDir(), "write_images.jpg"),
                 /* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_IMAGES, /* forWrite */ true);
     }
 
     @Test
     public void testWriteVideoInvalidation() throws Exception {
-        testAppOpInvalidation(TEST_APP_C, new File(getDcimDir(), "write_video.mp4"),
+        testAppOpInvalidation(APP_B_NO_PERMS, new File(getDcimDir(), "write_video.mp4"),
                 /* permission */ null, AppOpsManager.OPSTR_WRITE_MEDIA_VIDEO, /* forWrite */ true);
     }
 
@@ -1177,27 +1163,27 @@
             assertExifMetadataMatch(exif, originalExif);
 
             // Install test app
-            installAppWithStoragePermissions(TEST_APP_C);
+            installAppWithStoragePermissions(APP_C);
 
             // Grant A_M_L and verify access to sensitive data
-            grantPermission(TEST_APP_C.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION);
+            grantPermission(APP_C.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION);
             HashMap<String, String> exifFromTestApp =
-                    readExifMetadataFromTestApp(TEST_APP_C, imgFile.getPath());
+                    readExifMetadataFromTestApp(APP_C, imgFile.getPath());
             assertExifMetadataMatch(exifFromTestApp, originalExif);
 
             // Revoke A_M_L and verify sensitive data redaction
             revokePermission(
-                    TEST_APP_C.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION);
-            exifFromTestApp = readExifMetadataFromTestApp(TEST_APP_C, imgFile.getPath());
+                    APP_C.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION);
+            exifFromTestApp = readExifMetadataFromTestApp(APP_C, imgFile.getPath());
             assertExifMetadataMismatch(exifFromTestApp, originalExif);
 
             // Re-grant A_M_L and verify access to sensitive data
-            grantPermission(TEST_APP_C.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION);
-            exifFromTestApp = readExifMetadataFromTestApp(TEST_APP_C, imgFile.getPath());
+            grantPermission(APP_C.getPackageName(), Manifest.permission.ACCESS_MEDIA_LOCATION);
+            exifFromTestApp = readExifMetadataFromTestApp(APP_C, imgFile.getPath());
             assertExifMetadataMatch(exifFromTestApp, originalExif);
         } finally {
             imgFile.delete();
-            uninstallAppNoThrow(TEST_APP_C);
+            uninstallAppNoThrow(APP_C);
         }
     }
 
@@ -1208,24 +1194,24 @@
             assertThat(file.createNewFile()).isTrue();
 
             // Install legacy
-            installAppWithStoragePermissions(TEST_APP_C_LEGACY);
-            grantPermission(TEST_APP_C_LEGACY.getPackageName(),
+            installAppWithStoragePermissions(APP_C_LEGACY);
+            grantPermission(APP_C_LEGACY.getPackageName(),
                     Manifest.permission.WRITE_EXTERNAL_STORAGE); // Grants write access for legacy
             // Legacy app can read and write media files contributed by others
-            assertThat(canOpenFileAs(TEST_APP_C_LEGACY, file, /* forWrite */ false)).isTrue();
-            assertThat(canOpenFileAs(TEST_APP_C_LEGACY, file, /* forWrite */ true)).isTrue();
+            assertThat(canOpenFileAs(APP_C_LEGACY, file, /* forWrite */ false)).isTrue();
+            assertThat(canOpenFileAs(APP_C_LEGACY, file, /* forWrite */ true)).isTrue();
 
             // Update to non-legacy
-            installAppWithStoragePermissions(TEST_APP_C);
-            grantPermission(TEST_APP_C_LEGACY.getPackageName(),
+            installAppWithStoragePermissions(APP_C);
+            grantPermission(APP_C_LEGACY.getPackageName(),
                     Manifest.permission.WRITE_EXTERNAL_STORAGE); // No effect for non-legacy
             // Non-legacy app can read media files contributed by others
-            assertThat(canOpenFileAs(TEST_APP_C, file, /* forWrite */ false)).isTrue();
+            assertThat(canOpenFileAs(APP_C, file, /* forWrite */ false)).isTrue();
             // But cannot write
-            assertThat(canOpenFileAs(TEST_APP_C, file, /* forWrite */ true)).isFalse();
+            assertThat(canOpenFileAs(APP_C, file, /* forWrite */ true)).isFalse();
         } finally {
             file.delete();
-            uninstallAppNoThrow(TEST_APP_C);
+            uninstallAppNoThrow(APP_C);
         }
     }
 
@@ -1237,28 +1223,35 @@
             assertThat(file.createNewFile()).isTrue();
 
             // Install
-            installAppWithStoragePermissions(TEST_APP_C);
-            assertThat(canOpenFileAs(TEST_APP_C, file, /* forWrite */ false)).isTrue();
+            installAppWithStoragePermissions(APP_C);
+            assertThat(canOpenFileAs(APP_C, file, /* forWrite */ false)).isTrue();
 
             // Re-install
-            uninstallAppNoThrow(TEST_APP_C);
-            installApp(TEST_APP_C);
-            assertThat(canOpenFileAs(TEST_APP_C, file, /* forWrite */ false)).isFalse();
+            uninstallAppNoThrow(APP_C);
+            installApp(APP_C);
+            assertThat(canOpenFileAs(APP_C, file, /* forWrite */ false)).isFalse();
         } finally {
             file.delete();
-            uninstallAppNoThrow(TEST_APP_C);
+            uninstallAppNoThrow(APP_C);
         }
     }
 
     private void testAppOpInvalidation(TestApp app, File file, @Nullable String permission,
             String opstr, boolean forWrite) throws Exception {
+        boolean alreadyInstalled = true;
         try {
-            installApp(app);
+            if (!isAppInstalled(app)) {
+                alreadyInstalled = false;
+                installApp(app);
+            }
             assertThat(file.createNewFile()).isTrue();
             assertAppOpInvalidation(app, file, permission, opstr, forWrite);
         } finally {
             file.delete();
-            uninstallApp(app);
+            if (!alreadyInstalled) {
+                // only uninstall if we installed this app here
+                uninstallApp(app);
+            }
         }
     }
 
@@ -1300,11 +1293,10 @@
         final File imageInAnObviouslyWrongPlace = new File(getMusicDir(), IMAGE_FILE_NAME);
 
         try {
-            installApp(TEST_APP_A);
             allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
 
             // Have another app create an image file
-            assertThat(createFileAs(TEST_APP_A, otherAppImageFile.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppImageFile.getPath())).isTrue();
             assertThat(otherAppImageFile.exists()).isTrue();
 
             // Assert we can write to the file
@@ -1323,12 +1315,11 @@
             assertCanCreateFile(topLevelImageFile);
             assertCanCreateFile(imageInAnObviouslyWrongPlace);
 
-            // Put the file back in its place and let TEST_APP_A delete it
+            // Put the file back in its place and let APP B delete it
             assertThat(otherAppImageFile.createNewFile()).isTrue();
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, otherAppImageFile.getAbsolutePath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppImageFile.getAbsolutePath());
             otherAppImageFile.delete();
-            uninstallApp(TEST_APP_A);
             denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
         }
     }
@@ -1340,11 +1331,10 @@
         final File audioInAnObviouslyWrongPlace = new File(getPicturesDir(), AUDIO_FILE_NAME);
 
         try {
-            installApp(TEST_APP_A);
             allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
 
             // Have another app create an audio file
-            assertThat(createFileAs(TEST_APP_A, otherAppAudioFile.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppAudioFile.getPath())).isTrue();
             assertThat(otherAppAudioFile.exists()).isTrue();
 
             // Assert we can't access the file
@@ -1364,8 +1354,7 @@
                         audioInAnObviouslyWrongPlace.createNewFile();
                     });
         } finally {
-            deleteFileAs(TEST_APP_A, otherAppAudioFile.getPath());
-            uninstallApp(TEST_APP_A);
+            deleteFileAs(APP_B_NO_PERMS, otherAppAudioFile.getPath());
             topLevelAudioFile.delete();
             audioInAnObviouslyWrongPlace.delete();
             denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
@@ -1380,11 +1369,10 @@
         final File topLevelVideoFile = new File(getExternalStorageDir(), VIDEO_FILE_NAME);
         final File musicFile = new File(getMusicDir(), AUDIO_FILE_NAME);
         try {
-            installApp(TEST_APP_A);
             allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
 
             // Have another app create a video file
-            assertThat(createFileAs(TEST_APP_A, otherAppVideoFile.getPath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, otherAppVideoFile.getPath())).isTrue();
             assertThat(otherAppVideoFile.exists()).isTrue();
 
             // Write some data to the file
@@ -1408,8 +1396,7 @@
             assertThat(imageFile.renameTo(musicFile)).isTrue();
             assertThat(getFileRowIdFromDatabase(musicFile)).isEqualTo(-1);
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, otherAppVideoFile.getAbsolutePath());
-            uninstallApp(TEST_APP_A);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, otherAppVideoFile.getAbsolutePath());
             imageFile.delete();
             videoFile.delete();
             topLevelVideoFile.delete();
@@ -1524,20 +1511,18 @@
         final File videoFile1 = new File(getDcimDir(), VIDEO_FILE_NAME);
         final File videoFile2 = new File(getMoviesDir(), VIDEO_FILE_NAME);
         try {
-            installApp(TEST_APP_A);
-            assertThat(createFileAs(TEST_APP_A, videoFile1.getAbsolutePath())).isTrue();
-            // App can't rename a file owned by TEST_APP_A.
+            assertThat(createFileAs(APP_B_NO_PERMS, videoFile1.getAbsolutePath())).isTrue();
+            // App can't rename a file owned by APP B.
             assertCantRenameFile(videoFile1, videoFile2);
 
             assertThat(videoFile2.createNewFile()).isTrue();
-            // App can't rename a file to videoFile1 which is owned by TEST_APP_A
+            // App can't rename a file to videoFile1 which is owned by APP B.
             assertCantRenameFile(videoFile2, videoFile1);
             // TODO(b/146346138): Test that app with right URI permission should be able to rename
             // the corresponding file
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, videoFile1.getAbsolutePath());
+            deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile1.getAbsolutePath());
             videoFile2.delete();
-            uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
@@ -1625,20 +1610,17 @@
         File videoFile = new File(mediaDirectory1, VIDEO_FILE_NAME);
 
         try {
-            installApp(TEST_APP_A);
-
             if (!mediaDirectory1.exists()) {
                 assertThat(mediaDirectory1.mkdirs()).isTrue();
             }
-            assertThat(createFileAs(TEST_APP_A, videoFile.getAbsolutePath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, videoFile.getAbsolutePath())).isTrue();
             // App doesn't have access to videoFile1, can't rename mediaDirectory1.
             assertThat(mediaDirectory1.renameTo(mediaDirectory2)).isFalse();
             assertThat(videoFile.exists()).isTrue();
             // Test app can delete the file since the file is not moved to new directory.
-            assertThat(deleteFileAs(TEST_APP_A, videoFile.getAbsolutePath())).isTrue();
+            assertThat(deleteFileAs(APP_B_NO_PERMS, videoFile.getAbsolutePath())).isTrue();
         } finally {
-            deleteFileAsNoThrow(TEST_APP_A, videoFile.getAbsolutePath());
-            uninstallAppNoThrow(TEST_APP_A);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, videoFile.getAbsolutePath());
             mediaDirectory1.delete();
         }
     }
@@ -1844,17 +1826,17 @@
 
             assertDirectoryContains(dcimDir, hiddenImageFile);
 
-            installApp(TEST_APP_A, true);
             // TestApp with read permissions can't see the hidden image file created by other app
-            assertThat(listAs(TEST_APP_A, dcimDir.getAbsolutePath()))
+            assertThat(listAs(APP_B_NO_PERMS, dcimDir.getAbsolutePath()))
                     .doesNotContain(hiddenImageFileName);
 
             final int testAppUid =
-                    getContext().getPackageManager().getPackageUid(TEST_APP_A.getPackageName(), 0);
+                    getContext().getPackageManager().getPackageUid(APP_B_NO_PERMS.getPackageName(),
+                            0);
             // FileManager can see the hidden image file created by other app
             try {
                 allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
-                assertThat(listAs(TEST_APP_A, dcimDir.getAbsolutePath()))
+                assertThat(listAs(APP_B_NO_PERMS, dcimDir.getAbsolutePath()))
                         .contains(hiddenImageFileName);
             } finally {
                 denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
@@ -1863,14 +1845,13 @@
             // Gallery can not see the hidden image file created by other app
             try {
                 allowAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
-                assertThat(listAs(TEST_APP_A, dcimDir.getAbsolutePath()))
+                assertThat(listAs(APP_B_NO_PERMS, dcimDir.getAbsolutePath()))
                         .doesNotContain(hiddenImageFileName);
             } finally {
                 denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
             }
         } finally {
             hiddenImageFile.delete();
-            uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
@@ -1885,20 +1866,18 @@
         Uri pendingPdfFileUri = null;
         Uri trashedPdfFileUri = null;
         try {
-            installAppWithStoragePermissions(TEST_APP_A);
-
             pendingImgaeFileUri = createPendingFile(pendingImageFile);
-            assertOpenPendingOrTrashed(pendingImgaeFileUri, TEST_APP_A, /*isImageOrVideo*/ true);
+            assertOpenPendingOrTrashed(pendingImgaeFileUri, APP_A_HAS_RES, /*isImageOrVideo*/ true);
 
             pendingPdfFileUri = createPendingFile(pendingPdfFile);
-            assertOpenPendingOrTrashed(pendingPdfFileUri, TEST_APP_A,
+            assertOpenPendingOrTrashed(pendingPdfFileUri, APP_A_HAS_RES,
                     /*isImageOrVideo*/ false);
 
             trashedVideoFileUri = createTrashedFile(trashedVideoFile);
-            assertOpenPendingOrTrashed(trashedVideoFileUri, TEST_APP_A, /*isImageOrVideo*/ true);
+            assertOpenPendingOrTrashed(trashedVideoFileUri, APP_A_HAS_RES, /*isImageOrVideo*/ true);
 
             trashedPdfFileUri = createTrashedFile(trashedPdfFile);
-            assertOpenPendingOrTrashed(trashedPdfFileUri, TEST_APP_A,
+            assertOpenPendingOrTrashed(trashedPdfFileUri, APP_A_HAS_RES,
                     /*isImageOrVideo*/ false);
 
         } finally {
@@ -1906,7 +1885,6 @@
                     trashedPdfFile);
             deleteWithMediaProviderNoThrow(pendingImgaeFileUri, trashedVideoFileUri,
                     pendingPdfFileUri, trashedPdfFileUri);
-            uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
@@ -1917,33 +1895,30 @@
         Uri imageFileUri = null;
         Uri pdfFileUri = null;
         try {
-            installAppWithStoragePermissions(TEST_APP_A);
-
             imageFileUri = createPendingFile(imageFile);
             // Check that only owner package, file manager and system gallery can list pending image
             // file.
-            assertListPendingOrTrashed(imageFileUri, imageFile, TEST_APP_A,
+            assertListPendingOrTrashed(imageFileUri, imageFile, APP_A_HAS_RES,
                     /*isImageOrVideo*/ true);
 
             trashFile(imageFileUri);
             // Check that only owner package, file manager and system gallery can list trashed image
             // file.
-            assertListPendingOrTrashed(imageFileUri, imageFile, TEST_APP_A,
+            assertListPendingOrTrashed(imageFileUri, imageFile, APP_A_HAS_RES,
                     /*isImageOrVideo*/ true);
 
             pdfFileUri = createPendingFile(pdfFile);
             // Check that only owner package, file manager can list pending non media file.
-            assertListPendingOrTrashed(pdfFileUri, pdfFile, TEST_APP_A,
+            assertListPendingOrTrashed(pdfFileUri, pdfFile, APP_A_HAS_RES,
                     /*isImageOrVideo*/ false);
 
             trashFile(pdfFileUri);
             // Check that only owner package, file manager can list trashed non media file.
-            assertListPendingOrTrashed(pdfFileUri, pdfFile, TEST_APP_A,
+            assertListPendingOrTrashed(pdfFileUri, pdfFile, APP_A_HAS_RES,
                     /*isImageOrVideo*/ false);
         } finally {
             deleteWithMediaProviderNoThrow(imageFileUri, pdfFileUri);
             deleteFiles(imageFile, pdfFile);
-            uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
@@ -1973,18 +1948,17 @@
             pendingPdfFilePath = getFilePathFromUri(createPendingFile(pendingPdfFile));
             trashedPdfFilePath = getFilePathFromUri(createTrashedFile(trashedPdfFile));
 
-            installAppWithStoragePermissions(TEST_APP_A);
-
             // App can't delete other app's pending and trashed file.
-            assertCantDeletePathsAs(TEST_APP_A, pendingVideoFilePath, trashedImageFilePath,
+            assertCantDeletePathsAs(APP_A_HAS_RES, pendingVideoFilePath, trashedImageFilePath,
                     pendingPdfFilePath, trashedPdfFilePath);
 
             final int testAppUid =
-                    getContext().getPackageManager().getPackageUid(TEST_APP_A.getPackageName(), 0);
+                    getContext().getPackageManager().getPackageUid(APP_A_HAS_RES.getPackageName(),
+                            0);
             try {
                 allowAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
                 // File Manager can delete any pending and trashed file
-                assertCanDeletePathsAs(TEST_APP_A, pendingVideoFilePath, trashedImageFilePath,
+                assertCanDeletePathsAs(APP_A_HAS_RES, pendingVideoFilePath, trashedImageFilePath,
                         pendingPdfFilePath, trashedPdfFilePath);
             } finally {
                 denyAppOpsToUid(testAppUid, OPSTR_MANAGE_EXTERNAL_STORAGE);
@@ -2000,12 +1974,12 @@
                 // System Gallery can delete any pending and trashed image or video file.
                 assertTrue(isMediaTypeImageOrVideo(new File(pendingVideoFilePath)));
                 assertTrue(isMediaTypeImageOrVideo(new File(trashedImageFilePath)));
-                assertCanDeletePathsAs(TEST_APP_A, pendingVideoFilePath, trashedImageFilePath);
+                assertCanDeletePathsAs(APP_A_HAS_RES, pendingVideoFilePath, trashedImageFilePath);
 
                 // System Gallery can't delete other app's pending and trashed pdf file.
                 assertFalse(isMediaTypeImageOrVideo(new File(pendingPdfFilePath)));
                 assertFalse(isMediaTypeImageOrVideo(new File(trashedPdfFilePath)));
-                assertCantDeletePathsAs(TEST_APP_A, pendingPdfFilePath, trashedPdfFilePath);
+                assertCantDeletePathsAs(APP_A_HAS_RES, pendingPdfFilePath, trashedPdfFilePath);
             } finally {
                 denyAppOpsToUid(testAppUid, SYSTEM_GALERY_APPOPS);
             }
@@ -2013,7 +1987,6 @@
             deletePaths(pendingVideoFilePath, trashedImageFilePath, pendingPdfFilePath,
                     trashedPdfFilePath);
             deleteFiles(pendingVideoFile, trashedImageFile, pendingPdfFile, trashedPdfFile);
-            uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
@@ -2024,10 +1997,9 @@
         final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
         final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
         try {
-            installApp(TEST_APP_A);
             // Apps can't query other app's pending file, hence create file and publish it.
             assertCreatePublishedFilesAs(
-                    TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+                    APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
 
             // Since the test doesn't have READ_EXTERNAL_STORAGE nor any other special permissions,
             // it can't query for another app's contents.
@@ -2036,8 +2008,7 @@
             assertCantQueryFile(otherAppPdf);
             assertCantQueryFile(otherHiddenFile);
         } finally {
-            deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
-            uninstallApp(TEST_APP_A);
+            deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
         }
     }
 
@@ -2048,10 +2019,9 @@
         final File otherAppMusic = new File(getMusicDir(), "other" + AUDIO_FILE_NAME);
         final File otherHiddenFile = new File(getPicturesDir(), ".otherHiddenFile.jpg");
         try {
-            installApp(TEST_APP_A);
             // Apps can't query other app's pending file, hence create file and publish it.
             assertCreatePublishedFilesAs(
-                    TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
+                    APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
 
             // System gallery apps have access to video and image files
             allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
@@ -2064,8 +2034,7 @@
             assertCantQueryFile(otherAppPdf);
         } finally {
             denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
-            deleteFilesAs(TEST_APP_A, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
-            uninstallApp(TEST_APP_A);
+            deleteFilesAs(APP_B_NO_PERMS, otherAppImg, otherAppMusic, otherAppPdf, otherHiddenFile);
         }
     }
 
@@ -2091,10 +2060,9 @@
             executeShellCommand("touch " + otherAppPdfFile1);
             MediaStore.scanFile(getContentResolver(), otherAppPdfFile1);
 
-            installAppWithStoragePermissions(TEST_APP_A);
             allowAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
 
-            assertCreateFilesAs(TEST_APP_A, otherAppImageFile1, otherAppVideoFile1);
+            assertCreateFilesAs(APP_A_HAS_RES, otherAppImageFile1, otherAppVideoFile1);
 
             // System gallery privileges don't go beyond DCIM, Movies and Pictures boundaries.
             assertCantRenameDirectory(dirInDcim, dirInPodcasts, /*oldFilesList*/ null);
@@ -2118,7 +2086,6 @@
             otherAppPdfFile2.delete();
             dirInDcim.delete();
             dirInPictures.delete();
-            uninstallAppNoThrow(TEST_APP_A);
             denyAppOpsToUid(Process.myUid(), SYSTEM_GALERY_APPOPS);
         }
     }
@@ -2145,8 +2112,7 @@
             assertThat(cr.openFileDescriptor(uriOfOldFile, "rw")).isNotNull();
 
             assertThat(imageFile.delete()).isTrue();
-            installApp(TEST_APP_A);
-            assertThat(createFileAs(TEST_APP_A, imageFile.getAbsolutePath())).isTrue();
+            assertThat(createFileAs(APP_B_NO_PERMS, imageFile.getAbsolutePath())).isTrue();
 
             final Uri uriOfNewFile = MediaStore.scanFile(getContentResolver(), imageFile);
             assertThat(uriOfNewFile).isNotNull();
@@ -2155,8 +2121,7 @@
                     .isNotEqualTo(oldRowId);
         } finally {
             imageFile.delete();
-            deleteFileAsNoThrow(TEST_APP_A, imageFile.getAbsolutePath());
-            uninstallAppNoThrow(TEST_APP_A);
+            deleteFileAsNoThrow(APP_B_NO_PERMS, imageFile.getAbsolutePath());
         }
     }
 
@@ -2280,8 +2245,7 @@
                 assertThat(c.getInt(0)).isEqualTo(0);
             }
 
-            installAppWithStoragePermissions(TEST_APP_A);
-            assertCreateFilesAs(TEST_APP_A, otherPendingFile);
+            assertCreateFilesAs(APP_A_HAS_RES, otherPendingFile);
             // We can't query other apps pending file from FUSE with MATCH_EXCLUDE
             try (Cursor c = queryFileExcludingPending(otherPendingFile,
                     MediaStore.MediaColumns.IS_PENDING)) {
@@ -2289,8 +2253,7 @@
             }
         } finally {
             pendingFile.delete();
-            deleteFileAsNoThrow(TEST_APP_A, otherPendingFile.getAbsolutePath());
-            uninstallAppNoThrow(TEST_APP_A);
+            deleteFileAsNoThrow(APP_A_HAS_RES, otherPendingFile.getAbsolutePath());
         }
     }
 
@@ -2308,17 +2271,13 @@
                 assertThat(externalMediaPath.createNewFile()).isTrue();
             }
 
-            // Install TEST_APP_A with READ_EXTERNAL_STORAGE permission.
-            installAppWithStoragePermissions(TEST_APP_A);
-
-            // TEST_APP_A should not be able to setattr to other app's files.
+            // APP A should not be able to setattr to other app's files.
             assertWithMessage(
                     "setattr on directory/external media path [%s]", externalMediaPath.getPath())
-                    .that(setAttrAs(TEST_APP_A, externalMediaPath.getPath()))
+                    .that(setAttrAs(APP_A_HAS_RES, externalMediaPath.getPath()))
                     .isFalse();
         } finally {
             externalMediaPath.delete();
-            uninstallAppNoThrow(TEST_APP_A);
         }
     }
 
diff --git a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
index 07fbfe8..58259b2 100644
--- a/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
+++ b/hostsidetests/scopedstorage/legacy/AndroidManifest.xml
@@ -20,8 +20,6 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <application  android:requestLegacyExternalStorage="true" >
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
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 60400fb..e9d98c5 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
@@ -325,7 +325,7 @@
             final String packageName = testApp.getPackageName();
             uiAutomation.adoptShellPermissionIdentity(
                     Manifest.permission.INSTALL_PACKAGES, Manifest.permission.DELETE_PACKAGES);
-            if (InstallUtils.getInstalledVersion(packageName) != -1) {
+            if (isAppInstalled(testApp)) {
                 Uninstall.packages(packageName);
             }
             Install.single(testApp).commit();
@@ -338,6 +338,10 @@
         }
     }
 
+    public static boolean isAppInstalled(TestApp testApp) {
+        return InstallUtils.getInstalledVersion(testApp.getPackageName()) != -1;
+    }
+
     /**
      * Uninstalls a {@link TestApp}.
      */
@@ -939,7 +943,9 @@
             uiAutomation.adoptShellPermissionIdentity(Manifest.permission.FORCE_STOP_PACKAGES);
 
             getContext().getSystemService(ActivityManager.class).forceStopPackage(packageName);
-            Thread.sleep(1000);
+            pollForCondition(() -> {
+                return !isProcessRunning(packageName);
+            }, "Timed out while waiting for " + packageName + " to be stopped");
         } finally {
             uiAutomation.dropShellPermissionIdentity();
         }
@@ -1186,4 +1192,14 @@
             assertThat(parts[2]).isNotEqualTo("emulated");
         }
     }
+
+    private static boolean isProcessRunning(String packageName) {
+        try {
+            return getContext().getSystemService(
+                    ActivityManager.class).getRunningAppProcesses().stream().anyMatch(
+                            p -> packageName.equals(p.processName));
+        } catch (Exception e) {
+            return false;
+        }
+    }
 }
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/Android.bp b/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
index 5fd8e6b..0a22b57 100644
--- a/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
+++ b/hostsidetests/settings/app/DeviceOwnerApp/Android.bp
@@ -33,6 +33,7 @@
         "androidx.test.rules",
         "ub-uiautomator",
         "truth-prebuilt",
+        "cts-wm-util",
     ],
     // tag this module as a cts test artifact
     test_suites: [
diff --git a/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java
index 06b1259..6581614 100644
--- a/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java
+++ b/hostsidetests/settings/app/DeviceOwnerApp/src/com/google/android/cts/deviceowner/DeviceOwnerTest.java
@@ -15,6 +15,8 @@
  */
 package com.google.android.cts.deviceowner;
 
+import static android.server.wm.WindowManagerState.STATE_RESUMED;
+
 import android.app.admin.DeviceAdminReceiver;
 import android.app.admin.DevicePolicyManager;
 import android.content.ComponentName;
@@ -23,6 +25,7 @@
 import android.content.pm.PackageManager;
 import android.os.RemoteException;
 import android.provider.Settings;
+import android.server.wm.WindowManagerStateHelper;
 import android.support.test.uiautomator.By;
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.Until;
@@ -106,8 +109,15 @@
     private void launchSettingsPage(Context ctx, String pageName) throws Exception {
         Intent intent = new Intent(pageName);
         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+
+        ComponentName componentName =
+                ctx.getPackageManager()
+                        .resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY)
+                        .getComponentInfo()
+                        .getComponentName();
         ctx.startActivity(intent);
-        Thread.sleep(TIMEOUT * 2);
+
+        new WindowManagerStateHelper().waitForActivityState(componentName, STATE_RESUMED);
     }
 
     private void disableWorkPolicyInfoActivity() {
diff --git a/hostsidetests/stagedinstall/app/AndroidManifest.xml b/hostsidetests/stagedinstall/app/AndroidManifest.xml
index 679c55d..b0f548c 100644
--- a/hostsidetests/stagedinstall/app/AndroidManifest.xml
+++ b/hostsidetests/stagedinstall/app/AndroidManifest.xml
@@ -22,9 +22,6 @@
     </queries>
 
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-             android:exported="true"/>
-
         <!-- This activity is necessary to register the test app as the default home activity (i.e.
                          to receive SESSION_COMMITTED broadcasts.) -->
         <activity android:name=".LauncherActivity"
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
index 7e97be2..85de6dd 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/statsd/UidAtomTests.java
@@ -786,36 +786,6 @@
         assertThat(atom.getState().getNumber()).isEqualTo(TestAtomReported.State.OFF_VALUE);
         assertThat(atom.getBytesField().getExperimentIdList()).isEmpty();
     }
-
-    public void testNotificationReported() throws Exception {
-        StatsdConfig.Builder config = ConfigUtils.createConfigBuilder(
-                DeviceUtils.STATSD_ATOM_TEST_PKG);
-        FieldValueMatcher.Builder fvm = ConfigUtils.createFvm(
-                NotificationReported.PACKAGE_NAME_FIELD_NUMBER).setEqString(
-                DeviceUtils.STATSD_ATOM_TEST_PKG);
-        ConfigUtils.addEventMetric(config, Atom.NOTIFICATION_REPORTED_FIELD_NUMBER,
-                Collections.singletonList(fvm));
-        ConfigUtils.uploadConfig(getDevice(), config);
-
-        DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
-                "StatsdCtsForegroundActivity", "action", "action.show_notification");
-        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
-
-        // Sorted list of events in order in which they occurred.
-        List<EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
-        assertThat(data).hasSize(1);
-        assertThat(data.get(0).getAtom().hasNotificationReported()).isTrue();
-        AtomsProto.NotificationReported n = data.get(0).getAtom().getNotificationReported();
-        assertThat(n.getPackageName()).isEqualTo(DeviceUtils.STATSD_ATOM_TEST_PKG);
-        assertThat(n.getUid()).isEqualTo(DeviceUtils.getStatsdTestAppUid(getDevice()));
-        assertThat(n.getNotificationIdHash()).isEqualTo(1);  // smallHash(0x7f080001)
-        assertThat(n.getChannelIdHash()).isEqualTo(SmallHash.hash("StatsdCtsChannel"));
-        assertThat(n.getGroupIdHash()).isEqualTo(0);
-        assertFalse(n.getIsGroupSummary());
-        assertThat(n.getCategory()).isEmpty();
-        assertThat(n.getStyle()).isEqualTo(0);
-        assertThat(n.getNumPeople()).isEqualTo(0);
-    }
 /*
     public void testMobileBytesTransfer() throws Throwable {
         final int appUid = getUid();
diff --git a/hostsidetests/systemui/Android.bp b/hostsidetests/systemui/Android.bp
index 506b789..562bc5a 100644
--- a/hostsidetests/systemui/Android.bp
+++ b/hostsidetests/systemui/Android.bp
@@ -20,6 +20,11 @@
         "cts-tradefed",
         "tradefed",
         "compatibility-host-util",
+        "core_cts_test_resources",
+    ],
+
+    static_libs: [
+        "cts-statsd-atom-host-test-utils",
     ],
     // Tag this module as a cts test artifact
     test_suites: [
diff --git a/hostsidetests/systemui/app/AndroidManifest.xml b/hostsidetests/systemui/app/AndroidManifest.xml
index 3ca3ef5..0ffc130 100755
--- a/hostsidetests/systemui/app/AndroidManifest.xml
+++ b/hostsidetests/systemui/app/AndroidManifest.xml
@@ -19,6 +19,9 @@
      package="android.systemui.cts">
 
     <application>
+        <activity android:name=".TestNotificationActivity"
+                  android:exported="true"/>
+
         <service android:name=".TestTileService"
              android:icon="@android:drawable/ic_delete"
              android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
diff --git a/hostsidetests/systemui/app/res/layout/activity_notification.xml b/hostsidetests/systemui/app/res/layout/activity_notification.xml
new file mode 100644
index 0000000..836f7bb
--- /dev/null
+++ b/hostsidetests/systemui/app/res/layout/activity_notification.xml
@@ -0,0 +1,21 @@
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:orientation="vertical">
+</RelativeLayout>
\ No newline at end of file
diff --git a/hostsidetests/systemui/app/src/android/systemui/cts/TestNotificationActivity.java b/hostsidetests/systemui/app/src/android/systemui/cts/TestNotificationActivity.java
new file mode 100644
index 0000000..5ebf309
--- /dev/null
+++ b/hostsidetests/systemui/app/src/android/systemui/cts/TestNotificationActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2020 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.systemui.cts;
+
+import android.app.Activity;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+
+public class TestNotificationActivity extends Activity {
+    private static final String TAG = TestNotificationActivity.class.getSimpleName();
+
+    public static final String KEY_ACTION = "action";
+    public static final String ACTION_SHOW_NOTIFICATION = "action.show_notification";
+
+    @Override
+    public void onCreate(Bundle bundle) {
+        super.onCreate(bundle);
+
+        Intent intent = this.getIntent();
+        if (intent == null) {
+            Log.e(TAG, "Intent was null.");
+            finish();
+        }
+
+        String action = intent.getStringExtra(KEY_ACTION);
+        Log.i(TAG, "Starting " + action + " from foreground activity.");
+
+        switch (action) {
+            case ACTION_SHOW_NOTIFICATION:
+                doShowNotification();
+                break;
+            default:
+                Log.e(TAG, "Intent had invalid action " + action);
+                finish();
+        }
+    }
+
+    private void doShowNotification() {
+        final int notificationId = R.layout.activity_notification;
+        final String notificationChannelId = "SystemUiCtsChannel";
+
+        NotificationManager nm = getSystemService(NotificationManager.class);
+        NotificationChannel channel = new NotificationChannel(notificationChannelId, "SystemUi Cts",
+                NotificationManager.IMPORTANCE_DEFAULT);
+        channel.setDescription("SystemUi Cts Channel");
+        nm.createNotificationChannel(channel);
+
+        nm.notify(
+                notificationId,
+                new Notification.Builder(this, notificationChannelId)
+                        .setSmallIcon(android.R.drawable.stat_notify_chat)
+                        .setContentTitle("SystemUiCts")
+                        .setContentText("SystemUiCts")
+                        .build());
+        nm.cancel(notificationId);
+        finish();
+    }
+}
diff --git a/hostsidetests/systemui/src/android/host/systemui/StatsdNotificationAtomTest.java b/hostsidetests/systemui/src/android/host/systemui/StatsdNotificationAtomTest.java
new file mode 100644
index 0000000..f82edc4
--- /dev/null
+++ b/hostsidetests/systemui/src/android/host/systemui/StatsdNotificationAtomTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 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.host.systemui;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.internal.os.StatsdConfigProto;
+import com.android.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.server.notification.SmallHash;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.Collections;
+import java.util.List;
+
+public class StatsdNotificationAtomTest extends DeviceTestCase implements IBuildReceiver {
+    private static final String NOTIFICATION_TEST_APK = "CtsSystemUiDeviceApp.apk";
+    private static final String NOTIFICATION_TEST_PKG = "android.systemui.cts";
+
+    private static final String TEST_NOTIFICATION_ACTIVITY = "TestNotificationActivity";
+    private static final String ACTION_KEY = "action";
+    private static final String ACTION_VALUE = "action.show_notification";
+    private static final String NOTIFICATION_CHANNEL_ID = "SystemUiCtsChannel";
+
+    private IBuildInfo mCtsBuild;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        assertThat(mCtsBuild).isNotNull();
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.installTestApp(getDevice(), NOTIFICATION_TEST_APK, NOTIFICATION_TEST_PKG,
+                mCtsBuild);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        ConfigUtils.removeConfig(getDevice());
+        ReportUtils.clearReports(getDevice());
+        DeviceUtils.uninstallTestApp(getDevice(), NOTIFICATION_TEST_PKG);
+        super.tearDown();
+    }
+
+    @Override
+    public void setBuild(IBuildInfo buildInfo) {
+        mCtsBuild = buildInfo;
+    }
+
+    public void testNotificationReported() throws Exception {
+        StatsdConfigProto.StatsdConfig.Builder config = ConfigUtils.createConfigBuilder(
+                NOTIFICATION_TEST_PKG);
+        StatsdConfigProto.FieldValueMatcher.Builder fvm = ConfigUtils.createFvm(
+                AtomsProto.NotificationReported.PACKAGE_NAME_FIELD_NUMBER).setEqString(
+                NOTIFICATION_TEST_PKG);
+        ConfigUtils.addEventMetric(config, AtomsProto.Atom.NOTIFICATION_REPORTED_FIELD_NUMBER,
+                Collections.singletonList(fvm));
+        ConfigUtils.uploadConfig(getDevice(), config);
+
+        DeviceUtils.runActivity(getDevice(), NOTIFICATION_TEST_PKG,
+                TEST_NOTIFICATION_ACTIVITY, ACTION_KEY, ACTION_VALUE);
+        Thread.sleep(AtomTestUtils.WAIT_TIME_SHORT);
+
+        // Sorted list of events in order in which they occurred.
+        List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+        assertThat(data).hasSize(1);
+        assertThat(data.get(0).getAtom().hasNotificationReported()).isTrue();
+        AtomsProto.NotificationReported n = data.get(0).getAtom().getNotificationReported();
+        assertThat(n.getPackageName()).isEqualTo(NOTIFICATION_TEST_PKG);
+        assertThat(n.getUid()).isEqualTo(DeviceUtils.getAppUid(getDevice(), NOTIFICATION_TEST_PKG));
+        assertThat(n.getNotificationIdHash()).isEqualTo(0);  // smallHash(0x7F020000)
+        assertThat(n.getChannelIdHash()).isEqualTo(SmallHash.hash(NOTIFICATION_CHANNEL_ID));
+        assertThat(n.getGroupIdHash()).isEqualTo(0);
+        assertFalse(n.getIsGroupSummary());
+        assertThat(n.getCategory()).isEmpty();
+        assertThat(n.getStyle()).isEqualTo(0);
+        assertThat(n.getNumPeople()).isEqualTo(0);
+    }
+}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
index 819ee53..1a33df0 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobInfoTest.java
@@ -130,6 +130,13 @@
         assertTrue(persistableBundleEquals(pb, ji.getExtras()));
     }
 
+    public void testForegroundJob() {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setForeground(true)
+                .build();
+        assertTrue(ji.isForegroundJob());
+    }
+
     public void testImportantWhileForeground() {
         // Assert the default value is false
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
index 91b7dc9..8b7ca03 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/JobParametersTest.java
@@ -65,6 +65,32 @@
         assertTrue(persistableBundleEquals(pb, params.getExtras()));
     }
 
+    public void testForeground() throws Exception {
+        JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setForeground(true)
+                .build();
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(ji);
+        runSatisfiedJob(JOB_ID);
+        assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+        JobParameters params = kTestEnvironment.getLastJobParameters();
+        assertTrue(params.isForegroundJob());
+
+        ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
+                .setForeground(false)
+                .build();
+
+        kTestEnvironment.setExpectedExecutions(1);
+        mJobScheduler.schedule(ji);
+        runSatisfiedJob(JOB_ID);
+        assertTrue("Job didn't fire immediately", kTestEnvironment.awaitExecution());
+
+        params = kTestEnvironment.getLastJobParameters();
+        assertFalse(params.isForegroundJob());
+    }
+
     public void testJobId() throws Exception {
         JobInfo ji = new JobInfo.Builder(JOB_ID, kJobServiceComponent)
                 .build();
diff --git a/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java b/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
index c3fcf4c..fe194f2 100644
--- a/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
+++ b/tests/app/AppExitTest/src/android/app/cts/ActivityManagerAppExitInfoTest.java
@@ -101,7 +101,7 @@
     private static final int EXIT_CODE = 123;
     private static final int CRASH_SIGNAL = OsConstants.SIGSEGV;
 
-    private static final int WAITFOR_MSEC = 5000;
+    private static final int WAITFOR_MSEC = 10000;
     private static final int WAITFOR_SETTLE_DOWN = 2000;
 
     private static final int CMD_PID = 1;
diff --git a/tests/app/src/android/app/cts/DownloadManagerTest.java b/tests/app/src/android/app/cts/DownloadManagerTest.java
index 108f173..9bf0629 100644
--- a/tests/app/src/android/app/cts/DownloadManagerTest.java
+++ b/tests/app/src/android/app/cts/DownloadManagerTest.java
@@ -439,6 +439,16 @@
         return process;
     }
 
+    private int getExternalVolumeMediaStoreFilesCount() {
+        Uri rootUri = MediaStore.Files.getContentUri(MediaStore.VOLUME_EXTERNAL);
+        String[] projection = {MediaStore.MediaColumns._ID};
+        int count = 0;
+        try (Cursor cursor = mContext.getContentResolver().query(rootUri, projection, null, null)) {
+            count = cursor.getCount();
+        }
+        return count;
+    }
+
     @FlakyTest
     @Test
     public void testProviderAcceptsCleartext() throws Exception {
@@ -641,10 +651,14 @@
                 int allDownloads = getTotalNumberDownloads();
                 assertEquals(1, allDownloads);
 
+                int countBeforeDownload = getExternalVolumeMediaStoreFilesCount();
                 receiver.waitForDownloadComplete(SHORT_TIMEOUT, id);
                 assertSuccessfulDownload(id, new File(
                         Environment.getExternalStoragePublicDirectory(destination), subPath));
 
+                int countAfterDownload = getExternalVolumeMediaStoreFilesCount();
+                // Asserts that only one row entry is added for 1 download
+                assertEquals((countBeforeDownload + 1), countAfterDownload);
                 final Uri downloadUri = mDownloadManager.getUriForDownloadedFile(id);
                 mContext.grantUriPermission("com.android.shell", downloadUri,
                         Intent.FLAG_GRANT_READ_URI_PERMISSION);
diff --git a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
index d28520c..1ca6107 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CaptureRequestTest.java
@@ -2777,6 +2777,9 @@
         final Rect activeArraySize = mStaticInfo.getActiveArraySizeChecked();
         final Rect defaultCropRegion =
                 new Rect(0, 0, activeArraySize.width(), activeArraySize.height());
+        final Rect zoom2xCropRegion =
+                new Rect(activeArraySize.width()/4, activeArraySize.height()/4,
+                        activeArraySize.width()*3/4, activeArraySize.height()*3/4);
         MeteringRectangle[][] expectRegions = new MeteringRectangle[ZOOM_STEPS][];
         CaptureRequest.Builder requestBuilder =
                 mCamera.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
@@ -2818,6 +2821,7 @@
                 Log.v(TAG, "Testing Zoom ratio " + zoomFactor + " Preview size is " + previewSize);
             }
             requestBuilder.set(CaptureRequest.CONTROL_ZOOM_RATIO, zoomFactor);
+            requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, defaultCropRegion);
             CaptureRequest request = requestBuilder.build();
             for (int j = 0; j < captureSubmitRepeat; ++j) {
                 mSession.capture(request, listener, mHandler);
@@ -2894,6 +2898,37 @@
             }
 
             previousRatio = resultZoomRatio;
+
+            /*
+             * Set windowboxing cropRegion while zoomRatio is not 1.0x, and make sure the crop
+             * region was overwritten.
+             */
+            if (zoomFactor != 1.0f) {
+                requestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoom2xCropRegion);
+                CaptureRequest requestWithCrop = requestBuilder.build();
+                for (int j = 0; j < captureSubmitRepeat; ++j) {
+                    mSession.capture(requestWithCrop, listener, mHandler);
+                }
+
+                waitForNumResults(listener, captureSubmitRepeat - 1); // Drop first few frames
+                CaptureResult resultWithCrop = listener.getCaptureResultForRequest(
+                        requestWithCrop, NUM_RESULTS_WAIT_TIMEOUT);
+                float resultZoomRatioWithCrop = getValueNotNull(resultWithCrop,
+                        CaptureResult.CONTROL_ZOOM_RATIO);
+                Rect cropRegionWithCrop = getValueNotNull(resultWithCrop,
+                        CaptureResult.SCALER_CROP_REGION);
+
+                mCollector.expectTrue(String.format(
+                        "Result zoom ratio should remain the same (activeArrayCrop: %f, " +
+                        "zoomedCrop: %f)", resultZoomRatio, resultZoomRatioWithCrop),
+                        Math.abs(resultZoomRatio - resultZoomRatioWithCrop) < ZOOM_ERROR_MARGIN);
+
+                if (mStaticInfo.isHardwareLevelAtLeastLimited()) {
+                    mCollector.expectRectsAreSimilar(
+                            "Result crop region should remain the same with or without crop",
+                            cropRegion, cropRegionWithCrop, CROP_REGION_ERROR_PERCENT_DELTA);
+                }
+            }
         }
     }
 
diff --git a/tests/framework/base/windowmanager/intent_tests/clearCases/test-no-history-1.json b/tests/framework/base/windowmanager/intent_tests/clearCases/test-no-history-1.json
new file mode 100644
index 0000000..5e90c3f
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/clearCases/test-no-history-1.json
@@ -0,0 +1,44 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NO_HISTORY | FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.wm.intent.Activities$SingleTopActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "tasks": [
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+                        "state": "RESUMED"
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "tasks": [
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$SingleTopActivity",
+                        "state": "RESUMED"
+                    }
+                ]
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/intent_tests/clearCases/test-no-history-2.json b/tests/framework/base/windowmanager/intent_tests/clearCases/test-no-history-2.json
new file mode 100644
index 0000000..0e6e758
--- /dev/null
+++ b/tests/framework/base/windowmanager/intent_tests/clearCases/test-no-history-2.json
@@ -0,0 +1,58 @@
+{
+    "setup": {
+        "initialIntents": [
+            {
+                "flags": "FLAG_ACTIVITY_NEW_TASK",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            },
+            {
+                "flags": "FLAG_ACTIVITY_NO_HISTORY",
+                "class": "android.server.wm.intent.Activities$SingleTopActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ],
+        "act": [
+            {
+                "flags": "",
+                "class": "android.server.wm.intent.Activities$RegularActivity",
+                "package": "android.server.wm.cts",
+                "startForResult": false
+            }
+        ]
+    },
+    "initialState": {
+        "tasks": [
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$SingleTopActivity",
+                        "state": "RESUMED"
+                    },
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+                        "state": "STOPPED"
+                    }
+                ]
+            }
+        ]
+    },
+    "endState": {
+        "tasks": [
+            {
+                "activities": [
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+                        "state": "RESUMED"
+                    },
+                    {
+                        "name": "android.server.wm.cts/android.server.wm.intent.Activities$RegularActivity",
+                        "state": "STOPPED"
+                    }
+                ]
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/intent/Cases.java b/tests/framework/base/windowmanager/src/android/server/wm/intent/Cases.java
index 3428c15..a5cce2a 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/intent/Cases.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/intent/Cases.java
@@ -21,6 +21,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
 import static android.content.Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP;
 import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
 import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
@@ -67,6 +68,8 @@
             "FLAG_ACTIVITY_PREVIOUS_IS_TOP");
     public static final IntentFlag REORDER_TO_FRONT = flag(FLAG_ACTIVITY_REORDER_TO_FRONT,
             "FLAG_ACTIVITY_REORDER_TO_FRONT");
+    public static final IntentFlag NO_HISTORY = flag(FLAG_ACTIVITY_NO_HISTORY,
+            "FLAG_ACTIVITY_NO_HISTORY");
 
     // Flag only used for parsing intents that contain no flags.
     private static final IntentFlag NONE = flag(0, "");
@@ -80,7 +83,8 @@
             MULTIPLE_TASK,
             RESET_TASK_IF_NEEDED,
             PREVIOUS_IS_TOP,
-            REORDER_TO_FRONT
+            REORDER_TO_FRONT,
+            NO_HISTORY
     );
 
     // Definition of intents used across multiple test cases.
diff --git a/tests/rollback/AndroidManifest.xml b/tests/rollback/AndroidManifest.xml
index 3203f25..4ea3ee7 100644
--- a/tests/rollback/AndroidManifest.xml
+++ b/tests/rollback/AndroidManifest.xml
@@ -19,8 +19,6 @@
           package="com.android.cts.rollback" >
 
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/sensor/src/android/hardware/cts/SensorTest.java b/tests/sensor/src/android/hardware/cts/SensorTest.java
index f64bc75..68ab07d 100644
--- a/tests/sensor/src/android/hardware/cts/SensorTest.java
+++ b/tests/sensor/src/android/hardware/cts/SensorTest.java
@@ -89,7 +89,8 @@
 
         mAndroidSensorList = new ArrayList<>();
         for (Sensor s : mSensorList) {
-            if (s.getType() < Sensor.TYPE_DEVICE_PRIVATE_BASE) {
+            if (s.getType() < Sensor.TYPE_DEVICE_PRIVATE_BASE &&
+                    (!context.getPackageManager().isInstantApp() || s.getType() != Sensor.TYPE_HEART_RATE)) {
                 mAndroidSensorList.add(s);
             }
         }
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/Foo.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/Foo.aidl
index f2b551c..27dddbf 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/Foo.aidl
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/Foo.aidl
@@ -31,4 +31,9 @@
   test_package.LongEnum[] shouldContainTwoLongFoos;
   @nullable String[] g;
   @nullable test_package.SimpleUnion u;
+  int shouldSetBit0AndBit2;
+  @nullable test_package.SimpleUnion shouldBeConstS1;
+  const int BIT0 = 1;
+  const int BIT1 = 2;
+  const int BIT2 = 4;
 }
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/SimpleUnion.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/SimpleUnion.aidl
index 7c1564a..1d896cb 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/SimpleUnion.aidl
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/current/test_package/SimpleUnion.aidl
@@ -23,4 +23,5 @@
   test_package.ByteEnum d;
   test_package.ByteEnum[] e;
   @nullable test_package.Bar f;
+  const String S1 = "a string constant";
 }
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp b/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
index 85e5599..42e30a6 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
@@ -545,6 +545,8 @@
   foo.shouldContainTwoIntFoos = {IntEnum::FOO, IntEnum::FOO};
   foo.shouldContainTwoLongFoos = {LongEnum::FOO, LongEnum::FOO};
   foo.u = SimpleUnion::make<SimpleUnion::c>("hello");
+  foo.shouldSetBit0AndBit2 = Foo::BIT0 | Foo::BIT2;
+  foo.shouldBeConstS1 = SimpleUnion::S1;
 
   Foo retFoo;
 
@@ -561,6 +563,8 @@
   EXPECT_EQ(foo.shouldContainTwoIntFoos, retFoo.shouldContainTwoIntFoos);
   EXPECT_EQ(foo.shouldContainTwoLongFoos, retFoo.shouldContainTwoLongFoos);
   EXPECT_EQ(foo.u, retFoo.u);
+  EXPECT_EQ(foo.shouldSetBit0AndBit2, retFoo.shouldSetBit0AndBit2);
+  EXPECT_EQ(foo.shouldBeConstS1, retFoo.shouldBeConstS1);
 }
 
 TEST_P(NdkBinderTest_Aidl, RepeatGenericBar) {
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/test_package/Foo.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/test_package/Foo.aidl
index 9ecb97e..6973e6d 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/test_package/Foo.aidl
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/test_package/Foo.aidl
@@ -21,4 +21,11 @@
     LongEnum[] shouldContainTwoLongFoos;
     @nullable String[] g;
     @nullable SimpleUnion u;
+
+    const int BIT0 = 0x1;
+    const int BIT1 = 0x1 << 1;
+    const int BIT2 = 0x1 << 2;
+    int shouldSetBit0AndBit2;
+
+    @nullable SimpleUnion shouldBeConstS1;
 }
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/test_package/SimpleUnion.aidl b/tests/tests/binder_ndk/libbinder_ndk_test/test_package/SimpleUnion.aidl
index 5fa906a..d92f025 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/test_package/SimpleUnion.aidl
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/test_package/SimpleUnion.aidl
@@ -10,4 +10,6 @@
     ByteEnum d;
     ByteEnum[] e;
     @nullable Bar f;
+
+    const String S1 = "a string constant";
 }
diff --git a/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java b/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
index 79f231a..2d57d82 100644
--- a/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
+++ b/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
@@ -607,6 +607,9 @@
 
         foo.u = SimpleUnion.e(new byte[]{ByteEnum.FOO, ByteEnum.FOO});
 
+        foo.shouldSetBit0AndBit2 = Foo.BIT0 | Foo.BIT2;
+        foo.shouldBeConstS1 = SimpleUnion.c(SimpleUnion.S1);
+
         Foo repeatedFoo = mInterface.repeatFoo(foo);
 
         assertEquals(foo.a, repeatedFoo.a);
@@ -620,6 +623,8 @@
         Assert.assertArrayEquals(foo.shouldContainTwoIntFoos, repeatedFoo.shouldContainTwoIntFoos);
         Assert.assertArrayEquals(foo.shouldContainTwoLongFoos, repeatedFoo.shouldContainTwoLongFoos);
         Assert.assertArrayEquals(foo.u.getE(), repeatedFoo.u.getE());
+        assertEquals(foo.shouldSetBit0AndBit2, repeatedFoo.shouldSetBit0AndBit2);
+        assertEquals(foo.shouldBeConstS1.getC(), repeatedFoo.shouldBeConstS1.getC());
     }
 
     @Test
diff --git a/tests/tests/content/AndroidManifest.xml b/tests/tests/content/AndroidManifest.xml
index 2e67edf..3083940 100644
--- a/tests/tests/content/AndroidManifest.xml
+++ b/tests/tests/content/AndroidManifest.xml
@@ -540,9 +540,6 @@
             </intent-filter>
 	</activity>
 
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-             android:exported="true" />
-
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/content/pm/SecureFrp/AndroidManifest.xml b/tests/tests/content/pm/SecureFrp/AndroidManifest.xml
index 9e45d4a..309f4a1 100644
--- a/tests/tests/content/pm/SecureFrp/AndroidManifest.xml
+++ b/tests/tests/content/pm/SecureFrp/AndroidManifest.xml
@@ -21,8 +21,6 @@
     <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
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 91ec1a4..cc867e0 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -73,6 +73,7 @@
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.platform.test.annotations.AppModeFull;
@@ -932,7 +933,7 @@
     }
 
     @Test
-    public void testSetSystemAppHiddenUntilInstalled() {
+    public void testSetSystemAppHiddenUntilInstalled() throws Exception {
         String packageToManipulate = "com.android.cts.ctsshim";
         try {
             mPackageManager.getPackageInfo(packageToManipulate, MATCH_SYSTEM_ONLY);
@@ -949,11 +950,25 @@
                     mPackageManager.setSystemAppState(packageToManipulate,
                             PackageManager.SYSTEM_APP_STATE_HIDDEN_UNTIL_INSTALLED_HIDDEN));
 
-            try {
-                mPackageManager.getPackageInfo(packageToManipulate, MATCH_SYSTEM_ONLY);
+            // Setting the state to SYSTEM_APP_STATE_UNINSTALLED is an async operation in
+            // PackageManagerService with no way to listen for completion, so poll until the
+            // app is no longer found.
+            int pollingPeriodMs = 100;
+            int timeoutMs = 1000;
+            long startTimeMs = SystemClock.elapsedRealtime();
+            boolean isAppStillVisible = true;
+            while (SystemClock.elapsedRealtime() < startTimeMs + timeoutMs) {
+                try {
+                    mPackageManager.getPackageInfo(packageToManipulate, MATCH_SYSTEM_ONLY);
+                } catch (NameNotFoundException e) {
+                    // expected, stop polling
+                    isAppStillVisible = false;
+                    break;
+                }
+                Thread.sleep(pollingPeriodMs);
+            }
+            if (isAppStillVisible) {
                 fail(packageToManipulate + " should not be found via getPackageInfo.");
-            } catch (NameNotFoundException e) {
-                // expected
             }
         } finally {
             SystemUtil.runWithShellPermissionIdentity(() ->
diff --git a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
index 8b51f21..daeb4a4 100644
--- a/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
+++ b/tests/tests/graphics/src/android/graphics/cts/FrameRateCtsActivity.java
@@ -42,6 +42,7 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 
@@ -345,7 +346,7 @@
         super.onCreate(savedInstanceState);
         synchronized (mLock) {
             mDisplayManager = (DisplayManager) getSystemService(DISPLAY_SERVICE);
-            Display.Mode mode = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY).getMode();
+            Display.Mode mode = getDisplay().getMode();
             mDeviceFrameRate = mode.getRefreshRate();
             // Insert the initial mode so we have the full display mode history.
             mModeChangedEvents.add(mode);
@@ -516,7 +517,7 @@
     }
 
     // Returns true if we reached waitUntilNanos, false if some other event occurred.
-    private boolean waitForEvents(long waitUntilNanos, ArrayList<TestSurface> surfaces)
+    private boolean waitForEvents(long waitUntilNanos, List<TestSurface> surfaces)
             throws InterruptedException {
         int numModeChangedEvents = mModeChangedEvents.size();
         long nowNanos = System.nanoTime();
@@ -554,7 +555,7 @@
 
     // Set expectedFrameRate to 0.0 to verify only stable frame rate.
     private void verifyCompatibleAndStableFrameRate(float expectedFrameRate,
-            ArrayList<TestSurface> surfaces) throws InterruptedException {
+            List<TestSurface> surfaces) throws InterruptedException {
         Log.i(TAG, "Verifying compatible and stable frame rate");
         long nowNanos = System.nanoTime();
         long gracePeriodEndTimeNanos =
@@ -616,6 +617,10 @@
         void run(Api api) throws InterruptedException;
     }
 
+    private interface OneSurfaceTestInterface {
+        void run(TestSurface surface) throws InterruptedException;
+    }
+
     // Runs the given test for each api, waiting for the preconditions to be satisfied before
     // running the test. Includes retry logic when the test fails because the preconditions are
     // violated. E.g. if we lose the SurfaceHolder's surface, or the activity is paused/resumed,
@@ -690,17 +695,10 @@
 
     private void testExactFrameRateMatch(Api api, boolean shouldBeSeamless)
             throws InterruptedException {
-        Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
-        Display.Mode currentMode = display.getMode();
-        float initialRefreshRate = currentMode.getRefreshRate();
-
-        TestSurface surface = null;
-        try {
-            surface = new TestSurface(api, mSurfaceView.getSurfaceControl(), mSurface,
-                    "testSurface", mSurfaceView.getHolder().getSurfaceFrame(),
-                    /*visible=*/true, Color.RED);
-            ArrayList<TestSurface> surfaces = new ArrayList<>();
-            surfaces.add(surface);
+        runOneSurfaceTest(api, (TestSurface surface) -> {
+            Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+            Display.Mode currentMode = display.getMode();
+            float initialRefreshRate = currentMode.getRefreshRate();
 
             if (shouldBeSeamless) {
                 // Seamless rates should be seamlessly achieved with no resolution changes.
@@ -710,7 +708,7 @@
                     int initialNumEvents = mModeChangedEvents.size();
                     surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
                             /*shouldBeSeamless*/ true);
-                    verifyCompatibleAndStableFrameRate(frameRate, surfaces);
+                    verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
                     verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
                     verifyModeSwitchesDontChangeResolution(initialNumEvents,
                             mModeChangedEvents.size());
@@ -719,7 +717,7 @@
                 surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
                         /*shouldBeSeamless*/ true);
                 // Wait for potential mode switches
-                verifyCompatibleAndStableFrameRate(initialRefreshRate, surfaces);
+                verifyCompatibleAndStableFrameRate(initialRefreshRate, Arrays.asList(surface));
                 currentMode = display.getMode();
 
                 // Seamed rates should never generate a seamed switch.
@@ -739,18 +737,12 @@
                     int initialNumEvents = mModeChangedEvents.size();
                     surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
                             /*shouldBeSeamless*/ false);
-                    verifyCompatibleAndStableFrameRate(frameRate, surfaces);
+                    verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
                     verifyModeSwitchesDontChangeResolution(initialNumEvents,
                             mModeChangedEvents.size());
                 }
             }
-        } finally {
-            if (surface != null) {
-                surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
-                        /*shouldBeSeamless*/ false);
-                surface.release();
-            }
-        }
+        });
     }
 
     private String modeSwitchesToString(int fromId, int toId) {
@@ -765,7 +757,7 @@
     }
 
     private void testFixedSource(Api api, boolean shouldBeSeamless) throws InterruptedException {
-        Display display = mDisplayManager.getDisplay(Display.DEFAULT_DISPLAY);
+        Display display = getDisplay();
         float[] incompatibleFrameRates = getIncompatibleFrameRates(display);
         if (incompatibleFrameRates == null) {
             Log.i(TAG, "No incompatible frame rates to use for testing fixed_source behavior");
@@ -868,6 +860,133 @@
         runTestsWithPreconditions(api -> testInvalidParams(api), "invalid params behavior");
     }
 
+    private void runOneSurfaceTest(Api api, OneSurfaceTestInterface test)
+            throws InterruptedException {
+        TestSurface surface = null;
+        try {
+            surface = new TestSurface(api, mSurfaceView.getSurfaceControl(), mSurface,
+                    "testSurface", mSurfaceView.getHolder().getSurfaceFrame(),
+                    /*visible=*/true, Color.RED);
+
+            ArrayList<TestSurface> surfaces = new ArrayList<>();
+            surfaces.add(surface);
+
+            test.run(surface);
+        } finally {
+            if (surface != null) {
+                surface.release();
+            }
+        }
+    }
+
+    private void testSwitching(TestSurface surface, List<Float> frameRates, boolean expectSwitch,
+            boolean shouldBeSeamless) throws InterruptedException {
+        for (float frameRate : frameRates) {
+            int initialNumEvents = mModeChangedEvents.size();
+            surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    shouldBeSeamless);
+
+            if (expectSwitch) {
+                verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+            }
+            if (shouldBeSeamless) {
+                verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
+            }
+            verifyModeSwitchesDontChangeResolution(initialNumEvents,
+                    mModeChangedEvents.size());
+        }
+    }
+
+    private void testMatchContentFramerate_None(Api api) throws InterruptedException {
+        runOneSurfaceTest(api, (TestSurface surface) -> {
+            Display display = getDisplay();
+            Display.Mode currentMode = display.getMode();
+            List<Float> frameRates = getRefreshRates(currentMode, display);
+
+            for (float frameRate : frameRates) {
+                int initialNumEvents = mModeChangedEvents.size();
+                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                        /*shouldBeSeamless*/ false);
+
+                assertTrue("Mode switches are not expected but these were detected "
+                        + modeSwitchesToString(initialNumEvents, mModeChangedEvents.size()),
+                        mModeChangedEvents.size() == initialNumEvents);
+            }
+        });
+    }
+
+    public void testMatchContentFramerate_None() throws InterruptedException {
+        runTestsWithPreconditions(api -> testMatchContentFramerate_None(api),
+                "testMatchContentFramerate_None");
+    }
+
+    private void testMatchContentFramerate_Auto(Api api)
+            throws InterruptedException {
+        runOneSurfaceTest(api, (TestSurface surface) -> {
+            Display display = getDisplay();
+            Display.Mode currentMode = display.getMode();
+            float initialRefreshRate = currentMode.getRefreshRate();
+            List<Float> frameRatesToTest = Floats.asList(currentMode.getAlternativeRefreshRates());
+
+            for (float frameRate : frameRatesToTest) {
+                int initialNumEvents = mModeChangedEvents.size();
+                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                        /*shouldBeSeamless*/ false);
+
+                verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+                verifyModeSwitchesDontChangeResolution(initialNumEvents,
+                        mModeChangedEvents.size());
+            }
+
+            // Reset to default
+            surface.setFrameRate(0.f, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                    /*shouldBeSeamless*/ false);
+
+            // Wait for potential mode switches.
+            verifyCompatibleAndStableFrameRate(initialRefreshRate, Arrays.asList(surface));
+
+            currentMode = display.getMode();
+            List<Float> seamedRefreshRates = getSeamedRefreshRates(currentMode, display);
+
+            for (float frameRate : seamedRefreshRates) {
+                int initialNumEvents = mModeChangedEvents.size();
+                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                        /*shouldBeSeamless*/ false);
+
+                // Mode switches may have occurred, make sure they were all seamless.
+                verifyModeSwitchesAreSeamless(initialNumEvents, mModeChangedEvents.size());
+                verifyModeSwitchesDontChangeResolution(initialNumEvents,
+                        mModeChangedEvents.size());
+            }
+        });
+    }
+
+    public void testMatchContentFramerate_Auto() throws InterruptedException {
+        runTestsWithPreconditions(api -> testMatchContentFramerate_Auto(api),
+                "testMatchContentFramerate_Auto");
+    }
+
+    private void testMatchContentFramerate_Always(Api api) throws InterruptedException {
+        runOneSurfaceTest(api, (TestSurface surface) -> {
+            Display display = getDisplay();
+            List<Float> frameRates = getRefreshRates(display.getMode(), display);
+            for (float frameRate : frameRates) {
+                int initialNumEvents = mModeChangedEvents.size();
+                surface.setFrameRate(frameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT,
+                        /*shouldBeSeamless*/ false);
+
+                verifyCompatibleAndStableFrameRate(frameRate, Arrays.asList(surface));
+                verifyModeSwitchesDontChangeResolution(initialNumEvents,
+                        mModeChangedEvents.size());
+            }
+        });
+    }
+
+    public void testMatchContentFramerate_Always() throws InterruptedException {
+        runTestsWithPreconditions(api -> testMatchContentFramerate_Always(api),
+                "testMatchContentFramerate_Always");
+    }
+
     private static native int nativeWindowSetFrameRate(
             Surface surface, float frameRate, int compatibility, boolean shouldBeSeamless);
     private static native long nativeSurfaceControlCreate(
diff --git a/tests/tests/graphics/src/android/graphics/cts/MatchContentFrameRateTest.java b/tests/tests/graphics/src/android/graphics/cts/MatchContentFrameRateTest.java
new file mode 100644
index 0000000..25954d8
--- /dev/null
+++ b/tests/tests/graphics/src/android/graphics/cts/MatchContentFrameRateTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2020 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.graphics.cts;
+
+import static androidx.test.InstrumentationRegistry.getInstrumentation;
+
+import static org.junit.Assert.assertEquals;
+
+import android.Manifest;
+import android.hardware.display.DisplayManager;
+
+import androidx.test.filters.MediumTest;
+import androidx.test.rule.ActivityTestRule;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.AdoptShellPermissionsRule;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class MatchContentFrameRateTest {
+    private static final int SETTING_PROPAGATION_TIMEOUT_MILLIS = 50;
+
+    @Rule
+    public ActivityTestRule<FrameRateCtsActivity> mActivityRule =
+            new ActivityTestRule<>(FrameRateCtsActivity.class);
+
+    @Rule
+    public final AdoptShellPermissionsRule mShellPermissionsRule =
+            new AdoptShellPermissionsRule(getInstrumentation().getUiAutomation(),
+                    Manifest.permission.OVERRIDE_DISPLAY_MODE_REQUESTS,
+                    Manifest.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE);
+
+    private int mInitialMatchContentFrameRate;
+    private DisplayManager mDisplayManager;
+
+    @Before
+    public void setUp() throws Exception {
+        FrameRateCtsActivity activity = mActivityRule.getActivity();
+
+        // Prevent DisplayManager from limiting the allowed refresh rate range based on
+        // non-app policies (e.g. low battery, user settings, etc).
+        mDisplayManager = activity.getSystemService(DisplayManager.class);
+        mDisplayManager.setShouldAlwaysRespectAppRequestedMode(true);
+
+        mInitialMatchContentFrameRate = mDisplayManager.getRefreshRateSwitchingType();
+    }
+
+    @After
+    public void tearDown() {
+        mDisplayManager.setRefreshRateSwitchingType(mInitialMatchContentFrameRate);
+        mDisplayManager.setShouldAlwaysRespectAppRequestedMode(false);
+    }
+
+    @Test
+    public void testMatchContentFramerate_None() throws InterruptedException {
+        mDisplayManager.setRefreshRateSwitchingType(DisplayManager.SWITCHING_TYPE_NONE);
+        assertEquals(DisplayManager.SWITCHING_TYPE_NONE,
+                mDisplayManager.getRefreshRateSwitchingType());
+
+        FrameRateCtsActivity activity = mActivityRule.getActivity();
+        activity.testMatchContentFramerate_None();
+    }
+
+    @Test
+    public void testMatchContentFramerate_Auto() throws InterruptedException {
+        mDisplayManager.setRefreshRateSwitchingType(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS);
+        assertEquals(DisplayManager.SWITCHING_TYPE_WITHIN_GROUPS,
+                mDisplayManager.getRefreshRateSwitchingType());
+
+        FrameRateCtsActivity activity = mActivityRule.getActivity();
+        activity.testMatchContentFramerate_Auto();
+    }
+
+    @Test
+    public void testMatchContentFramerate_Always() throws InterruptedException {
+        mDisplayManager.setRefreshRateSwitchingType(
+                DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS);
+        assertEquals(DisplayManager.SWITCHING_TYPE_ACROSS_AND_WITHIN_GROUPS,
+                mDisplayManager.getRefreshRateSwitchingType());
+        FrameRateCtsActivity activity = mActivityRule.getActivity();
+        activity.testMatchContentFramerate_Always();
+    }
+}
diff --git a/tests/tests/hardware/res/raw/google_gamepad_keyevent_media_tests.json b/tests/tests/hardware/res/raw/google_gamepad_keyevent_media_tests.json
new file mode 100644
index 0000000..37b7fb2
--- /dev/null
+++ b/tests/tests/hardware/res/raw/google_gamepad_keyevent_media_tests.json
@@ -0,0 +1,15 @@
+[
+  {
+    "name": "Press BUTTON Play/Pause",
+    "reports": [
+      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
+      [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
+    ],
+    "source": "KEYBOARD | GAMEPAD",
+    "events": [
+      {"action": "DOWN", "keycode": "MEDIA_PLAY_PAUSE"},
+      {"action": "UP", "keycode": "MEDIA_PLAY_PAUSE"}
+    ]
+  }
+
+]
diff --git a/tests/tests/hardware/res/raw/google_gamepad_keyeventtests.json b/tests/tests/hardware/res/raw/google_gamepad_keyevent_volume_tests.json
similarity index 66%
rename from tests/tests/hardware/res/raw/google_gamepad_keyeventtests.json
rename to tests/tests/hardware/res/raw/google_gamepad_keyevent_volume_tests.json
index 0ca751d..70f7f24 100644
--- a/tests/tests/hardware/res/raw/google_gamepad_keyeventtests.json
+++ b/tests/tests/hardware/res/raw/google_gamepad_keyevent_volume_tests.json
@@ -1,18 +1,5 @@
 [
   {
-    "name": "Press BUTTON Play/Pause",
-    "reports": [
-      [0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
-      [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
-    ],
-    "source": "KEYBOARD | GAMEPAD",
-    "events": [
-      {"action": "DOWN", "keycode": "MEDIA_PLAY_PAUSE"},
-      {"action": "UP", "keycode": "MEDIA_PLAY_PAUSE"}
-    ]
-  },
-
-  {
     "name": "Press BUTTON VOLUME_UP",
     "reports": [
         [0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00],
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/UsbVoiceCommandTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/UsbVoiceCommandTest.java
index dfb34bf..889e415 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/UsbVoiceCommandTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/UsbVoiceCommandTest.java
@@ -18,6 +18,8 @@
 
 import static com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity;
 
+import static org.junit.Assume.assumeFalse;
+
 import android.app.UiAutomation;
 import android.content.ComponentName;
 import android.content.Intent;
@@ -115,8 +117,18 @@
     }
 
     @Test
-    public void testAllKeys() {
-        testInputEvents(R.raw.google_gamepad_keyeventtests);
+    public void testMediaKeys() {
+        testInputEvents(R.raw.google_gamepad_keyevent_media_tests);
+    }
+
+    @Test
+    public void testVolumeKeys() {
+        // {@link PhoneWindowManager} in interceptKeyBeforeDispatching, on TVs platform,
+        // volume keys never go to the foreground app.
+        // Skip the key test for TV platform.
+        assumeFalse("TV platform doesn't send volume keys to app, test should be skipped",
+                mPackageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK));
+        testInputEvents(R.raw.google_gamepad_keyevent_volume_tests);
     }
 
     /**
diff --git a/tests/tests/media/src/android/media/cts/MediaControllerTest.java b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
index f65f9ab..eab4b09 100644
--- a/tests/tests/media/src/android/media/cts/MediaControllerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaControllerTest.java
@@ -18,8 +18,9 @@
 import static android.media.cts.Utils.compareRemoteUserInfo;
 import static android.media.session.PlaybackState.STATE_PLAYING;
 
+import static org.junit.Assert.assertNotEquals;
+
 import android.content.Intent;
-import android.media.AudioAttributes;
 import android.media.AudioManager;
 import android.media.Rating;
 import android.media.VolumeProvider;
@@ -31,6 +32,7 @@
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.HandlerThread;
 import android.os.Looper;
 import android.os.Process;
 import android.os.ResultReceiver;
@@ -130,12 +132,26 @@
         assertEquals(Rating.RATING_5_STARS, mController.getRatingType());
     }
 
-    public void testGetSessionToken() throws Exception {
+    public void testGetSessionToken() {
         assertEquals(mSession.getSessionToken(), mController.getSessionToken());
+    }
 
+    public void testGetSessionInfo() {
         Bundle sessionInfo = mController.getSessionInfo();
         assertNotNull(sessionInfo);
         assertEquals(EXTRAS_VALUE, sessionInfo.getString(EXTRAS_KEY));
+
+        Bundle cachedSessionInfo = mController.getSessionInfo();
+        assertEquals(EXTRAS_VALUE, cachedSessionInfo.getString(EXTRAS_KEY));
+    }
+
+    public void testGetSessionInfoReturnsAnEmptyBundleWhenNotSet() {
+        MediaSession session = new MediaSession(getContext(), "test_tag", /*sessionInfo=*/ null);
+        try {
+            assertTrue(session.getController().getSessionInfo().isEmpty());
+        } finally {
+            session.release();
+        }
     }
 
     public void testGetTag() {
@@ -158,6 +174,25 @@
         }
     }
 
+    public void testSendCommandWithIllegalArgumentsThrowsIAE() {
+        Bundle args = new Bundle();
+        ResultReceiver resultReceiver = new ResultReceiver(mHandler);
+
+        try {
+            mController.sendCommand(/*command=*/ null, args, resultReceiver);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            mController.sendCommand(/*command=*/ "", args, resultReceiver);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
     public void testSetPlaybackSpeed() throws Exception {
         synchronized (mWaitLock) {
             mCallback.reset();
@@ -415,6 +450,185 @@
         }
     }
 
+    public void testRegisterCallbackWithNullThrowsIAE() {
+        try {
+            mController.registerCallback(/*handler=*/ null);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            mController.registerCallback(/*handler=*/ null, mHandler);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testRegisteringSameCallbackWithDifferentHandlerHasNoEffect() {
+        MediaController.Callback callback = new MediaController.Callback() {};
+        mController.registerCallback(callback, mHandler);
+
+        Handler initialHandler = mController.getHandlerForCallback(callback);
+        assertEquals(mHandler.getLooper(), initialHandler.getLooper());
+
+        // Create a separate handler with a new looper.
+        HandlerThread handlerThread = new HandlerThread("Test thread");
+        handlerThread.start();
+
+        // This call should not change the handler which is previously set.
+        mController.registerCallback(callback, new Handler(handlerThread.getLooper()));
+        Handler currentHandlerInController = mController.getHandlerForCallback(callback);
+
+        // The handler should not have been replaced.
+        assertEquals(initialHandler, currentHandlerInController);
+        assertNotEquals(handlerThread.getLooper(), currentHandlerInController.getLooper());
+
+        handlerThread.quitSafely();
+    }
+
+    public void testUnregisterCallbackWithNull() {
+        try {
+            mController.unregisterCallback(/*handler=*/ null);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testUnregisterCallbackShouldRemoveCallback() {
+        MediaController.Callback callback = new MediaController.Callback() {};
+        mController.registerCallback(callback, mHandler);
+        assertEquals(mHandler.getLooper(), mController.getHandlerForCallback(callback).getLooper());
+
+        mController.unregisterCallback(callback);
+        assertNull(mController.getHandlerForCallback(callback));
+    }
+
+    public void testDispatchMediaButtonEventWithNullKeyEvent() {
+        try {
+            mController.dispatchMediaButtonEvent(/*keyEvent=*/ null);
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testDispatchMediaButtonEventWithNonMediaKeyEventReturnsFalse() {
+        KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_CAPS_LOCK);
+        assertFalse(mController.dispatchMediaButtonEvent(keyEvent));
+    }
+
+    public void testPlaybackInfoCreatorNewArray() {
+        final int arrayLength = 5;
+        MediaController.PlaybackInfo[] playbackInfoArrayInitializedWithNulls
+                = MediaController.PlaybackInfo.CREATOR.newArray(arrayLength);
+        assertNotNull(playbackInfoArrayInitializedWithNulls);
+        assertEquals(arrayLength, playbackInfoArrayInitializedWithNulls.length);
+        for (MediaController.PlaybackInfo playbackInfo : playbackInfoArrayInitializedWithNulls) {
+            assertNull(playbackInfo);
+        }
+    }
+
+    public void testTransportControlsPlayAndPrepareFromMediaIdWithIllegalArgumentsThrowsIAE() {
+        MediaController.TransportControls transportControls = mController.getTransportControls();
+
+        try {
+            transportControls.playFromMediaId(/*mediaId=*/ null, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.playFromMediaId(/*mediaId=*/ "", /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.prepareFromMediaId(/*mediaId=*/ null, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.prepareFromMediaId(/*mediaId=*/ "", /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testTransportControlsPlayAndPrepareFromUriWithIllegalArgumentsThrowsIAE() {
+        MediaController.TransportControls transportControls = mController.getTransportControls();
+
+        try {
+            transportControls.playFromUri(/*uri=*/ null, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.playFromUri(Uri.EMPTY, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.prepareFromUri(/*uri=*/ null, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.prepareFromUri(Uri.EMPTY, /*extras=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
+    public void testTransportControlsPlayAndPrepareFromSearchWithNullDoesNotCrash() {
+        MediaController.TransportControls transportControls = mController.getTransportControls();
+
+        // These calls should not crash. Null is accepted on purpose.
+        transportControls.playFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
+        transportControls.prepareFromSearch(/*query=*/ null, /*extras=*/ new Bundle());
+    }
+
+    public void testSendCustomActionWithIllegalArgumentsThrowsIAE() {
+        MediaController.TransportControls transportControls = mController.getTransportControls();
+
+        try {
+            transportControls.sendCustomAction((PlaybackState.CustomAction) null,
+                    /*args=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.sendCustomAction(/*action=*/ (String) null, /*args=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+
+        try {
+            transportControls.sendCustomAction(/*action=*/ "", /*args=*/ new Bundle());
+            fail();
+        } catch (IllegalArgumentException ex) {
+            // Expected
+        }
+    }
+
     private class MediaSessionCallback extends MediaSession.Callback {
         private long mSeekPosition;
         private long mQueueItemId;
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionTest.java b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
index f76038f..55f0558 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
@@ -937,6 +937,7 @@
 
         MediaController.PlaybackInfo info = controller.getPlaybackInfo();
         assertNotNull(info);
+        info.toString(); // Test that calling PlaybackInfo.toString() does not crash.
         assertEquals(MediaController.PlaybackInfo.PLAYBACK_TYPE_LOCAL, info.getPlaybackType());
         AudioAttributes attrs = info.getAudioAttributes();
         assertNotNull(attrs);
diff --git a/tests/tests/mediatranscoding/assets/ConflictSupportedValue.xml b/tests/tests/mediatranscoding/assets/ConflictSupportedValue.xml
new file mode 100644
index 0000000..9b2fa3b
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/ConflictSupportedValue.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    <format android:name="HEVC" supported="true"/>
+    <format android:name="HEVC" supported="false"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/EmptyFormat.xml b/tests/tests/mediatranscoding/assets/EmptyFormat.xml
new file mode 100644
index 0000000..5ef5e51
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/EmptyFormat.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    <format />
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/FormatWithoutSupported.xml b/tests/tests/mediatranscoding/assets/FormatWithoutSupported.xml
new file mode 100644
index 0000000..e50c212
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/FormatWithoutSupported.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    <format android:name="HEVC"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/MediaCapabilities.xml b/tests/tests/mediatranscoding/assets/MediaCapabilities.xml
new file mode 100644
index 0000000..3bff61e
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/MediaCapabilities.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    <format android:name="HEVC" supported="true"/>
+    <format android:name="HDR10" supported="false"/>
+    <format android:name="SlowMotion" supported="false"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/SupportAllHdr.xml b/tests/tests/mediatranscoding/assets/SupportAllHdr.xml
new file mode 100644
index 0000000..8c331a0
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/SupportAllHdr.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    <format android:name="HEVC" supported="true"/>
+    <format android:name="HDR10" supported="true"/>
+    <format android:name="HDR10Plus" supported="true"/>
+    <format android:name="Dolby-Vision" supported="true"/>
+    <format android:name="HLG" supported="true"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/SupportHdrWithoutHevc.xml b/tests/tests/mediatranscoding/assets/SupportHdrWithoutHevc.xml
new file mode 100644
index 0000000..309aa1d
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/SupportHdrWithoutHevc.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    <format android:name="HDR10" supported="true"/>
+    <format android:name="SlowMotion" supported="false"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/SupportedWithoutFormat.xml b/tests/tests/mediatranscoding/assets/SupportedWithoutFormat.xml
new file mode 100644
index 0000000..29454fc
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/SupportedWithoutFormat.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    <format supported="true"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/WrongBooleanValue.xml b/tests/tests/mediatranscoding/assets/WrongBooleanValue.xml
new file mode 100644
index 0000000..62de6cd
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/WrongBooleanValue.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    <format android:name="HEVC" supported="yes"/>
+    <format android:name="HDR10" supported="false"/>
+    <format android:name="SlowMotion" supported="false"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/WrongMediaCapabilityTag.xml b/tests/tests/mediatranscoding/assets/WrongMediaCapabilityTag.xml
new file mode 100644
index 0000000..5db42e5
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/WrongMediaCapabilityTag.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<media-capability xmlns:android="http://schemas.android.com/apk/res/android">
+    <format android:name="HEVC" supported="true"/>
+    <format android:name="HDR10" supported="true"/>
+    <format android:name="SlowMotion" supported="true"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/assets/WrongMediaCapabilityTag2.xml b/tests/tests/mediatranscoding/assets/WrongMediaCapabilityTag2.xml
new file mode 100644
index 0000000..e924c48
--- /dev/null
+++ b/tests/tests/mediatranscoding/assets/WrongMediaCapabilityTag2.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<MediaCapability xmlns:android="http://schemas.android.com/apk/res/android">
+    <format android:name="HEVC" supported="true"/>
+    <format android:name="HDR10" supported="true"/>
+    <format android:name="SlowMotion" supported="true"/>
+</MediaCapability>
diff --git a/tests/tests/mediatranscoding/res/xml/mediacapabilities.xml b/tests/tests/mediatranscoding/res/xml/mediacapabilities.xml
new file mode 100644
index 0000000..3bff61e
--- /dev/null
+++ b/tests/tests/mediatranscoding/res/xml/mediacapabilities.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 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.
+-->
+
+<media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    <format android:name="HEVC" supported="true"/>
+    <format android:name="HDR10" supported="false"/>
+    <format android:name="SlowMotion" supported="false"/>
+</media-capabilities>
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
new file mode 100644
index 0000000..dac13f9
--- /dev/null
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/ApplicationMediaCapabilitiesTest.java
@@ -0,0 +1,264 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.media.mediatranscoding.cts;
+
+import static org.testng.Assert.assertThrows;
+
+import android.content.res.XmlResourceParser;
+import android.media.ApplicationMediaCapabilities;
+import android.media.MediaFeature;
+import android.media.MediaFormat;
+import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresDevice;
+import android.test.AndroidTestCase;
+import android.util.Xml;
+
+import org.junit.Test;
+import org.xmlpull.v1.XmlPullParser;
+
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+@Presubmit
+@AppModeFull(reason = "Instant apps cannot access the SD card")
+public class ApplicationMediaCapabilitiesTest extends AndroidTestCase {
+    private static final String TAG = "ApplicationMediaCapabilitiesTest";
+
+    public void testSetSupportHevc() throws Exception {
+        // Default HEVC support is false.
+        ApplicationMediaCapabilities capability =
+                new ApplicationMediaCapabilities.Builder().build();
+        assertFalse(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
+
+        ApplicationMediaCapabilities capability2 =
+                new ApplicationMediaCapabilities.Builder().addSupportedVideoMimeType(
+                        MediaFormat.MIMETYPE_VIDEO_HEVC).build();
+        assertTrue(capability2.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
+    }
+
+    public void testSetSupportHdr() throws Exception {
+        ApplicationMediaCapabilities capability =
+                new ApplicationMediaCapabilities.Builder().addSupportedHdrType(
+                        MediaFeature.HdrType.HDR10_PLUS).addSupportedVideoMimeType(
+                        MediaFormat.MIMETYPE_VIDEO_HEVC).build();
+        assertEquals(true, capability.isHdrTypeSupported(MediaFeature.HdrType.HDR10_PLUS));
+    }
+
+    // Test supports HDR without supporting hevc, expect exception.
+    public void testSupportHdrWithoutSupportHevc() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            ApplicationMediaCapabilities capability =
+                    new ApplicationMediaCapabilities.Builder().addSupportedHdrType(
+                            MediaFeature.HdrType.HDR10_PLUS).build();
+        });
+    }
+
+    public void testSetSupportSlowMotion() throws Exception {
+        ApplicationMediaCapabilities capability =
+                new ApplicationMediaCapabilities.Builder().setSlowMotionSupported(true).build();
+        assertTrue(capability.isSlowMotionSupported());
+    }
+
+    // Test builder with all supports are set to true.
+    public void testBuilder() throws Exception {
+        ApplicationMediaCapabilities capability =
+                new ApplicationMediaCapabilities.Builder().addSupportedVideoMimeType(
+                        MediaFormat.MIMETYPE_VIDEO_HEVC).addSupportedHdrType(
+                        MediaFeature.HdrType.HDR10_PLUS).setSlowMotionSupported(true).build();
+        assertTrue(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
+        assertTrue(capability.isHdrTypeSupported(MediaFeature.HdrType.HDR10_PLUS));
+        assertTrue(capability.isSlowMotionSupported());
+    }
+
+    //   Test read the application's xml from res/xml folder using the XmlResourceParser.
+    //    <format android:name="HEVC" supported="true"/>
+    //    <format android:name="HDR10" supported="false"/>
+    //    <format android:name="SlowMotion" supported="false"/>
+    public void testReadMediaCapabilitiesXml() throws Exception {
+        XmlResourceParser parser = mContext.getResources().getXml(R.xml.mediacapabilities);
+        ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                parser);
+        assertFalse(capability.isHdrTypeSupported(MediaFeature.HdrType.HDR10));
+        assertFalse(capability.isSlowMotionSupported());
+        assertTrue(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
+    }
+
+    //   Test read the xml from assets folder using the InputStream.
+    //    <format android:name="HEVC" supported="true"/>
+    //    <format android:name="HDR10" supported="false"/>
+    //    <format android:name="SlowMotion" supported="false"/>
+    public void testReadFromCorrectXmlWithInputStreamInAssets() throws Exception {
+        InputStream xmlIs = mContext.getAssets().open("MediaCapabilities.xml");
+        final XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+
+        ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                parser);
+        assertFalse(capability.isHdrTypeSupported(MediaFeature.HdrType.HDR10));
+        assertFalse(capability.isSlowMotionSupported());
+        assertTrue(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
+    }
+
+    //   Test read the application's xml from assets folder using the XmlResourceParser.
+    //    <format android:name="HEVC" supported="true"/>
+    //    <format android:name="HDR10" supported="true"/>
+    //    <format android:name="HDR10Plus" supported="true"/>
+    //    <format android:name="Dolby-Vision" supported="true"/>
+    //    <format android:name="HLG" supported="true"/>
+    //    <format android:name="SlowMotion" supported="true"/>
+    public void testReadMediaCapabilitiesXmlWithSupportAllHdr() throws Exception {
+        InputStream xmlIs = mContext.getAssets().open("SupportAllHdr.xml");
+        final XmlPullParser parser = Xml.newPullParser();
+        parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+
+        ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                parser);
+        assertTrue(capability.isHdrTypeSupported(MediaFeature.HdrType.HDR10));
+        assertTrue(capability.isHdrTypeSupported(MediaFeature.HdrType.HDR10_PLUS));
+        assertTrue(capability.isHdrTypeSupported(MediaFeature.HdrType.DOLBY_VISION));
+        assertTrue(capability.isHdrTypeSupported(MediaFeature.HdrType.HLG));
+        assertTrue(capability.isVideoMimeTypeSupported(MediaFormat.MIMETYPE_VIDEO_HEVC));
+    }
+
+    // Test parsing invalid xml with wrong tag expect UnsupportedOperationException
+    // MediaCapability does not match MediaCapabilities at the end which will lead to
+    // exception with "Ill-formatted xml file"
+    // <MediaCapability xmlns:android="http://schemas.android.com/apk/res/android">
+    //    <format android:name="HEVC" supported="true"/>
+    //    <format android:name="HDR10" supported="true"/>
+    //    <format android:name="SlowMotion" supported="true"/>
+    // </MediaCapabilities>
+    public void testReadFromWrongMediaCapabilityXml() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("WrongMediaCapabilityTag.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test invalid xml with wrong tag expect UnsupportedOperationException
+    // MediaCapability is wrong tag.
+    // <MediaCapability xmlns:android="http://schemas.android.com/apk/res/android">
+    //    <format android:name="HEVC" supported="true"/>
+    //    <format android:name="HDR10" supported="true"/>
+    //    <format android:name="SlowMotion" supported="true"/>
+    // </MediaCapability>
+    public void testReadFromWrongMediaCapabilityXml2() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("WrongMediaCapabilityTag2.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test invalid attribute value of "support" with true->yes expect UnsupportedOperationException
+    // <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    //    <format android:name="HEVC" supported="yes"/>
+    //    <format android:name="HDR10" supported="false"/>
+    //    <format android:name="SlowMotion" supported="false"/>
+    // </media-capabilities>
+    public void testReadFromXmlWithWrongBoolean() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("WrongBooleanValue.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test parsing capabilities that support HDR10 but not support HEVC.
+    // Expect UnsupportedOperationException
+    // <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    //    <format android:name="HDR10" supported="true"/>
+    //    <format android:name="SlowMotion" supported="false"/>
+    // </media-capabilities>
+    public void testReadXmlSupportHdrWithoutSupportHevc() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("SupportHdrWithoutHevc.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test parsing capabilities that has conflicted supported value.
+    // Expect UnsupportedOperationException
+    // <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    //     <format android:name="HEVC" supported="true"/>
+    //     <format android:name="HEVC" supported="false"/>
+    // </media-capabilities>
+    public void testReadXmlConflictSupportedValue() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("ConflictSupportedValue.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test parsing capabilities that has empty format.
+    // Expect UnsupportedOperationException
+    // <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    //     <format/>
+    // </media-capabilities>
+    public void testReadXmlWithEmptyFormat() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("EmptyFormat.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test parsing capabilities that has empty format.
+    // Expect UnsupportedOperationException
+    // <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    //     <format android:name="HEVC"/>
+    // </media-capabilities>
+    public void testReadXmlFormatWithoutSupported() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("FormatWithoutSupported.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+
+    // Test parsing capabilities that has supported without the format name.
+    // Expect UnsupportedOperationException
+    // <media-capabilities xmlns:android="http://schemas.android.com/apk/res/android">
+    //     <format supported="true"/>
+    // </media-capabilities>
+    public void testReadXmlSupportedWithoutFormat() throws Exception {
+        assertThrows(UnsupportedOperationException.class, () -> {
+            InputStream xmlIs = mContext.getAssets().open("SupportedWithoutFormat.xml");
+            final XmlPullParser parser = Xml.newPullParser();
+            parser.setInput(xmlIs, StandardCharsets.UTF_8.name());
+            ApplicationMediaCapabilities capability = ApplicationMediaCapabilities.createFromXml(
+                    parser);
+        });
+    }
+}
diff --git a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
index a4e429a..07b5876 100644
--- a/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
+++ b/tests/tests/mediatranscoding/src/android/media/mediatranscoding/cts/MediaTranscodeManagerTest.java
@@ -24,6 +24,7 @@
 import android.media.ApplicationMediaCapabilities;
 import android.media.MediaFormat;
 import android.media.MediaTranscodeManager;
+import android.media.MediaTranscodeManager.TranscodingRequest;
 import android.media.MediaTranscodeManager.TranscodingSession;
 import android.net.Uri;
 import android.os.Bundle;
@@ -148,8 +149,8 @@
      */
     public void testCreateTranscodingRequestWithNullDestinationUri() throws Exception {
         assertThrows(IllegalArgumentException.class, () -> {
-            MediaTranscodeManager.TranscodingRequest request =
-                    new MediaTranscodeManager.TranscodingRequest.Builder()
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
                             .setSourceUri(mSourceHEVCVideoUri)
                             .setDestinationUri(null)
                             .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
@@ -164,8 +165,8 @@
      */
     public void testCreateTranscodingWithInvalidClientPid() throws Exception {
         assertThrows(IllegalArgumentException.class, () -> {
-            MediaTranscodeManager.TranscodingRequest request =
-                    new MediaTranscodeManager.TranscodingRequest.Builder()
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
                             .setSourceUri(mSourceHEVCVideoUri)
                             .setDestinationUri(mDestinationUri)
                             .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
@@ -181,8 +182,8 @@
      */
     public void testCreateTranscodingWithInvalidClientUid() throws Exception {
         assertThrows(IllegalArgumentException.class, () -> {
-            MediaTranscodeManager.TranscodingRequest request =
-                    new MediaTranscodeManager.TranscodingRequest.Builder()
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
                             .setSourceUri(mSourceHEVCVideoUri)
                             .setDestinationUri(mDestinationUri)
                             .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
@@ -198,8 +199,8 @@
      */
     public void testCreateTranscodingRequestWithNullSourceUri() throws Exception {
         assertThrows(IllegalArgumentException.class, () -> {
-            MediaTranscodeManager.TranscodingRequest request =
-                    new MediaTranscodeManager.TranscodingRequest.Builder()
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
                             .setSourceUri(null)
                             .setDestinationUri(mDestinationUri)
                             .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
@@ -213,8 +214,8 @@
      */
     public void testCreateTranscodingRequestWithoutSourceUri() throws Exception {
         assertThrows(UnsupportedOperationException.class, () -> {
-            MediaTranscodeManager.TranscodingRequest request =
-                    new MediaTranscodeManager.TranscodingRequest.Builder()
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
                             .setDestinationUri(mDestinationUri)
                             .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
                             .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
@@ -228,8 +229,8 @@
      */
     public void testCreateTranscodingRequestWithoutDestinationUri() throws Exception {
         assertThrows(UnsupportedOperationException.class, () -> {
-            MediaTranscodeManager.TranscodingRequest request =
-                    new MediaTranscodeManager.TranscodingRequest.Builder()
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
                             .setSourceUri(mSourceHEVCVideoUri)
                             .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
                             .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
@@ -244,8 +245,8 @@
      */
     public void testCreateTranscodingRequestWithoutVideoFormat() throws Exception {
         assertThrows(UnsupportedOperationException.class, () -> {
-            MediaTranscodeManager.TranscodingRequest request =
-                    new MediaTranscodeManager.TranscodingRequest.Builder()
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
                             .setSourceUri(mSourceHEVCVideoUri)
                             .setDestinationUri(mDestinationUri)
                             .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
@@ -258,8 +259,8 @@
             throws Exception {
         Semaphore transcodeCompleteSemaphore = new Semaphore(0);
 
-        MediaTranscodeManager.TranscodingRequest request =
-                new MediaTranscodeManager.TranscodingRequest.Builder()
+        TranscodingRequest request =
+                new TranscodingRequest.Builder()
                         .setSourceUri(srcUri)
                         .setDestinationUri(dstUri)
                         .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
@@ -268,7 +269,7 @@
                         .build();
         Executor listenerExecutor = Executors.newSingleThreadExecutor();
 
-        MediaTranscodeManager.TranscodingSession session = mMediaTranscodeManager.enqueueRequest(
+        TranscodingSession session = mMediaTranscodeManager.enqueueRequest(
                 request,
                 listenerExecutor,
                 transcodingSession -> {
@@ -287,7 +288,7 @@
         }
 
         File dstFile = new File(dstUri.getPath());;
-        if (expectedResult == MediaTranscodeManager.TranscodingSession.RESULT_SUCCESS) {
+        if (expectedResult == TranscodingSession.RESULT_SUCCESS) {
             // Checks the destination file get generated.
             assertTrue("Failed to create destination file", dstFile.exists());
         }
@@ -307,7 +308,7 @@
         Log.d(TAG, "Transcoding " + invalidSrcUri + "to destination: " + destinationUri);
 
         testTranscodingWithExpectResult(invalidSrcUri, destinationUri,
-                MediaTranscodeManager.TranscodingSession.RESULT_ERROR);
+                TranscodingSession.RESULT_ERROR);
     }
 
     // Tests transcoding to a uri in res folder and expects failure as test could not write to res
@@ -319,7 +320,7 @@
         Log.d(TAG, "Transcoding to destination: " + destinationUri);
 
         testTranscodingWithExpectResult(mSourceHEVCVideoUri, destinationUri,
-                MediaTranscodeManager.TranscodingSession.RESULT_ERROR);
+                TranscodingSession.RESULT_ERROR);
     }
 
     // Tests transcoding to a uri in internal cache folder and expects success.
@@ -330,7 +331,7 @@
         Log.d(TAG, "Transcoding to cache: " + destinationUri);
 
         testTranscodingWithExpectResult(mSourceHEVCVideoUri, destinationUri,
-                MediaTranscodeManager.TranscodingSession.RESULT_SUCCESS);
+                TranscodingSession.RESULT_SUCCESS);
     }
 
     // Tests transcoding to a uri in internal files directory and expects success.
@@ -340,7 +341,7 @@
         Log.i(TAG, "Transcoding to files dir: " + destinationUri);
 
         testTranscodingWithExpectResult(mSourceHEVCVideoUri, destinationUri,
-                MediaTranscodeManager.TranscodingSession.RESULT_SUCCESS);
+                TranscodingSession.RESULT_SUCCESS);
     }
 
     public void testTranscodingFromHevcToAvc() throws Exception {
@@ -353,8 +354,8 @@
         ApplicationMediaCapabilities clientCaps =
                 new ApplicationMediaCapabilities.Builder().build();
 
-        MediaTranscodeManager.TranscodingRequest.MediaFormatResolver
-                resolver = new MediaTranscodeManager.TranscodingRequest.MediaFormatResolver()
+        TranscodingRequest.MediaFormatResolver
+                resolver = new TranscodingRequest.MediaFormatResolver()
                 .setSourceVideoFormatHint(MediaFormat.createVideoFormat(
                         MediaFormat.MIMETYPE_VIDEO_HEVC, WIDTH, HEIGHT))
                 .setClientCapabilities(clientCaps);
@@ -365,8 +366,8 @@
         int pid = android.os.Process.myPid();
         int uid = android.os.Process.myUid();
 
-        MediaTranscodeManager.TranscodingRequest request =
-                new MediaTranscodeManager.TranscodingRequest.Builder()
+        TranscodingRequest request =
+                new TranscodingRequest.Builder()
                         .setSourceUri(mSourceHEVCVideoUri)
                         .setDestinationUri(destinationUri)
                         .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
@@ -381,7 +382,7 @@
 
         Log.d(TAG, "transcoding to format: " + videoTrackFormat);
 
-        MediaTranscodeManager.TranscodingSession session = mMediaTranscodeManager.enqueueRequest(
+        TranscodingSession session = mMediaTranscodeManager.enqueueRequest(
                 request,
                 listenerExecutor,
                 transcodingSession -> {
@@ -416,8 +417,8 @@
         Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
                 + mContext.getCacheDir().getAbsolutePath() + "/HevcTranscode.mp4");
 
-        MediaTranscodeManager.TranscodingRequest request =
-                new MediaTranscodeManager.TranscodingRequest.Builder()
+        TranscodingRequest request =
+                new TranscodingRequest.Builder()
                         .setSourceUri(mSourceHEVCVideoUri)
                         .setDestinationUri(destinationUri)
                         .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
@@ -426,7 +427,7 @@
                         .build();
         Executor listenerExecutor = Executors.newSingleThreadExecutor();
 
-        MediaTranscodeManager.TranscodingSession session = mMediaTranscodeManager.enqueueRequest(
+        TranscodingSession session = mMediaTranscodeManager.enqueueRequest(
                 request,
                 listenerExecutor,
                 transcodingSession -> {
@@ -467,8 +468,8 @@
             // Use init dameon's pid and uid.
             int pid = 1;
             int uid = 0;
-            MediaTranscodeManager.TranscodingRequest request =
-                    new MediaTranscodeManager.TranscodingRequest.Builder()
+            TranscodingRequest request =
+                    new TranscodingRequest.Builder()
                             .setSourceUri(mSourceHEVCVideoUri)
                             .setDestinationUri(mDestinationUri)
                             .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
@@ -479,7 +480,7 @@
                             .build();
             Executor listenerExecutor = Executors.newSingleThreadExecutor();
 
-            MediaTranscodeManager.TranscodingSession session =
+            TranscodingSession session =
                     mMediaTranscodeManager.enqueueRequest(
                             request,
                             listenerExecutor,
@@ -489,5 +490,56 @@
         });
     }
 
-    // TODO(hkuang): Add test for progress update.
+    public void testTranscodingProgressUpdate() throws Exception {
+        Log.d(TAG, "Starting: testTranscodingProgressUpdate");
+
+        Semaphore transcodeCompleteSemaphore = new Semaphore(0);
+
+        // Create a file Uri: file:///data/user/0/android.media.mediatranscoding.cts/cache/HevcTranscode.mp4
+        Uri destinationUri = Uri.parse(ContentResolver.SCHEME_FILE + "://"
+                + mContext.getCacheDir().getAbsolutePath() + "/HevcTranscode.mp4");
+
+         TranscodingRequest request =
+                new TranscodingRequest.Builder()
+                        .setSourceUri(mSourceHEVCVideoUri)
+                        .setDestinationUri(destinationUri)
+                        .setType(MediaTranscodeManager.TRANSCODING_TYPE_VIDEO)
+                        .setPriority(MediaTranscodeManager.PRIORITY_REALTIME)
+                        .setVideoTrackFormat(createMediaFormat())
+                        .build();
+        Executor listenerExecutor = Executors.newSingleThreadExecutor();
+
+        TranscodingSession session = mMediaTranscodeManager.enqueueRequest(request,
+                listenerExecutor,
+                TranscodingSession -> {
+                    Log.d(TAG,
+                            "Transcoding completed with result: " + TranscodingSession.getResult());
+                    assertEquals(TranscodingSession.RESULT_SUCCESS, TranscodingSession.getResult());
+                    transcodeCompleteSemaphore.release();
+                });
+        assertNotNull(session);
+
+        AtomicInteger progressUpdateCount = new AtomicInteger(0);
+
+        // Set progress update executor and use the same executor as result listener.
+        session.setOnProgressUpdateListener(listenerExecutor,
+                new TranscodingSession.OnProgressUpdateListener() {
+                    int mPreviousProgress = 0;
+
+                    @Override
+                    public void onProgressUpdate(TranscodingSession session, int newProgress) {
+                        assertTrue("Invalid proress update", newProgress > mPreviousProgress);
+                        assertTrue("Invalid proress update", newProgress <= 100);
+                        mPreviousProgress = newProgress;
+                        progressUpdateCount.getAndIncrement();
+                        Log.i(TAG, "Get progress update " + newProgress);
+                    }
+                });
+
+        boolean finishedOnTime = transcodeCompleteSemaphore.tryAcquire(
+                TRANSCODE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
+        assertTrue("Transcode failed to complete in time.", finishedOnTime);
+        assertTrue("Failed to receive at least 10 progress updates",
+                progressUpdateCount.get() > 10);
+    }
 }
\ No newline at end of file
diff --git a/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml b/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml
index 1f8f283..457b7ef 100644
--- a/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml
+++ b/tests/tests/packageinstaller/atomicinstall/AndroidManifest.xml
@@ -18,8 +18,6 @@
           package="com.android.tests.atomicinstall" >
 
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 71bd0ee..9a175c3 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -3996,6 +3996,14 @@
     <permission android:name="android.permission.OVERRIDE_DISPLAY_MODE_REQUESTS"
         android:protectionLevel="signature" />
 
+    <!-- Allows an application to override the display mode requests
+         so the app requested mode will be selected and user settings and display
+         policies will be ignored.
+         @hide
+         @TestApi -->
+    <permission android:name="android.permission.MODIFY_REFRESH_RATE_SWITCHING_TYPE"
+                android:protectionLevel="signature" />
+
     <!-- @SystemApi Allows an application to control VPN.
          <p>Not for use by third-party applications.</p>
          @hide -->
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 340fb90..7e4402e 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -92,8 +92,6 @@
             </intent-filter>
         </activity>
 
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-             android:exported="true"/>
         <receiver android:name="android.security.cts.PackageVerificationsBroadcastReceiver"
              android:permission="android.permission.BIND_PACKAGE_VERIFIER"
              android:exported="true">
diff --git a/tests/tests/tv/src/android/media/tv/cts/BundledTvInputServiceTest.java b/tests/tests/tv/src/android/media/tv/cts/BundledTvInputServiceTest.java
index a50531d..0df3e55 100644
--- a/tests/tests/tv/src/android/media/tv/cts/BundledTvInputServiceTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/BundledTvInputServiceTest.java
@@ -23,6 +23,7 @@
 import android.media.tv.TvInputInfo;
 import android.media.tv.TvInputManager;
 import android.media.tv.TvView;
+import android.platform.test.annotations.Presubmit;
 import android.test.ActivityInstrumentationTestCase2;
 import android.util.ArrayMap;
 
@@ -37,6 +38,7 @@
 /**
  * Test {@link android.media.tv.TvView}.
  */
+@Presubmit
 public class BundledTvInputServiceTest
         extends ActivityInstrumentationTestCase2<TvViewStubActivity> {
     /** The maximum time to wait for an operation. */
diff --git a/tests/tests/tv/src/android/media/tv/cts/HardwareSessionTest.java b/tests/tests/tv/src/android/media/tv/cts/HardwareSessionTest.java
index c0b5e80..57fb3e7 100644
--- a/tests/tests/tv/src/android/media/tv/cts/HardwareSessionTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/HardwareSessionTest.java
@@ -26,6 +26,7 @@
 import android.media.tv.TvView;
 import android.media.tv.cts.HardwareSessionTest.HardwareProxyTvInputService.CountingSession;
 import android.net.Uri;
+import android.platform.test.annotations.Presubmit;
 import android.test.ActivityInstrumentationTestCase2;
 
 import android.tv.cts.R;
@@ -38,6 +39,7 @@
 /**
  * Test {@link android.media.tv.TvInputService.HardwareSession}.
  */
+@Presubmit
 public class HardwareSessionTest extends ActivityInstrumentationTestCase2<TvViewStubActivity> {
     /** The maximum time to wait for an operation. */
     private static final long TIME_OUT = 15000L;
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvContentRatingTest.java b/tests/tests/tv/src/android/media/tv/cts/TvContentRatingTest.java
index 9acff92..4c0bcc7 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvContentRatingTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvContentRatingTest.java
@@ -17,6 +17,7 @@
 package android.media.tv.cts;
 
 import android.media.tv.TvContentRating;
+import android.platform.test.annotations.Presubmit;
 
 import java.util.List;
 
@@ -25,6 +26,7 @@
 /**
  * Test for {@link android.media.tv.TvContentRating}.
  */
+@Presubmit
 public class TvContentRatingTest extends TestCase {
 
     private static final String DOMAIN = "android.media.tv";
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvInputInfoTest.java b/tests/tests/tv/src/android/media/tv/cts/TvInputInfoTest.java
index 2b2e269..115a9c4 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvInputInfoTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvInputInfoTest.java
@@ -25,12 +25,14 @@
 import android.media.tv.TvInputManager;
 import android.os.Bundle;
 import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
 import android.test.AndroidTestCase;
 import android.text.TextUtils;
 
 /**
  * Test for {@link android.media.tv.TvInputInfo}.
  */
+@Presubmit
 public class TvInputInfoTest extends AndroidTestCase {
     private TvInputInfo mStubInfo;
     private PackageManager mPackageManager;
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvTrackInfoTest.java b/tests/tests/tv/src/android/media/tv/cts/TvTrackInfoTest.java
index dcae3c8..b2c2041 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvTrackInfoTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvTrackInfoTest.java
@@ -23,6 +23,7 @@
 import android.content.Context;
 import android.media.tv.TvTrackInfo;
 import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
 
 import androidx.test.core.os.Parcelables;
 
@@ -34,13 +35,13 @@
 /**
  * Test {@link android.media.tv.TvTrackInfo}.
  */
+@Presubmit
 public class TvTrackInfoTest {
 
     @Rule
     public final RequiredServiceRule requiredServiceRule = new RequiredServiceRule(
             Context.TV_INPUT_SERVICE);
 
-
     @Test
     public void newAudioTrack_default() {
         final TvTrackInfo info = new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "default")
diff --git a/tests/tests/util/AndroidManifest.xml b/tests/tests/util/AndroidManifest.xml
index 77ab380..0e71890 100644
--- a/tests/tests/util/AndroidManifest.xml
+++ b/tests/tests/util/AndroidManifest.xml
@@ -21,8 +21,6 @@
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.READ_LOGS" />
     <application>
-        <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
-                  android:exported="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
index 658fde7..2cf5e3f 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewSslTest.java
@@ -824,6 +824,26 @@
         assertEquals(callCount + 1, webViewClient.getClientCertRequestCount());
     }
 
+    /**
+     * Loads a url until a specific error code. This is meant to be used when two different errors
+     * can race. Specifically, this is meant to be used to workaround the TLS 1.3 (Android Q and
+     * above) race condition where a server <b>may</b> close the connection at the same time the
+     * client sends the HTTP request, emitting {@code ERROR_CONNECT} instead of {@code
+     * ERROR_FAILED_SSL_HANDSHAKE}.
+     */
+    private void loadUrlUntilError(SslErrorWebViewClient client, String url,
+            int expectedErrorCode) {
+        int maxTries = 40;
+        for (int i = 0; i < maxTries; i++) {
+            mOnUiThread.loadUrlAndWaitForCompletion(url);
+            if (client.onReceivedErrorCode() == expectedErrorCode) {
+                return;
+            }
+        }
+        throw new RuntimeException(
+                "Reached max number of tries and never saw error " + expectedErrorCode);
+    }
+
     @FlakyTest(bugId = 172332767)
     public void testIgnoreClientCertRequest() throws Throwable {
         if (!NullWebViewUtils.isWebViewAvailable()) {
@@ -836,18 +856,23 @@
         clearClientCertPreferences();
         // Ignore the request. Load should fail.
         webViewClient.setAction(ClientCertWebViewClient.IGNORE);
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        loadUrlUntilError(webViewClient, url, WebViewClient.ERROR_FAILED_SSL_HANDSHAKE);
         assertFalse(TestHtmlConstants.HELLO_WORLD_TITLE.equals(mOnUiThread.getTitle()));
-        assertFailedHandshakeOrConnectionError(webViewClient.onReceivedErrorCode());
+        // At least one of the loads done by loadUrlUntilError() should produce
+        // onReceivedClientCertRequest.
+        assertTrue("onReceivedClientCertRequest should be called at least once",
+                webViewClient.getClientCertRequestCount() >= 1);
 
         // Load a different page from the same domain, ignoring the request. We should get a callback,
         // and load should fail.
         int callCount = webViewClient.getClientCertRequestCount();
         url = mWebServer.getAssetUrl(TestHtmlConstants.HTML_URL1);
-        mOnUiThread.loadUrlAndWaitForCompletion(url);
+        loadUrlUntilError(webViewClient, url, WebViewClient.ERROR_FAILED_SSL_HANDSHAKE);
         assertFalse(TestHtmlConstants.HTML_URL1_TITLE.equals(mOnUiThread.getTitle()));
-        assertFailedHandshakeOrConnectionError(webViewClient.onReceivedErrorCode());
-        assertEquals(callCount + 1, webViewClient.getClientCertRequestCount());
+        // At least one of the loads done by loadUrlUntilError() should produce
+        // onReceivedClientCertRequest.
+        assertTrue("onReceivedClientCertRequest should be called at least once for second URL",
+                webViewClient.getClientCertRequestCount() >= callCount + 1);
 
         // Reload, proceeding the request. Load should succeed.
         webViewClient.setAction(ClientCertWebViewClient.PROCEED);
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java b/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java
index 8d8f8eb..e6c5a1d 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/EasyConnectStatusCallbackTest.java
@@ -247,12 +247,15 @@
             // skip the test if Easy Connect is not supported
             return;
         }
+        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
         try {
+            uiAutomation.adoptShellPermissionIdentity();
             mWifiManager.startEasyConnectAsEnrolleeResponder(TEST_WRONG_DEVICE_INFO,
                     EASY_CONNECT_CRYPTOGRAPHY_CURVE_DEFAULT, mExecutor,
                     mEasyConnectStatusCallback);
             fail("startEasyConnectAsEnrolleeResponder did not throw an IllegalArgumentException"
                     + "on passing a wrong device info!");
         } catch (IllegalArgumentException expected) {}
+        uiAutomation.dropShellPermissionIdentity();
     }
 }
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 0729b44..7ee1b72 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -2880,6 +2880,51 @@
     }
 
     /**
+     * Verify WifiNetworkSuggestion.Builder.setIsEnhancedMacRandomizationEnabled(true) creates a
+     * WifiConfiguration with macRandomizationSetting == RANDOMIZATION_ENHANCED.
+     * Then verify by default, a WifiConfiguration created by suggestions should have
+     * macRandomizationSetting == RANDOMIZATION_PERSISTENT.
+     * TODO(b/167575586): Wait for S SDK finalization to determine the final minSdkVersion.
+     */
+    @SdkSuppress(minSdkVersion = 31, codeName = "S")
+    public void testSuggestionBuilderEnhancedMacRandomizationSetting() throws Exception {
+        if (!WifiFeature.isWifiSupported(getContext())) {
+            // skip the test if WiFi is not supported
+            return;
+        }
+        WifiNetworkSuggestion suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID).setWpa2Passphrase(TEST_PASSPHRASE)
+                .setIsEnhancedMacRandomizationEnabled(true)
+                .build();
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)));
+        verifySuggestionFoundWithMacRandomizationSetting(TEST_SSID,
+                WifiConfiguration.RANDOMIZATION_ENHANCED);
+
+        suggestion = new WifiNetworkSuggestion.Builder()
+                .setSsid(TEST_SSID).setWpa2Passphrase(TEST_PASSPHRASE)
+                .build();
+        assertEquals(WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS,
+                mWifiManager.addNetworkSuggestions(Arrays.asList(suggestion)));
+        verifySuggestionFoundWithMacRandomizationSetting(TEST_SSID,
+                WifiConfiguration.RANDOMIZATION_PERSISTENT);
+    }
+
+    private void verifySuggestionFoundWithMacRandomizationSetting(String ssid,
+            int macRandomizationSetting) {
+        List<WifiNetworkSuggestion> retrievedSuggestions = mWifiManager.getNetworkSuggestions();
+        for (WifiNetworkSuggestion entry : retrievedSuggestions) {
+            if (entry.getSsid().equals(ssid)) {
+                assertEquals(macRandomizationSetting,
+                        entry.getWifiConfiguration().macRandomizationSetting);
+                return; // pass test after the MAC randomization setting is verified.
+            }
+        }
+        fail("WifiNetworkSuggestion not found for SSID=" + ssid + ", macRandomizationSetting="
+                + macRandomizationSetting);
+    }
+
+    /**
      * Tests {@link WifiManager#getWifiConfigForMatchedNetworkSuggestionsSharedWithUser(List)}
      */
     public void testGetAllWifiConfigForMatchedNetworkSuggestion() {