Merge "Use DE storage for writing shared prefs during the test."
diff --git a/apps/CameraITS/tests/scene2_e/test_continuous_picture.py b/apps/CameraITS/tests/scene2_e/test_continuous_picture.py
new file mode 100644
index 0000000..3f64e3a
--- /dev/null
+++ b/apps/CameraITS/tests/scene2_e/test_continuous_picture.py
@@ -0,0 +1,98 @@
+# 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
+#
+#      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.
+
+import os.path
+import its.caps
+import its.device
+import its.image
+import its.objects
+
+CONTINUOUS_PICTURE_MODE = 4
+CONVERGED_3A = [[2, 2, 2], [2, 5, 2], [2, 6, 2]]  # [AE, AF, AWB]
+# AE_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED,
+#             4: FLASH_REQ, 5: PRECAPTURE}
+# AF_STATES: {0: INACTIVE, 1: PASSIVE_SCAN, 2: PASSIVE_FOCUSED,
+#             3: PASSIVE_UNFOCUSED, 4: ACTIVE_SCAN, 5: FOCUS_LOCKED,
+#             6: NOT_FOCUSED_LOCKED}
+# AWB_STATES: {0: INACTIVE, 1: SEARCHING, 2: CONVERGED, 3: LOCKED}
+NAME = os.path.basename(__file__).split('.')[0]
+NUM_FRAMES = 50
+W, H = 640, 480
+
+
+def capture_frames(cam, debug):
+    """Capture frames."""
+    cap_data_list = []
+    req = its.objects.auto_capture_request()
+    req['android.control.afMode'] = CONTINUOUS_PICTURE_MODE
+    fmt = {'format': 'yuv', 'width': W, 'height': H}
+    caps = cam.do_capture([req]*NUM_FRAMES, fmt)
+
+    # extract frame metadata and frame
+    for i, cap in enumerate(caps):
+        cap_data = {}
+        md = cap['metadata']
+        exp = md['android.sensor.exposureTime']
+        iso = md['android.sensor.sensitivity']
+        fd = md['android.lens.focalLength']
+        ae_state = md['android.control.aeState']
+        af_state = md['android.control.afState']
+        awb_state = md['android.control.awbState']
+        fd_str = 'infinity'
+        if fd != 0.0:
+            fd_str = str(round(1.0E2/fd, 2)) + 'cm'
+        img = its.image.convert_capture_to_rgb_image(cap)
+        tile = its.image.get_image_patch(img, 0.45, 0.45, 0.1, 0.1)
+        g = its.image.compute_image_means(tile)[1]
+        print '%d, iso: %d, exp: %.2fms, fd: %s, avg: %.3f' % (
+                i, iso, exp*1E-6, fd_str, g),
+        print '[ae,af,awb]: [%d,%d,%d]' % (ae_state, af_state, awb_state)
+        cap_data['exp'] = exp
+        cap_data['iso'] = iso
+        cap_data['fd'] = fd
+        cap_data['3a_state'] = [ae_state, af_state, awb_state]
+        cap_data['avg'] = g
+        cap_data_list.append(cap_data)
+        if debug:
+            its.image.write_image(img, '%s_%d.jpg' % (NAME, i))
+    return cap_data_list
+
+
+def main():
+    """Test 3A converges in CONTINUOUS_PICTURE mode.
+
+    Set camera into CONTINUOUS_PICTURE mode and do NUM_FRAMES capture.
+    By the end of NUM_FRAMES capture, 3A should be in converged state.
+    """
+
+    # check for skip conditions and do 3a up front
+    with its.device.ItsSession() as cam:
+        props = cam.get_camera_properties()
+        props = cam.override_with_hidden_physical_camera_props(props)
+        its.caps.skip_unless(its.caps.continuous_picture(props) and
+                             its.caps.read_3a(props))
+        debug = its.caps.debug_mode()
+        cam.do_3a()
+
+        # ensure 3a settles in CONTINUOUS_PICTURE mode with no scene change
+        cap_data = capture_frames(cam, debug)
+        final_3a = cap_data[NUM_FRAMES-1]['3a_state']
+        msg = '\n Last frame [ae, af, awb] state: [%d, %d, %d]' % (
+                final_3a[0], final_3a[1], final_3a[2])
+        msg += '\n Converged states:' + str(CONVERGED_3A)
+        assert final_3a in CONVERGED_3A, msg
+
+
+if __name__ == '__main__':
+    main()
diff --git a/apps/CameraITS/tools/run_all_tests.py b/apps/CameraITS/tools/run_all_tests.py
index 20fd98b..021a324 100644
--- a/apps/CameraITS/tools/run_all_tests.py
+++ b/apps/CameraITS/tools/run_all_tests.py
@@ -113,7 +113,8 @@
                 ['test_num_faces', 30]
         ],
         'scene2_e': [
-                ['test_num_faces', 30]
+                ['test_num_faces', 30],
+                ['test_continuous_picture', 30]
         ],
         'scene3': [],
         'scene4': [],
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/AndroidManifest.xml b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/AndroidManifest.xml
index 02b480a..5564c3a 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/AndroidManifest.xml
@@ -42,6 +42,8 @@
                 <category android:name="android.intent.category.DEFAULT"/>
             </intent-filter>
         </activity>
+
+        <activity android:name=".CrossProfileSameTaskLauncherActivity" android:exported="true"/>
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/res/layout/cross_profile_same_task_launcher.xml b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/res/layout/cross_profile_same_task_launcher.xml
new file mode 100644
index 0000000..8d9cf94
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/res/layout/cross_profile_same_task_launcher.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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+              android:orientation="vertical"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent">
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="CrossProfileSameTaskLauncherActivity"
+    />
+
+</LinearLayout>
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java
index 87878a1..be5c4b1 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileAppsStartActivityTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
+import android.app.Activity;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -36,7 +37,6 @@
 import android.support.test.uiautomator.UiDevice;
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.Until;
-import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -104,7 +104,7 @@
         intent.setComponent(MainActivity.getComponentName(mContext));
         ShellIdentityUtils.dropShellPermissionIdentity();
 
-        mCrossProfileApps.startActivity(intent, mTargetUser);
+        mCrossProfileApps.startActivity(intent, mTargetUser, /* callingActivity= */ null);
     }
 
     @Test
@@ -113,8 +113,11 @@
         intent.setComponent(MainActivity.getComponentName(mContext));
         ShellIdentityUtils.dropShellPermissionIdentity();
 
-        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCrossProfileApps,
-                crossProfileApps -> crossProfileApps.startActivity(intent, mTargetUser), INTERACT_ACROSS_PROFILES);
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mCrossProfileApps,
+                crossProfileApps -> crossProfileApps.startActivity(
+                        intent, mTargetUser, /* callingActivity= */ null),
+                INTERACT_ACROSS_PROFILES);
 
         // Look for the text view to verify that MainActivity is started.
         UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW)),
@@ -130,8 +133,11 @@
         intent.setComponent(MainActivity.getComponentName(mContext));
         ShellIdentityUtils.dropShellPermissionIdentity();
 
-        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCrossProfileApps,
-                crossProfileApps -> crossProfileApps.startActivity(intent, mTargetUser), INTERACT_ACROSS_USERS);
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mCrossProfileApps,
+                crossProfileApps -> crossProfileApps.startActivity(
+                        intent, mTargetUser, /* callingActivity= */ null),
+                INTERACT_ACROSS_USERS);
 
         // Look for the text view to verify that MainActivity is started.
         UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW)),
@@ -147,8 +153,11 @@
         intent.setComponent(MainActivity.getComponentName(mContext));
         ShellIdentityUtils.dropShellPermissionIdentity();
 
-        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCrossProfileApps,
-                crossProfileApps -> crossProfileApps.startActivity(intent, mTargetUser), INTERACT_ACROSS_USERS_FULL);
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mCrossProfileApps,
+                crossProfileApps -> crossProfileApps.startActivity(
+                        intent, mTargetUser, /* callingActivity= */ null),
+                INTERACT_ACROSS_USERS_FULL);
 
         // Look for the text view to verify that MainActivity is started.
         UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW)),
@@ -164,9 +173,10 @@
         Intent nonMainActivityImplicitIntent = new Intent();
         nonMainActivityImplicitIntent.setAction(Intent.ACTION_VIEW);
 
-        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCrossProfileApps,
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mCrossProfileApps,
                 crossProfileApps -> crossProfileApps.startActivity(
-                        nonMainActivityImplicitIntent, mTargetUser));
+                        nonMainActivityImplicitIntent, mTargetUser, /* callingActivity= */ null));
     }
 
     @Test
@@ -175,9 +185,10 @@
         mainActivityIntent.setComponent(MainActivity.getComponentName(mContext));
 
         try {
-            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCrossProfileApps,
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mCrossProfileApps,
                     crossProfileApps -> mCrossProfileApps.startActivity(
-                            mainActivityIntent, mTargetUser));
+                            mainActivityIntent, mTargetUser, /* callingActivity= */ null));
 
             // Look for the text view to verify that MainActivity is started.
             UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW)),
@@ -196,9 +207,10 @@
         nonMainActivityIntent.setComponent(NonMainActivity.getComponentName(mContext));
 
         try {
-            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCrossProfileApps,
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                    mCrossProfileApps,
                     crossProfileApps -> mCrossProfileApps.startActivity(
-                            nonMainActivityIntent, mTargetUser));
+                            nonMainActivityIntent, mTargetUser, /* callingActivity= */ null));
 
             // Look for the text view to verify that NonMainActivity is started.
             UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW2)),
@@ -212,7 +224,33 @@
     }
 
     /**
-     * Calls {@link CrossProfileApps#startActivity(Intent, UserHandle)}. This can then be used by
+     * Starts an activity in the same task in the target user. Asserts that the activity is
+     * correctly started in the correct user, but the host-side test should verify that the tasks
+     * are the same using the log messages printed by each activity.
+     */
+    @Test
+    public void testStartActivityIntent_sameTaskByDefault() throws Exception {
+        try {
+            final Intent crossProfileSameTaskCheckerIntent = new Intent();
+            crossProfileSameTaskCheckerIntent.setComponent(
+                    CrossProfileSameTaskLauncherActivity.getComponentName(mContext));
+            crossProfileSameTaskCheckerIntent.putExtra(
+                    CrossProfileSameTaskLauncherActivity.TARGET_USER_EXTRA, mTargetUser);
+            mContext.startActivity(crossProfileSameTaskCheckerIntent);
+
+            // Look for the text view to verify that NonMainActivity is started.
+            UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW2)),
+                    TIMEOUT_WAIT_UI);
+            assertNotNull("Failed to start non-main activity in target user", textView);
+            assertEquals("Non-Main Activity is started in wrong user",
+                    String.valueOf(mUserSerialNumber), textView.getText());
+        } catch (Exception e) {
+            fail("unable to start cross-profile activity in the same task: " + e);
+        }
+    }
+
+    /**
+     * Calls {@link CrossProfileApps#startActivity(Intent, UserHandle, Activity)}. This can then be used by
      * host-side tests.
      */
     @Test
@@ -222,8 +260,8 @@
 
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
                 mCrossProfileApps,
-                crossProfileApps ->
-                        mCrossProfileApps.startActivity(nonMainActivityIntent, mTargetUser));
+                crossProfileApps -> mCrossProfileApps.startActivity(
+                        nonMainActivityIntent, mTargetUser, /* callingActivity= */ null));
     }
 
     @Test
@@ -267,8 +305,10 @@
         Intent nonExportedActivityIntent = new Intent();
         nonExportedActivityIntent.setComponent(NonExportedActivity.getComponentName(mContext));
 
-        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mCrossProfileApps,
-                crossProfileApps -> mCrossProfileApps.startActivity(nonExportedActivityIntent, mTargetUser));
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                mCrossProfileApps,
+                crossProfileApps -> mCrossProfileApps.startActivity(
+                        nonExportedActivityIntent, mTargetUser, /* callingActivity= */ null));
 
         // Look for the text view to verify that NonExportedActivity is started.
         UiObject2 textView = mDevice.wait(Until.findObject(By.res(ID_USER_TEXTVIEW2)),
@@ -290,9 +330,8 @@
         otherPackageIntent.setComponent(new ComponentName(
                 "com.android.cts.launcherapps.simpleapp",
                 "com.android.cts.launcherapps.simpleapp.SimpleActivity"));
-        mCrossProfileApps.startActivity(otherPackageIntent,
-                mTargetUser
-        );
+        mCrossProfileApps.startActivity(
+                otherPackageIntent, mTargetUser, /* callingActivity= */ null);
     }
 
     @Test(expected = SecurityException.class)
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileSameTaskLauncherActivity.java b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileSameTaskLauncherActivity.java
new file mode 100644
index 0000000..9d1f209
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/CrossProfileSameTaskLauncherActivity.java
@@ -0,0 +1,66 @@
+/*
+ * 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.crossprofileappstest;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.CrossProfileApps;
+import android.os.Bundle;
+import android.os.UserHandle;
+import android.util.Log;
+
+import androidx.annotation.Nullable;
+
+import com.android.compatibility.common.util.ShellIdentityUtils;
+
+/**
+ * An activity that launches the {@link NonMainActivity} in a different user within the same task.
+ *
+ * <p>Logs its task ID with the following format:
+ * "CrossProfileSameTaskLauncherActivity#taskId#[taskId]#", where [taskId] is the actual task ID,
+ * such as NonMainActivity#taskId#4#.
+ */
+public class CrossProfileSameTaskLauncherActivity extends Activity {
+    static final String TARGET_USER_EXTRA = "TARGET_USER";
+
+    private static final String LOG_TAG = "CrossProfileSameTaskChe"; // 23 chars max
+
+    @Override
+    public void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        final Intent intent = getIntent();
+        if (!intent.hasExtra(TARGET_USER_EXTRA)) {
+            throw new IllegalStateException(
+                    "ActivityForwarder started without the extra: " + TARGET_USER_EXTRA);
+        }
+        setContentView(R.layout.cross_profile_same_task_launcher);
+        Log.w(LOG_TAG, "CrossProfileSameTaskLauncherActivity#taskId#" + getTaskId() + "#");
+        final UserHandle targetUser = intent.getParcelableExtra(TARGET_USER_EXTRA);
+        final Intent nonMainActivityIntent = new Intent();
+        nonMainActivityIntent.setComponent(NonMainActivity.getComponentName(this));
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+                getSystemService(CrossProfileApps.class),
+                crossProfileApps
+                        -> crossProfileApps.startActivity(nonMainActivityIntent, targetUser, this));
+    }
+
+    static ComponentName getComponentName(Context context) {
+        return new ComponentName(context, CrossProfileSameTaskLauncherActivity.class);
+    }
+}
diff --git a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonMainActivity.java b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonMainActivity.java
index 4131a98..c2ad97c 100644
--- a/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonMainActivity.java
+++ b/hostsidetests/devicepolicy/app/CrossProfileTestApps/CrossProfileAppsTest/src/com/android/cts/crossprofileappstest/NonMainActivity.java
@@ -21,14 +21,23 @@
 import android.os.Bundle;
 import android.os.Process;
 import android.os.UserManager;
+import android.util.Log;
 import android.widget.TextView;
 
+/**
+ * An activity that is not the main activity of the application.
+ *
+ * <p>Logs its task ID with the following format: "NonMainActivity#taskId#[taskId]#", where [taskId]
+ * is the actual task ID, such as NonMainActivity#taskId#4#.
+ */
 public class NonMainActivity extends Activity {
+    private static final String LOG_TAG = "NonMainActivity";
 
     @Override
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
         setContentView(R.layout.non_main);
+        Log.w(LOG_TAG, "NonMainActivity#taskId#" + getTaskId() + "#");
     }
 
     @Override
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
index eff3ce7..d427645 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/CrossProfileAppsHostSideTest.java
@@ -7,17 +7,25 @@
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
 import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+
 import android.platform.test.annotations.FlakyTest;
 import android.platform.test.annotations.LargeTest;
 
 import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.result.InputStreamSource;
+import com.android.tradefed.util.StreamUtil;
 
 import org.junit.Test;
 
 import java.io.FileNotFoundException;
 import java.util.Collections;
 import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 import javax.annotation.Nullable;
 
@@ -158,6 +166,33 @@
 
     @LargeTest
     @Test
+    public void testStartActivityIntent_sameTaskByDefault() throws Exception {
+        if (!mHasManagedUserFeature) {
+            return;
+        }
+        getDevice().clearLogcat();
+        verifyCrossProfileAppsApi(
+                mProfileId,
+                mPrimaryUserId,
+                START_ACTIVITY_TEST_CLASS,
+                "testStartActivityIntent_sameTaskByDefault");
+        assertThat(findTaskId("CrossProfileSameTaskLauncherActivity"))
+                .isEqualTo(findTaskId("NonMainActivity"));
+    }
+
+    private int findTaskId(String className) throws Exception {
+        final Matcher matcher =
+                Pattern.compile(className + "#taskId#" + "(.*?)" + "#").matcher(readLogcat());
+        boolean isFound = matcher.find();
+        if (!isFound) {
+            fail("Task not found for " + className);
+            return -1;
+        }
+        return Integer.parseInt(matcher.group(1));
+    }
+
+    @LargeTest
+    @Test
     public void testPrimaryUserToSecondaryUser() throws Exception {
         if (!mCanTestMultiUser) {
             return;
@@ -257,4 +292,14 @@
         return Collections.singletonMap(PARAM_TARGET_USER,
                 Integer.toString(getUserSerialNumber(targetUserId)));
     }
+
+    private String readLogcat() throws Exception {
+        getDevice().stopLogcat();
+        final String logcat;
+        try (InputStreamSource logcatStream = getDevice().getLogcat()) {
+            logcat = StreamUtil.getStringFromSource(logcatStream);
+        }
+        getDevice().startLogcat();
+        return logcat;
+    }
 }
diff --git a/hostsidetests/hdmicec/OWNERS b/hostsidetests/hdmicec/OWNERS
index 254c58d..e7f5c32 100644
--- a/hostsidetests/hdmicec/OWNERS
+++ b/hostsidetests/hdmicec/OWNERS
@@ -1,3 +1,3 @@
-# Bug component: 141606867
+# Bug component: 114946
 nchalko@google.com
 amyjojo@google.com
diff --git a/hostsidetests/packagemanager/dynamicmime/Android.bp b/hostsidetests/packagemanager/dynamicmime/Android.bp
new file mode 100644
index 0000000..e23c99a
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/Android.bp
@@ -0,0 +1,30 @@
+// Copyright (C) 2017 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.
+java_test_host {
+    name: "CtsDynamicMimeHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+        "truth-prebuilt",
+    ],
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/dynamicmime/AndroidTest.xml b/hostsidetests/packagemanager/dynamicmime/AndroidTest.xml
new file mode 100644
index 0000000..56ab6ca
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/AndroidTest.xml
@@ -0,0 +1,47 @@
+<?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.
+  -->
+<configuration description="Config for the CTS dynamic MIME tests">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="CtsDynamicMimeHelperApp.apk" />
+        <option name="test-file-name" value="CtsDynamicMimeTestApp.apk" />
+    </target_preparer>
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command" value="mkdir -p /data/local/tmp/dynamic-mime-test" />
+        <option name="teardown-command" value="rm -rf /data/local/tmp/dynamic-mime-test"/>
+    </target_preparer>
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="CtsDynamicMimeUpdateAppFirstGroup.apk->/data/local/tmp/dynamic-mime-test/CtsDynamicMimeUpdateAppFirstGroup.apk"/>
+        <option name="push" value="CtsDynamicMimeUpdateAppSecondGroup.apk->/data/local/tmp/dynamic-mime-test/CtsDynamicMimeUpdateAppSecondGroup.apk"/>
+        <option name="push" value="CtsDynamicMimeUpdateAppBothGroups.apk->/data/local/tmp/dynamic-mime-test/CtsDynamicMimeUpdateAppBothGroups.apk"/>
+        <option name="push" value="CtsDynamicMimePreferredApp.apk->/data/local/tmp/dynamic-mime-test/CtsDynamicMimePreferredApp.apk"/>
+    </target_preparer>
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsDynamicMimeHostTestCases.jar" />
+    </test>
+
+</configuration>
diff --git a/hostsidetests/packagemanager/dynamicmime/OWNERS b/hostsidetests/packagemanager/dynamicmime/OWNERS
new file mode 100644
index 0000000..af9eee9
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 136635677
+tantoshchuk@google.com
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/dynamicmime/app/Android.bp b/hostsidetests/packagemanager/dynamicmime/app/Android.bp
new file mode 100644
index 0000000..1e983e1
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/app/Android.bp
@@ -0,0 +1,88 @@
+// 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.
+
+android_test_helper_app {
+    name: "CtsDynamicMimeHelperApp",
+    defaults: ["cts_support_defaults"],
+    static_libs: ["CtsDynamicMimeCommon"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+    manifest: "manifests/AndroidManifest_helper.xml",
+}
+
+android_test_helper_app {
+    name: "CtsDynamicMimeUpdateAppFirstGroup",
+    defaults: ["cts_support_defaults"],
+    static_libs: ["CtsDynamicMimeCommon"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+    manifest: "manifests/AndroidManifest_update_firstGroup.xml",
+}
+
+android_test_helper_app {
+    name: "CtsDynamicMimeUpdateAppSecondGroup",
+    defaults: ["cts_support_defaults"],
+    static_libs: ["CtsDynamicMimeCommon"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+    manifest: "manifests/AndroidManifest_update_secondGroup.xml",
+}
+
+android_test_helper_app {
+    name: "CtsDynamicMimeUpdateAppBothGroups",
+    defaults: ["cts_support_defaults"],
+    static_libs: ["CtsDynamicMimeCommon"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+    manifest: "manifests/AndroidManifest_update_bothGroups.xml",
+}
+
+android_test_helper_app {
+    name: "CtsDynamicMimePreferredApp",
+    defaults: ["cts_support_defaults"],
+    static_libs: ["CtsDynamicMimeCommon"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+    manifest: "manifests/AndroidManifest_preferred.xml",
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_helper.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_helper.xml
new file mode 100644
index 0000000..1373430
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_helper.xml
@@ -0,0 +1,52 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.dynamicmime.helper">
+    <application android:testOnly="true">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.dynamicmime.helper.FILTER_INFO_HOOK_group_first"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeGroup="group_first"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.dynamicmime.helper.FILTER_INFO_HOOK_group_second"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeGroup="group_second"/>
+            </intent-filter>
+        </activity>
+
+        <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.dynamicmime.helper" >
+    </instrumentation>
+</manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_preferred.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_preferred.xml
new file mode 100644
index 0000000..83cdf0e
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_preferred.xml
@@ -0,0 +1,55 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.dynamicmime.preferred">
+    <application android:testOnly="true"
+                 android:label="TestApp.Application">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+            <intent-filter android:label="TestApp.FirstActivity">
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.dynamicmime.preferred.FILTER_INFO_HOOK_group_first"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeGroup="group_first"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.dynamicmime.common.activity.TwoGroupsActivity">
+            <intent-filter android:label="TestApp.TwoGroupsActivity">
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <action android:name="android.dynamicmime.preferred.FILTER_INFO_HOOK_group_both"/>
+                <data android:mimeGroup="group_third"/>
+                <data android:mimeGroup="group_second"/>
+            </intent-filter>
+        </activity>
+
+        <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.dynamicmime.preferred">
+    </instrumentation>
+</manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_bothGroups.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_bothGroups.xml
new file mode 100644
index 0000000..802d13c
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_bothGroups.xml
@@ -0,0 +1,52 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.dynamicmime.update">
+    <application android:testOnly="true">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_first"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeGroup="group_first"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_second"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeGroup="group_second"/>
+            </intent-filter>
+        </activity>
+
+        <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.dynamicmime.update" >
+    </instrumentation>
+</manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_firstGroup.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_firstGroup.xml
new file mode 100644
index 0000000..adc93cf
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_firstGroup.xml
@@ -0,0 +1,51 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.dynamicmime.update">
+    <application android:testOnly="true">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_first"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeGroup="group_first"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_second"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+
+        <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.dynamicmime.update" >
+    </instrumentation>
+</manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_secondGroup.xml b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_secondGroup.xml
new file mode 100644
index 0000000..e93a3d5
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/app/manifests/AndroidManifest_update_secondGroup.xml
@@ -0,0 +1,51 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.dynamicmime.update">
+    <application android:testOnly="true">
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_first"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.dynamicmime.update.FILTER_INFO_HOOK_group_second"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeGroup="group_second"/>
+            </intent-filter>
+        </activity>
+
+        <receiver android:name="android.dynamicmime.app.AppMimeGroupsReceiver"
+                  android:exported="true">
+            <intent-filter>
+                <action android:name="android.dynamicmime.UPDATE_MIME_GROUP_REQUEST"/>
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.dynamicmime.update" >
+    </instrumentation>
+</manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/app/src/android/dynamicmime/app/AppMimeGroupsReceiver.java b/hostsidetests/packagemanager/dynamicmime/app/src/android/dynamicmime/app/AppMimeGroupsReceiver.java
new file mode 100644
index 0000000..7c79fef
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/app/src/android/dynamicmime/app/AppMimeGroupsReceiver.java
@@ -0,0 +1,74 @@
+/*
+ * 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.dynamicmime.app;
+
+import static android.dynamicmime.common.Constants.ACTION_REQUEST;
+import static android.dynamicmime.common.Constants.ACTION_RESPONSE;
+import static android.dynamicmime.common.Constants.EXTRA_GROUP;
+import static android.dynamicmime.common.Constants.EXTRA_MIMES;
+import static android.dynamicmime.common.Constants.EXTRA_REQUEST;
+import static android.dynamicmime.common.Constants.EXTRA_RESPONSE;
+import static android.dynamicmime.common.Constants.REQUEST_CLEAR;
+import static android.dynamicmime.common.Constants.REQUEST_GET;
+import static android.dynamicmime.common.Constants.REQUEST_SET;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.util.ArraySet;
+
+import java.util.Set;
+
+/**
+ * Receiver of {@link android.dynamicmime.testapp.util.AppMimeGroups} commands
+ */
+public class AppMimeGroupsReceiver extends BroadcastReceiver {
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (!ACTION_REQUEST.equals(intent.getAction())) {
+            return;
+        }
+
+        String mimeGroup = intent.getStringExtra(EXTRA_GROUP);
+        String[] mimeTypes = intent.getStringArrayExtra(EXTRA_MIMES);
+        int requestCode = intent.getIntExtra(EXTRA_REQUEST, -1);
+
+        Intent response = new Intent(ACTION_RESPONSE);
+        switch (requestCode) {
+            case REQUEST_SET:
+                context.getPackageManager().setMimeGroup(mimeGroup, new ArraySet<>(mimeTypes));
+                break;
+            case REQUEST_CLEAR:
+                context.getPackageManager().clearMimeGroup(mimeGroup);
+                break;
+            case REQUEST_GET:
+                response.putExtra(EXTRA_RESPONSE, getMimeGroup(context, mimeGroup));
+                break;
+            default:
+                //do not respond with broadcast to indicate that something is wrong
+                return;
+        }
+
+        context.sendBroadcast(response);
+    }
+
+    private String[] getMimeGroup(Context context, String mimeGroup) {
+        Set<String> mimeTypes = context.getPackageManager().getMimeGroup(mimeGroup);
+        return mimeTypes != null ? mimeTypes.toArray(new String[0]) : null;
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/common/Android.bp b/hostsidetests/packagemanager/dynamicmime/common/Android.bp
new file mode 100644
index 0000000..a80aba7
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/common/Android.bp
@@ -0,0 +1,20 @@
+// Copyright (C) 2017 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.
+
+java_library_static {
+    name: "CtsDynamicMimeCommon",
+    defaults: ["cts_defaults"],
+    sdk_version: "test_current",
+    srcs: ["src/**/*.java"],
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/Constants.java b/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/Constants.java
new file mode 100644
index 0000000..e3289ce
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/Constants.java
@@ -0,0 +1,71 @@
+/*
+ * 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.dynamicmime.common;
+
+public class Constants {
+    public static final String ACTION_REQUEST = "android.dynamicmime.UPDATE_MIME_GROUP_REQUEST";
+    public static final String ACTION_RESPONSE = "android.dynamicmime.UPDATE_MIME_GROUP_RESPONSE";
+
+    public static final String EXTRA_GROUP = "EXTRA_GROUP";
+    public static final String EXTRA_MIMES = "EXTRA_MIME";
+    public static final String EXTRA_REQUEST = "EXTRA_REQUEST";
+    public static final String EXTRA_RESPONSE = "EXTRA_RESPONSE";
+
+    public static final String GROUP_FIRST = "group_first";
+    public static final String GROUP_SECOND = "group_second";
+    public static final String GROUP_THIRD = "group_third";
+    public static final String GROUP_UNDEFINED = "undefined";
+
+    public static final String ALIAS_BOTH_GROUPS = "group_both";
+    public static final String ALIAS_BOTH_GROUPS_AND_TYPE = "groups_and_type";
+
+    public static final String[] GROUPS = {
+            GROUP_FIRST, GROUP_SECOND, GROUP_UNDEFINED
+    };
+
+    public static final int REQUEST_CLEAR = 1;
+    public static final int REQUEST_SET = 2;
+    public static final int REQUEST_GET = 3;
+
+    public static final String MIME_TEXT_PLAIN = "text/plain";
+    public static final String MIME_TEXT_XML = "text/xml";
+    public static final String MIME_TEXT_ANY = "text/*";
+    public static final String MIME_IMAGE_PNG = "image/png";
+    public static final String MIME_IMAGE_JPEG = "image/jpeg";
+    public static final String MIME_IMAGE_ANY = "image/*";
+    public static final String MIME_ANY = "*/*";
+
+    public static final String PACKAGE_TEST_APP = "android.dynamicmime.testapp";
+    public static final String PACKAGE_ACTIVITIES = "android.dynamicmime.common.activity";
+    public static final String PACKAGE_HELPER_APP = "android.dynamicmime.helper";
+    public static final String PACKAGE_UPDATE_APP = "android.dynamicmime.update";
+    public static final String PACKAGE_PREFERRED_APP = "android.dynamicmime.preferred";
+
+    public static final String ACTIVITY_FIRST = ".FirstActivity";
+    public static final String ACTIVITY_SECOND = ".SecondActivity";
+    public static final String ACTIVITY_BOTH = ".TwoGroupsActivity";
+    public static final String ACTIVITY_BOTH_AND_TYPE = ".TwoGroupsAndTypeActivity";
+
+    public static final String DATA_DIR = "/data/local/tmp/dynamic-mime-test/";
+
+    public static final String APK_PREFERRED_APP = DATA_DIR + "CtsDynamicMimePreferredApp.apk";
+    public static final String APK_BOTH_GROUPS = DATA_DIR + "CtsDynamicMimeUpdateAppBothGroups.apk";
+    public static final String APK_FIRST_GROUP = DATA_DIR + "CtsDynamicMimeUpdateAppFirstGroup.apk";
+    public static final String APK_SECOND_GROUP = DATA_DIR +
+            "CtsDynamicMimeUpdateAppSecondGroup.apk";
+
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/activity/FirstActivity.java b/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/activity/FirstActivity.java
new file mode 100644
index 0000000..94aeb8f
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/activity/FirstActivity.java
@@ -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.
+ */
+package android.dynamicmime.common.activity;
+
+import android.app.Activity;
+
+public class FirstActivity extends Activity {
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/activity/SecondActivity.java b/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/activity/SecondActivity.java
new file mode 100644
index 0000000..3ce0ff3
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/activity/SecondActivity.java
@@ -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.
+ */
+package android.dynamicmime.common.activity;
+
+import android.app.Activity;
+
+public class SecondActivity extends Activity {
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/activity/TwoGroupsActivity.java b/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/activity/TwoGroupsActivity.java
new file mode 100644
index 0000000..9171f0b
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/activity/TwoGroupsActivity.java
@@ -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.
+ */
+package android.dynamicmime.common.activity;
+
+import android.app.Activity;
+
+public class TwoGroupsActivity extends Activity {
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/activity/TwoGroupsAndTypeActivity.java b/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/activity/TwoGroupsAndTypeActivity.java
new file mode 100644
index 0000000..0a2f04d
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/common/src/android/dynamicmime/common/activity/TwoGroupsAndTypeActivity.java
@@ -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.
+ */
+package android.dynamicmime.common.activity;
+
+import android.app.Activity;
+
+public class TwoGroupsAndTypeActivity extends Activity {
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/dynamicmime/src/android/dynamicmime/cts/PreferredActivitiesTestCases.java b/hostsidetests/packagemanager/dynamicmime/src/android/dynamicmime/cts/PreferredActivitiesTestCases.java
new file mode 100644
index 0000000..25ac458
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/src/android/dynamicmime/cts/PreferredActivitiesTestCases.java
@@ -0,0 +1,75 @@
+/*
+ * 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.dynamicmime.cts;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Preferred Activities test cases
+ *
+ * Verifies that preferred activity is removed after any change to any MIME group
+ * declared by preferred activity package
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class PreferredActivitiesTestCases extends BaseHostJUnit4Test {
+    private static final String PACKAGE_TEST_APP = "android.dynamicmime.testapp";
+
+    @Before
+    public void setUp() throws DeviceNotAvailableException {
+        // wake up and unlock device
+        getDevice().executeShellCommand("input keyevent KEYCODE_WAKEUP");
+        getDevice().disableKeyguard();
+    }
+
+    @Test
+    public void testRemoveFromGroup() throws DeviceNotAvailableException {
+        runDeviceTest("testRemoveFromGroup");
+    }
+
+    @Test
+    public void testAddToGroup() throws DeviceNotAvailableException {
+        runDeviceTest("testAddToGroup");
+    }
+
+    @Test
+    public void testClearGroup() throws DeviceNotAvailableException {
+        runDeviceTest("testClearGroup");
+    }
+
+    @Test
+    public void testModifyGroupWithoutActualGroupChanges() throws DeviceNotAvailableException {
+        runDeviceTest("testModifyGroupWithoutActualGroupChanges");
+    }
+
+    @Test
+    public void testModifyGroupWithoutActualIntentFilterChanges()
+            throws DeviceNotAvailableException {
+        runDeviceTest("testModifyGroupWithoutActualIntentFilterChanges");
+    }
+
+    private void runDeviceTest(String testMethodName) throws DeviceNotAvailableException {
+        runDeviceTests(PACKAGE_TEST_APP, PACKAGE_TEST_APP + ".preferred.PreferredActivitiesTest",
+                testMethodName);
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/src/android/dynamicmime/cts/RebootTestCases.java b/hostsidetests/packagemanager/dynamicmime/src/android/dynamicmime/cts/RebootTestCases.java
new file mode 100644
index 0000000..26ac19f
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/src/android/dynamicmime/cts/RebootTestCases.java
@@ -0,0 +1,266 @@
+/*
+ * 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.dynamicmime.cts;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Reboot test cases
+ *
+ * Reuses existing test cases from {@link android.dynamicmime.testapp.ComplexFilterTest}
+ * and {@link android.dynamicmime.testapp.SingleAppTest}
+ * by "inserting" device reboot between setup part (MIME group commands) and verification part
+ * (MIME group assertions) in each test case
+ *
+ * @see android.dynamicmime.testapp.reboot.PreRebootSingleAppTest
+ * @see android.dynamicmime.testapp.reboot.PostRebootSingleAppTest
+ * @see #runTestWithReboot(String, String)
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class RebootTestCases extends BaseHostJUnit4Test {
+    private static final String PACKAGE_TEST_APP = "android.dynamicmime.testapp";
+    private static final String PACKAGE_REBOOT_TESTS = PACKAGE_TEST_APP + ".reboot";
+
+    @Test
+    public void testGroupWithExactType() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testGroupWithExactType");
+    }
+
+    @Test
+    public void testGroupWithWildcardType() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testGroupWithWildcardType");
+    }
+
+    @Test
+    public void testGroupWithAnyType() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testGroupWithAnyType");
+    }
+
+    @Test
+    public void testAddSimilarTypes() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testAddSimilarTypes");
+    }
+
+    @Test
+    public void testAddDifferentTypes() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testAddDifferentTypes");
+    }
+
+    @Test
+    public void testRemoveExactType() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testRemoveExactType");
+    }
+
+    @Test
+    public void testRemoveDifferentType() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testRemoveDifferentType");
+    }
+
+    @Test
+    public void testRemoveSameBaseType_keepSpecific() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testRemoveSameBaseType_keepSpecific");
+    }
+
+    @Test
+    public void testRemoveSameBaseType_keepWildcard() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testRemoveSameBaseType_keepWildcard");
+    }
+
+    @Test
+    public void testRemoveAnyType_keepSpecific() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testRemoveAnyType_keepSpecific");
+    }
+
+    @Test
+    public void testRemoveAnyType_keepWildcard() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testRemoveAnyType_keepWildcard");
+    }
+
+    @Test
+    public void testResetWithoutIntersection() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testResetWithoutIntersection");
+    }
+
+    @Test
+    public void testResetWithIntersection() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testResetWithIntersection");
+    }
+
+    @Test
+    public void testResetToEmpty() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testResetToEmpty");
+    }
+
+    @Test
+    public void testClear() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testClear");
+    }
+
+    @Test
+    public void testUndefinedGroupIsNullAfterAdd() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testUndefinedGroupIsNullAfterAdd");
+    }
+
+    @Test
+    public void testUndefinedGroupIsNullAfterSet() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testUndefinedGroupIsNullAfterSet");
+    }
+
+    @Test
+    public void testUndefinedGroupIsNullAfterRemove() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testUndefinedGroupIsNullAfterRemove");
+    }
+
+    @Test
+    public void testUndefinedGroupIsNullAfterClear() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testUndefinedGroupIsNullAfterClear");
+    }
+
+    @Test
+    public void testDefinedGroupNotNullAfterRemove() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testDefinedGroupNotNullAfterRemove");
+    }
+
+    @Test
+    public void testDefinedGroupNotNullAfterClear() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testDefinedGroupNotNullAfterClear");
+    }
+
+    @Test
+    public void testDefinedGroupNotNullAfterSetEmpty() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testDefinedGroupNotNullAfterSetEmpty");
+    }
+
+    @Test
+    public void testDefinedGroupNotNullAfterSetNull() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testDefinedGroupNotNullAfterSetNull");
+    }
+
+    @Test
+    public void testMimeGroupsIndependentAdd() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testMimeGroupsIndependentAdd");
+    }
+
+    @Test
+    public void testMimeGroupsIndependentAddToBoth() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testMimeGroupsIndependentAddToBoth");
+    }
+
+    @Test
+    public void testMimeGroupsIndependentRemove() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testMimeGroupsIndependentRemove");
+    }
+
+    @Test
+    public void testMimeGroupsIndependentClear() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testMimeGroupsIndependentClear");
+    }
+
+    @Test
+    public void testMimeGroupsIndependentClearBoth() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testMimeGroupsIndependentClearBoth");
+    }
+
+    @Test
+    public void testMimeGroupsIndependentSet() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testMimeGroupsIndependentSet");
+    }
+
+    @Test
+    public void testMimeGroupsIndependentSetBoth() throws DeviceNotAvailableException {
+        runTestWithReboot("SingleAppTest", "testMimeGroupsIndependentSetBoth");
+    }
+
+    @Test
+    public void testMimeGroupsNotIntersect() throws DeviceNotAvailableException {
+        runTestWithReboot("ComplexFilterTest", "testMimeGroupsNotIntersect");
+    }
+
+    @Test
+    public void testMimeGroupsIntersect() throws DeviceNotAvailableException {
+        runTestWithReboot("ComplexFilterTest", "testMimeGroupsIntersect");
+    }
+
+    @Test
+    public void testRemoveTypeFromIntersection() throws DeviceNotAvailableException {
+        runTestWithReboot("ComplexFilterTest", "testRemoveTypeFromIntersection");
+    }
+
+    @Test
+    public void testRemoveIntersectionFromBothGroups() throws DeviceNotAvailableException {
+        runTestWithReboot("ComplexFilterTest", "testRemoveIntersectionFromBothGroups");
+    }
+
+    @Test
+    public void testClearGroupWithoutIntersection() throws DeviceNotAvailableException {
+        runTestWithReboot("ComplexFilterTest", "testClearGroupWithoutIntersection");
+    }
+
+    @Test
+    public void testClearGroupWithIntersection() throws DeviceNotAvailableException {
+        runTestWithReboot("ComplexFilterTest", "testClearGroupWithIntersection");
+    }
+
+    @Test
+    public void testMimeGroupNotIntersectWithStaticType() throws DeviceNotAvailableException {
+        runTestWithReboot("ComplexFilterTest", "testMimeGroupNotIntersectWithStaticType");
+    }
+
+    @Test
+    public void testMimeGroupIntersectWithStaticType() throws DeviceNotAvailableException {
+        runTestWithReboot("ComplexFilterTest", "testMimeGroupIntersectWithStaticType");
+    }
+
+    @Test
+    public void testRemoveStaticTypeFromMimeGroup() throws DeviceNotAvailableException {
+        runTestWithReboot("ComplexFilterTest", "testRemoveStaticTypeFromMimeGroup");
+    }
+
+    @Test
+    public void testClearGroupContainsStaticType() throws DeviceNotAvailableException {
+        runTestWithReboot("ComplexFilterTest", "testClearGroupContainsStaticType");
+    }
+
+    @Test
+    public void testClearGroupNotContainStaticType() throws DeviceNotAvailableException {
+        runTestWithReboot("ComplexFilterTest", "testClearGroupNotContainStaticType");
+    }
+
+    private void runTestWithReboot(String testClassName, String testMethodName)
+            throws DeviceNotAvailableException {
+        runPreReboot(testClassName, testMethodName);
+        getDevice().reboot();
+        runPostReboot(testClassName, testMethodName);
+    }
+
+    private void runPostReboot(String testClassName, String testMethodName)
+        throws DeviceNotAvailableException {
+        runDeviceTests(PACKAGE_TEST_APP, PACKAGE_REBOOT_TESTS + ".PostReboot" + testClassName,
+            testMethodName);
+    }
+
+    private void runPreReboot(String testClassName, String testMethodName)
+        throws DeviceNotAvailableException {
+        runDeviceTests(PACKAGE_TEST_APP, PACKAGE_REBOOT_TESTS + ".PreReboot" + testClassName,
+            testMethodName);
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/src/android/dynamicmime/cts/TestCases.java b/hostsidetests/packagemanager/dynamicmime/src/android/dynamicmime/cts/TestCases.java
new file mode 100644
index 0000000..d3a2e89
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/src/android/dynamicmime/cts/TestCases.java
@@ -0,0 +1,62 @@
+/*
+ * 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.dynamicmime.cts;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Invokes device-side tests as is, no need for any host-side setup
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class TestCases extends BaseHostJUnit4Test {
+    private static final String PACKAGE_TEST_APP = "android.dynamicmime.testapp";
+
+    @Test
+    public void testDynamicMimeWithSingleApp() throws DeviceNotAvailableException {
+        runDeviceTests("SingleAppTest");
+    }
+
+    @Test
+    public void testDynamicMimeWithMultipleApps() throws DeviceNotAvailableException {
+        runDeviceTests("MultipleAppsTest");
+    }
+
+    @Test
+    public void testDynamicMimeWithMultipleGroupsPerFilter() throws DeviceNotAvailableException {
+        runDeviceTests("ComplexFilterTest");
+    }
+
+    @Test
+    public void testDynamicMimeWithAppUpdate() throws DeviceNotAvailableException {
+        runDeviceTests("update.SameGroupsTest");
+    }
+
+    @Test
+    public void testDynamicMimeWithChangedGroupsAppUpdate() throws DeviceNotAvailableException {
+        runDeviceTests("update.ChangedGroupsTest");
+    }
+
+    private void runDeviceTests(String className)
+            throws DeviceNotAvailableException {
+        runDeviceTests(PACKAGE_TEST_APP, PACKAGE_TEST_APP + "." + className);
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/Android.bp b/hostsidetests/packagemanager/dynamicmime/test/Android.bp
new file mode 100644
index 0000000..207f050
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/Android.bp
@@ -0,0 +1,35 @@
+// Copyright (C) 2017 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+android_test_helper_app {
+    name: "CtsDynamicMimeTestApp",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "androidx.test.ext.junit",
+        "androidx.test.rules",
+        "compatibility-device-util-axt",
+        "CtsDynamicMimeCommon",
+        "hamcrest-library",
+        "android-support-test",
+        "androidx.test.uiautomator_uiautomator",
+    ],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+    platform_apis: true,
+}
\ No newline at end of file
diff --git a/hostsidetests/packagemanager/dynamicmime/test/AndroidManifest.xml b/hostsidetests/packagemanager/dynamicmime/test/AndroidManifest.xml
new file mode 100644
index 0000000..d31dcf3
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/AndroidManifest.xml
@@ -0,0 +1,66 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.dynamicmime.testapp">
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="android.dynamicmime.common.activity.FirstActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.dynamicmime.testapp.FILTER_INFO_HOOK_group_first"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeGroup="group_first"/>
+            </intent-filter>
+        </activity>
+        <activity android:name="android.dynamicmime.common.activity.SecondActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND"/>
+                <action android:name="android.dynamicmime.testapp.FILTER_INFO_HOOK_group_second"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:mimeGroup="group_second"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.dynamicmime.common.activity.TwoGroupsActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <action android:name="android.dynamicmime.testapp.FILTER_INFO_HOOK_group_both"/>
+                <data android:mimeGroup="group_first"/>
+                <data android:mimeGroup="group_second"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:name="android.dynamicmime.common.activity.TwoGroupsAndTypeActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <action android:name="android.dynamicmime.testapp.FILTER_INFO_HOOK_groups_and_type"/>
+                <data android:mimeType="text/plain"/>
+                <data android:mimeGroup="group_first"/>
+                <data android:mimeGroup="group_second"/>
+            </intent-filter>
+        </activity>
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.dynamicmime.testapp">
+    </instrumentation>
+</manifest>
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/BaseDynamicMimeTest.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/BaseDynamicMimeTest.java
new file mode 100644
index 0000000..ab16c74
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/BaseDynamicMimeTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.dynamicmime.testapp;
+
+import android.app.Instrumentation;
+import android.content.Context;
+import android.dynamicmime.testapp.assertions.MimeGroupAssertions;
+import android.dynamicmime.testapp.commands.MimeGroupCommands;
+import android.dynamicmime.testapp.util.MimeGroupOperations;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+
+/**
+ * Base class for dynamic MIME handlers feature test cases
+ */
+public abstract class BaseDynamicMimeTest extends MimeGroupOperations {
+    public BaseDynamicMimeTest(MimeGroupCommands commands, MimeGroupAssertions assertions) {
+        super(commands, assertions);
+    }
+
+    protected static Context context() {
+        return instrumentation().getContext();
+    }
+
+    protected static Context targetContext() {
+        return instrumentation().getTargetContext();
+    }
+
+    protected static Instrumentation instrumentation() {
+        return InstrumentationRegistry.getInstrumentation();
+    }
+
+    @After
+    public void tearDown() {
+        clearGroups();
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/ComplexFilterTest.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/ComplexFilterTest.java
new file mode 100644
index 0000000..0ad981c
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/ComplexFilterTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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.dynamicmime.testapp;
+
+import static android.dynamicmime.common.Constants.ALIAS_BOTH_GROUPS;
+import static android.dynamicmime.common.Constants.ALIAS_BOTH_GROUPS_AND_TYPE;
+import static android.dynamicmime.common.Constants.GROUP_FIRST;
+import static android.dynamicmime.common.Constants.GROUP_SECOND;
+import static android.dynamicmime.common.Constants.MIME_IMAGE_PNG;
+import static android.dynamicmime.common.Constants.MIME_TEXT_ANY;
+import static android.dynamicmime.common.Constants.MIME_TEXT_PLAIN;
+
+import android.dynamicmime.testapp.assertions.AssertionsByIntentResolution;
+import android.dynamicmime.testapp.commands.MimeGroupCommands;
+
+import org.junit.Test;
+
+/**
+ * Test cases for:
+ *  - multiple MIME groups per intent filter
+ *  - MIME groups and android:mimeType attribute in one filter
+ */
+public class ComplexFilterTest extends BaseDynamicMimeTest {
+    public ComplexFilterTest() {
+        super(MimeGroupCommands.testApp(context()),
+                AssertionsByIntentResolution.testApp(context()));
+    }
+
+    @Test
+    public void testMimeGroupsNotIntersect() {
+        setMimeGroup(GROUP_FIRST, MIME_IMAGE_PNG);
+        setMimeGroup(GROUP_SECOND, MIME_TEXT_ANY);
+
+        assertBothGroups(MIME_IMAGE_PNG, MIME_TEXT_ANY);
+    }
+
+    @Test
+    public void testMimeGroupsIntersect() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN, MIME_IMAGE_PNG);
+        setMimeGroup(GROUP_SECOND, MIME_IMAGE_PNG, MIME_TEXT_ANY);
+
+        assertBothGroups(MIME_TEXT_PLAIN, MIME_TEXT_ANY, MIME_IMAGE_PNG);
+    }
+
+    @Test
+    public void testRemoveTypeFromIntersection() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN, MIME_IMAGE_PNG);
+        setMimeGroup(GROUP_SECOND, MIME_IMAGE_PNG, MIME_TEXT_ANY);
+        removeMimeTypeFromGroup(GROUP_FIRST, MIME_IMAGE_PNG);
+
+        assertBothGroups(MIME_TEXT_PLAIN, MIME_TEXT_ANY, MIME_IMAGE_PNG);
+    }
+
+    @Test
+    public void testRemoveIntersectionFromBothGroups() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN, MIME_IMAGE_PNG);
+        setMimeGroup(GROUP_SECOND, MIME_IMAGE_PNG, MIME_TEXT_ANY);
+        removeMimeTypeFromGroup(GROUP_FIRST, MIME_IMAGE_PNG);
+        removeMimeTypeFromGroup(GROUP_SECOND, MIME_IMAGE_PNG);
+
+        assertBothGroups(MIME_TEXT_PLAIN, MIME_TEXT_ANY);
+    }
+
+    @Test
+    public void testClearGroupWithoutIntersection() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+        setMimeGroup(GROUP_SECOND, MIME_IMAGE_PNG);
+        clearMimeGroup(GROUP_FIRST);
+
+        assertBothGroups(MIME_IMAGE_PNG);
+    }
+
+    @Test
+    public void testClearGroupWithIntersection() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN, MIME_IMAGE_PNG);
+        setMimeGroup(GROUP_SECOND, MIME_IMAGE_PNG, MIME_TEXT_ANY);
+        clearMimeGroup(GROUP_FIRST);
+
+        assertBothGroups(MIME_IMAGE_PNG, MIME_TEXT_ANY);
+    }
+
+    @Test
+    public void testMimeGroupNotIntersectWithStaticType() {
+        setMimeGroup(GROUP_FIRST, MIME_IMAGE_PNG);
+
+        assertGroupsAndType(MIME_IMAGE_PNG, MIME_TEXT_PLAIN);
+    }
+
+    @Test
+    public void testMimeGroupIntersectWithStaticType() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+
+        assertGroupsAndType(MIME_TEXT_PLAIN);
+    }
+
+    @Test
+    public void testRemoveStaticTypeFromMimeGroup() {
+        setMimeGroup(GROUP_FIRST, MIME_IMAGE_PNG, MIME_TEXT_PLAIN);
+        removeMimeTypeFromGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+
+        assertGroupsAndType(MIME_IMAGE_PNG, MIME_TEXT_PLAIN);
+    }
+
+    @Test
+    public void testClearGroupContainsStaticType() {
+        setMimeGroup(GROUP_FIRST, MIME_IMAGE_PNG, MIME_TEXT_PLAIN);
+        clearMimeGroup(GROUP_FIRST);
+
+        assertGroupsAndType(MIME_TEXT_PLAIN);
+    }
+
+    @Test
+    public void testClearGroupNotContainStaticType() {
+        setMimeGroup(GROUP_FIRST, MIME_IMAGE_PNG, MIME_TEXT_ANY);
+        clearMimeGroup(GROUP_FIRST);
+
+        assertGroupsAndType(MIME_TEXT_PLAIN);
+    }
+
+    private void assertGroupsAndType(String... expectedTypes) {
+        assertMimeGroup(ALIAS_BOTH_GROUPS_AND_TYPE, expectedTypes);
+    }
+
+    private void assertBothGroups(String... expectedTypes) {
+        assertMimeGroup(ALIAS_BOTH_GROUPS, expectedTypes);
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/MultipleAppsTest.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/MultipleAppsTest.java
new file mode 100644
index 0000000..fa5876d
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/MultipleAppsTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.dynamicmime.testapp;
+
+import static android.dynamicmime.common.Constants.GROUP_FIRST;
+import static android.dynamicmime.common.Constants.MIME_IMAGE_ANY;
+import static android.dynamicmime.common.Constants.MIME_TEXT_ANY;
+
+import android.dynamicmime.testapp.assertions.MimeGroupAssertions;
+import android.dynamicmime.testapp.commands.MimeGroupCommands;
+import android.dynamicmime.testapp.util.MimeGroupOperations;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test cases that verify independence of MIME groups from different apps
+ */
+@RunWith(AndroidJUnit4.class)
+public class MultipleAppsTest extends BaseDynamicMimeTest {
+    public MultipleAppsTest() {
+        super(MimeGroupCommands.testApp(context()), MimeGroupAssertions.testApp(context()));
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        helperApp().clearGroups();
+        super.tearDown();
+    }
+
+    @Test
+    public void testMimeGroupsIndependentSet() {
+        helperApp().setMimeGroup(GROUP_FIRST, MIME_TEXT_ANY);
+
+        assertMimeGroupIsEmpty(GROUP_FIRST);
+        helperApp().assertMimeGroup(GROUP_FIRST, MIME_TEXT_ANY);
+
+        setMimeGroup(GROUP_FIRST, MIME_IMAGE_ANY);
+
+        assertMimeGroup(GROUP_FIRST, MIME_IMAGE_ANY);
+        helperApp().assertMimeGroup(GROUP_FIRST, MIME_TEXT_ANY);
+    }
+
+    @Test
+    public void testMimeGroupsIndependentReset() {
+        setMimeGroup(GROUP_FIRST, MIME_IMAGE_ANY);
+        helperApp().setMimeGroup(GROUP_FIRST, MIME_TEXT_ANY);
+
+        removeMimeTypeFromGroup(GROUP_FIRST, MIME_TEXT_ANY);
+        helperApp().removeMimeTypeFromGroup(GROUP_FIRST, MIME_IMAGE_ANY);
+
+        assertMimeGroup(GROUP_FIRST, MIME_IMAGE_ANY);
+        helperApp().assertMimeGroup(GROUP_FIRST, MIME_TEXT_ANY);
+    }
+
+    @Test
+    public void testMimeGroupsIndependentClear() {
+        setMimeGroup(GROUP_FIRST, MIME_IMAGE_ANY, MIME_TEXT_ANY);
+        helperApp().setMimeGroup(GROUP_FIRST, MIME_TEXT_ANY);
+
+        clearMimeGroup(GROUP_FIRST);
+
+        assertMimeGroupIsEmpty(GROUP_FIRST);
+        helperApp().assertMimeGroup(GROUP_FIRST, MIME_TEXT_ANY);
+
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_ANY);
+        helperApp().clearMimeGroup(GROUP_FIRST);
+
+        assertMimeGroup(GROUP_FIRST, MIME_TEXT_ANY);
+        helperApp().assertMimeGroupIsEmpty(GROUP_FIRST);
+    }
+
+    /**
+     * {@link MimeGroupOperations} implementation to interact with sample app MIME groups
+     */
+    private MimeGroupOperations helperApp() {
+        return new MimeGroupOperations(MimeGroupCommands.helperApp(context()),
+                MimeGroupAssertions.helperApp(context()));
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/SingleAppTest.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/SingleAppTest.java
new file mode 100644
index 0000000..84eab63
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/SingleAppTest.java
@@ -0,0 +1,294 @@
+/*
+ * 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.dynamicmime.testapp;
+
+import static android.dynamicmime.common.Constants.GROUP_FIRST;
+import static android.dynamicmime.common.Constants.GROUP_SECOND;
+import static android.dynamicmime.common.Constants.GROUP_UNDEFINED;
+import static android.dynamicmime.common.Constants.MIME_ANY;
+import static android.dynamicmime.common.Constants.MIME_IMAGE_ANY;
+import static android.dynamicmime.common.Constants.MIME_IMAGE_JPEG;
+import static android.dynamicmime.common.Constants.MIME_IMAGE_PNG;
+import static android.dynamicmime.common.Constants.MIME_TEXT_ANY;
+import static android.dynamicmime.common.Constants.MIME_TEXT_PLAIN;
+import static android.dynamicmime.common.Constants.MIME_TEXT_XML;
+
+import android.dynamicmime.testapp.assertions.MimeGroupAssertions;
+import android.dynamicmime.testapp.commands.MimeGroupCommands;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test cases for single app and one MIME group per intent filter scenario
+ */
+@RunWith(AndroidJUnit4.class)
+public class SingleAppTest extends BaseDynamicMimeTest {
+    public SingleAppTest() {
+        super(MimeGroupCommands.testApp(context()), MimeGroupAssertions.testApp(context()));
+    }
+
+    @Test
+    public void testGroupWithExactType() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+
+        assertMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+        assertMatchedByMimeGroup(GROUP_FIRST, MIME_TEXT_ANY, MIME_ANY);
+        assertNotMatchedByMimeGroup(GROUP_FIRST, MIME_IMAGE_ANY);
+    }
+
+    @Test
+    public void testGroupWithWildcardType() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_ANY);
+
+        assertMimeGroup(GROUP_FIRST, MIME_TEXT_ANY);
+        assertMatchedByMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN, MIME_ANY);
+        assertNotMatchedByMimeGroup(GROUP_FIRST, MIME_IMAGE_ANY);
+    }
+
+    @Test
+    public void testGroupWithAnyType() {
+        setMimeGroup(GROUP_FIRST, MIME_ANY);
+
+        assertMimeGroup(GROUP_FIRST, MIME_ANY);
+        assertMatchedByMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN, MIME_TEXT_ANY, MIME_IMAGE_ANY);
+    }
+
+    @Test
+    public void testAddSimilarTypes() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+        addMimeTypeToGroup(GROUP_FIRST, MIME_TEXT_ANY);
+
+        assertMimeGroup(GROUP_FIRST, MIME_TEXT_ANY, MIME_TEXT_PLAIN);
+    }
+
+    @Test
+    public void testAddDifferentTypes() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+        addMimeTypeToGroup(GROUP_FIRST, MIME_IMAGE_ANY);
+
+        assertMimeGroup(GROUP_FIRST, MIME_IMAGE_ANY, MIME_TEXT_PLAIN);
+    }
+
+    @Test
+    public void testRemoveExactType() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+        removeMimeTypeFromGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+
+        assertMimeGroupIsEmpty(GROUP_FIRST);
+        assertNotMatchedByMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+    }
+
+    @Test
+    public void testRemoveDifferentType() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+        removeMimeTypeFromGroup(GROUP_FIRST, MIME_TEXT_ANY);
+
+        assertMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+    }
+
+    @Test
+    public void testRemoveSameBaseType_keepSpecific() {
+        testKeepAndRemoveSimilarTypes(MIME_TEXT_PLAIN, MIME_TEXT_ANY);
+    }
+
+    @Test
+    public void testRemoveSameBaseType_keepWildcard() {
+        testKeepAndRemoveSimilarTypes(MIME_TEXT_ANY, MIME_TEXT_PLAIN);
+    }
+
+    @Test
+    public void testRemoveAnyType_keepSpecific() {
+        testKeepAndRemoveSimilarTypes(MIME_TEXT_PLAIN, MIME_ANY);
+    }
+
+    @Test
+    public void testRemoveAnyType_keepWildcard() {
+        testKeepAndRemoveSimilarTypes(MIME_ANY, MIME_TEXT_PLAIN);
+    }
+
+    @Test
+    public void testResetWithoutIntersection() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN, MIME_TEXT_XML);
+        setMimeGroup(GROUP_FIRST, MIME_IMAGE_JPEG, MIME_IMAGE_PNG);
+
+        assertMimeGroup(GROUP_FIRST, MIME_IMAGE_JPEG, MIME_IMAGE_PNG);
+    }
+
+    @Test
+    public void testResetWithIntersection() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN, MIME_TEXT_XML);
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN, MIME_IMAGE_PNG);
+
+        assertMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN, MIME_IMAGE_PNG);
+    }
+
+    @Test
+    public void testResetToEmpty() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN, MIME_TEXT_XML);
+        setMimeGroup(GROUP_FIRST, (String[]) null);
+
+        assertMimeGroupIsEmpty(GROUP_FIRST);
+    }
+
+    @Test
+    public void testClear() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN, MIME_IMAGE_PNG);
+        clearMimeGroup(GROUP_FIRST);
+
+        assertMimeGroupIsEmpty(GROUP_FIRST);
+    }
+
+    @Test
+    public void testUndefinedGroupIsNullAfterAdd() {
+        addMimeTypeToGroup(GROUP_UNDEFINED, MIME_TEXT_PLAIN);
+
+        assertMimeGroupIsNull(GROUP_UNDEFINED);
+    }
+
+    @Test
+    public void testUndefinedGroupIsNullAfterSet() {
+        setMimeGroup(GROUP_UNDEFINED, MIME_TEXT_PLAIN);
+
+        assertMimeGroupIsNull(GROUP_UNDEFINED);
+    }
+
+    @Test
+    public void testUndefinedGroupIsNullAfterRemove() {
+        removeMimeTypeFromGroup(GROUP_UNDEFINED, MIME_TEXT_PLAIN);
+
+        assertMimeGroupIsNull(GROUP_UNDEFINED);
+    }
+
+    @Test
+    public void testUndefinedGroupIsNullAfterClear() {
+        clearMimeGroup(GROUP_UNDEFINED);
+
+        assertMimeGroupIsNull(GROUP_UNDEFINED);
+    }
+
+    @Test
+    public void testDefinedGroupNotNullAfterRemove() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+        removeMimeTypeFromGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+
+        assertMimeGroupIsEmpty(GROUP_FIRST);
+    }
+
+    @Test
+    public void testDefinedGroupNotNullAfterClear() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+        clearMimeGroup(GROUP_FIRST);
+
+        assertMimeGroupIsEmpty(GROUP_FIRST);
+    }
+
+    @Test
+    public void testDefinedGroupNotNullAfterSetEmpty() {
+        setMimeGroup(GROUP_FIRST, (String[]) null);
+        clearMimeGroup(GROUP_FIRST);
+
+        assertMimeGroupIsEmpty(GROUP_FIRST);
+    }
+
+    @Test
+    public void testDefinedGroupNotNullAfterSetNull() {
+        setMimeGroup(GROUP_FIRST, (String[]) null);
+        clearMimeGroup(GROUP_FIRST);
+
+        assertMimeGroupIsEmpty(GROUP_FIRST);
+    }
+
+    @Test
+    public void testMimeGroupsIndependentAdd() {
+        addMimeTypeToGroup(GROUP_SECOND, MIME_TEXT_ANY);
+
+        assertMimeGroup(GROUP_SECOND, MIME_TEXT_ANY);
+        assertMimeGroupIsEmpty(GROUP_FIRST);
+    }
+
+    @Test
+    public void testMimeGroupsIndependentAddToBoth() {
+        addMimeTypeToGroup(GROUP_SECOND, MIME_TEXT_ANY);
+        addMimeTypeToGroup(GROUP_FIRST, MIME_IMAGE_ANY);
+
+        assertMimeGroup(GROUP_FIRST, MIME_IMAGE_ANY);
+        assertMimeGroup(GROUP_SECOND, MIME_TEXT_ANY);
+    }
+
+    @Test
+    public void testMimeGroupsIndependentRemove() {
+        setMimeGroup(GROUP_FIRST, MIME_IMAGE_ANY);
+        setMimeGroup(GROUP_SECOND, MIME_TEXT_ANY);
+
+        removeMimeTypeFromGroup(GROUP_FIRST, MIME_TEXT_ANY);
+        removeMimeTypeFromGroup(GROUP_SECOND, MIME_IMAGE_ANY);
+
+        assertMimeGroup(GROUP_FIRST, MIME_IMAGE_ANY);
+        assertMimeGroup(GROUP_SECOND, MIME_TEXT_ANY);
+    }
+
+    @Test
+    public void testMimeGroupsIndependentClear() {
+        setMimeGroup(GROUP_FIRST, MIME_IMAGE_ANY, MIME_TEXT_ANY);
+        setMimeGroup(GROUP_SECOND, MIME_TEXT_ANY);
+
+        clearMimeGroup(GROUP_FIRST);
+
+        assertMimeGroupIsEmpty(GROUP_FIRST);
+        assertMimeGroup(GROUP_SECOND, MIME_TEXT_ANY);
+    }
+
+    @Test
+    public void testMimeGroupsIndependentClearBoth() {
+        setMimeGroup(GROUP_FIRST, MIME_IMAGE_ANY, MIME_TEXT_ANY);
+        setMimeGroup(GROUP_SECOND, MIME_TEXT_ANY);
+        clearMimeGroup(GROUP_FIRST);
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_ANY);
+        clearMimeGroup(GROUP_SECOND);
+
+        assertMimeGroup(GROUP_FIRST, MIME_TEXT_ANY);
+        assertMimeGroupIsEmpty(GROUP_SECOND);
+    }
+
+    @Test
+    public void testMimeGroupsIndependentSet() {
+        setMimeGroup(GROUP_FIRST, MIME_IMAGE_PNG, MIME_IMAGE_ANY);
+
+        assertMimeGroup(GROUP_FIRST, MIME_IMAGE_PNG, MIME_IMAGE_ANY);
+        assertMimeGroupIsEmpty(GROUP_SECOND);
+    }
+
+    @Test
+    public void testMimeGroupsIndependentSetBoth() {
+        setMimeGroup(GROUP_FIRST, MIME_IMAGE_PNG, MIME_IMAGE_ANY);
+        setMimeGroup(GROUP_SECOND, MIME_TEXT_PLAIN, MIME_TEXT_ANY);
+
+        assertMimeGroup(GROUP_FIRST, MIME_IMAGE_PNG, MIME_IMAGE_ANY);
+        assertMimeGroup(GROUP_SECOND, MIME_TEXT_PLAIN, MIME_TEXT_ANY);
+    }
+
+    private void testKeepAndRemoveSimilarTypes(String mimeTypeToKeep, String mimeTypeToRemove) {
+        addMimeTypeToGroup(GROUP_FIRST, mimeTypeToKeep, mimeTypeToRemove);
+        removeMimeTypeFromGroup(GROUP_FIRST, mimeTypeToRemove);
+
+        assertMimeGroup(GROUP_FIRST, mimeTypeToKeep);
+        assertMatchedByMimeGroup(GROUP_FIRST, mimeTypeToRemove);
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/AppAssertionsByGroupData.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/AppAssertionsByGroupData.java
new file mode 100644
index 0000000..95eaa12
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/AppAssertionsByGroupData.java
@@ -0,0 +1,37 @@
+/*
+ * 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.dynamicmime.testapp.assertions;
+
+import android.content.Context;
+import android.dynamicmime.testapp.util.AppMimeGroups;
+import android.util.ArraySet;
+
+import java.util.Set;
+
+public class AppAssertionsByGroupData extends AssertionsByGroupData {
+    private final AppMimeGroups mAppMimeGroups;
+
+    AppAssertionsByGroupData(Context context, String targetPackage) {
+        mAppMimeGroups = AppMimeGroups.with(context, targetPackage);
+    }
+
+    @Override
+    protected Set<String> getMimeGroup(String mimeGroup) {
+        String[] mimeTypes = mAppMimeGroups.get(mimeGroup);
+        return mimeTypes != null ? new ArraySet<>(mimeTypes) : null;
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/AssertionsByGroupData.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/AssertionsByGroupData.java
new file mode 100644
index 0000000..eb74b3d
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/AssertionsByGroupData.java
@@ -0,0 +1,57 @@
+/*
+ * 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.dynamicmime.testapp.assertions;
+
+import static android.dynamicmime.common.Constants.PACKAGE_HELPER_APP;
+import static android.dynamicmime.common.Constants.PACKAGE_UPDATE_APP;
+
+import android.content.Context;
+
+import org.junit.Assert;
+
+import java.util.Set;
+
+public abstract class AssertionsByGroupData extends MimeGroupAssertions {
+    @Override
+    public void assertMatchedByMimeGroup(String mimeGroup, String mimeType) {
+        // We cannot check if type is matched by group via group info from PackageManager
+    }
+
+    @Override
+    public void assertNotMatchedByMimeGroup(String mimeGroup, String mimeType) {
+        // We cannot check if type is matched by group via group info from PackageManager
+    }
+
+    @Override
+    public void assertMimeGroupInternal(String mimeGroup, Set<String> mimeTypes) {
+        Assert.assertEquals(getMimeGroup(mimeGroup), mimeTypes);
+    }
+
+    protected abstract Set<String> getMimeGroup(String mimeGroup);
+
+    public static MimeGroupAssertions testApp(Context context) {
+        return new TestAppAssertionsByGroupData(context);
+    }
+
+    public static MimeGroupAssertions helperApp(Context context) {
+        return new AppAssertionsByGroupData(context, PACKAGE_HELPER_APP);
+    }
+
+    public static MimeGroupAssertions appWithUpdates(Context context) {
+        return new AppAssertionsByGroupData(context, PACKAGE_UPDATE_APP);
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/AssertionsByIntentResolution.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/AssertionsByIntentResolution.java
new file mode 100644
index 0000000..13f4d4d
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/AssertionsByIntentResolution.java
@@ -0,0 +1,150 @@
+/*
+ * 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.dynamicmime.testapp.assertions;
+
+import static android.dynamicmime.common.Constants.ACTIVITY_BOTH;
+import static android.dynamicmime.common.Constants.ACTIVITY_BOTH_AND_TYPE;
+import static android.dynamicmime.common.Constants.ACTIVITY_FIRST;
+import static android.dynamicmime.common.Constants.ACTIVITY_SECOND;
+import static android.dynamicmime.common.Constants.ALIAS_BOTH_GROUPS;
+import static android.dynamicmime.common.Constants.ALIAS_BOTH_GROUPS_AND_TYPE;
+import static android.dynamicmime.common.Constants.GROUP_FIRST;
+import static android.dynamicmime.common.Constants.GROUP_SECOND;
+import static android.dynamicmime.common.Constants.PACKAGE_HELPER_APP;
+import static android.dynamicmime.common.Constants.PACKAGE_TEST_APP;
+import static android.dynamicmime.common.Constants.PACKAGE_UPDATE_APP;
+import static android.dynamicmime.testapp.util.IntentsResolutionHelper.resolve;
+
+import static org.junit.Assert.assertEquals;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.util.ArraySet;
+
+import java.util.Set;
+
+public class AssertionsByIntentResolution extends MimeGroupAssertions {
+    private final Context mContext;
+    private final String mPackageName;
+
+    private AssertionsByIntentResolution(String packageName, Context context) {
+        mContext = context;
+        mPackageName = packageName;
+    }
+
+    @Override
+    public void assertMatchedByMimeGroup(String mimeGroup, String mimeType) {
+        resolve(mContext, intentSend(mimeType))
+                .assertMatched(mPackageName, activity(mimeGroup));
+    }
+
+    @Override
+    public void assertNotMatchedByMimeGroup(String mimeGroup, String mimeType) {
+        resolve(mContext, intentSend(mimeType))
+                .assertNotMatched(mPackageName, activity(mimeGroup));
+    }
+
+    @Override
+    public void assertMimeGroupInternal(String mimeGroup, Set<String> mimeTypes) {
+        if (mimeTypes == null) {
+            // We can't distinguish empty group from null group via intent resolution
+            return;
+        }
+
+        // Check MIME group types retrieved from declared intent filter
+        assertEquals("Mismatch for " + mimeGroup + " MIME types",
+                mimeTypes, getMimeGroup(mimeGroup, mimeTypes));
+
+        // Additionally we check if all types are matched by this MIME group
+        for (String mimeType: mimeTypes) {
+            assertMatchedByMimeGroup(mimeGroup, mimeType);
+        }
+    }
+
+    public static MimeGroupAssertions testApp(Context context) {
+        return new AssertionsByIntentResolution(PACKAGE_TEST_APP, context);
+    }
+
+    public static MimeGroupAssertions helperApp(Context context) {
+        return new AssertionsByIntentResolution(PACKAGE_HELPER_APP, context);
+    }
+
+    public static MimeGroupAssertions appWithUpdates(Context context) {
+        return new AssertionsByIntentResolution(PACKAGE_UPDATE_APP, context);
+    }
+
+    private Set<String> getMimeGroup(String mimeGroup, Set<String> mimeTypes) {
+        IntentFilter filter = resolve(mContext, intentGroup(mimeGroup, mimeTypes))
+                .getAny()
+                .filter;
+
+        Set<String> actualTypes = new ArraySet<>(filter.countDataTypes());
+
+        for (int i = 0; i < filter.countDataTypes(); i++) {
+            actualTypes.add(appendWildcard(filter.getDataType(i)));
+        }
+
+        return actualTypes;
+    }
+
+    private Intent intentGroup(String mimeGroup, Set<String> mimeTypes) {
+        Intent intent = new Intent(action(mimeGroup));
+
+        if (mimeTypes != null && !mimeTypes.isEmpty()) {
+            intent.setType(mimeTypes.iterator().next());
+        }
+
+        return intent;
+    }
+
+    private static Intent intentSend(String mimeType) {
+        Intent sendIntent = new Intent();
+        sendIntent.setAction(Intent.ACTION_SEND);
+        sendIntent.setType(mimeType);
+        return sendIntent;
+    }
+
+    private static String appendWildcard(String actualType) {
+        int slashpos = actualType.indexOf('/');
+
+        if (slashpos < 0) {
+            return actualType + "/*";
+        } else {
+            return actualType;
+        }
+    }
+
+    private static String activity(String mimeGroup) {
+        switch (mimeGroup) {
+            case GROUP_FIRST:
+                return ACTIVITY_FIRST;
+            case GROUP_SECOND:
+                return ACTIVITY_SECOND;
+            case ALIAS_BOTH_GROUPS:
+                return ACTIVITY_BOTH;
+            case ALIAS_BOTH_GROUPS_AND_TYPE:
+                return ACTIVITY_BOTH_AND_TYPE;
+            default:
+                throw new IllegalArgumentException("Unexpected MIME group " + mimeGroup);
+        }
+    }
+
+    private String action(String mimeGroup) {
+        return mPackageName + ".FILTER_INFO_HOOK_"+ mimeGroup;
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/MimeGroupAssertions.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/MimeGroupAssertions.java
new file mode 100644
index 0000000..105e6eb
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/MimeGroupAssertions.java
@@ -0,0 +1,127 @@
+/*
+ * 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.dynamicmime.testapp.assertions;
+
+import android.content.Context;
+import android.util.ArraySet;
+
+import java.util.Collections;
+import java.util.Set;
+
+public abstract class MimeGroupAssertions {
+    public abstract void assertMatchedByMimeGroup(String mimeGroup, String mimeType);
+
+    public abstract void assertNotMatchedByMimeGroup(String mimeGroup, String mimeType);
+
+    public abstract void assertMimeGroupInternal(String mimeGroup, Set<String> mimeTypes);
+
+    public final void assertMimeGroup(String mimeGroup, String... mimeTypes) {
+        assertMimeGroupInternal(mimeGroup, new ArraySet<>(mimeTypes));
+    }
+
+    public final void assertMimeGroupIsEmpty(String mimeGroup) {
+        assertMimeGroupInternal(mimeGroup, Collections.emptySet());
+    }
+
+    public final void assertMimeGroupIsNull(String mimeGroup) {
+        assertMimeGroupInternal(mimeGroup, null);
+    }
+
+    public final void assertMatchedByMimeGroup(String mimeGroup, String... mimeTypes) {
+        for (String mimeType: mimeTypes) {
+            assertMatchedByMimeGroup(mimeGroup, mimeType);
+        }
+    }
+
+    public final void assertNotMatchedByMimeGroup(String mimeGroup, String... mimeTypes) {
+        for (String mimeType: mimeTypes) {
+            assertNotMatchedByMimeGroup(mimeGroup, mimeType);
+        }
+    }
+
+    public static MimeGroupAssertions testApp(Context context) {
+        return AssertionsByGroupData.testApp(context)
+                .mergeWith(AssertionsByIntentResolution.testApp(context));
+    }
+
+    public static MimeGroupAssertions helperApp(Context context) {
+        return AssertionsByGroupData.helperApp(context)
+                .mergeWith(AssertionsByIntentResolution.helperApp(context));
+    }
+
+    public static MimeGroupAssertions appWithUpdates(Context context) {
+        return AssertionsByGroupData.appWithUpdates(context)
+                .mergeWith(AssertionsByIntentResolution.appWithUpdates(context));
+    }
+
+    public static MimeGroupAssertions noOp() {
+        return new MimeGroupAssertions() {
+            @Override
+            public void assertMatchedByMimeGroup(String mimeGroup, String mimeType) {
+            }
+
+            @Override
+            public void assertNotMatchedByMimeGroup(String mimeGroup, String mimeType) {
+            }
+
+            @Override
+            public void assertMimeGroupInternal(String mimeGroup, Set<String> mimeTypes) {
+            }
+        };
+    }
+
+    public static MimeGroupAssertions notUsed() {
+        return new MimeGroupAssertions() {
+            @Override
+            public void assertMatchedByMimeGroup(String group, String type) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public void assertNotMatchedByMimeGroup(String group, String type) {
+                throw new UnsupportedOperationException();
+            }
+
+            @Override
+            public void assertMimeGroupInternal(String group, Set<String> types) {
+                throw new UnsupportedOperationException();
+            }
+        };
+    }
+
+    protected final MimeGroupAssertions mergeWith(MimeGroupAssertions other) {
+        return new MimeGroupAssertions() {
+            @Override
+            public void assertMatchedByMimeGroup(String mimeGroup, String mimeType) {
+                other.assertMatchedByMimeGroup(mimeGroup, mimeType);
+                MimeGroupAssertions.this.assertMatchedByMimeGroup(mimeGroup, mimeType);
+            }
+
+            @Override
+            public void assertNotMatchedByMimeGroup(String mimeGroup, String mimeType) {
+                other.assertNotMatchedByMimeGroup(mimeGroup, mimeType);
+                MimeGroupAssertions.this.assertNotMatchedByMimeGroup(mimeGroup, mimeType);
+            }
+
+            @Override
+            public void assertMimeGroupInternal(String mimeGroup, Set<String> mimeTypes) {
+                other.assertMimeGroupInternal(mimeGroup, mimeTypes);
+                MimeGroupAssertions.this.assertMimeGroupInternal(mimeGroup, mimeTypes);
+            }
+        };
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/TestAppAssertionsByGroupData.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/TestAppAssertionsByGroupData.java
new file mode 100644
index 0000000..017fa44
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/assertions/TestAppAssertionsByGroupData.java
@@ -0,0 +1,34 @@
+/*
+ * 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.dynamicmime.testapp.assertions;
+
+import android.content.Context;
+
+import java.util.Set;
+
+class TestAppAssertionsByGroupData extends AssertionsByGroupData {
+    private final Context mContext;
+
+    TestAppAssertionsByGroupData(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    protected Set<String> getMimeGroup(String mimeGroup) {
+        return mContext.getPackageManager().getMimeGroup(mimeGroup);
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/commands/AppCommands.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/commands/AppCommands.java
new file mode 100644
index 0000000..83400f4
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/commands/AppCommands.java
@@ -0,0 +1,49 @@
+/*
+ * 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.dynamicmime.testapp.commands;
+
+import android.content.Context;
+import android.dynamicmime.testapp.util.AppMimeGroups;
+import android.util.ArraySet;
+
+import androidx.annotation.Nullable;
+
+import java.util.Set;
+
+public class AppCommands implements MimeGroupCommands {
+    private final AppMimeGroups mAppMimeGroups;
+
+    AppCommands(Context context, String targetPackage) {
+        mAppMimeGroups = AppMimeGroups.with(context, targetPackage);
+    }
+
+    @Override
+    public void setMimeGroup(String mimeGroup, Set<String> mimeTypes) {
+        mAppMimeGroups.set(mimeGroup, mimeTypes.toArray(new String[0]));
+    }
+
+    @Override
+    public void clearMimeGroup(String mimeGroup) {
+        mAppMimeGroups.clear(mimeGroup);
+    }
+
+    @Nullable
+    @Override
+    public Set<String> getMimeGroupInternal(String mimeGroup) {
+        return new ArraySet<>(mAppMimeGroups.get(mimeGroup));
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/commands/MimeGroupCommands.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/commands/MimeGroupCommands.java
new file mode 100644
index 0000000..27a9cb2
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/commands/MimeGroupCommands.java
@@ -0,0 +1,102 @@
+/*
+ * 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.dynamicmime.testapp.commands;
+
+import static android.dynamicmime.common.Constants.GROUP_FIRST;
+import static android.dynamicmime.common.Constants.GROUP_SECOND;
+import static android.dynamicmime.common.Constants.GROUP_THIRD;
+import static android.dynamicmime.common.Constants.GROUP_UNDEFINED;
+import static android.dynamicmime.common.Constants.PACKAGE_HELPER_APP;
+import static android.dynamicmime.common.Constants.PACKAGE_PREFERRED_APP;
+import static android.dynamicmime.common.Constants.PACKAGE_UPDATE_APP;
+
+import android.content.Context;
+import android.util.ArraySet;
+
+import androidx.annotation.Nullable;
+
+import java.util.Set;
+
+public interface MimeGroupCommands {
+    void setMimeGroup(String mimeGroup, Set<String> mimeTypes);
+    void clearMimeGroup(String mimeGroup);
+
+    @Nullable
+    Set<String> getMimeGroupInternal(String mimeGroup);
+
+    default void addMimeTypeToGroup(String mimeGroup, String... mimeTypes) {
+        ArraySet<String> typesToAdd = new ArraySet<>(mimeTypes);
+
+        Set<String> newMimeTypes = new ArraySet<>(getMimeGroupInternal(mimeGroup));
+        newMimeTypes.addAll(typesToAdd);
+
+        setMimeGroup(mimeGroup, newMimeTypes);
+    }
+
+    default void removeMimeTypeFromGroup(String mimeGroup, String... mimeTypes) {
+        ArraySet<String> typesToRemove = new ArraySet<>(mimeTypes);
+
+        Set<String> newMimeTypes = new ArraySet<>(getMimeGroupInternal(mimeGroup));
+        newMimeTypes.removeAll(typesToRemove);
+
+        setMimeGroup(mimeGroup, newMimeTypes);
+    }
+
+    default void setMimeGroup(String mimeGroup, String... mimeTypes) {
+        setMimeGroup(mimeGroup, new ArraySet<>(mimeTypes));
+    }
+
+    default void clearGroups() {
+        clearMimeGroup(GROUP_FIRST);
+        clearMimeGroup(GROUP_SECOND);
+        clearMimeGroup(GROUP_THIRD);
+        clearMimeGroup(GROUP_UNDEFINED);
+    }
+
+    static MimeGroupCommands testApp(Context context) {
+        return new TestAppCommands(context);
+    }
+
+    static MimeGroupCommands helperApp(Context context) {
+        return new AppCommands(context, PACKAGE_HELPER_APP);
+    }
+
+    static MimeGroupCommands appWithUpdates(Context context) {
+        return new AppCommands(context, PACKAGE_UPDATE_APP);
+    }
+
+    static MimeGroupCommands preferredApp(Context context) {
+        return new AppCommands(context, PACKAGE_PREFERRED_APP);
+    }
+
+    static MimeGroupCommands noOp() {
+        return new MimeGroupCommands() {
+            @Override
+            public void setMimeGroup(String mimeGroup, Set<String> mimeTypes) {
+            }
+
+            @Override
+            public void clearMimeGroup(String mimeGroup) {
+            }
+
+            @Override
+            public Set<String> getMimeGroupInternal(String mimeGroup) {
+                return null;
+            }
+        };
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/commands/TestAppCommands.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/commands/TestAppCommands.java
new file mode 100644
index 0000000..82dcb8853
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/commands/TestAppCommands.java
@@ -0,0 +1,48 @@
+/*
+ * 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.dynamicmime.testapp.commands;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+
+import androidx.annotation.Nullable;
+
+import java.util.Set;
+
+public class TestAppCommands implements MimeGroupCommands {
+    private PackageManager mPM;
+
+    TestAppCommands(Context context) {
+        mPM = context.getPackageManager();
+    }
+
+    @Override
+    public void setMimeGroup(String mimeGroup, Set<String> mimeTypes) {
+        mPM.setMimeGroup(mimeGroup, mimeTypes);
+    }
+
+    @Override
+    public void clearMimeGroup(String mimeGroup) {
+        mPM.clearMimeGroup(mimeGroup);
+    }
+
+    @Nullable
+    @Override
+    public Set<String> getMimeGroupInternal(String mimeGroup) {
+        return mPM.getMimeGroup(mimeGroup);
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/preferred/PreferredActivitiesTest.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/preferred/PreferredActivitiesTest.java
new file mode 100644
index 0000000..5ef052ef
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/preferred/PreferredActivitiesTest.java
@@ -0,0 +1,323 @@
+/*
+ * 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.dynamicmime.testapp.preferred;
+
+import static android.dynamicmime.common.Constants.ACTIVITY_BOTH;
+import static android.dynamicmime.common.Constants.ACTIVITY_FIRST;
+import static android.dynamicmime.common.Constants.APK_PREFERRED_APP;
+import static android.dynamicmime.common.Constants.GROUP_FIRST;
+import static android.dynamicmime.common.Constants.GROUP_SECOND;
+import static android.dynamicmime.common.Constants.GROUP_THIRD;
+import static android.dynamicmime.common.Constants.MIME_IMAGE_PNG;
+import static android.dynamicmime.common.Constants.MIME_TEXT_PLAIN;
+import static android.dynamicmime.common.Constants.PACKAGE_PREFERRED_APP;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Intent;
+import android.dynamicmime.testapp.BaseDynamicMimeTest;
+import android.dynamicmime.testapp.assertions.MimeGroupAssertions;
+import android.dynamicmime.testapp.commands.MimeGroupCommands;
+import android.dynamicmime.testapp.util.Utils;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.uiautomator.By;
+import androidx.test.uiautomator.BySelector;
+import androidx.test.uiautomator.Direction;
+import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class PreferredActivitiesTest extends BaseDynamicMimeTest {
+    private static final BySelector BUTTON_ALWAYS = By.res("android:id/button_always");
+    private static final BySelector RESOLVER_DIALOG = By.res("android:id/contentPanel");
+
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+
+    private TestStrategy mTest;
+
+    public PreferredActivitiesTest() {
+        super(MimeGroupCommands.preferredApp(context()), MimeGroupAssertions.notUsed());
+    }
+
+    @Before
+    public void setUp() {
+        Utils.installApk(APK_PREFERRED_APP);
+    }
+
+    @After
+    public void tearDown() {
+        super.tearDown();
+        Utils.uninstallApp(PACKAGE_PREFERRED_APP);
+    }
+
+    @Test
+    public void testRemoveFromGroup() {
+        setStrategyAndRun(new TestStrategy() {
+            @Override
+            public void prepareMimeGroups() {
+                addMimeTypeToGroup(GROUP_FIRST, mimeType());
+            }
+
+            @Override
+            public void changeMimeGroups() {
+                removeMimeTypeFromGroup(GROUP_FIRST, mimeType());
+            }
+
+            @Override
+            public String preferredActivity() {
+                return ACTIVITY_FIRST;
+            }
+        });
+    }
+
+    @Test
+    public void testAddToGroup() {
+        setStrategyAndRun(new TestStrategy() {
+            @Override
+            public void prepareMimeGroups() {
+                addMimeTypeToGroup(GROUP_FIRST, mimeType());
+            }
+
+            @Override
+            public void changeMimeGroups() {
+                addMimeTypeToGroup(GROUP_FIRST, MIME_IMAGE_PNG);
+            }
+
+            @Override
+            public void revertMimeGroupsChange() {
+                removeMimeTypeFromGroup(GROUP_FIRST, MIME_IMAGE_PNG);
+            }
+
+            @Override
+            public String preferredActivity() {
+                return ACTIVITY_FIRST;
+            }
+        });
+    }
+
+    @Test
+    public void testClearGroup() {
+        setStrategyAndRun(new TestStrategy() {
+            @Override
+            public void prepareMimeGroups() {
+                addMimeTypeToGroup(GROUP_FIRST, mimeType());
+            }
+
+            @Override
+            public void changeMimeGroups() {
+                clearMimeGroup(GROUP_FIRST);
+            }
+
+            @Override
+            public String preferredActivity() {
+                return ACTIVITY_FIRST;
+            }
+        });
+    }
+
+    @Test
+    public void testModifyGroupWithoutActualGroupChanges() {
+        setStrategyAndRun(new TestStrategy() {
+            @Override
+            public void prepareMimeGroups() {
+                addMimeTypeToGroup(GROUP_FIRST, mimeType());
+            }
+
+            @Override
+            public void changeMimeGroups() {
+                addMimeTypeToGroup(GROUP_FIRST, mimeType());
+            }
+
+            @Override
+            public String preferredActivity() {
+                return ACTIVITY_FIRST;
+            }
+
+            @Override
+            public boolean isActivityPreferredAfterRevert() {
+                return true;
+            }
+
+            @Override
+            public boolean isActivityPreferredAfterChange() {
+                return true;
+            }
+        });
+    }
+
+    @Test
+    public void testModifyGroupWithoutActualIntentFilterChanges() {
+        setStrategyAndRun(new TestStrategy() {
+            @Override
+            public void prepareMimeGroups() {
+                addMimeTypeToGroup(GROUP_THIRD, mimeType());
+                addMimeTypeToGroup(GROUP_SECOND, mimeType());
+            }
+
+            @Override
+            public void changeMimeGroups() {
+                removeMimeTypeFromGroup(GROUP_THIRD, mimeType());
+            }
+
+            @Override
+            public void revertMimeGroupsChange() {
+                addMimeTypeToGroup(GROUP_THIRD, mimeType());
+            }
+
+            @Override
+            public String preferredActivity() {
+                return ACTIVITY_BOTH;
+            }
+
+            @Override
+            public boolean isActivityPreferredAfterRevert() {
+                return true;
+            }
+
+            @Override
+            public boolean isActivityPreferredAfterChange() {
+                return true;
+            }
+        });
+    }
+
+    private void setStrategyAndRun(TestStrategy test) {
+        mTest = test;
+        runTest();
+    }
+
+    private void runTest() {
+        mTest.prepareMimeGroups();
+        setPreferredActivity();
+
+        mTest.changeMimeGroups();
+        checkPreferredActivityAfterChange();
+
+        mTest.revertMimeGroupsChange();
+        checkPreferredActivityAfterRevert();
+
+        getUiDevice().pressHome();
+    }
+
+    private void setPreferredActivity() {
+        triggerResolutionDialog();
+
+        verifyDialogIsShown(true);
+
+        chooseActivity("TestApp" + mTest.preferredActivity());
+    }
+
+    private void checkPreferredActivityAfterChange() {
+        checkPreferredActivity(mTest.isActivityPreferredAfterChange());
+    }
+
+    private void checkPreferredActivityAfterRevert() {
+        checkPreferredActivity(mTest.isActivityPreferredAfterRevert());
+    }
+
+    private void checkPreferredActivity(boolean hasPreferredActivity) {
+        triggerResolutionDialog();
+        verifyResolutionDialog(hasPreferredActivity);
+    }
+
+    private void triggerResolutionDialog() {
+        getUiDevice().pressHome();
+        sendIntent(mTest.mimeType());
+    }
+
+    private void verifyResolutionDialog(boolean shouldLaunchActivity) {
+        verifyDialogIsShown(!shouldLaunchActivity);
+        getUiDevice().pressBack();
+    }
+
+    private void verifyDialogIsShown(boolean shouldBeShown) {
+        UiObject2 buttonAlways = getUiDevice().wait(Until.findObject(BUTTON_ALWAYS), TIMEOUT);
+
+        if (shouldBeShown) {
+            assertNotNull(buttonAlways);
+        } else {
+            assertNull(buttonAlways);
+        }
+    }
+
+    private void chooseActivity(String label) {
+        findActivityInDialog(label).click();
+        chooseUseAlways();
+
+        getUiDevice().pressBack();
+    }
+
+    private UiObject2 findActivityInDialog(String label) {
+        getUiDevice()
+                .wait(Until.findObject(RESOLVER_DIALOG), TIMEOUT)
+                .swipe(Direction.UP, 1f);
+
+        return getUiDevice().findObject(By.text(label));
+    }
+
+    private void chooseUseAlways() {
+        getUiDevice()
+                .findObject(BUTTON_ALWAYS)
+                .click();
+    }
+
+    private interface TestStrategy {
+        void prepareMimeGroups();
+
+        void changeMimeGroups();
+
+        default void revertMimeGroupsChange() {
+            prepareMimeGroups();
+        }
+
+        default String mimeType() {
+            return MIME_TEXT_PLAIN;
+        }
+
+        String preferredActivity();
+
+        default boolean isActivityPreferredAfterChange() {
+            return false;
+        }
+
+        default boolean isActivityPreferredAfterRevert() {
+            return false;
+        }
+    }
+
+    private static UiDevice getUiDevice() {
+        return UiDevice.getInstance(instrumentation());
+    }
+
+    private static void sendIntent(String mimeType) {
+        Intent sendIntent = new Intent();
+        sendIntent.setAction(Intent.ACTION_SEND);
+        sendIntent.setType(mimeType);
+        sendIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        targetContext().startActivity(sendIntent, null);
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/reboot/PostRebootComplexFilterTest.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/reboot/PostRebootComplexFilterTest.java
new file mode 100644
index 0000000..19f8bab
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/reboot/PostRebootComplexFilterTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.dynamicmime.testapp.reboot;
+
+import android.dynamicmime.testapp.ComplexFilterTest;
+import android.dynamicmime.testapp.commands.MimeGroupCommands;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+/**
+ * Post-reboot part of {@link ComplexFilterTest}
+ *
+ * {@link MimeGroupCommands} are disabled during test body execution
+ */
+@RunWith(AndroidJUnit4.class)
+public class PostRebootComplexFilterTest extends ComplexFilterTest {
+    private MimeGroupCommands mCommands;
+
+    public PostRebootComplexFilterTest() {
+        super();
+        mCommands = getCommands();
+    }
+
+    @Before
+    public void setUp() {
+        setCommands(MimeGroupCommands.noOp());
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        setCommands(mCommands);
+        super.tearDown();
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/reboot/PostRebootSingleAppTest.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/reboot/PostRebootSingleAppTest.java
new file mode 100644
index 0000000..3e4c86d
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/reboot/PostRebootSingleAppTest.java
@@ -0,0 +1,53 @@
+/*
+ * 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.dynamicmime.testapp.reboot;
+
+import android.dynamicmime.testapp.SingleAppTest;
+import android.dynamicmime.testapp.commands.MimeGroupCommands;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+/**
+ * Post-reboot part of {@link SingleAppTest}
+ *
+ * {@link MimeGroupCommands} are disabled during test body execution
+ */
+@RunWith(AndroidJUnit4.class)
+public class PostRebootSingleAppTest extends SingleAppTest {
+    private MimeGroupCommands mCommands;
+
+    public PostRebootSingleAppTest() {
+        super();
+        mCommands = getCommands();
+    }
+
+    @Before
+    public void setUp() {
+        setCommands(MimeGroupCommands.noOp());
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        setCommands(mCommands);
+        super.tearDown();
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/reboot/PreRebootComplexFilterTest.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/reboot/PreRebootComplexFilterTest.java
new file mode 100644
index 0000000..9f290e3
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/reboot/PreRebootComplexFilterTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.dynamicmime.testapp.reboot;
+
+import android.dynamicmime.testapp.ComplexFilterTest;
+import android.dynamicmime.testapp.assertions.MimeGroupAssertions;
+import android.dynamicmime.testapp.commands.MimeGroupCommands;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+/**
+ * Pre-reboot part of {@link ComplexFilterTest}
+ *
+ * {@link MimeGroupAssertions} are disabled
+ * {@link MimeGroupCommands} are disabled during tear down
+ */
+@RunWith(AndroidJUnit4.class)
+public class PreRebootComplexFilterTest extends ComplexFilterTest {
+    private MimeGroupCommands mCommands;
+
+    public PreRebootComplexFilterTest() {
+        super();
+        setAssertions(MimeGroupAssertions.noOp());
+        mCommands = getCommands();
+    }
+
+    @Before
+    public void setUp() {
+        setCommands(mCommands);
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        setCommands(MimeGroupCommands.noOp());
+        super.tearDown();
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/reboot/PreRebootSingleAppTest.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/reboot/PreRebootSingleAppTest.java
new file mode 100644
index 0000000..5a65df7
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/reboot/PreRebootSingleAppTest.java
@@ -0,0 +1,56 @@
+/*
+ * 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.dynamicmime.testapp.reboot;
+
+import android.dynamicmime.testapp.SingleAppTest;
+import android.dynamicmime.testapp.assertions.MimeGroupAssertions;
+import android.dynamicmime.testapp.commands.MimeGroupCommands;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.runner.RunWith;
+
+/**
+ * Pre-reboot part of {@link SingleAppTest}
+ *
+ * {@link MimeGroupAssertions} are disabled
+ * {@link MimeGroupCommands} are disabled during tear down
+ */
+@RunWith(AndroidJUnit4.class)
+public class PreRebootSingleAppTest extends SingleAppTest {
+    private MimeGroupCommands mCommands;
+
+    public PreRebootSingleAppTest() {
+        super();
+        setAssertions(MimeGroupAssertions.noOp());
+        mCommands = getCommands();
+    }
+
+    @Before
+    public void setUp() {
+        setCommands(mCommands);
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        setCommands(MimeGroupCommands.noOp());
+        super.tearDown();
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/update/BaseUpdateTest.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/update/BaseUpdateTest.java
new file mode 100644
index 0000000..43f590a
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/update/BaseUpdateTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.dynamicmime.testapp.update;
+
+import static android.dynamicmime.common.Constants.PACKAGE_UPDATE_APP;
+
+import android.dynamicmime.testapp.BaseDynamicMimeTest;
+import android.dynamicmime.testapp.assertions.MimeGroupAssertions;
+import android.dynamicmime.testapp.commands.MimeGroupCommands;
+import android.dynamicmime.testapp.util.Utils;
+
+import org.junit.After;
+import org.junit.Before;
+
+import java.io.IOException;
+
+abstract class BaseUpdateTest extends BaseDynamicMimeTest {
+    BaseUpdateTest() {
+        super(MimeGroupCommands.appWithUpdates(context()),
+                MimeGroupAssertions.appWithUpdates(context()));
+    }
+
+    @Before
+    public void setUp() throws IOException {
+        Utils.installApk(installApkPath());
+    }
+
+    @After
+    @Override
+    public void tearDown() {
+        super.tearDown();
+        Utils.uninstallApp(PACKAGE_UPDATE_APP);
+    }
+
+    void updateApp() {
+        Utils.updateApp(updateApkPath());
+    }
+
+    abstract String installApkPath();
+    abstract String updateApkPath();
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/update/ChangedGroupsTest.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/update/ChangedGroupsTest.java
new file mode 100644
index 0000000..b209699
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/update/ChangedGroupsTest.java
@@ -0,0 +1,73 @@
+/*
+ * 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.dynamicmime.testapp.update;
+
+import static android.dynamicmime.common.Constants.APK_FIRST_GROUP;
+import static android.dynamicmime.common.Constants.APK_SECOND_GROUP;
+import static android.dynamicmime.common.Constants.GROUP_FIRST;
+import static android.dynamicmime.common.Constants.GROUP_SECOND;
+import static android.dynamicmime.common.Constants.GROUP_UNDEFINED;
+import static android.dynamicmime.common.Constants.MIME_TEXT_PLAIN;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test cases for app update with MIME groups changes
+ */
+@RunWith(AndroidJUnit4.class)
+public class ChangedGroupsTest extends BaseUpdateTest {
+    @Test
+    public void testUpdateRemoveEmptyGroup() {
+        assertMimeGroupIsEmpty(GROUP_FIRST);
+        assertMimeGroupIsNull(GROUP_SECOND);
+        assertMimeGroupIsNull(GROUP_UNDEFINED);
+
+        updateApp();
+
+        assertMimeGroupIsNull(GROUP_FIRST);
+        assertMimeGroupIsEmpty(GROUP_SECOND);
+        assertMimeGroupIsNull(GROUP_UNDEFINED);
+    }
+
+    @Test
+    public void testUpdateRemoveNonEmptyGroup() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+
+        assertMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+        assertMimeGroupIsNull(GROUP_SECOND);
+        assertMimeGroupIsNull(GROUP_UNDEFINED);
+
+        updateApp();
+
+        assertMimeGroupIsNull(GROUP_FIRST);
+        assertMimeGroupIsEmpty(GROUP_SECOND);
+        assertMimeGroupIsNull(GROUP_UNDEFINED);
+    }
+
+    @Override
+    String installApkPath() {
+        return APK_FIRST_GROUP;
+    }
+
+    @Override
+    String updateApkPath() {
+        return APK_SECOND_GROUP;
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/update/SameGroupsTest.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/update/SameGroupsTest.java
new file mode 100644
index 0000000..0c3349a
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/update/SameGroupsTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.dynamicmime.testapp.update;
+
+import static android.dynamicmime.common.Constants.APK_BOTH_GROUPS;
+import static android.dynamicmime.common.Constants.GROUP_FIRST;
+import static android.dynamicmime.common.Constants.GROUP_SECOND;
+import static android.dynamicmime.common.Constants.GROUP_UNDEFINED;
+import static android.dynamicmime.common.Constants.MIME_TEXT_PLAIN;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test cases for app update, without MIME groups changes
+ */
+@RunWith(AndroidJUnit4.class)
+public class SameGroupsTest extends BaseUpdateTest {
+    @Test
+    public void testUpdate() {
+        setMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+
+        updateApp();
+
+        assertMimeGroup(GROUP_FIRST, MIME_TEXT_PLAIN);
+        assertMimeGroupIsEmpty(GROUP_SECOND);
+        assertMimeGroupIsNull(GROUP_UNDEFINED);
+    }
+
+    @Override
+    String installApkPath() {
+        return APK_BOTH_GROUPS;
+    }
+
+    @Override
+    String updateApkPath() {
+        return APK_BOTH_GROUPS;
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/util/AppMimeGroups.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/util/AppMimeGroups.java
new file mode 100644
index 0000000..b034946
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/util/AppMimeGroups.java
@@ -0,0 +1,99 @@
+/*
+ * 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.dynamicmime.testapp.util;
+
+import static android.dynamicmime.common.Constants.ACTION_REQUEST;
+import static android.dynamicmime.common.Constants.ACTION_RESPONSE;
+import static android.dynamicmime.common.Constants.EXTRA_GROUP;
+import static android.dynamicmime.common.Constants.EXTRA_MIMES;
+import static android.dynamicmime.common.Constants.EXTRA_REQUEST;
+import static android.dynamicmime.common.Constants.EXTRA_RESPONSE;
+import static android.dynamicmime.common.Constants.REQUEST_CLEAR;
+import static android.dynamicmime.common.Constants.REQUEST_GET;
+import static android.dynamicmime.common.Constants.REQUEST_SET;
+
+import static org.junit.Assert.assertNotNull;
+
+import android.content.Context;
+import android.content.Intent;
+
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Class that dispatches MIME groups related commands to another app
+ * Target app should register {@link android.dynamicmime.app.AppMimeGroupsReceiver}
+ * to handler requests
+ */
+public class AppMimeGroups {
+    private final Context mContext;
+    private final String mTargetPackage;
+
+    private AppMimeGroups(Context context, String targetPackage) {
+        mContext = context;
+        mTargetPackage = targetPackage;
+    }
+
+    public static AppMimeGroups with(Context context, String targetPackage) {
+        return new AppMimeGroups(context, targetPackage);
+    }
+
+    public void set(String mimeGroup, String[] mimeTypes) {
+        sendRequestAndAwait(mimeGroup, REQUEST_SET, mimeTypes);
+    }
+
+    public void clear(String mimeGroup) {
+        sendRequestAndAwait(mimeGroup, REQUEST_CLEAR);
+    }
+
+    public String[] get(String mimeGroup) {
+        return sendRequestAndAwait(mimeGroup, REQUEST_GET)
+                .getStringArrayExtra(EXTRA_RESPONSE);
+    }
+
+    private Intent sendRequestAndAwait(String mimeGroup, int requestSet) {
+        return sendRequestAndAwait(mimeGroup, requestSet, null);
+    }
+
+    private Intent sendRequestAndAwait(String mimeGroup, int request, String[] mimeTypes) {
+        BlockingBroadcastReceiver receiver =
+                new BlockingBroadcastReceiver(mContext, ACTION_RESPONSE);
+        receiver.register();
+
+        mContext.sendBroadcast(getRequestIntent(mimeGroup, mimeTypes, request));
+
+        Intent response = receiver.awaitForBroadcast(TimeUnit.SECONDS.toMillis(5L));
+        assertNotNull(response);
+
+        mContext.unregisterReceiver(receiver);
+
+        return response;
+    }
+
+    private Intent getRequestIntent(String mimeGroup, String[] mimeTypes, int request) {
+        Intent intent = new Intent(ACTION_REQUEST);
+        intent.setPackage(mTargetPackage);
+        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
+
+        intent.putExtra(EXTRA_GROUP, mimeGroup);
+        intent.putExtra(EXTRA_MIMES, mimeTypes);
+        intent.putExtra(EXTRA_REQUEST, request);
+
+        return intent;
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/util/IntentsResolutionHelper.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/util/IntentsResolutionHelper.java
new file mode 100644
index 0000000..fceb48f
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/util/IntentsResolutionHelper.java
@@ -0,0 +1,91 @@
+/*
+ * 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.dynamicmime.testapp.util;
+
+import static android.dynamicmime.common.Constants.PACKAGE_ACTIVITIES;
+
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.Matchers.empty;
+import static org.hamcrest.Matchers.hasItem;
+import static org.junit.Assert.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+
+import java.util.List;
+
+/**
+ * Helper class for {@link PackageManager#queryIntentActivities} results verification
+ */
+public class IntentsResolutionHelper {
+    private final List<ResolveInfo> resolvedActivities;
+    private final Intent mIntent;
+
+    public static IntentsResolutionHelper resolve(Context context, Intent intent) {
+        return new IntentsResolutionHelper(context, intent);
+    }
+
+    private IntentsResolutionHelper(Context context, Intent intent) {
+        mIntent = intent;
+        this.resolvedActivities = context.getPackageManager().queryIntentActivities(mIntent, PackageManager.GET_RESOLVED_FILTER);
+    }
+
+    public void assertMatched(String packageName, String simpleClassName) {
+        assertThat("Missing expected match for " + mIntent, resolvedActivities,
+                hasItem(activity(packageName, simpleClassName)));
+    }
+
+    public void assertNotMatched(String packageName, String simpleClassName) {
+        assertThat("Unexpected match for " + mIntent, resolvedActivities,
+                not(hasItem(activity(packageName, simpleClassName))));
+    }
+
+    public ResolveInfo getAny() {
+        assertThat("Missing match for " + mIntent, resolvedActivities, not(empty()));
+
+        return resolvedActivities
+                .stream()
+                .findAny()
+                .orElse(null);
+    }
+
+    private Matcher<ResolveInfo> activity(String packageName, String simpleClassName) {
+        return new BaseMatcher<ResolveInfo>() {
+            @Override
+            public void describeTo(Description description) {
+                description.appendText("packageName=" + packageName + ", simpleClassName=" + simpleClassName);
+            }
+
+            @Override
+            public boolean matches(Object item) {
+                if (item == null) {
+                    return false;
+                }
+                ResolveInfo info = (ResolveInfo) item;
+
+                return info.activityInfo.packageName.equals(packageName)
+                        && info.activityInfo.name.equals(PACKAGE_ACTIVITIES + simpleClassName);
+            }
+        };
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/util/MimeGroupOperations.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/util/MimeGroupOperations.java
new file mode 100644
index 0000000..ebd4cc4
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/util/MimeGroupOperations.java
@@ -0,0 +1,85 @@
+/*
+ * 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.dynamicmime.testapp.util;
+
+import android.dynamicmime.testapp.assertions.MimeGroupAssertions;
+import android.dynamicmime.testapp.commands.MimeGroupCommands;
+
+import androidx.annotation.Nullable;
+
+import java.util.Set;
+
+/**
+ * Base class for commands/queries related to MIME groups
+ * Allows independent implementations for commands and assertions
+ */
+public class MimeGroupOperations extends MimeGroupAssertions implements MimeGroupCommands {
+    private MimeGroupCommands mCommands;
+    private MimeGroupAssertions mAssertions;
+
+    public MimeGroupOperations(MimeGroupCommands commands, MimeGroupAssertions assertions) {
+        mCommands = commands;
+        mAssertions = assertions;
+    }
+
+    @Override
+    public final void assertMatchedByMimeGroup(String mimeGroup, String mimeType) {
+        mAssertions.assertMatchedByMimeGroup(mimeGroup, mimeType);
+    }
+
+    @Override
+    public final void assertNotMatchedByMimeGroup(String mimeGroup, String mimeType) {
+        mAssertions.assertNotMatchedByMimeGroup(mimeGroup, mimeType);
+    }
+
+    @Override
+    public final void assertMimeGroupInternal(String mimeGroup, Set<String> mimeTypes) {
+        mAssertions.assertMimeGroupInternal(mimeGroup, mimeTypes);
+    }
+
+    @Override
+    public void setMimeGroup(String mimeGroup, Set<String> mimeTypes) {
+        mCommands.setMimeGroup(mimeGroup, mimeTypes);
+    }
+
+    @Override
+    public final void clearMimeGroup(String mimeGroup) {
+        mCommands.clearMimeGroup(mimeGroup);
+    }
+
+    @Nullable
+    @Override
+    public Set<String> getMimeGroupInternal(String mimeGroup) {
+        return mCommands.getMimeGroupInternal(mimeGroup);
+    }
+
+    public MimeGroupCommands getCommands() {
+        return mCommands;
+    }
+
+    public MimeGroupAssertions getAssertions() {
+        return mAssertions;
+    }
+
+    public void setCommands(MimeGroupCommands commands) {
+        mCommands = commands;
+    }
+
+    public void setAssertions(MimeGroupAssertions assertions) {
+        mAssertions = assertions;
+    }
+}
diff --git a/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/util/Utils.java b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/util/Utils.java
new file mode 100644
index 0000000..fce0c98f
--- /dev/null
+++ b/hostsidetests/packagemanager/dynamicmime/test/src/android/dynamicmime/testapp/util/Utils.java
@@ -0,0 +1,72 @@
+/*
+ * 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.dynamicmime.testapp.util;
+
+import static org.junit.Assert.assertTrue;
+
+import android.os.ParcelFileDescriptor;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class Utils {
+    public static void installApk(String pathToApk) {
+        executeShellCommand("pm install -t " + pathToApk);
+    }
+
+    public static void updateApp(String pathToApk) {
+        executeShellCommand("pm install -t -r  " + pathToApk);
+    }
+
+    public static void uninstallApp(String appPackage) {
+        executeShellCommand("pm uninstall " + appPackage);
+    }
+
+    private static void executeShellCommand(String command) {
+        ParcelFileDescriptor pfd = InstrumentationRegistry
+                .getInstrumentation()
+                .getUiAutomation()
+                .executeShellCommand(command);
+        InputStream is = new FileInputStream(pfd.getFileDescriptor());
+
+        try {
+            readFully(is);
+        } catch (IOException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    private static void readFully(InputStream in) throws IOException {
+        try {
+            ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+            byte[] buffer = new byte[1024];
+            int count;
+            while ((count = in.read(buffer)) != -1) {
+                bytes.write(buffer, 0, count);
+            }
+
+            String result = new String(bytes.toByteArray());
+            assertTrue(result, result.isEmpty() || result.contains("Success"));
+        } finally {
+            in.close();
+        }
+    }
+}
diff --git a/hostsidetests/stagedinstall/app/AndroidManifest.xml b/hostsidetests/stagedinstall/app/AndroidManifest.xml
index 067e1cc..bc8bb0b 100644
--- a/hostsidetests/stagedinstall/app/AndroidManifest.xml
+++ b/hostsidetests/stagedinstall/app/AndroidManifest.xml
@@ -17,7 +17,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.android.tests.stagedinstall" >
 
-    <uses-permission android:name="QUERY_ALL_PACKAGES" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
     <application>
         <receiver android:name="com.android.cts.install.lib.LocalIntentSender"
                   android:exported="true" />
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java
index d0a05f4..b3ab63c 100644
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/StatsdCtsForegroundActivity.java
@@ -21,6 +21,7 @@
 import android.app.Activity;
 import android.app.Notification;
 import android.app.NotificationChannel;
+import android.app.NotificationChannelGroup;
 import android.app.NotificationManager;
 import android.content.Intent;
 import android.graphics.Color;
@@ -44,6 +45,7 @@
     public static final String ACTION_SHOW_APPLICATION_OVERLAY = "action.show_application_overlay";
     public static final String ACTION_SHOW_NOTIFICATION = "action.show_notification";
     public static final String ACTION_CRASH = "action.crash";
+    public static final String ACTION_CREATE_CHANNEL_GROUP = "action.create_channel_group";
 
     public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
     public static final int SLEEP_OF_ACTION_SHOW_APPLICATION_OVERLAY = 2_000;
@@ -81,6 +83,9 @@
             case ACTION_CRASH:
                 doCrash();
                 break;
+            case ACTION_CREATE_CHANNEL_GROUP:
+                doCreateChannelGroup();
+                break;
             default:
                 Log.e(TAG, "Intent had invalid action " + action);
                 finish();
@@ -135,15 +140,17 @@
 
     private void doShowNotification() {
         final int notificationId = R.layout.activity_main;
-        final String notificationChannel = "StatsdCtsChannel";
+        final String notificationChannelId = "StatsdCtsChannel";
 
         NotificationManager nm = getSystemService(NotificationManager.class);
-        nm.createNotificationChannel(new NotificationChannel(notificationChannel, "Statsd Cts",
-                NotificationManager.IMPORTANCE_DEFAULT));
+        NotificationChannel channel = new NotificationChannel(notificationChannelId, "Statsd Cts",
+                NotificationManager.IMPORTANCE_DEFAULT);
+        channel.setDescription("Statsd Cts Channel");
+        nm.createNotificationChannel(channel);
 
         nm.notify(
                 notificationId,
-                new Notification.Builder(this, notificationChannel)
+                new Notification.Builder(this, notificationChannelId)
                         .setSmallIcon(android.R.drawable.stat_notify_chat)
                         .setContentTitle("StatsdCts")
                         .setContentText("StatsdCts")
@@ -152,6 +159,15 @@
         finish();
     }
 
+    private void doCreateChannelGroup() {
+        NotificationManager nm = getSystemService(NotificationManager.class);
+        NotificationChannelGroup channelGroup = new NotificationChannelGroup("StatsdCtsGroup",
+                "Statsd Cts Group");
+        channelGroup.setDescription("StatsdCtsGroup Description");
+        nm.createNotificationChannelGroup(channelGroup);
+        finish();
+    }
+
     @SuppressWarnings("ConstantOverflow")
     private void doCrash() {
         Log.e(TAG, "About to crash the app with 1/0 " + (long)1/0);
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
index 5e9e2ba..1ac8a18 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -48,6 +48,9 @@
 import com.android.os.AtomsProto.LmkKillOccurred;
 import com.android.os.AtomsProto.MediaCodecStateChanged;
 import com.android.os.AtomsProto.OverlayStateChanged;
+import com.android.os.AtomsProto.PackageNotificationPreferences;
+import com.android.os.AtomsProto.PackageNotificationChannelPreferences;
+import com.android.os.AtomsProto.PackageNotificationChannelGroupPreferences;
 import com.android.os.AtomsProto.PictureInPictureStateChanged;
 import com.android.os.AtomsProto.ProcessMemoryHighWaterMark;
 import com.android.os.AtomsProto.ProcessMemorySnapshot;
@@ -1531,4 +1534,130 @@
         assertThat(atom.getBytesField().getExperimentIdList()).isEmpty();
     }
 
+    public void testNotificationPackagePreferenceExtraction() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config,
+                    Atom.PACKAGE_NOTIFICATION_PREFERENCES_FIELD_NUMBER,
+                    null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+        runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification");
+        Thread.sleep(WAIT_TIME_SHORT);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        List<PackageNotificationPreferences> allPreferences = new ArrayList<>();
+        for (Atom atom : getGaugeMetricDataList()){
+            if(atom.hasPackageNotificationPreferences()) {
+                allPreferences.add(atom.getPackageNotificationPreferences());
+            }
+        }
+        assertThat(allPreferences.size()).isGreaterThan(0);
+
+        boolean foundTestPackagePreferences = false;
+        int uid = getUid();
+        for (PackageNotificationPreferences pref : allPreferences) {
+            assertThat(pref.getUid()).isGreaterThan(0);
+            assertTrue(pref.hasImportance());
+            assertTrue(pref.hasVisibility());
+            assertTrue(pref.hasUserLockedFields());
+            if(pref.getUid() == uid){
+                assertThat(pref.getImportance()).isEqualTo(-1000);  //UNSPECIFIED_IMPORTANCE
+                assertThat(pref.getVisibility()).isEqualTo(-1000);  //UNSPECIFIED_VISIBILITY
+                foundTestPackagePreferences = true;
+            }
+        }
+        assertTrue(foundTestPackagePreferences);
+    }
+
+    public void testNotificationChannelPreferencesExtraction() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config,
+                    Atom.PACKAGE_NOTIFICATION_CHANNEL_PREFERENCES_FIELD_NUMBER,
+                    null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+        runActivity("StatsdCtsForegroundActivity", "action", "action.show_notification");
+        Thread.sleep(WAIT_TIME_SHORT);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        List<PackageNotificationChannelPreferences> allChannelPreferences = new ArrayList<>();
+        for(Atom atom : getGaugeMetricDataList()) {
+            if (atom.hasPackageNotificationChannelPreferences()) {
+               allChannelPreferences.add(atom.getPackageNotificationChannelPreferences());
+            }
+        }
+        assertThat(allChannelPreferences.size()).isGreaterThan(0);
+
+        boolean foundTestPackagePreferences = false;
+        int uid = getUid();
+        for (PackageNotificationChannelPreferences pref : allChannelPreferences) {
+            assertThat(pref.getUid()).isGreaterThan(0);
+            assertTrue(pref.hasChannelId());
+            assertTrue(pref.hasChannelName());
+            assertTrue(pref.hasDescription());
+            assertTrue(pref.hasImportance());
+            assertTrue(pref.hasUserLockedFields());
+            assertTrue(pref.hasIsDeleted());
+            if(uid == pref.getUid() && pref.getChannelId().equals("StatsdCtsChannel")) {
+                assertThat(pref.getChannelName()).isEqualTo("Statsd Cts");
+                assertThat(pref.getDescription()).isEqualTo("Statsd Cts Channel");
+                assertThat(pref.getImportance()).isEqualTo(3);  // IMPORTANCE_DEFAULT
+                foundTestPackagePreferences = true;
+            }
+        }
+        assertTrue(foundTestPackagePreferences);
+    }
+
+    public void testNotificationChannelGroupPreferencesExtraction() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+
+        StatsdConfig.Builder config = getPulledConfig();
+        addGaugeAtomWithDimensions(config,
+                    Atom.PACKAGE_NOTIFICATION_CHANNEL_GROUP_PREFERENCES_FIELD_NUMBER,
+                    null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+        runActivity("StatsdCtsForegroundActivity", "action", "action.create_channel_group");
+        Thread.sleep(WAIT_TIME_SHORT);
+        setAppBreadcrumbPredicate();
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        List<PackageNotificationChannelGroupPreferences> allGroupPreferences = new ArrayList<>();
+        for(Atom atom : getGaugeMetricDataList()) {
+            if (atom.hasPackageNotificationChannelGroupPreferences()) {
+                allGroupPreferences.add(atom.getPackageNotificationChannelGroupPreferences());
+            }
+        }
+        assertThat(allGroupPreferences.size()).isGreaterThan(0);
+
+        boolean foundTestPackagePreferences = false;
+        int uid = getUid();
+        for(PackageNotificationChannelGroupPreferences pref : allGroupPreferences) {
+            assertThat(pref.getUid()).isGreaterThan(0);
+            assertTrue(pref.hasGroupId());
+            assertTrue(pref.hasGroupName());
+            assertTrue(pref.hasDescription());
+            assertTrue(pref.hasIsBlocked());
+            assertTrue(pref.hasUserLockedFields());
+            if(uid == pref.getUid() && pref.getGroupId().equals("StatsdCtsGroup")) {
+                assertThat(pref.getGroupName()).isEqualTo("Statsd Cts Group");
+                assertThat(pref.getDescription()).isEqualTo("StatsdCtsGroup Description");
+                assertThat(pref.getIsBlocked()).isFalse();
+                foundTestPackagePreferences = true;
+            }
+        }
+        assertTrue(foundTestPackagePreferences);
+    }
 }
diff --git a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
index 8dcc4d3..0b4882a 100644
--- a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
+++ b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
@@ -162,5 +162,7 @@
         } finally {
             in.close();
         }
+        //Re-enable Wi-Fi as part of CTS Pre-conditions
+        device.executeShellCommand("svc wifi enable; sleep 1");
     }
 }
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
index 9042d62..bc30c7d4 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/TouchExplorerTest.java
@@ -68,8 +68,10 @@
 import android.content.pm.PackageManager;
 import android.graphics.Point;
 import android.graphics.PointF;
+import android.graphics.Region;
 import android.platform.test.annotations.AppModeFull;
 import android.util.DisplayMetrics;
+import android.view.Display;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -353,8 +355,7 @@
         dispatch(doubleTap(mTapLocation));
         mHoverListener.assertNonePropagated();
         mTouchListener.assertNonePropagated();
-        mService.assertPropagated(
-                TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
+        mService.assertPropagated(TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END);
         mService.clearEvents();
         mLongClickListener.assertNoneLongClicked();
     }
@@ -470,6 +471,84 @@
                 ACTION_DOWN, ACTION_POINTER_DOWN, ACTION_MOVE, ACTION_POINTER_UP, ACTION_UP);
     }
 
+    /**
+     * Test the gesture detection passthrough by performing a fast swipe in the passthrough region.
+     * It should bypass the gesture detector entirely.
+     */
+    @Test
+    @AppModeFull
+    public void testGestureDetectionPassthrough_initiatesTouchExploration() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        setRightSideOfScreenGestureDetectionPassthrough();
+        // Swipe in the passthrough region. This should generate hover events.
+        dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0)));
+        mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_END,
+                TYPE_TOUCH_INTERACTION_END);
+        mService.clearEvents();
+        // Swipe starting inside the passthrough region but ending outside of it. This should still
+        // behave as a passthrough interaction.
+        dispatch(swipe(mTapLocation, add(mTapLocation, -mSwipeDistance, 0)));
+        mHoverListener.assertPropagated(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_START,
+                TYPE_TOUCH_EXPLORATION_GESTURE_END,
+                TYPE_TOUCH_INTERACTION_END);
+        mService.clearEvents();
+        // Swipe outside the passthrough region. This should not generate hover events.
+        dispatch(swipe(add(mTapLocation, -1, 0), add(mTapLocation, -mSwipeDistance, 0)));
+        mHoverListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_GESTURE_DETECTION_START,
+                TYPE_GESTURE_DETECTION_END,
+                TYPE_TOUCH_INTERACTION_END);
+        mService.clearEvents();
+        // There should be no touch events in this test.
+        mTouchListener.assertNonePropagated();
+        clearPassthroughRegions();
+    }
+
+    /**
+     * Test the touch exploration passthrough by performing a fast swipe in the passthrough region.
+     * It should generate touch events.
+     */
+    @Test
+    @AppModeFull
+    public void testTouchExplorationPassthrough_sendsTouchEvents() {
+        if (!mHasTouchscreen || !mScreenBigEnough) return;
+        setRightSideOfScreenTouchExplorationPassthrough();
+        // Swipe in the passthrough region. This should generate  touch events.
+        dispatch(swipe(mTapLocation, add(mTapLocation, mSwipeDistance, 0)));
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
+        // We still want accessibility events to tell us when the gesture starts and ends.
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END, TYPE_VIEW_CLICKED);
+        mService.clearEvents();
+        // Swipe starting inside the passthrough region but ending outside of it. This should still
+        // behave as a passthrough interaction.
+        dispatch(swipe(mTapLocation, add(mTapLocation, -mSwipeDistance, 0)));
+        mTouchListener.assertPropagated(ACTION_DOWN, ACTION_MOVE, ACTION_UP);
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START, TYPE_TOUCH_INTERACTION_END, TYPE_VIEW_CLICKED);
+        mService.clearEvents();
+        // Swipe outside the passthrough region. This should not generate touch events.
+        dispatch(swipe(add(mTapLocation, -1, 0), add(mTapLocation, -mSwipeDistance, 0)));
+        mTouchListener.assertNonePropagated();
+        mService.assertPropagated(
+                TYPE_TOUCH_INTERACTION_START,
+                TYPE_GESTURE_DETECTION_START,
+                TYPE_GESTURE_DETECTION_END,
+                TYPE_TOUCH_INTERACTION_END);
+        // There should be no hover events in this test.
+        mHoverListener.assertNonePropagated();
+        clearPassthroughRegions();
+    }
+
     public void dispatch(StrokeDescription firstStroke, StrokeDescription... rest) {
         GestureDescription.Builder builder =
                 new GestureDescription.Builder().addStroke(firstStroke);
@@ -493,4 +572,44 @@
                             .performAction(AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
                 });
     }
+
+    private void setRightSideOfScreenGestureDetectionPassthrough() {
+        Region region = getRightSideOfScreenRegion();
+        mService.runOnServiceSync(
+                () -> {
+                    mService.setGestureDetectionPassthroughRegion(Display.DEFAULT_DISPLAY, region);
+                });
+    }
+
+    private void setRightSideOfScreenTouchExplorationPassthrough() {
+        Region region = getRightSideOfScreenRegion();
+        mService.runOnServiceSync(
+                () -> {
+                    mService.setTouchExplorationPassthroughRegion(Display.DEFAULT_DISPLAY, region);
+                });
+    }
+
+    private void clearPassthroughRegions() {
+        mService.runOnServiceSync(
+                () -> {
+                    mService.setGestureDetectionPassthroughRegion(
+                            Display.DEFAULT_DISPLAY, new Region());
+                    mService.setTouchExplorationPassthroughRegion(
+                            Display.DEFAULT_DISPLAY, new Region());
+                });
+    }
+
+    private Region getRightSideOfScreenRegion() {
+        WindowManager windowManager =
+                (WindowManager)
+                        mInstrumentation.getContext().getSystemService(Context.WINDOW_SERVICE);
+        final DisplayMetrics metrics = new DisplayMetrics();
+        windowManager.getDefaultDisplay().getRealMetrics(metrics);
+        int top = 0;
+        int left = metrics.widthPixels / 2;
+        int right = metrics.widthPixels;
+        int bottom = metrics.heightPixels;
+        Region region = new Region(left, top, right, bottom);
+        return region;
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InlinePresentationTest.java b/tests/autofillservice/src/android/autofillservice/cts/InlinePresentationTest.java
new file mode 100644
index 0000000..e50b772
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/InlinePresentationTest.java
@@ -0,0 +1,92 @@
+/*
+ * 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.autofillservice.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.testng.Assert.assertThrows;
+
+import android.app.slice.Slice;
+import android.app.slice.SliceSpec;
+import android.net.Uri;
+import android.os.Parcel;
+import android.service.autofill.InlinePresentation;
+import android.util.Size;
+import android.view.inline.InlinePresentationSpec;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class InlinePresentationTest {
+
+    @Test
+    public void testNullInlinePresentationSpecsThrowsException() {
+        assertThrows(NullPointerException.class,
+                () -> createInlinePresentation(/* createSlice */true, /* createSpec */  false));
+    }
+
+    @Test
+    public void testNullSliceThrowsException() {
+        assertThrows(NullPointerException.class,
+                () -> createInlinePresentation(/* createSlice */false, /* createSpec */  true));
+    }
+
+    @Test
+    public void testInlinePresentationValues() {
+        InlinePresentation presentation =
+                createInlinePresentation(/* createSlice */true, /* createSpec */  true);
+
+        assertThat(presentation.isPinned()).isFalse();
+        assertThat(presentation.getInlinePresentationSpec()).isNotNull();
+        assertThat(presentation.getSlice()).isNotNull();
+        assertThat(presentation.getSlice().getItems().size()).isEqualTo(0);
+    }
+
+    @Test
+    public void testtInlinePresentationParcelizeDeparcelize() {
+        InlinePresentation presentation =
+                createInlinePresentation(/* createSlice */true, /* createSpec */  true);
+
+        Parcel p = Parcel.obtain();
+        presentation.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        InlinePresentation targetPresentation = InlinePresentation.CREATOR.createFromParcel(p);
+        p.recycle();
+
+        assertThat(targetPresentation.isPinned()).isEqualTo(presentation.isPinned());
+        assertThat(targetPresentation.getInlinePresentationSpec()).isEqualTo(
+                presentation.getInlinePresentationSpec());
+        assertThat(targetPresentation.getSlice().getUri()).isEqualTo(
+                presentation.getSlice().getUri());
+        assertThat(targetPresentation.getSlice().getSpec()).isEqualTo(
+                presentation.getSlice().getSpec());
+    }
+
+    private InlinePresentation createInlinePresentation(boolean createSlice, boolean createSpec) {
+        Slice slice = createSlice ? new Slice.Builder(Uri.parse("testuri"),
+                new SliceSpec("type", 1)).build() : null;
+        InlinePresentationSpec spec = createSpec ? new InlinePresentationSpec.Builder(
+                new Size(100, 100), new Size(400, 100)).build() : null;
+        return new InlinePresentation(slice, spec, /* pined */ false);
+    }
+}
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java
index 929eae0..18ab33e 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/CarrierApiTest.java
@@ -131,6 +131,7 @@
     private static final String ALPHA_TAG_B = "tagB";
     private static final String NUMBER_A = "1234567890";
     private static final String NUMBER_B = "0987654321";
+    private static final String TESTING_PLMN = "12345";
 
     private static final int DSDS_PHONE_COUNT = 2;
 
@@ -485,6 +486,7 @@
             mTelephonyManager.getVoiceMailAlphaTag();
             mTelephonyManager.getForbiddenPlmns();
             mTelephonyManager.getServiceState();
+            mTelephonyManager.getManualNetworkSelectionPlmn();
             mTelephonyManager.setForbiddenPlmns(new ArrayList<String>());
         } catch (SecurityException e) {
             failMessage();
@@ -553,6 +555,19 @@
     static final int CARRIER_PRIVILEGE_LISTENERS =
             READ_PHONE_STATE_LISTENERS | READ_PRECISE_PHONE_STATE_LISTENERS;
 
+    public void testGetManualNetworkSelectionPlmnPersisted() throws Exception {
+        if (!hasCellular) return;
+
+        try {
+            mTelephonyManager.setNetworkSelectionModeManual(
+                     TESTING_PLMN/* operatorNumeric */, true /* persistSelection */);
+            String plmn = mTelephonyManager.getManualNetworkSelectionPlmn();
+            assertEquals(TESTING_PLMN, plmn);
+        } finally {
+            mTelephonyManager.setNetworkSelectionModeAutomatic();
+        }
+    }
+
     public void testPhoneStateListener() throws Exception {
         if (!hasCellular) return;
         PhoneStateListener psl = new PhoneStateListener((Runnable r) -> { });
@@ -563,6 +578,16 @@
         }
     }
 
+    public void testIsManualNetworkSelectionAllowed() throws Exception {
+        if (!hasCellular) return;
+
+        try {
+            assertTrue(mTelephonyManager.isManualNetworkSelectionAllowed());
+        } catch (SecurityException e) {
+            failMessage();
+        }
+    }
+
     public void testSubscriptionInfoChangeListener() throws Exception {
         if (!hasCellular) return;
         final AtomicReference<SecurityException> error = new AtomicReference<>();
diff --git a/tests/tests/content/OWNERS b/tests/tests/content/OWNERS
new file mode 100644
index 0000000..12c955a
--- /dev/null
+++ b/tests/tests/content/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 36137
+toddke@google.com
+patb@google.com
+schfan@google.com
+alexbuy@google.com
diff --git a/tests/tests/content/res/xml/intentfilter.xml b/tests/tests/content/res/xml/intentfilter.xml
index 7ed3a53..9041513 100644
--- a/tests/tests/content/res/xml/intentfilter.xml
+++ b/tests/tests/content/res/xml/intentfilter.xml
@@ -20,6 +20,8 @@
     <action name="testAction"/>
     <cat name="testCategory" />
     <type name="vnd.android.cursor.dir/person"/>
+    <staticType name="text/plain"/>
+    <group name="testMimeGroup"/>
     <scheme name="testScheme"/>
     <auth host="testHost" port="80"/>
     <path name="testPath" literal="test"/>
diff --git a/tests/tests/content/src/android/content/cts/IntentFilterTest.java b/tests/tests/content/src/android/content/cts/IntentFilterTest.java
index 80f47f9..6248044 100644
--- a/tests/tests/content/src/android/content/cts/IntentFilterTest.java
+++ b/tests/tests/content/src/android/content/cts/IntentFilterTest.java
@@ -20,18 +20,6 @@
 import static android.os.PatternMatcher.PATTERN_PREFIX;
 import static android.os.PatternMatcher.PATTERN_SIMPLE_GLOB;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.Set;
-
-import org.xmlpull.v1.XmlPullParser;
-import org.xmlpull.v1.XmlPullParserException;
-import org.xmlpull.v1.XmlSerializer;
-
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Intent;
@@ -52,14 +40,28 @@
 
 import com.android.internal.util.FastXmlSerializer;
 
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
 
 public class IntentFilterTest extends AndroidTestCase {
 
     private IntentFilter mIntentFilter;
     private static final String ACTION = "testAction";
     private static final String CATEGORY = "testCategory";
-    private static final String DATA_TYPE = "vnd.android.cursor.dir/person";
+    private static final String DATA_DYNAMIC_TYPE = "type/dynamic";
+    private static final String DATA_STATIC_TYPE = "vnd.android.cursor.dir/person";
     private static final String DATA_SCHEME = "testDataSchemes.";
+    private static final String MIME_GROUP = "mime_group";
     private static final String SSP = "testSsp";
     private static final String HOST = "testHost";
     private static final int PORT = 80;
@@ -80,11 +82,11 @@
         filter = new IntentFilter(ACTION);
         verifyContent(filter, ACTION, null);
 
-        final IntentFilter actionTypeFilter = new IntentFilter(ACTION, DATA_TYPE);
-        verifyContent(actionTypeFilter, ACTION, DATA_TYPE);
+        final IntentFilter actionTypeFilter = new IntentFilter(ACTION, DATA_STATIC_TYPE);
+        verifyContent(actionTypeFilter, ACTION, DATA_STATIC_TYPE);
 
         filter = new IntentFilter(actionTypeFilter);
-        verifyContent(filter, ACTION, DATA_TYPE);
+        verifyContent(filter, ACTION, DATA_STATIC_TYPE);
 
         final String dataType = "testdataType";
         try {
@@ -110,8 +112,10 @@
         if (dataType != null) {
             assertEquals(1, filter.countDataTypes());
             assertEquals(dataType, filter.getDataType(0));
+            assertEquals(1, filter.countStaticDataTypes());
         } else {
             assertEquals(0, filter.countDataTypes());
+            assertEquals(0, filter.countStaticDataTypes());
         }
     }
 
@@ -215,6 +219,125 @@
                         null), });
     }
 
+    public void testDynamicMimeTypes() {
+        IntentFilter filter = new Match()
+                .addDynamicMimeTypes(new String[] { "which1/what1" });
+        checkMatches(filter, new MatchCondition[] {
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, null, null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/what1",
+                        null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/*", null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "*/*", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which2/what2", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which2/*", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which1/what2", null),
+        });
+
+        filter = new Match()
+                .addDynamicMimeTypes(new String[] { "which1/what1", "which2/what2" });
+        checkMatches(filter, new MatchCondition[] {
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, null, null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/what1",
+                        null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/*", null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "*/*", null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which2/what2",
+                        null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which2/*", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which1/what2", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which3/what3", null),
+        });
+
+        filter = new Match()
+                .addDynamicMimeTypes(new String[] { "which1/*" });
+        checkMatches(filter, new MatchCondition[] {
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, null, null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/what1",
+                        null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/*", null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "*/*", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which2/what2", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which2/*", null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/what2",
+                        null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which3/what3", null),
+        });
+
+        filter = new Match()
+                .addDynamicMimeTypes(new String[] { "*/*" });
+        checkMatches(filter, new MatchCondition[] {
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, null, null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/what1",
+                        null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/*", null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "*/*", null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which2/what2",
+                        null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which2/*", null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/what2",
+                        null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which3/what3",
+                        null), });
+    }
+
+    public void testClearDynamicMimeTypesWithStaticType() {
+        IntentFilter filter = new Match()
+                .addMimeTypes(new String[] {"which1/what1"})
+                .addDynamicMimeTypes(new String[] { "which2/what2" });
+
+        checkMatches(filter, new MatchCondition[] {
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, null, null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/what1",
+                        null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/*", null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "*/*", null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which2/what2",
+                        null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which2/*", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which1/what2", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which3/what3", null),
+        });
+
+        filter.clearDynamicDataTypes();
+
+        checkMatches(filter, new MatchCondition[] {
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, null, null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/what1",
+                        null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/*", null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "*/*", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which2/what2", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which2/*", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which1/what2", null),
+        });
+    }
+
+    public void testClearDynamicMimeTypesWithAction() {
+        IntentFilter filter = new Match()
+                .addActions(new String[] {"action1"})
+                .addDynamicMimeTypes(new String[] { "which1/what1" });
+
+        checkMatches(filter, new MatchCondition[] {
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, null, null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/what1",
+                        null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "which1/*", null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_TYPE, null, null, "*/*", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which2/what2", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which2/*", null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, null, null, "which1/what2", null),
+        });
+
+        filter.clearDynamicDataTypes();
+
+        checkMatches(filter, new MatchCondition[] {
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, null, null, null, null),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, "action1", null, null, null),
+                new MatchCondition(IntentFilter.NO_MATCH_TYPE, "action1", null, "which2/what2",
+                        null),
+        });
+    }
+
     public void testAccessPriority() {
         final int expected = 1;
         mIntentFilter.setPriority(expected);
@@ -255,9 +378,9 @@
     }
 
     public void testCreate() {
-        IntentFilter filter = IntentFilter.create(ACTION, DATA_TYPE);
+        IntentFilter filter = IntentFilter.create(ACTION, DATA_STATIC_TYPE);
         assertNotNull(filter);
-        verifyContent(filter, ACTION, DATA_TYPE);
+        verifyContent(filter, ACTION, DATA_STATIC_TYPE);
     }
 
 
@@ -492,18 +615,83 @@
 
     public void testDataTypes() throws MalformedMimeTypeException {
         for (int i = 0; i < 10; i++) {
-            mIntentFilter.addDataType(DATA_TYPE + i);
+            mIntentFilter.addDataType(DATA_STATIC_TYPE + i);
         }
         assertEquals(10, mIntentFilter.countDataTypes());
+        assertEquals(10, mIntentFilter.countStaticDataTypes());
         final Iterator<String> iter = mIntentFilter.typesIterator();
+        String actual;
+        int i = 0;
+        while (iter.hasNext()) {
+            actual = iter.next();
+            assertEquals(DATA_STATIC_TYPE + i, actual);
+            assertEquals(DATA_STATIC_TYPE + i, mIntentFilter.getDataType(i));
+            assertTrue(mIntentFilter.hasDataType(DATA_STATIC_TYPE + i));
+            assertFalse(mIntentFilter.hasDataType(DATA_STATIC_TYPE + i + 10));
+            i++;
+        }
+    }
+
+    public void testDynamicDataTypes() throws MalformedMimeTypeException {
+        for (int i = 0; i < 10; i++) {
+            mIntentFilter.addDynamicDataType(DATA_DYNAMIC_TYPE + i);
+        }
+        assertEquals(10, mIntentFilter.countDataTypes());
+        assertEquals(0, mIntentFilter.countStaticDataTypes());
+
+        final Iterator<String> iter = mIntentFilter.typesIterator();
+        String actual;
+        int i = 0;
+        while (iter.hasNext()) {
+            actual = iter.next();
+            assertEquals(DATA_DYNAMIC_TYPE + i, actual);
+            assertEquals(DATA_DYNAMIC_TYPE + i, mIntentFilter.getDataType(i));
+            assertTrue(mIntentFilter.hasDataType(DATA_DYNAMIC_TYPE + i));
+            assertFalse(mIntentFilter.hasDataType(DATA_DYNAMIC_TYPE + i + 10));
+            i++;
+        }
+    }
+
+    public void testClearDynamicDataTypes() throws MalformedMimeTypeException {
+        for (int i = 0; i < 10; i++) {
+            mIntentFilter.addDataType(DATA_STATIC_TYPE + i);
+            mIntentFilter.addDynamicDataType(DATA_DYNAMIC_TYPE + i);
+        }
+        assertEquals(20, mIntentFilter.countDataTypes());
+        assertEquals(10, mIntentFilter.countStaticDataTypes());
+
+        mIntentFilter.clearDynamicDataTypes();
+
+        assertEquals(10, mIntentFilter.countDataTypes());
+        assertEquals(10, mIntentFilter.countStaticDataTypes());
+
+        final Iterator<String> iter = mIntentFilter.typesIterator();
+        String actual;
+        int i = 0;
+        while (iter.hasNext()) {
+            actual = iter.next();
+            assertEquals(DATA_STATIC_TYPE + i, actual);
+            assertEquals(DATA_STATIC_TYPE + i, mIntentFilter.getDataType(i));
+            assertTrue(mIntentFilter.hasDataType(DATA_STATIC_TYPE + i));
+            assertFalse(mIntentFilter.hasDataType(DATA_DYNAMIC_TYPE + i));
+            i++;
+        }
+    }
+
+    public void testMimeGroups() {
+        for (int i = 0; i < 10; i++) {
+            mIntentFilter.addMimeGroup(MIME_GROUP + i);
+        }
+        assertEquals(10, mIntentFilter.countMimeGroups());
+        final Iterator<String> iter = mIntentFilter.mimeGroupsIterator();
         String actual = null;
         int i = 0;
         while (iter.hasNext()) {
             actual = iter.next();
-            assertEquals(DATA_TYPE + i, actual);
-            assertEquals(DATA_TYPE + i, mIntentFilter.getDataType(i));
-            assertTrue(mIntentFilter.hasDataType(DATA_TYPE + i));
-            assertFalse(mIntentFilter.hasDataType(DATA_TYPE + i + 10));
+            assertEquals(MIME_GROUP + i, actual);
+            assertEquals(MIME_GROUP + i, mIntentFilter.getMimeGroup(i));
+            assertTrue(mIntentFilter.hasMimeGroup(MIME_GROUP + i));
+            assertFalse(mIntentFilter.hasMimeGroup(MIME_GROUP + i + 10));
             i++;
         }
     }
@@ -514,22 +702,23 @@
         assertEquals(expected, mIntentFilter.matchData(null, DATA_SCHEME, null));
 
         assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.matchData(null, DATA_SCHEME, URI));
-        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.matchData(DATA_TYPE, DATA_SCHEME,
-                URI));
+        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.matchData(DATA_STATIC_TYPE,
+                DATA_SCHEME, URI));
 
         mIntentFilter.addDataScheme(DATA_SCHEME);
-        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.matchData(DATA_TYPE,
+        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.matchData(DATA_STATIC_TYPE,
                 "mDataSchemestest", URI));
-        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.matchData(DATA_TYPE, "", URI));
+        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.matchData(DATA_STATIC_TYPE, "",
+                URI));
 
         expected = IntentFilter.MATCH_CATEGORY_SCHEME + IntentFilter.MATCH_ADJUSTMENT_NORMAL;
         assertEquals(expected, mIntentFilter.matchData(null, DATA_SCHEME, URI));
-        assertEquals(IntentFilter.NO_MATCH_TYPE, mIntentFilter.matchData(DATA_TYPE, DATA_SCHEME,
-                URI));
+        assertEquals(IntentFilter.NO_MATCH_TYPE, mIntentFilter.matchData(DATA_STATIC_TYPE,
+                DATA_SCHEME, URI));
 
-        mIntentFilter.addDataType(DATA_TYPE);
+        mIntentFilter.addDataType(DATA_STATIC_TYPE);
         assertEquals(IntentFilter.MATCH_CATEGORY_TYPE + IntentFilter.MATCH_ADJUSTMENT_NORMAL,
-                mIntentFilter.matchData(DATA_TYPE, DATA_SCHEME, URI));
+                mIntentFilter.matchData(DATA_STATIC_TYPE, DATA_SCHEME, URI));
 
         mIntentFilter.addDataAuthority(HOST, String.valueOf(PORT));
         assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.matchData(null, DATA_SCHEME, URI));
@@ -584,7 +773,9 @@
         mIntentFilter.addDataAuthority(HOST, String.valueOf(PORT));
         mIntentFilter.addDataPath(DATA_PATH, 1);
         mIntentFilter.addDataScheme(DATA_SCHEME);
-        mIntentFilter.addDataType(DATA_TYPE);
+        mIntentFilter.addDataType(DATA_STATIC_TYPE);
+        mIntentFilter.addDynamicDataType(DATA_DYNAMIC_TYPE);
+        mIntentFilter.addMimeGroup(MIME_GROUP);
         mIntentFilter.writeToXml(xml);
         xml.flush();
         final XmlPullParser parser = Xml.newPullParser();
@@ -594,7 +785,9 @@
         intentFilter.readFromXml(parser);
         assertEquals(ACTION, intentFilter.getAction(0));
         assertEquals(CATEGORY, intentFilter.getCategory(0));
-        assertEquals(DATA_TYPE, intentFilter.getDataType(0));
+        assertTrue(intentFilter.hasExactStaticDataType(DATA_STATIC_TYPE));
+        assertTrue(intentFilter.hasExactDynamicDataType(DATA_DYNAMIC_TYPE));
+        assertEquals(MIME_GROUP, intentFilter.getMimeGroup(0));
         assertEquals(DATA_SCHEME, intentFilter.getDataScheme(0));
         assertEquals(DATA_PATH, intentFilter.getDataPath(0).getPath());
         assertEquals(HOST, intentFilter.getDataAuthority(0).getHost());
@@ -655,7 +848,11 @@
 
         assertEquals("testAction", mIntentFilter.getAction(0));
         assertEquals("testCategory", mIntentFilter.getCategory(0));
-        assertEquals("vnd.android.cursor.dir/person", mIntentFilter.getDataType(0));
+
+        assertTrue(mIntentFilter.hasExactDynamicDataType("vnd.android.cursor.dir/person"));
+        assertTrue(mIntentFilter.hasExactStaticDataType("text/plain"));
+
+        assertEquals("testMimeGroup", mIntentFilter.getMimeGroup(0));
         assertEquals("testScheme", mIntentFilter.getDataScheme(0));
         assertEquals("testHost", mIntentFilter.getDataAuthority(0).getHost());
         assertEquals(80, mIntentFilter.getDataAuthority(0).getPort());
@@ -781,10 +978,10 @@
         assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(resolver, intent, true, null));
         mIntentFilter.addDataAuthority(HOST, String.valueOf(PORT));
         assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(resolver, intent, true, null));
-        intent.setType(DATA_TYPE);
+        intent.setType(DATA_STATIC_TYPE);
         assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(resolver, intent, true, null));
 
-        mIntentFilter.addDataType(DATA_TYPE);
+        mIntentFilter.addDataType(DATA_STATIC_TYPE);
 
         assertEquals(IntentFilter.MATCH_CATEGORY_TYPE + IntentFilter.MATCH_ADJUSTMENT_NORMAL,
                 mIntentFilter.match(resolver, intent, true, null));
@@ -797,7 +994,7 @@
         assertEquals(IntentFilter.MATCH_CATEGORY_TYPE + IntentFilter.MATCH_ADJUSTMENT_NORMAL,
                 mIntentFilter.match(resolver, intent, true, null));
 
-        intent.setDataAndType(uri, DATA_TYPE);
+        intent.setDataAndType(uri, DATA_STATIC_TYPE);
         assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(resolver, intent, true, null));
 
     }
@@ -815,18 +1012,18 @@
 
         assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.matchData(null, DATA_SCHEME, URI));
 
-        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_TYPE,
+        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_STATIC_TYPE,
                 DATA_SCHEME, URI, null, null));
 
         mIntentFilter.addDataScheme(DATA_SCHEME);
-        assertEquals(IntentFilter.NO_MATCH_TYPE, mIntentFilter.match(ACTION, DATA_TYPE,
+        assertEquals(IntentFilter.NO_MATCH_TYPE, mIntentFilter.match(ACTION, DATA_STATIC_TYPE,
                 DATA_SCHEME, URI, null, null));
-        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_TYPE, "", URI,
-                null, null));
-        mIntentFilter.addDataType(DATA_TYPE);
+        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_STATIC_TYPE, "",
+                URI, null, null));
+        mIntentFilter.addDataType(DATA_STATIC_TYPE);
 
         assertEquals(IntentFilter.MATCH_CATEGORY_TYPE + IntentFilter.MATCH_ADJUSTMENT_NORMAL,
-                mIntentFilter.match(ACTION, DATA_TYPE, DATA_SCHEME, URI, null, null));
+                mIntentFilter.match(ACTION, DATA_STATIC_TYPE, DATA_SCHEME, URI, null, null));
 
         assertEquals(IntentFilter.NO_MATCH_TYPE, mIntentFilter.match(ACTION, null, DATA_SCHEME,
                 URI, null, null));
@@ -835,29 +1032,29 @@
                 URI, cat, null));
 
         cat.add(CATEGORY);
-        assertEquals(IntentFilter.NO_MATCH_CATEGORY, mIntentFilter.match(ACTION, DATA_TYPE,
+        assertEquals(IntentFilter.NO_MATCH_CATEGORY, mIntentFilter.match(ACTION, DATA_STATIC_TYPE,
                 DATA_SCHEME, URI, cat, null));
         cat = new HashSet<String>();
         mIntentFilter.addDataAuthority(HOST, String.valueOf(PORT));
         assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, null, DATA_SCHEME,
                 URI, null, null));
-        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_TYPE,
+        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_STATIC_TYPE,
                 DATA_SCHEME, URI, null, null));
 
         final Uri uri = Uri.parse(DATA_SCHEME + "://" + HOST + ":" + PORT);
         mIntentFilter.addDataPath(DATA_PATH, PatternMatcher.PATTERN_LITERAL);
-        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_TYPE,
+        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_STATIC_TYPE,
                 DATA_SCHEME, uri, null, null));
-        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_TYPE,
+        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_STATIC_TYPE,
                 DATA_SCHEME, URI, null, null));
 
-        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_TYPE,
+        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_STATIC_TYPE,
                 DATA_SCHEME, URI, cat, null));
         cat.add(CATEGORY);
-        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_TYPE,
+        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_STATIC_TYPE,
                 DATA_SCHEME, URI, cat, null));
         mIntentFilter.addCategory(CATEGORY);
-        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_TYPE,
+        assertEquals(IntentFilter.NO_MATCH_DATA, mIntentFilter.match(ACTION, DATA_STATIC_TYPE,
                 DATA_SCHEME, URI, cat, null));
     }
 
@@ -867,7 +1064,9 @@
         mIntentFilter.addDataAuthority(HOST, String.valueOf(PORT));
         mIntentFilter.addDataPath(DATA_PATH, 1);
         mIntentFilter.addDataScheme(DATA_SCHEME);
-        mIntentFilter.addDataType(DATA_TYPE);
+        mIntentFilter.addDataType(DATA_STATIC_TYPE);
+        mIntentFilter.addDynamicDataType(DATA_DYNAMIC_TYPE);
+        mIntentFilter.addMimeGroup(MIME_GROUP);
         Parcel parcel = Parcel.obtain();
         mIntentFilter.writeToParcel(parcel, 1);
         parcel.setDataPosition(0);
@@ -880,6 +1079,11 @@
                 target.getDataAuthority(0).getPort());
         assertEquals(mIntentFilter.getDataPath(0).getPath(), target.getDataPath(0).getPath());
         assertEquals(mIntentFilter.getDataScheme(0), target.getDataScheme(0));
+        assertEquals(mIntentFilter.getDataType(0), target.getDataType(0));
+        assertEquals(mIntentFilter.getDataType(1), target.getDataType(1));
+        assertEquals(mIntentFilter.countStaticDataTypes(), target.countStaticDataTypes());
+        assertEquals(mIntentFilter.countDataTypes(), target.countDataTypes());
+        assertEquals(mIntentFilter.getMimeGroup(0), target.getMimeGroup(0));
     }
 
     public void testAddDataType() throws MalformedMimeTypeException {
@@ -890,17 +1094,18 @@
             // expected
         }
 
-        mIntentFilter.addDataType(DATA_TYPE);
-        assertEquals(DATA_TYPE, mIntentFilter.getDataType(0));
+        mIntentFilter.addDataType(DATA_STATIC_TYPE);
+        assertEquals(DATA_STATIC_TYPE, mIntentFilter.getDataType(0));
     }
 
     private static class Match extends IntentFilter {
+        Match() {
+        }
+
         Match(String[] actions, String[] categories, String[] mimeTypes, String[] schemes,
                 String[] authorities, String[] ports) {
             if (actions != null) {
-                for (int i = 0; i < actions.length; i++) {
-                    addAction(actions[i]);
-                }
+                addActions(actions);
             }
             if (categories != null) {
                 for (int i = 0; i < categories.length; i++) {
@@ -948,6 +1153,35 @@
                 }
             }
         }
+
+        Match addDynamicMimeTypes(String[] dynamicMimeTypes) {
+            for (int i = 0; i < dynamicMimeTypes.length; i++) {
+                try {
+                    addDynamicDataType(dynamicMimeTypes[i]);
+                } catch (IntentFilter.MalformedMimeTypeException e) {
+                    throw new RuntimeException("Bad mime type", e);
+                }
+            }
+            return this;
+        }
+
+        Match addMimeTypes(String[] mimeTypes) {
+            for (int i = 0; i < mimeTypes.length; i++) {
+                try {
+                    addDataType(mimeTypes[i]);
+                } catch (IntentFilter.MalformedMimeTypeException e) {
+                    throw new RuntimeException("Bad mime type", e);
+                }
+            }
+            return this;
+        }
+
+        Match addActions(String[] actions) {
+            for (int i = 0; i < actions.length; i++) {
+                addAction(actions[i]);
+            }
+            return this;
+        }
     }
 
     private static class MatchCondition {
@@ -1179,7 +1413,7 @@
     public void testDump() throws MalformedMimeTypeException {
         TestPrinter printer = new TestPrinter();
         String prefix = "TestIntentFilter";
-        IntentFilter filter = new IntentFilter(ACTION, DATA_TYPE);
+        IntentFilter filter = new IntentFilter(ACTION, DATA_STATIC_TYPE);
         filter.dump(printer, prefix);
         assertTrue(printer.isPrintlnCalled);
     }
diff --git a/tests/tests/content/src/android/content/om/cts/OverlayInfoTest.java b/tests/tests/content/src/android/content/om/cts/OverlayInfoTest.java
index d2e1998..3eed1b2 100644
--- a/tests/tests/content/src/android/content/om/cts/OverlayInfoTest.java
+++ b/tests/tests/content/src/android/content/om/cts/OverlayInfoTest.java
@@ -108,6 +108,6 @@
         assertEquals(info.state, copyInfo.state);
         assertEquals(info.userId, copyInfo.userId);
         assertEquals(info.priority, copyInfo.priority);
-        assertEquals(info.isStatic, copyInfo.isStatic);
+        assertEquals(info.isMutable, copyInfo.isMutable);
     }
 }
\ No newline at end of file
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
index a78069c..36ba232 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandTest.java
@@ -74,8 +74,7 @@
 
     @Parameters
     public static Iterable<Object> initParameters() {
-        return Arrays.asList(DATA_LOADER_TYPE_NONE, DATA_LOADER_TYPE_STREAMING,
-                DATA_LOADER_TYPE_INCREMENTAL);
+        return Arrays.asList(DATA_LOADER_TYPE_NONE);
     }
 
     private boolean mStreaming = false;
diff --git a/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java b/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java
index 0374082..3f8c011 100644
--- a/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/VulkanDeqpLevelTest.java
@@ -29,6 +29,7 @@
 import com.android.compatibility.common.util.CddTest;
 
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -69,6 +70,7 @@
     }
 
     @CddTest(requirement = "7.1.4.2/C-1-8")
+    @Ignore("b/149464764: Test disabled until certain targets get the new feature flag.")
     @Test
     public void testVulkanDeqpLevel() {
         if (mVulkanHardwareVersion.version >= VULKAN_1_0) {
diff --git a/tests/tests/identity/src/android/security/identity/cts/AttestationTest.java b/tests/tests/identity/src/android/security/identity/cts/AttestationTest.java
new file mode 100644
index 0000000..34b0cf2
--- /dev/null
+++ b/tests/tests/identity/src/android/security/identity/cts/AttestationTest.java
@@ -0,0 +1,156 @@
+/*
+ * 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
+ *
+ *      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.security.identity.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+
+import android.security.identity.IdentityCredential;
+import android.security.identity.IdentityCredentialStore;
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.Ignore;
+
+import com.google.common.primitives.Bytes;
+
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+
+import java.util.Optional;
+
+public class AttestationTest {
+    private static final String TAG = "AttestationTest";
+
+    // The subset of Keymaster tags we care about. See
+    //
+    //   https://source.android.com/security/keystore/tags
+    //
+    // for a list of known tags.
+    //
+    public static final int KM_TAG_ATTESTATION_APPLICATION_ID = 709;
+    public static final int KM_TAG_IDENTITY_CREDENTIAL_KEY = 721;
+
+    @Ignore("Not ready until SW implementation produces correct attestations")
+    @Test
+    public void attestationTest() throws Exception {
+        Context appContext = InstrumentationRegistry.getTargetContext();
+        IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+
+        // Use a random challenge of length between 16 and 32.
+        SecureRandom random = new SecureRandom();
+        int challengeLength = 16 + random.nextInt(16);
+        byte[] challenge = new byte[challengeLength];
+        random.nextBytes(challenge);
+
+        String credentialName = "test";
+
+        store.deleteCredentialByName(credentialName);
+        Collection<X509Certificate> certChain = ProvisioningTest.createCredentialWithChallenge(
+            store, credentialName, challenge);
+        store.deleteCredentialByName("test");
+
+        // Check that each certificate in the chain isn't expired and also that it's signed by the
+        // next one.
+        assertTrue(verifyCertificateChain(certChain));
+
+        // Parse the attestation record... Identity Credential uses standard KeyMaster-style
+        // attestation with only a few differences:
+        //
+        // - Tag::IDENTITY_CREDENTIAL_KEY (boolean) must be set to identify the key as an Identity
+        //   Credential key.
+        //
+        // - the KeyMaster version and security-level should be set to that of Identity Credential
+        //
+        // In this test we only test for these two things and a) the attestationApplicationId
+        // matches the calling application; and b) the given challenge matches what was sent.
+        //
+        // We could test for all the other expected attestation records but currently we assume
+        // this is already tested by Keymaster/Android Keystore tests since the TA is expected to
+        // re-use the same infrastructure.
+        //
+        X509Certificate cert = certChain.iterator().next();
+        ParsedAttestationRecord record = new ParsedAttestationRecord(cert);
+
+        // Need at least attestation version 3 and attestations to be done in either the TEE
+        // or in a StrongBox.
+        assertTrue(record.getAttestationVersion() >= 3);
+
+        // Also per the IC HAL, the keymasterSecurityLevel field is used as the securityLevel of
+        // the IC TA and this must be either TEE or in a StrongBox... except we specifically
+        // allow our software implementation to report Software here.
+        //
+        // Since we cannot get the implementation name or author at this layer, we can't test for
+        // it. This can be tested for in the VTS test, however.
+
+        // As per the IC HAL, the keymasterVersion field should be the version of the Identity
+        // Credential HAL - 1.0 - and this is encoded as major*10 + minor. This field is used by
+        // Keymaster which is known to report integers less than or equal to 4 (for KM up to 4.0)
+        // and integers greater or equal than 41 (for KM starting with 4.1).
+        //
+        // Since we won't get to version 4.0 of the IC HAL for a while, let's also check that a KM
+        // version isn't errornously returned.
+        assertTrue(record.getKeymasterVersion() >= 10);
+        assertTrue(record.getKeymasterVersion() < 40);
+
+        // Check that the challenge we passed in, is in fact in the attestation record.
+        assertArrayEquals(challenge, record.getAttestationChallenge());
+
+        // Tag::ATTESTATION_APPLICATION_ID is used to identify the set of possible applications of
+        // which one has initiated a key attestation. This is complicated ASN.1 which we don't want
+        // to parse and we don't need to [1].. we can however easily check that our applicationId
+        // is appearing as a substring somewhere in this blob.
+        //
+        // [1] : and the point of this test isn't to verify that attestations are done correctly,
+        // that's tested elsewhere in e.g. KM and Android Keystore.
+        //
+        Optional<byte[]> attestationApplicationId =
+                record.getSoftwareAuthorizationByteString(KM_TAG_ATTESTATION_APPLICATION_ID);
+        assertTrue(attestationApplicationId.isPresent());
+        String appId = appContext.getPackageName();
+        assertTrue(Bytes.indexOf(attestationApplicationId.get(), appId.getBytes()) != -1);
+
+        // Tag::IDENTITY_CREDENTIAL_KEY is used in attestations produced by the Identity Credential
+        // HAL when that HAL attests to Credential Keys.
+        boolean isIdentityCredentialKey =
+                record.getTeeAuthorizationBoolean(KM_TAG_IDENTITY_CREDENTIAL_KEY);
+        assertTrue(isIdentityCredentialKey);
+    }
+
+    // This only verifies each cert hasn't expired and signed by the next one.
+    private static boolean verifyCertificateChain(Collection<X509Certificate> certChain) {
+        X509Certificate[] certs = new X509Certificate[certChain.size()];
+        certs = certChain.toArray(certs);
+        X509Certificate parent = certs[certs.length - 1];
+        for (int i = certs.length - 1; i >= 0; i--) {
+            X509Certificate cert = certs[i];
+            try {
+                cert.checkValidity();
+                cert.verify(parent.getPublicKey());
+            } catch (Exception e) {
+                e.printStackTrace();
+                return false;
+            }
+            parent = cert;
+        }
+        return true;
+    }
+}
diff --git a/tests/tests/identity/src/android/security/identity/cts/ParsedAttestationRecord.java b/tests/tests/identity/src/android/security/identity/cts/ParsedAttestationRecord.java
new file mode 100644
index 0000000..e4297d7
--- /dev/null
+++ b/tests/tests/identity/src/android/security/identity/cts/ParsedAttestationRecord.java
@@ -0,0 +1,219 @@
+/*
+ * 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
+ *
+ *      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.security.identity.cts;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.Optional;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DEROctetString;
+
+// This code is loosely based on https://github.com/google/android-key-attestation
+
+class ParsedAttestationRecord {
+    private static final String KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17";
+
+    enum SecurityLevel {
+        SOFTWARE,
+        TRUSTED_ENVIRONMENT,
+        STRONG_BOX
+    }
+
+    private static final int ATTESTATION_VERSION_INDEX = 0;
+    private static final int ATTESTATION_SECURITY_LEVEL_INDEX = 1;
+    private static final int KEYMASTER_VERSION_INDEX = 2;
+    private static final int KEYMASTER_SECURITY_LEVEL_INDEX = 3;
+    private static final int ATTESTATION_CHALLENGE_INDEX = 4;
+    private static final int UNIQUE_ID_INDEX = 5;
+    private static final int SW_ENFORCED_INDEX = 6;
+    private static final int TEE_ENFORCED_INDEX = 7;
+
+    // Some security values. The complete list is in this AOSP file:
+    // hardware/libhardware/include/hardware/keymaster_defs.h
+    private static final int KM_SECURITY_LEVEL_SOFTWARE = 0;
+    private static final int KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT = 1;
+    private static final int KM_SECURITY_LEVEL_STRONG_BOX = 2;
+
+    private int attestationVersion;
+    private SecurityLevel attestationSecurityLevel;
+    private int keymasterVersion;
+    private SecurityLevel keymasterSecurityLevel;
+    private byte[] attestationChallenge;
+    private byte[] uniqueId;
+
+    private Map<Integer, ASN1Primitive> softwareEnforcedAuthorizations;
+    private Map<Integer, ASN1Primitive> teeEnforcedAuthorizations;
+
+    public int getAttestationVersion() {
+        return attestationVersion;
+    }
+
+    public SecurityLevel getAttestationSecurityLevel() {
+        return attestationSecurityLevel;
+    }
+
+    public int getKeymasterVersion() {
+        return keymasterVersion;
+    }
+
+    public SecurityLevel getKeymasterSecurityLevel() {
+        return attestationSecurityLevel;
+    }
+
+    public byte[] getAttestationChallenge() {
+        return attestationChallenge;
+    }
+
+    public byte[] getUniqueId() {
+        return uniqueId;
+    }
+
+    public Set<Integer> getSoftwareEnforcedAuthorizationTags() {
+        return softwareEnforcedAuthorizations.keySet();
+    }
+
+    public Set<Integer> getTeeEnforcedAuthorizationTags() {
+        return teeEnforcedAuthorizations.keySet();
+    }
+
+    private static ASN1Primitive findAuthorizationListEntry(
+        Map<Integer, ASN1Primitive> authorizationMap, int tag) {
+        return authorizationMap.getOrDefault(tag, null);
+    }
+
+    public Optional<Integer> getSoftwareAuthorizationInteger(int tag) {
+        ASN1Primitive entry = findAuthorizationListEntry(softwareEnforcedAuthorizations, tag);
+        return Optional.ofNullable(entry).map(ParsedAttestationRecord::getIntegerFromAsn1);
+    }
+
+    public Optional<Integer> getTeeAuthorizationInteger(int tag) {
+        ASN1Primitive entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag);
+        return Optional.ofNullable(entry).map(ParsedAttestationRecord::getIntegerFromAsn1);
+    }
+    public boolean getSoftwareAuthorizationBoolean(int tag) {
+        ASN1Primitive entry = findAuthorizationListEntry(softwareEnforcedAuthorizations, tag);
+        return entry != null;
+    }
+
+    public boolean getTeeAuthorizationBoolean(int tag) {
+        ASN1Primitive entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag);
+        return entry != null;
+    }
+
+    public Optional<byte[]> getSoftwareAuthorizationByteString(int tag) {
+        ASN1OctetString entry = (ASN1OctetString) findAuthorizationListEntry(softwareEnforcedAuthorizations, tag);
+        return Optional.ofNullable(entry).map(ASN1OctetString::getOctets);
+    }
+
+    public Optional<byte[]> getTeeAuthorizationByteString(int tag) {
+        ASN1OctetString entry = (ASN1OctetString) findAuthorizationListEntry(teeEnforcedAuthorizations, tag);
+        return Optional.ofNullable(entry).map(ASN1OctetString::getOctets);
+    }
+
+    private static boolean getBooleanFromAsn1(ASN1Encodable asn1Value) {
+        if (asn1Value instanceof ASN1Boolean) {
+            return ((ASN1Boolean) asn1Value).isTrue();
+        } else {
+            throw new RuntimeException(
+                "Boolean value expected; found " + asn1Value.getClass().getName() + " instead.");
+        }
+    }
+
+    private static int getIntegerFromAsn1(ASN1Encodable asn1Value) {
+        if (asn1Value instanceof ASN1Integer) {
+            return ((ASN1Integer) asn1Value).getValue().intValue();
+        } else if (asn1Value instanceof ASN1Enumerated) {
+            return ((ASN1Enumerated) asn1Value).getValue().intValue();
+        } else {
+            throw new IllegalArgumentException(
+                "Integer value expected; found " + asn1Value.getClass().getName() + " instead.");
+        }
+    }
+
+    private static Map<Integer, ASN1Primitive> getAuthorizationMap(
+        ASN1Encodable[] authorizationList) {
+        Map<Integer, ASN1Primitive> authorizationMap = new HashMap<>();
+        for (ASN1Encodable entry : authorizationList) {
+            ASN1TaggedObject taggedEntry = (ASN1TaggedObject) entry;
+            authorizationMap.put(taggedEntry.getTagNo(), taggedEntry.getObject());
+        }
+        return authorizationMap;
+    }
+
+    public ParsedAttestationRecord(X509Certificate cert) throws IOException {
+        byte[] attestationExtensionBytes = cert.getExtensionValue(KEY_DESCRIPTION_OID);
+        if (attestationExtensionBytes == null || attestationExtensionBytes.length == 0) {
+            throw new IllegalArgumentException("Couldn't find keystore attestation extension.");
+        }
+
+        ASN1Sequence seq;
+        try (ASN1InputStream asn1InputStream = new ASN1InputStream(attestationExtensionBytes)) {
+            // The extension contains one object, a sequence, in the
+            // Distinguished Encoding Rules (DER)-encoded form. Get the DER
+            // bytes.
+            byte[] derSequenceBytes = ((ASN1OctetString) asn1InputStream.readObject()).getOctets();
+            // Decode the bytes as an ASN1 sequence object.
+            try (ASN1InputStream seqInputStream = new ASN1InputStream(derSequenceBytes)) {
+                seq = (ASN1Sequence) seqInputStream.readObject();
+            }
+        }
+
+        this.attestationVersion = getIntegerFromAsn1(seq.getObjectAt(ATTESTATION_VERSION_INDEX));
+        this.attestationSecurityLevel =
+                securityLevelToEnum(getIntegerFromAsn1(
+                    seq.getObjectAt(ATTESTATION_SECURITY_LEVEL_INDEX)));
+        this.keymasterVersion = getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_VERSION_INDEX));
+        this.keymasterSecurityLevel = securityLevelToEnum(
+            getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_SECURITY_LEVEL_INDEX)));
+        this.attestationChallenge =
+                ((ASN1OctetString) seq.getObjectAt(ATTESTATION_CHALLENGE_INDEX)).getOctets();
+        this.uniqueId = ((ASN1OctetString) seq.getObjectAt(UNIQUE_ID_INDEX)).getOctets();
+
+        this.softwareEnforcedAuthorizations = getAuthorizationMap(
+            ((ASN1Sequence) seq.getObjectAt(SW_ENFORCED_INDEX)).toArray());
+
+        this.teeEnforcedAuthorizations = getAuthorizationMap(
+            ((ASN1Sequence) seq.getObjectAt(TEE_ENFORCED_INDEX)).toArray());
+    }
+
+    private static SecurityLevel securityLevelToEnum(int securityLevel) {
+        switch (securityLevel) {
+            case KM_SECURITY_LEVEL_SOFTWARE:
+                return SecurityLevel.SOFTWARE;
+            case KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT:
+                return SecurityLevel.TRUSTED_ENVIRONMENT;
+            case KM_SECURITY_LEVEL_STRONG_BOX:
+                return SecurityLevel.STRONG_BOX;
+            default:
+                throw new IllegalArgumentException("Invalid security level.");
+        }
+    }
+}
diff --git a/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java b/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java
index 8269234..48cc405 100644
--- a/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java
@@ -107,11 +107,17 @@
 
     static Collection<X509Certificate> createCredential(IdentityCredentialStore store,
             String credentialName) throws IdentityCredentialException {
+        return createCredentialWithChallenge(store, credentialName, "SomeChallenge".getBytes());
+    }
+
+    static Collection<X509Certificate> createCredentialWithChallenge(IdentityCredentialStore store,
+            String credentialName,
+            byte[] challenge) throws IdentityCredentialException {
         WritableIdentityCredential wc = null;
         wc = store.createCredential(credentialName, "org.iso.18013-5.2019.mdl");
 
         Collection<X509Certificate> certificateChain =
-                wc.getCredentialKeyCertificateChain("SomeChallenge".getBytes());
+                wc.getCredentialKeyCertificateChain(challenge);
         // TODO: inspect cert-chain
 
         // Profile 0 (no authentication)
diff --git a/tests/tests/match_flags/Android.bp b/tests/tests/match_flags/Android.bp
new file mode 100644
index 0000000..7debb8a
--- /dev/null
+++ b/tests/tests/match_flags/Android.bp
@@ -0,0 +1,32 @@
+// 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.
+
+android_test {
+    name: "CtsMatchFlagTestCases",
+    defaults: ["cts_defaults"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    static_libs: [
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+	    "androidx.test.ext.junit",
+    ],
+
+    srcs: ["src/**/*.java"],
+    sdk_version: "test_current",
+}
diff --git a/tests/tests/match_flags/AndroidManifest.xml b/tests/tests/match_flags/AndroidManifest.xml
new file mode 100644
index 0000000..1b6cb42
--- /dev/null
+++ b/tests/tests/match_flags/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.matchflags.cts">
+
+    <application>
+      <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.matchflags.cts"
+                     android:label="CTS tests for match flags">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+</manifest>
+
diff --git a/tests/tests/match_flags/AndroidTest.xml b/tests/tests/match_flags/AndroidTest.xml
new file mode 100644
index 0000000..a5fadab
--- /dev/null
+++ b/tests/tests/match_flags/AndroidTest.xml
@@ -0,0 +1,34 @@
+<?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.
+  -->
+<configuration description="Config for match flag CTS test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsMatchFlagTestCases.apk" />
+        <option name="test-file-name" value="CtsMatchFlagsUniqueAndSharedUri.apk" />
+        <option name="test-file-name" value="CtsMatchFlagsSharedUri.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.matchflags.cts" />
+        <option name="runtime-hint" value="12m30s" />
+    </test>
+</configuration>
diff --git a/tests/tests/match_flags/OWNERS b/tests/tests/match_flags/OWNERS
new file mode 100644
index 0000000..8a44fb2
--- /dev/null
+++ b/tests/tests/match_flags/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 36137
+patb@google.com
+toddke@google.com
+chiuwinson@google.com
+rtmitchell@google.com
diff --git a/tests/tests/match_flags/app/a/Android.bp b/tests/tests/match_flags/app/a/Android.bp
new file mode 100644
index 0000000..9a5db56
--- /dev/null
+++ b/tests/tests/match_flags/app/a/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsMatchFlagsUniqueAndSharedUri",
+    manifest: "AndroidManifest.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
\ No newline at end of file
diff --git a/tests/tests/match_flags/app/a/AndroidManifest.xml b/tests/tests/match_flags/app/a/AndroidManifest.xml
new file mode 100644
index 0000000..8ad54de
--- /dev/null
+++ b/tests/tests/match_flags/app/a/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.matchflags.app.uniqueandshared">
+    <application>
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.matchflags.cts.app.TestActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.VIEW"/>
+                <category android:name="android.intent.category.BROWSABLE"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+                <data android:scheme="https"
+                      android:host="unique-5gle2bs6woovjn8xabwyb3js01xl0ducci3gd3fpe622h48lyg.com"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.matchflags.app.UNIQUE_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+            <intent-filter>
+                <action android:name="android.matchflags.app.SHARED_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/tests/match_flags/app/a/src/android/matchflags/cts/app/TestActivity.java b/tests/tests/match_flags/app/a/src/android/matchflags/cts/app/TestActivity.java
new file mode 100644
index 0000000..e9046eb
--- /dev/null
+++ b/tests/tests/match_flags/app/a/src/android/matchflags/cts/app/TestActivity.java
@@ -0,0 +1,35 @@
+/*
+ * 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.matchflags.cts.app;
+
+import static android.content.Intent.EXTRA_RETURN_RESULT;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.Random;
+
+public class TestActivity extends Activity {
+}
\ No newline at end of file
diff --git a/tests/tests/match_flags/app/b/Android.bp b/tests/tests/match_flags/app/b/Android.bp
new file mode 100644
index 0000000..8c49047
--- /dev/null
+++ b/tests/tests/match_flags/app/b/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2015 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+    name: "CtsMatchFlagsSharedUri",
+    manifest: "AndroidManifest.xml",
+    defaults: ["cts_support_defaults"],
+    srcs: ["src/**/*.java"],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+    sdk_version: "test_current",
+}
\ No newline at end of file
diff --git a/tests/tests/match_flags/app/b/AndroidManifest.xml b/tests/tests/match_flags/app/b/AndroidManifest.xml
new file mode 100644
index 0000000..6d2c9d0
--- /dev/null
+++ b/tests/tests/match_flags/app/b/AndroidManifest.xml
@@ -0,0 +1,29 @@
+<?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.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="android.matchflags.app.shared">
+    <application>
+        <uses-library android:name="android.test.runner"/>
+        <activity android:name="android.matchflags.cts.app.TestActivity">
+            <intent-filter>
+                <action android:name="android.matchflags.app.SHARED_ACTION"/>
+                <category android:name="android.intent.category.DEFAULT"/>
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
diff --git a/tests/tests/match_flags/app/b/src/android/matchflags/cts/app/TestActivity.java b/tests/tests/match_flags/app/b/src/android/matchflags/cts/app/TestActivity.java
new file mode 100644
index 0000000..e9046eb
--- /dev/null
+++ b/tests/tests/match_flags/app/b/src/android/matchflags/cts/app/TestActivity.java
@@ -0,0 +1,35 @@
+/*
+ * 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.matchflags.cts.app;
+
+import static android.content.Intent.EXTRA_RETURN_RESULT;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.os.RemoteCallback;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.Random;
+
+public class TestActivity extends Activity {
+}
\ No newline at end of file
diff --git a/tests/tests/match_flags/src/android/matchflags/cts/MatchFlagTests.java b/tests/tests/match_flags/src/android/matchflags/cts/MatchFlagTests.java
new file mode 100644
index 0000000..da7e212
--- /dev/null
+++ b/tests/tests/match_flags/src/android/matchflags/cts/MatchFlagTests.java
@@ -0,0 +1,134 @@
+/*
+ * 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.matchflags.cts;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.RemoteCallback;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
+import org.hamcrest.core.IsNull;
+import org.junit.AfterClass;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.util.Objects;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+@RunWith(AndroidJUnit4.class)
+public class MatchFlagTests {
+
+    private static final String ONLY_BROWSER_URI =
+            "https://nohandler-02xgpcssu1v7xvpek0skc905glnyu7ihjtza3eufox0mauqyri.com";
+    private static final String UNIQUE_URI =
+            "https://unique-5gle2bs6woovjn8xabwyb3js01xl0ducci3gd3fpe622h48lyg.com";
+    @Rule
+    public TestName name = new TestName();
+
+    @BeforeClass
+    public static void setup() {
+
+    }
+
+    @Test
+    public void startNoBrowserIntentWithNoMatchingApps() throws Exception {
+        Intent onlyBrowserIntent = new Intent(Intent.ACTION_VIEW)
+                .addCategory(Intent.CATEGORY_BROWSABLE)
+                .setData(Uri.parse(ONLY_BROWSER_URI));
+
+        startActivity(onlyBrowserIntent);
+
+        Intent noBrowserWithBrowserOnlyIntent = new Intent(onlyBrowserIntent)
+                .addFlags(Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER);
+
+        try {
+            startActivity(noBrowserWithBrowserOnlyIntent);
+            fail("Should not have started a browser-only intent with NON_BROWSER flag set");
+        } catch (ActivityNotFoundException e) {
+            // hooray!
+        }
+    }
+
+    @Test
+    public void startRequireDefaultWithNoDefault() throws Exception {
+        Intent sharedIntent = new Intent("android.matchflags.app.SHARED_ACTION");
+
+        startActivity(sharedIntent);
+
+        Intent sharedIntentRequireDefault = new Intent(sharedIntent)
+                .addFlags(Intent.FLAG_ACTIVITY_REQUIRE_DEFAULT);
+
+        try {
+            startActivity(sharedIntentRequireDefault);
+            fail("Should have started intent with no default when default required");
+        } catch (ActivityNotFoundException e) {
+            // hooray!
+        }
+    }
+
+    @Test
+    public void startRequireDefaultWithSingleMatch() throws Exception {
+        Intent uniqueIntent = new Intent("android.matchflags.app.UNIQUE_ACTION")
+                .addFlags(Intent.FLAG_ACTIVITY_REQUIRE_DEFAULT);
+        startActivity(uniqueIntent);
+    }
+
+    @Test
+    public void startNoBrowserRequireDefault() throws Exception {
+        Intent uniqueUriIntent = new Intent(Intent.ACTION_VIEW)
+                .addCategory(Intent.CATEGORY_BROWSABLE)
+                .setData(Uri.parse(UNIQUE_URI));
+
+        startActivity(uniqueUriIntent);
+
+        Intent uniqueUriIntentNoBrowserRequireDefault = new Intent(uniqueUriIntent)
+                .addFlags(Intent.FLAG_ACTIVITY_REQUIRE_NON_BROWSER)
+                .addFlags(Intent.FLAG_ACTIVITY_REQUIRE_DEFAULT);
+
+        startActivity(uniqueUriIntentNoBrowserRequireDefault);
+    }
+
+    private static void startActivity(Intent onlyBrowserIntent) {
+        InstrumentationRegistry.getInstrumentation().getContext().startActivity(
+                onlyBrowserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+    }
+
+}
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
index ee23442..7b00c9a 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
@@ -42,7 +42,6 @@
 import android.media.projection.MediaProjection;
 import android.os.Handler;
 import android.os.Looper;
-import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.rule.ActivityTestRule;
@@ -78,7 +77,6 @@
     private AudioManager mAudioManager;
     private boolean mPlaybackBeforeCapture;
     private int mUid; //< UID of this test
-    private int mUserId; // userId of this test
     private AudioPlaybackCaptureActivity mActivity;
     private MediaProjection mMediaProjection;
     @Rule
@@ -90,8 +88,6 @@
         public @AttributeUsage int[] excludeUsages;
         public int[] matchingUids;
         public int[] excludeUids;
-        public int[] matchingUserIds;
-        public int[] excludeUserIds;
         private AudioPlaybackCaptureConfiguration build(MediaProjection projection)
                 throws Exception {
             AudioPlaybackCaptureConfiguration.Builder apccBuilder =
@@ -117,16 +113,6 @@
                     apccBuilder.excludeUid(uid);
                 }
             }
-            if (matchingUserIds != null) {
-                for (int userId : matchingUserIds) {
-                    apccBuilder.addMatchingUserId(userId);
-                }
-            }
-            if (excludeUserIds != null) {
-                for (int userId : excludeUserIds) {
-                    apccBuilder.excludeUserId(userId);
-                }
-            }
             AudioPlaybackCaptureConfiguration config = apccBuilder.build();
             assertCorreclyBuilt(config);
             return config;
@@ -137,8 +123,6 @@
             assertEqualNullIsEmpty("excludeUsages", excludeUsages, config.getExcludeUsages());
             assertEqualNullIsEmpty("matchingUids", matchingUids, config.getMatchingUids());
             assertEqualNullIsEmpty("excludeUids", excludeUids, config.getExcludeUids());
-            assertEqualNullIsEmpty("matchingUserIds", matchingUserIds, config.getMatchingUserIds());
-            assertEqualNullIsEmpty("excludeUserIds", excludeUserIds, config.getExcludeUserIds());
         }
 
         private void assertEqualNullIsEmpty(String msg, int[] expected, int[] found) {
@@ -158,8 +142,6 @@
         mActivity = mActivityRule.getActivity();
         mAudioManager = mActivity.getSystemService(AudioManager.class);
         mUid = mActivity.getApplicationInfo().uid;
-        mUserId = UserHandle.getUserHandleForUid(mActivity.getApplicationInfo().uid)
-                .getIdentifier();
         mMediaProjection = mActivity.waitForMediaProjection();
     }
 
@@ -339,13 +321,6 @@
         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testCombineUserId() throws Exception {
-        mAPCTestConfig.matchingUserIds = new int[]{ mUserId };
-        mAPCTestConfig.excludeUserIds = new int[]{ mUserId + 1 };
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
-    }
-
     @Test
     public void testCaptureMatchingAllowedUsage() throws Exception {
         for (int usage : ALLOWED_USAGES) {
@@ -396,17 +371,6 @@
     }
 
     @Test
-    public void testCaptureMatchingUserId() throws Exception {
-        mAPCTestConfig.matchingUserIds = new int[]{ mUserId };
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_DATA);
-        testPlaybackCapture(OPT_OUT, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_VOICE_COMMUNICATION, EXPECT_SILENCE);
-
-        mAPCTestConfig.matchingUserIds = new int[]{ mUserId + 1 };
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
-    }
-
-    @Test
     public void testCaptureExcludeUid() throws Exception {
         mAPCTestConfig.excludeUids = new int[]{ 0 };
         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_DATA);
@@ -417,17 +381,6 @@
         testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
     }
 
-    @Test
-    public void testCaptureExcludeUserId() throws Exception {
-        mAPCTestConfig.excludeUserIds = new int[]{ mUserId + 1 };
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_DATA);
-        testPlaybackCapture(OPT_OUT, AudioAttributes.USAGE_UNKNOWN, EXPECT_SILENCE);
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_VOICE_COMMUNICATION, EXPECT_SILENCE);
-
-        mAPCTestConfig.excludeUserIds = new int[]{ mUserId };
-        testPlaybackCapture(OPT_IN, AudioAttributes.USAGE_GAME, EXPECT_SILENCE);
-    }
-
     @Test(expected = UnsupportedOperationException.class)
     public void testStoppedMediaProjection() throws Exception {
         mMediaProjection.stop();
diff --git a/tests/tests/media/src/android/media/cts/CodecState.java b/tests/tests/media/src/android/media/cts/CodecState.java
index 5f1d322..2384704 100644
--- a/tests/tests/media/src/android/media/cts/CodecState.java
+++ b/tests/tests/media/src/android/media/cts/CodecState.java
@@ -19,6 +19,7 @@
 import android.media.MediaCodec;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
+import android.os.Handler;
 import android.util.Log;
 import android.view.Surface;
 import java.nio.ByteBuffer;
@@ -50,6 +51,7 @@
     private MediaFormat mFormat;
     private MediaFormat mOutputFormat;
     private NonBlockingAudioTrack mAudioTrack;
+    private OnFrameRenderedListener mOnFrameRenderedListener;
 
     /**
      * Manages audio and video playback using MediaCodec and AudioTrack.
@@ -84,6 +86,11 @@
         String mime = mFormat.getString(MediaFormat.KEY_MIME);
         Log.d(TAG, "CodecState::CodecState " + mime);
         mIsAudio = mime.startsWith("audio/");
+
+        if (mTunneled && !mIsAudio) {
+            mOnFrameRenderedListener = new OnFrameRenderedListener();
+            codec.setOnFrameRenderedListener(mOnFrameRenderedListener, new Handler());
+        }
     }
 
     public void release() {
@@ -100,6 +107,11 @@
         mAvailableOutputBufferIndices = null;
         mAvailableOutputBufferInfos = null;
 
+        if (mOnFrameRenderedListener != null) {
+            mCodec.setOnFrameRenderedListener(null, null);
+            mOnFrameRenderedListener = null;
+        }
+
         mCodec.release();
         mCodec = null;
 
@@ -218,11 +230,6 @@
                 Log.d(TAG, "sampleSize: " + sampleSize + " trackIndex:" + trackIndex +
                         " sampleTime:" + sampleTime + " sampleFlags:" + sampleFlags);
                 mSawInputEOS = true;
-                // FIX-ME: in tunneled mode we currently use input EOS as output EOS indicator
-                // we should use stream duration
-                if (mTunneled && !mIsAudio) {
-                    mSawOutputEOS = true;
-                }
                 return false;
             }
 
@@ -231,9 +238,6 @@
                     mSampleBaseTimeUs = sampleTime;
                 }
                 sampleTime -= mSampleBaseTimeUs;
-                // FIX-ME: in tunneled mode we currently use input buffer time
-                // as video presentation time. This is not accurate and should be fixed
-                mPresentationTimeUs = sampleTime;
             }
 
             if ((sampleFlags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) {
@@ -255,11 +259,6 @@
             Log.d(TAG, "saw input EOS on track " + mTrackIndex);
 
             mSawInputEOS = true;
-            // FIX-ME: in tunneled mode we currently use input EOS as output EOS indicator
-            // we should use stream duration
-            if (mTunneled && !mIsAudio) {
-                mSawOutputEOS = true;
-            }
 
             mCodec.queueInputBuffer(
                     index, 0 /* offset */, 0 /* sampleSize */,
@@ -375,6 +374,23 @@
         }
     }
 
+    /** Callback called by the renderer in tunneling mode. */
+    private class OnFrameRenderedListener implements MediaCodec.OnFrameRenderedListener {
+        private static final long TUNNELING_EOS_PRESENTATION_TIME_US = Long.MAX_VALUE;
+
+        @Override
+        public void onFrameRendered(MediaCodec codec, long presentationTimeUs, long nanoTime) {
+            if (this != mOnFrameRenderedListener) {
+                return; // stale event
+            }
+            if (presentationTimeUs == TUNNELING_EOS_PRESENTATION_TIME_US) {
+                 mSawOutputEOS = true;
+            } else {
+                 mPresentationTimeUs = presentationTimeUs;
+            }
+        }
+    }
+
     public long getAudioTimeUs() {
         if (mAudioTrack == null) {
             return 0;
diff --git a/tests/tests/media/src/android/media/cts/MediaCasTest.java b/tests/tests/media/src/android/media/cts/MediaCasTest.java
index f5e5027..27a8879 100644
--- a/tests/tests/media/src/android/media/cts/MediaCasTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCasTest.java
@@ -252,7 +252,7 @@
         MediaDescrambler descrambler = null;
 
         try {
-            mediaCas = new MediaCas(sClearKeySystemId, "TIS_Session_1",
+            mediaCas = new MediaCas(getContext(), sClearKeySystemId, "TIS_Session_1",
                 android.media.tv.TvInputService.PRIORITY_HINT_USE_CASE_TYPE_LIVE);
             descrambler = new MediaDescrambler(sClearKeySystemId);
 
@@ -266,9 +266,7 @@
                 fail("Can't open session for program");
             }
 
-            if (!PropertyUtil.isVendorApiLevelNewerThan(API_LEVEL_BEFORE_CAS_SESSION + 1)) {
-                Log.d(TAG, "Session Id = " + Arrays.toString(session.getSessionId()));
-            }
+            Log.d(TAG, "Session Id = " + Arrays.toString(session.getSessionId()));
 
             session.setPrivateData(pvtData);
 
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
new file mode 100644
index 0000000..0a80047
--- /dev/null
+++ b/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -0,0 +1,82 @@
+/*
+ * 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.net.cts;
+
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.net.ConnectivityDiagnosticsManager;
+import android.net.NetworkRequest;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public class ConnectivityDiagnosticsManagerTest {
+    private static final Executor INLINE_EXECUTOR = x -> x.run();
+    private static final NetworkRequest DEFAULT_REQUEST = new NetworkRequest.Builder().build();
+
+    private Context mContext;
+    private ConnectivityDiagnosticsManager mCdm;
+    private ConnectivityDiagnosticsCallback mCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mCdm = mContext.getSystemService(ConnectivityDiagnosticsManager.class);
+
+        mCallback = new ConnectivityDiagnosticsCallback() {};
+    }
+
+    @Test
+    public void testRegisterConnectivityDiagnosticsCallback() {
+        mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+    }
+
+    @Test
+    public void testRegisterDuplicateConnectivityDiagnosticsCallback() {
+        mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+
+        try {
+            mCdm.registerConnectivityDiagnosticsCallback(
+                    DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+            fail("Registering the same callback twice should throw an IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testUnregisterConnectivityDiagnosticsCallback() {
+        mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+        mCdm.unregisterConnectivityDiagnosticsCallback(mCallback);
+    }
+
+    @Test
+    public void testUnregisterUnknownConnectivityDiagnosticsCallback() {
+        // Expected to silently ignore the unregister() call
+        mCdm.unregisterConnectivityDiagnosticsCallback(mCallback);
+    }
+}
diff --git a/tests/tests/permission/telephony/OWNERS b/tests/tests/permission/telephony/OWNERS
index 423a16b..b19c963 100644
--- a/tests/tests/permission/telephony/OWNERS
+++ b/tests/tests/permission/telephony/OWNERS
@@ -1,4 +1,2 @@
 set noparent
-amitmahajan@google.com
-fionaxu@google.com
-jminjie@google.com
\ No newline at end of file
+include ../../telephony/OWNERS
\ No newline at end of file
diff --git a/tests/tests/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java b/tests/tests/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java
index b050ee0..805847a 100644
--- a/tests/tests/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java
+++ b/tests/tests/permission/telephony/src/android/permission/cts/telephony/TelephonyManagerPermissionTest.java
@@ -200,6 +200,38 @@
         }
     }
 
+     /**
+     * Tests that isManualNetworkSelectionAllowed requires permission
+     * Expects a security exception since the caller does not have carrier privileges.
+     */
+    @Test
+    public void testIsManualNetworkSelectionAllowedWithoutPermission() {
+        if (!mHasTelephony) {
+            return;
+        }
+        try {
+            mTelephonyManager.isManualNetworkSelectionAllowed();
+            fail("Expected SecurityException. App does not have carrier privileges.");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    /**
+     * Tests that getManualNetworkSelectionPlmn requires permission
+     * Expects a security exception since the caller does not have carrier privileges.
+     */
+    @Test
+    public void testGetManualNetworkSelectionPlmnWithoutPermission() {
+        if (!mHasTelephony) {
+            return;
+        }
+        try {
+            mTelephonyManager.getManualNetworkSelectionPlmn();
+            fail("Expected SecurityException. App does not have carrier privileges.");
+        } catch (SecurityException expected) {
+        }
+    }
+
     /**
      * Verify that Telephony related broadcasts are protected.
      */
diff --git a/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java b/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java
index 25a343d..eeb8312 100644
--- a/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java
+++ b/tests/tests/selinux/common/src/android/security/SELinuxTargetSdkTestBase.java
@@ -29,27 +29,6 @@
         }
     }
 
-    protected static void noExecuteOnly() throws IOException {
-        String[] maps = {"^[0-9a-z]+-[0-9a-z]+\\s+--xp.*\\/apex\\/com\\.android\\.runtime\\/.*",
-            "^[0-9a-z]+-[0-9a-z]+\\s+--xp.*\\/system\\/.*"};
-        for (String map : maps) {
-            final Pattern mapsPattern = Pattern.compile(map);
-            BufferedReader reader = new BufferedReader(new FileReader("/proc/self/maps"));
-            String line;
-            try {
-                while ((line = reader.readLine()) != null) {
-                    Matcher m = mapsPattern.matcher(line);
-                    assertFalse("System provided libraries should be not be marked execute-only " +
-                           "for apps with targetSdkVersion<Q, but an execute-only segment was " +
-                           "found:\n" + line, m.matches());
-                }
-
-            } finally {
-                reader.close();
-            }
-        }
-    }
-
     protected static String getProperty(String property)
             throws IOException {
         Process process = new ProcessBuilder("getprop", property).start();
diff --git a/tests/tests/selinux/selinuxTargetSdk25/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk25/src/android/security/SELinuxTargetSdkTest.java
index dddd3d9..ec2af2d 100644
--- a/tests/tests/selinux/selinuxTargetSdk25/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdk25/src/android/security/SELinuxTargetSdkTest.java
@@ -68,8 +68,4 @@
     public void testDex2oat() throws Exception {
         checkDex2oatAccess(true);
     }
-
-    public void testNoExecuteOnly() throws IOException {
-        noExecuteOnly();
-    }
 }
diff --git a/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java
index e7c0e67..0394a18 100644
--- a/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdk27/src/android/security/SELinuxTargetSdkTest.java
@@ -64,8 +64,4 @@
     public void testDex2oat() throws Exception {
         checkDex2oatAccess(true);
     }
-
-    public void testNoExecuteOnly() throws IOException {
-        noExecuteOnly();
-    }
 }
diff --git a/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java b/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java
index 1ab9706..92e1070 100644
--- a/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java
+++ b/tests/tests/selinux/selinuxTargetSdk28/src/android/security/SELinuxTargetSdkTest.java
@@ -64,8 +64,4 @@
     public void testDex2oat() throws Exception {
         checkDex2oatAccess(true);
     }
-
-    public void testNoExecuteOnly() throws IOException {
-        noExecuteOnly();
-    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
index 4492561..9550526 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -168,6 +168,7 @@
     private static final int MIN_FPLMN_NUM = 3;
 
     private static final String TEST_FORWARD_NUMBER = "54321";
+    private static final String TESTING_PLMN = "12345";
 
     static {
         EMERGENCY_NUMBER_SOURCE_SET = new HashSet<Integer>();
@@ -512,6 +513,11 @@
                 (tm) -> tm.getImei());
         ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
                 (tm) -> tm.getImei(mTelephonyManager.getSlotIndex()));
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.isManualNetworkSelectionAllowed());
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getManualNetworkSelectionPlmn());
+
         mTelephonyManager.getPhoneCount();
         mTelephonyManager.getDataEnabled();
         mTelephonyManager.getNetworkSpecifier();
@@ -1764,6 +1770,52 @@
     }
 
     /**
+     * Tests that the device properly reports the contents of ManualNetworkSelectionPlmn
+     * The setting is not persisted selection
+     */
+    @Test
+    public void testGetManualNetworkSelectionPlmnNonPersisted() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    (tm) -> tm.setNetworkSelectionModeManual(
+                     TESTING_PLMN/* operatorNumeric */, false /* persistSelection */));
+            String plmn = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                     (tm) -> tm.getManualNetworkSelectionPlmn());
+            assertEquals(TESTING_PLMN, plmn);
+        } finally {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    (tm) -> tm.setNetworkSelectionModeAutomatic());
+        }
+    }
+
+    /**
+     * Tests that the device properly reports the contents of ManualNetworkSelectionPlmn
+     * The setting is persisted selection
+     */
+    @Test
+    public void testGetManualNetworkSelectionPlmnPersisted() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            return;
+        }
+
+        try {
+            ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    (tm) -> tm.setNetworkSelectionModeManual(
+                     TESTING_PLMN/* operatorNumeric */, true /* persistSelection */));
+            String plmn = ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                     (tm) -> tm.getManualNetworkSelectionPlmn());
+            assertEquals(TESTING_PLMN, plmn);
+        } finally {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    (tm) -> tm.setNetworkSelectionModeAutomatic());
+        }
+    }
+
+    /**
      * Verify that TelephonyManager.getCardIdForDefaultEuicc returns a positive value or either
      * UNINITIALIZED_CARD_ID or UNSUPPORTED_CARD_ID.
      */
@@ -1855,6 +1907,19 @@
     }
 
     /**
+     * Tests that the device properly check whether selection mode was manual.
+     */
+    @Test
+    public void testIsManualNetworkSelectionAllowed() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "Skipping test that requires FEATURE_TELEPHONY");
+            return;
+        }
+        assertTrue(ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.isManualNetworkSelectionAllowed()));
+    }
+
+    /**
      * Construct a CallAttributes object and test getters.
      */
     @Test
diff --git a/tests/tests/widget29/AndroidTest.xml b/tests/tests/widget29/AndroidTest.xml
index 39d3911..f867df3 100644
--- a/tests/tests/widget29/AndroidTest.xml
+++ b/tests/tests/widget29/AndroidTest.xml
@@ -18,6 +18,7 @@
     <option name="config-descriptor:metadata" key="component" value="uitoolkit" />
     <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="cleanup-apks" value="true" />
         <option name="test-file-name" value="CtsWidgetTestCases29.apk" />