Merge "Prevent the test from being run on Cuttlefish" into rvc-dev
diff --git a/apps/CameraITS/pymodules/its/caps.py b/apps/CameraITS/pymodules/its/caps.py
index 9acbb3c..ccbaa65 100644
--- a/apps/CameraITS/pymodules/its/caps.py
+++ b/apps/CameraITS/pymodules/its/caps.py
@@ -597,6 +597,12 @@
            props['android.control.zoomRatioRange'] is not None
 
 
+def jpeg_quality(props):
+    """Returns whether a device supports JPEG quality."""
+    return props.has_key('camera.characteristics.requestKeys') and \
+              'android.jpeg.quality' in props['camera.characteristics.requestKeys']
+
+
 class __UnitTest(unittest.TestCase):
     """Run a suite of unit tests on this module.
     """
diff --git a/apps/CameraITS/tests/scene0/test_metadata.py b/apps/CameraITS/tests/scene0/test_metadata.py
index 4468c5d..88c7eef 100644
--- a/apps/CameraITS/tests/scene0/test_metadata.py
+++ b/apps/CameraITS/tests/scene0/test_metadata.py
@@ -101,7 +101,7 @@
         fl = md["android.lens.focalLength"]
         fov = 2 * math.degrees(math.atan(diag / (2 * fl)))
         print "Assert field of view: %.1f degrees" % fov
-        assert 30 <= fov <= 130
+        assert 10 <= fov <= 130
 
         if its.caps.lens_approx_calibrated(props):
             diopter_hyperfocal = props["android.lens.info.hyperfocalDistance"]
diff --git a/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py b/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
index b52fb1e..d8acb2a 100644
--- a/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
+++ b/apps/CameraITS/tests/scene2_a/test_jpeg_quality.py
@@ -209,6 +209,7 @@
 
     with its.device.ItsSession() as cam:
         props = cam.get_camera_properties()
+        its.caps.skip_unless(its.caps.jpeg_quality(props))
         cam.do_3a()
 
         # do captures over jpeg quality range
diff --git a/apps/CameraITS/tests/scene6/test_zoom.py b/apps/CameraITS/tests/scene6/test_zoom.py
index eef7144..8d70059 100644
--- a/apps/CameraITS/tests/scene6/test_zoom.py
+++ b/apps/CameraITS/tests/scene6/test_zoom.py
@@ -123,9 +123,18 @@
 
     # mark image center
     size = gray.shape
-    cv2.drawMarker(img, (size[1]/2, size[0]/2), LINE_COLOR,
-                   markerType=cv2.MARKER_CROSS, markerSize=LINE_THICKNESS*10,
-                   thickness=LINE_THICKNESS)
+    m_x, m_y = size[1]/2, size[0]/2
+    marker_size = LINE_THICKNESS * 10
+    if cv2_version.startswith('2.4.'):
+        cv2.line(img, (m_x-marker_size/2, m_y), (m_x+marker_size/2, m_y),
+                 LINE_COLOR, LINE_THICKNESS)
+        cv2.line(img, (m_x, m_y-marker_size/2), (m_x, m_y+marker_size/2),
+                 LINE_COLOR, LINE_THICKNESS)
+    elif cv2_version.startswith('3.2.'):
+        cv2.drawMarker(img, (m_x, m_y), LINE_COLOR,
+                       markerType=cv2.MARKER_CROSS,
+                       markerSize=marker_size,
+                       thickness=LINE_THICKNESS)
 
     # add circle to saved image
     center_i = (int(round(circle[0], 0)), int(round(circle[1], 0)))
diff --git a/apps/CtsVerifier/AndroidManifest.xml b/apps/CtsVerifier/AndroidManifest.xml
index 0289af9..345ed05 100644
--- a/apps/CtsVerifier/AndroidManifest.xml
+++ b/apps/CtsVerifier/AndroidManifest.xml
@@ -2432,6 +2432,15 @@
         <activity android:name=".notifications.BubbleActivity"
                   android:label="@string/bubble_activity_title"
                   android:resizeableActivity="true">
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:mimeType="text/plain" />
+                <data android:mimeType="image/*" />
+            </intent-filter>
+
+            <meta-data android:name="android.app.shortcuts"
+                       android:resource="@xml/shortcuts" />
         </activity>
 
         <service android:name=".notifications.MockListener"
@@ -3275,11 +3284,6 @@
             </intent-filter>
         </receiver>
 
-        <activity android:name=".managedprovisioning.CompTestActivity"
-                android:launchMode="singleTask"
-                android:label="@string/comp_test">
-        </activity>
-
         <activity android:name=".managedprovisioning.ByodProvisioningTestActivity"
                 android:label="@string/provisioning_tests_byod">
             <intent-filter>
@@ -4125,25 +4129,6 @@
             </intent-filter>
         </service>
 
-        <receiver android:name=".managedprovisioning.CompDeviceAdminTestReceiver"
-                android:label="@string/afw_device_admin"
-                android:permission="android.permission.BIND_DEVICE_ADMIN">
-            <meta-data android:name="android.app.device_admin"
-                       android:resource="@xml/device_admin_comp_profile" />
-            <intent-filter>
-                <action android:name="android.app.action.PROFILE_PROVISIONING_COMPLETE"/>
-            </intent-filter>
-        </receiver>
-
-        <activity android:name=".managedprovisioning.CompHelperActivity">
-            <intent-filter>
-                <action android:name="com.android.cts.verifier.managedprovisioning.COMP_SET_ALWAYS_ON_VPN" />
-                <action android:name="com.android.cts.verifier.managedprovisioning.COMP_INSTALL_CA_CERT" />
-                <action android:name="com.android.cts.verifier.managedprovisioning.COMP_SET_MAXIMUM_PASSWORD_ATTEMPTS" />
-                <category android:name="android.intent.category.DEFAULT"></category>
-            </intent-filter>
-        </activity>
-
         <activity
             android:name=".telecom.EnablePhoneAccountTestActivity"
             android:label="@string/telecom_enable_phone_account_test">
diff --git a/apps/CtsVerifier/res/values/strings.xml b/apps/CtsVerifier/res/values/strings.xml
index 0210ee6..349b0b7 100755
--- a/apps/CtsVerifier/res/values/strings.xml
+++ b/apps/CtsVerifier/res/values/strings.xml
@@ -2241,7 +2241,8 @@
     <string name="cacert_info">This test checks that when a CA Certificate is installed, the user is notified.</string>
     <string name="cacert_do_something">Do it</string>
     <string name="cacert_done">Done</string>
-    <string name="cacert_install_cert">Use the CertInstaller to install the certificate "MyCA.cer" from device storage. When it opens, choose any name and tap "Okay". If this button does nothing, pass the test and move on.</string>
+    <string name="cacert_install_cert_title">Tap to install a CA certificate</string>
+    <string name="cacert_install_cert">Press the button to open Security settings. Navigate to \"Install a certificate\" (normally under \"Encryptions &amp; credentials\") and select \"CA certificate\". Pick \"MyCA.cer\" from device storage and proceed with the installation. If this button does nothing, pass the test and move on.</string>
     <string name="cacert_check_cert_in_settings">Visit the user-installed trusted credentials page and confirm that the "Internet Widgits Pty Ltd" cert appears in the list.</string>
     <string name="cacert_remove_screen_lock">You may have been prompted to set a screen lock when installing the certificate. If so, remove it. If not, you may skip this step.</string>
     <string name="cacert_check_notification">Look at the system notifications. Confirm that:\n
@@ -3232,10 +3233,13 @@
     <string name="provisioning_byod_turn_off_work_launcher">Starting work apps when work profile is off</string>
     <string name="provisioning_byod_turn_off_work_launcher_instruction">
         This test verifies that work applications cannot be started if work profile is off.\n
-        1. Press home to go to the launcher.\n
-        2. Verify that work applications are greyed out.\n
-        3. Tap on a work application.\n
-        4. Verify that the application does not start.\n
+        1. Open settings to turn work profile back on, either manually or using the "Open Settings to toggle work" on the previous page.\n
+        2. Add a work app to your home screen.\n
+        3. Turn work profile off from Settings.\n
+        4. Press home to go to the launcher.\n
+        5. Verify that the work app is greyed out.\n
+        6. Tap on the work app.\n
+        7. Verify that the application does not start.\n
     </string>
 
     <string name="provisioning_byod_turn_off_work_turned_on">Please turn work profile back on</string>
@@ -4143,17 +4147,6 @@
         6) Press the Finish button to clear the always-on VPN.
     </string>
     <string name="enterprise_privacy_set_always_on_vpn">Set VPN</string>
-    <string name="enterprise_privacy_comp_always_on_vpn">Always-on VPN (managed profile)</string>
-    <string name="enterprise_privacy_comp_always_on_vpn_info">
-        Please do the following:\n
-        1) Press the Start button to create a work profile.\n
-        2) Press the Open Settings button.\n
-        3) In the screen that opens, verify that you are not told that an always-on VPN is set.\n
-        4) Use the Back button to return to this page.\n
-        5) Press the Set VPN button to set an always-on VPN in your work profile.\n
-        6) Repeat steps (2) through (4), verifying that in step (3), you are told now that an always-on VPN is set in your work profile.\n
-        7) Press the Finish button to remove the work profile and always-on VPN.
-    </string>
     <string name="enterprise_privacy_start">Start</string>
     <string name="enterprise_privacy_global_http_proxy">Global HTTP Proxy</string>
     <string name="enterprise_privacy_global_http_proxy_info">
@@ -4176,17 +4169,6 @@
         6) Press the Finish button to clear the cert.
     </string>
     <string name="enterprise_privacy_install_cert">Install Cert</string>
-    <string name="enterprise_privacy_comp_ca_certs">Trusted CA certs (managed profile)</string>
-    <string name="enterprise_privacy_comp_ca_certs_info">
-        Please do the following:\n
-        1) Press the Start button to create a work profile.\n
-        2) Press the Settings button.\n
-        3) In the screen that opens, verify that you are not told that your administrator installed trusted CA certs.\n
-        4) Use the Back button to return to this page.\n
-        5) Press the Install Cert button to install a trusted CA cert in your work profile.\n
-        6) Repeat steps (2) through (4), verifying that in step (3), you are told now that the administrator has installed at least one trusted CA cert in your work profile.\n
-        7) Press the Finish button to remove the work profile and cert.
-    </string>
     <string name="enterprise_privacy_settings">Settings</string>
     <string name="enterprise_privacy_set_proxy">Set Proxy</string>
     <string name="enterprise_privacy_clear_proxy">Clear Proxy</string>
@@ -4201,17 +4183,6 @@
         6) Press the Finish button to clear the maximum number of password attempts.
     </string>
     <string name="enterprise_privacy_set_limit">Set Limit</string>
-    <string name="enterprise_privacy_comp_failed_password_wipe">Wipe on authentication failure (managed profile)</string>
-    <string name="enterprise_privacy_comp_failed_password_wipe_info">
-        Please do the following:\n
-        1) Press the Start button to create a work profile.\n
-        2) Press the Open Settings button.\n
-        3) In the screen that opens, verify that you are not told work profile data will be deleted if you mistype your password too many times.\n
-        4) Use the Back button to return to this page.\n
-        5) Press the Set Limit button to set the maximum number of password attempts.\n
-        6) Repeat steps (2) through (4), verifying that in step (3), you are told now that work profile data will be deleted if you mistype your password 100 times.\n
-        7) Press the Finish button to remove the work profile.
-    </string>
     <string name="enterprise_privacy_quick_settings">Quick settings disclosure</string>
     <string name="enterprise_privacy_quick_settings_info">
         Please do the following:\n
@@ -4305,10 +4276,6 @@
     <string name="device_owner_set_lockscreen_message_hint">My lock screen message</string>
     <string name="device_owner_lockscreen_message_cannot_be_empty">Lock screen message cannot be empty.</string>
 
-    <string name="comp_test">Corporate Owned Managed Profile</string>
-    <string name="comp_provision_profile_dialog_title">Provision work profile</string>
-    <string name="comp_provision_profile_dialog_text">Press the OK button to start the managed provisioning flow, and complete the flow to create a work profile</string>
-
     <string name="managed_user_test">Managed User</string>
     <string name="managed_user_positive_tests">Managed User positive tests</string>
     <string name="managed_user_positive_tests_instructions">
diff --git a/apps/CtsVerifier/res/xml/shortcuts.xml b/apps/CtsVerifier/res/xml/shortcuts.xml
new file mode 100644
index 0000000..b4c9efa
--- /dev/null
+++ b/apps/CtsVerifier/res/xml/shortcuts.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
+    <share-target android:targetClass="com.android.cts.verifier.notifications.BubbleActivity">
+        <data android:mimeType="text/plain" />
+        <data android:mimeType="image/*" />
+        <category android:name="com.android.cts.verifier.notifications.SHORTCUT_CATEGORY" />
+    </share-target>
+</shortcuts>
\ No newline at end of file
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/AbstractUserAuthenticationTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/AbstractUserAuthenticationTest.java
index b937e92..92869f3 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/AbstractUserAuthenticationTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/biometrics/AbstractUserAuthenticationTest.java
@@ -50,7 +50,7 @@
 
     private static final String TAG = "AbstractUserAuthenticationCredentialTest";
 
-    private static final int TIMED_KEY_DURATION = 1;
+    private static final int TIMED_KEY_DURATION = 3;
     private static final byte[] PAYLOAD = new byte[] {1, 2, 3, 4, 5, 6};
 
     abstract class ExpectedResults {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
index dc7b609..2c2c45a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CommandReceiverActivity.java
@@ -30,6 +30,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
@@ -402,28 +403,6 @@
                             PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
                             PackageManager.DONT_KILL_APP);
                 } break;
-                case COMMAND_CREATE_MANAGED_PROFILE: {
-                    if (!mDpm.isDeviceOwnerApp(getPackageName())) {
-                        return;
-                    }
-                    if (mUm.getUserProfiles().size() > 1) {
-                        return;
-                    }
-                    startActivityForResult(new Intent(
-                            DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE)
-                            .putExtra(DevicePolicyManager
-                                    .EXTRA_PROVISIONING_DEVICE_ADMIN_COMPONENT_NAME,
-                                    CompDeviceAdminTestReceiver.getReceiverComponentName())
-                            .putExtra(DevicePolicyManager.EXTRA_PROVISIONING_SKIP_ENCRYPTION, true)
-                            .putExtra(DevicePolicyManager.EXTRA_PROVISIONING_SKIP_USER_CONSENT,
-                                true), 0);
-                } break;
-                case COMMAND_REMOVE_MANAGED_PROFILE: {
-                    if (!mDpm.isDeviceOwnerApp(getPackageName())) {
-                        return;
-                    }
-                    removeManagedProfile();
-                } break;
                 case COMMAND_SET_ALWAYS_ON_VPN: {
                     if (!mDpm.isDeviceOwnerApp(getPackageName())) {
                         return;
@@ -652,7 +631,8 @@
     }
 
     private boolean isSystemInputMethodInfo(InputMethodInfo inputMethodInfo) {
-        return inputMethodInfo.getServiceInfo().applicationInfo.isSystemApp();
+        final ApplicationInfo applicationInfo = inputMethodInfo.getServiceInfo().applicationInfo;
+        return (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
     }
 
     private void createAndSwitchUserWithMessage(String startUserSessionMessage,
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompDeviceAdminTestReceiver.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompDeviceAdminTestReceiver.java
deleted file mode 100644
index 9298135..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompDeviceAdminTestReceiver.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.cts.verifier.managedprovisioning;
-
-import android.app.admin.DeviceAdminReceiver;
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-
-/**
- * Profile owner receiver for COMP tests. Sets up cross-profile intent filters that allow the
- * CtsVerifier running in the primary user to send it commands after successful provisioning.
- */
-public class CompDeviceAdminTestReceiver extends DeviceAdminReceiver {
-        private static final ComponentName RECEIVER_COMPONENT_NAME = new ComponentName(
-                "com.android.cts.verifier", CompDeviceAdminTestReceiver.class.getName());
-
-        public static ComponentName getReceiverComponentName() {
-            return RECEIVER_COMPONENT_NAME;
-        }
-
-        @Override
-        public void onProfileProvisioningComplete(Context context, Intent intent) {
-            final DevicePolicyManager dpm =
-                    (DevicePolicyManager) context.getSystemService(Context.DEVICE_POLICY_SERVICE);
-            dpm.setProfileEnabled(new ComponentName(context.getApplicationContext(), getClass()));
-
-            // Set up cross-profile intent filter to allow the CtsVerifier running in the primary
-            // user to send us commands.
-            final IntentFilter filter = new IntentFilter();
-            filter.addAction(CompHelperActivity.ACTION_SET_ALWAYS_ON_VPN);
-            filter.addAction(CompHelperActivity.ACTION_INSTALL_CA_CERT);
-            filter.addAction(CompHelperActivity.ACTION_SET_MAXIMUM_PASSWORD_ATTEMPTS);
-            dpm.addCrossProfileIntentFilter(getWho(context), filter,
-                    DevicePolicyManager.FLAG_MANAGED_CAN_ACCESS_PARENT);
-        }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompHelperActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompHelperActivity.java
deleted file mode 100644
index be46a6e..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompHelperActivity.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * 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.
- */
-
-package com.android.cts.verifier.managedprovisioning;
-
-import android.app.Activity;
-import android.app.admin.DevicePolicyManager;
-import android.content.ComponentName;
-import android.content.Context;
-import android.os.Bundle;
-import android.util.Log;
-
-/**
- * A helper activity that executes commands sent from CtsVerifier in the primary user to the managed
- * profile in COMP mode.
- *
- * Note: We have to use a dummy activity because cross-profile intents only work for activities.
- */
-public class CompHelperActivity extends Activity {
-
-    public static final String TAG = "CompHelperActivity";
-
-    // Set always-on VPN.
-    public static final String ACTION_SET_ALWAYS_ON_VPN
-            = "com.android.cts.verifier.managedprovisioning.COMP_SET_ALWAYS_ON_VPN";
-    // Install trusted CA cert.
-    public static final String ACTION_INSTALL_CA_CERT
-            = "com.android.cts.verifier.managedprovisioning.COMP_INSTALL_CA_CERT";
-    // Set the number of login failures after which the managed profile is wiped.
-    public static final String ACTION_SET_MAXIMUM_PASSWORD_ATTEMPTS
-            = "com.android.cts.verifier.managedprovisioning.COMP_SET_MAXIMUM_PASSWORD_ATTEMPTS";
-
-    /*
-     * The CA cert below is the content of cacert.pem as generated by:
-     *
-     * openssl req -new -x509 -days 3650 -extensions v3_ca -keyout cakey.pem -out cacert.pem
-     */
-    private static final String TEST_CA =
-            "-----BEGIN CERTIFICATE-----\n" +
-            "MIIDXTCCAkWgAwIBAgIJAK9Tl/F9V8kSMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV\n" +
-            "BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX\n" +
-            "aWRnaXRzIFB0eSBMdGQwHhcNMTUwMzA2MTczMjExWhcNMjUwMzAzMTczMjExWjBF\n" +
-            "MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50\n" +
-            "ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB\n" +
-            "CgKCAQEAvItOutsE75WBTgTyNAHt4JXQ3JoseaGqcC3WQij6vhrleWi5KJ0jh1/M\n" +
-            "Rpry7Fajtwwb4t8VZa0NuM2h2YALv52w1xivql88zce/HU1y7XzbXhxis9o6SCI+\n" +
-            "oVQSbPeXRgBPppFzBEh3ZqYTVhAqw451XhwdA4Aqs3wts7ddjwlUzyMdU44osCUg\n" +
-            "kVg7lfPf9sTm5IoHVcfLSCWH5n6Nr9sH3o2ksyTwxuOAvsN11F/a0mmUoPciYPp+\n" +
-            "q7DzQzdi7akRG601DZ4YVOwo6UITGvDyuAAdxl5isovUXqe6Jmz2/myTSpAKxGFs\n" +
-            "jk9oRoG6WXWB1kni490GIPjJ1OceyQIDAQABo1AwTjAdBgNVHQ4EFgQUH1QIlPKL\n" +
-            "p2OQ/AoLOjKvBW4zK3AwHwYDVR0jBBgwFoAUH1QIlPKLp2OQ/AoLOjKvBW4zK3Aw\n" +
-            "DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAcMi4voMMJHeQLjtq8Oky\n" +
-            "Azpyk8moDwgCd4llcGj7izOkIIFqq/lyqKdtykVKUWz2bSHO5cLrtaOCiBWVlaCV\n" +
-            "DYAnnVLM8aqaA6hJDIfaGs4zmwz0dY8hVMFCuCBiLWuPfiYtbEmjHGSmpQTG6Qxn\n" +
-            "ZJlaK5CZyt5pgh5EdNdvQmDEbKGmu0wpCq9qjZImwdyAul1t/B0DrsWApZMgZpeI\n" +
-            "d2od0VBrCICB1K4p+C51D93xyQiva7xQcCne+TAnGNy9+gjQ/MyR8MRpwRLv5ikD\n" +
-            "u0anJCN8pXo6IMglfMAsoton1J6o5/ae5uhC6caQU8bNUsCK570gpNfjkzo6rbP0\n" +
-            "wQ==\n" +
-            "-----END CERTIFICATE-----";
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        final ComponentName admin = CompDeviceAdminTestReceiver.getReceiverComponentName();
-        final DevicePolicyManager dpm = (DevicePolicyManager) getSystemService(
-                Context.DEVICE_POLICY_SERVICE);
-
-        final String action = getIntent().getAction();
-        if (ACTION_SET_ALWAYS_ON_VPN.equals(action)) {
-            try {
-                dpm.setAlwaysOnVpnPackage(admin, getPackageName(), false /* lockdownEnabled */);
-            } catch (Exception e) {
-                Log.e(TAG, "Unable to set always-on VPN", e);
-            }
-        } else if (ACTION_INSTALL_CA_CERT.equals(action)) {
-            dpm.installCaCert(admin, TEST_CA.getBytes());
-        } else if (ACTION_SET_MAXIMUM_PASSWORD_ATTEMPTS.equals(action)) {
-            dpm.setMaximumFailedPasswordsForWipe(admin, 100);
-        }
-        finish();
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompTestActivity.java
deleted file mode 100644
index d515097..0000000
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/CompTestActivity.java
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * 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.
- */
-package com.android.cts.verifier.managedprovisioning;
-
-import android.app.Activity;
-import android.app.AlertDialog;
-import android.content.Intent;
-import android.os.Bundle;
-import android.util.Log;
-
-import com.android.cts.verifier.R;
-
-/**
- * Creates a managed profile on a device owner device, and checks that the user is not able to
- * remove the profile if {@link android.os.UserManager#DISALLOW_REMOVE_MANAGED_PROFILE} is set.
- */
-public class CompTestActivity extends Activity {
-
-    private static final String TAG = "CompTestActivity";
-
-    private static final int PROVISION_MANAGED_PROFILE_REQUEST_CODE = 1;
-    private static final int POLICY_TRANSPARENCY_REQUEST_CODE = 2;
-
-    @Override
-    protected void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-        new AlertDialog.Builder(
-                CompTestActivity.this)
-                .setIcon(android.R.drawable.ic_dialog_info)
-                .setTitle(R.string.comp_provision_profile_dialog_title)
-                .setMessage(R.string.comp_provision_profile_dialog_text)
-                .setPositiveButton(android.R.string.ok,
-                        (dialog, whichButton) -> {
-                            Utils.provisionManagedProfile(CompTestActivity.this,
-                                    CompDeviceAdminTestReceiver.getReceiverComponentName(),
-                                    PROVISION_MANAGED_PROFILE_REQUEST_CODE);
-                        }).show();
-    }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        if (requestCode == PROVISION_MANAGED_PROFILE_REQUEST_CODE) {
-            if (resultCode != Activity.RESULT_OK) {
-                Log.i(TAG, "Provisioning failed or was cancelled by the user.");
-                setResult(Activity.RESULT_CANCELED);
-                finish();
-                return;
-            }
-
-            final Intent policyTransparencyTestIntent = new Intent(this,
-                    PolicyTransparencyTestListActivity.class);
-            policyTransparencyTestIntent.putExtra(
-                    PolicyTransparencyTestListActivity.EXTRA_MODE,
-                    PolicyTransparencyTestListActivity.MODE_COMP);
-            String testId = getIntent().getStringExtra(
-                    PolicyTransparencyTestActivity.EXTRA_TEST_ID);
-            policyTransparencyTestIntent.putExtra(
-                    PolicyTransparencyTestActivity.EXTRA_TEST_ID,
-                    testId);
-            startActivityForResult(policyTransparencyTestIntent, POLICY_TRANSPARENCY_REQUEST_CODE);
-        } else if (requestCode == POLICY_TRANSPARENCY_REQUEST_CODE) {
-            startActivity(new Intent(CommandReceiverActivity.ACTION_EXECUTE_COMMAND)
-                    .putExtra(CommandReceiverActivity.EXTRA_COMMAND,
-                            CommandReceiverActivity.COMMAND_REMOVE_MANAGED_PROFILE));
-            // forward the result to the caller activity so that it can update the test result
-            setResult(resultCode, data);
-            finish();
-        } else {
-            Log.e(TAG, "Unknown request code received " + requestCode);
-        }
-    }
-}
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
index d3f4889..0c905c0 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/DeviceOwnerPositiveTestActivity.java
@@ -73,7 +73,6 @@
     private static final String DISALLOW_USER_SWITCH_TEST_ID = "DISALLOW_USER_SWITCH";
     private static final String USER_SWITCHER_MESSAGE_TEST_ID = "USER_SWITCHER_MESSAGE";
     private static final String ENABLE_LOGOUT_TEST_ID = "ENABLE_LOGOUT";
-    private static final String COMP_TEST_ID = "COMP_UI";
     private static final String MANAGED_USER_TEST_ID = "MANAGED_USER_UI";
     private static final String REMOVE_DEVICE_OWNER_TEST_ID = "REMOVE_DEVICE_OWNER";
     private static final String DISALLOW_AMBIENT_DISPLAY_ID = "DISALLOW_AMBIENT_DISPLAY";
@@ -355,15 +354,6 @@
                 R.string.enterprise_privacy_test,
                 enterprisePolicyTestIntent));
 
-        // COMP
-        if (packageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
-            Intent compIntent = new Intent(this, CompTestActivity.class)
-                    .putExtra(PolicyTransparencyTestActivity.EXTRA_TEST_ID, COMP_TEST_ID);
-            adapter.add(createTestItem(this, COMP_TEST_ID,
-                    R.string.comp_test,
-                    compIntent));
-        }
-
         if (packageManager.hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)
                 && UserManager.supportsMultipleUsers()) {
             // Managed user
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
index 3a16297..e4ea653 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/EnterprisePrivacyTestListActivity.java
@@ -58,17 +58,11 @@
             = "ENTERPRISE_PRIVACY_DEFAULT_IME";
     private static final String ENTERPRISE_PRIVACY_ALWAYS_ON_VPN
             = "ENTERPRISE_PRIVACY_ALWAYS_ON_VPN";
-    private static final String ENTERPRISE_PRIVACY_COMP_ALWAYS_ON_VPN
-            = "ENTERPRISE_PRIVACY_COMP_ALWAYS_ON_VPN";
     private static final String ENTERPRISE_PRIVACY_GLOBAL_HTTP_PROXY
             = "ENTERPRISE_PRIVACY_GLOBAL_HTTP_PROXY";
     private static final String ENTERPRISE_PRIVACY_CA_CERTS = "ENTERPRISE_PRIVACY_CA_CERTS";
-    private static final String ENTERPRISE_PRIVACY_COMP_CA_CERTS
-            = "ENTERPRISE_PRIVACY_COMP_CA_CERTS";
     private static final String ENTERPRISE_PRIVACY_FAILED_PASSWORD_WIPE
             = "ENTERPRISE_PRIVACY_FAILED_PASSWORD_WIPE";
-    private static final String ENTERPRISE_PRIVACY_COMP_FAILED_PASSWORD_WIPE
-            = "ENTERPRISE_PRIVACY_COMP_FAILED_PASSWORD_WIPE";
     private static final String ENTERPRISE_PRIVACY_QUICK_SETTINGS
             = "ENTERPRISE_PRIVACY_QUICK_SETTINGS";
     private static final String ENTERPRISE_PRIVACY_KEYGUARD = "ENTERPRISE_PRIVACY_KEYGUARD";
@@ -91,10 +85,6 @@
             }
         });
         setTestListAdapter(adapter);
-        getPackageManager().setComponentEnabledSetting(
-                new ComponentName(this, CompHelperActivity.class),
-                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
-                PackageManager.DONT_KILL_APP);
     }
 
     private Intent buildCommandIntent(String command) {
@@ -209,24 +199,7 @@
                         new ButtonInfo(R.string.enterprise_privacy_finish,
                                 buildCommandIntent(
                                         CommandReceiverActivity.COMMAND_CLEAR_ALWAYS_ON_VPN))}));
-        if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
-            adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_COMP_ALWAYS_ON_VPN,
-                    R.string.enterprise_privacy_comp_always_on_vpn,
-                    R.string.enterprise_privacy_comp_always_on_vpn_info,
-                    new ButtonInfo[] {
-                            new ButtonInfo(R.string.enterprise_privacy_start,
-                                    buildCommandIntent(
-                                            CommandReceiverActivity.
-                                            COMMAND_CREATE_MANAGED_PROFILE)),
-                            new ButtonInfo(R.string.enterprise_privacy_open_settings,
-                                    new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS)),
-                            new ButtonInfo(R.string.enterprise_privacy_set_always_on_vpn,
-                                    new Intent(CompHelperActivity.ACTION_SET_ALWAYS_ON_VPN)),
-                            new ButtonInfo(R.string.enterprise_privacy_finish,
-                                    buildCommandIntent(
-                                            CommandReceiverActivity.
-                                            COMMAND_REMOVE_MANAGED_PROFILE))}));
-        }
+
         adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_GLOBAL_HTTP_PROXY,
                 R.string.enterprise_privacy_global_http_proxy,
                 R.string.enterprise_privacy_global_http_proxy_info,
@@ -251,24 +224,6 @@
                         new ButtonInfo(R.string.enterprise_privacy_finish,
                                 buildCommandIntent(
                                         CommandReceiverActivity.COMMAND_CLEAR_CA_CERT))}));
-        if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
-            adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_COMP_CA_CERTS,
-                    R.string.enterprise_privacy_comp_ca_certs,
-                    R.string.enterprise_privacy_comp_ca_certs_info,
-                    new ButtonInfo[] {
-                            new ButtonInfo(R.string.enterprise_privacy_start,
-                                    buildCommandIntent(
-                                            CommandReceiverActivity.
-                                            COMMAND_CREATE_MANAGED_PROFILE)),
-                            new ButtonInfo(R.string.enterprise_privacy_settings,
-                                    new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS)),
-                            new ButtonInfo(R.string.enterprise_privacy_install_cert,
-                                    new Intent(CompHelperActivity.ACTION_INSTALL_CA_CERT)),
-                            new ButtonInfo(R.string.enterprise_privacy_finish,
-                                    buildCommandIntent(
-                                            CommandReceiverActivity.
-                                            COMMAND_REMOVE_MANAGED_PROFILE))}));
-        }
         adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_FAILED_PASSWORD_WIPE,
                 R.string.enterprise_privacy_failed_password_wipe,
                 R.string.enterprise_privacy_failed_password_wipe_info,
@@ -281,25 +236,6 @@
                         new ButtonInfo(R.string.enterprise_privacy_finish,
                                 buildCommandIntent(CommandReceiverActivity
                                         .COMMAND_CLEAR_MAXIMUM_PASSWORD_ATTEMPTS))}));
-        if (getPackageManager().hasSystemFeature(PackageManager.FEATURE_MANAGED_USERS)) {
-            adapter.add(createInteractiveTestItem(this,
-                    ENTERPRISE_PRIVACY_COMP_FAILED_PASSWORD_WIPE,
-                    R.string.enterprise_privacy_comp_failed_password_wipe,
-                    R.string.enterprise_privacy_comp_failed_password_wipe_info,
-                    new ButtonInfo[]{
-                            new ButtonInfo(R.string.enterprise_privacy_start,
-                                    buildCommandIntent(
-                                            CommandReceiverActivity.
-                                                    COMMAND_CREATE_MANAGED_PROFILE)),
-                            new ButtonInfo(R.string.enterprise_privacy_open_settings,
-                                    new Intent(Settings.ACTION_ENTERPRISE_PRIVACY_SETTINGS)),
-                            new ButtonInfo(R.string.enterprise_privacy_set_limit, new Intent(
-                                    CompHelperActivity.ACTION_SET_MAXIMUM_PASSWORD_ATTEMPTS)),
-                            new ButtonInfo(R.string.enterprise_privacy_finish,
-                                    buildCommandIntent(
-                                            CommandReceiverActivity.
-                                                    COMMAND_REMOVE_MANAGED_PROFILE))}));
-        }
         adapter.add(createInteractiveTestItem(this, ENTERPRISE_PRIVACY_QUICK_SETTINGS,
                 R.string.enterprise_privacy_quick_settings,
                 R.string.enterprise_privacy_quick_settings_info,
@@ -354,6 +290,5 @@
                 .putExtra(PolicyTransparencyTestListActivity.EXTRA_MODE,
                         PolicyTransparencyTestListActivity.MODE_DEVICE_OWNER);
         startActivity(intent);
-        startActivity(buildCommandIntent(CommandReceiverActivity.COMMAND_REMOVE_MANAGED_PROFILE));
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestListActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestListActivity.java
index d5fbb01..0022c90 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestListActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/PolicyTransparencyTestListActivity.java
@@ -45,7 +45,6 @@
 
     public static final int MODE_DEVICE_OWNER = 1;
     public static final int MODE_MANAGED_PROFILE = 2;
-    public static final int MODE_COMP = 3;
     public static final int MODE_MANAGED_USER = 4;
 
     /**
@@ -124,7 +123,7 @@
                     + EXTRA_MODE);
         }
         mMode = getIntent().getIntExtra(EXTRA_MODE, MODE_DEVICE_OWNER);
-        if (mMode != MODE_DEVICE_OWNER && mMode != MODE_MANAGED_PROFILE && mMode != MODE_COMP
+        if (mMode != MODE_DEVICE_OWNER && mMode != MODE_MANAGED_PROFILE
                 && mMode != MODE_MANAGED_USER) {
             throw new RuntimeException("Unknown mode " + mMode);
         }
@@ -153,10 +152,6 @@
             intent.putExtra(PolicyTransparencyTestActivity.EXTRA_TEST_ID, testId);
             adapter.add(TestListItem.newTest(title, testId, intent, null));
         }
-        if (mMode == MODE_COMP) {
-            // no other policies for COMP
-            return;
-        }
         for (Pair<Intent, Integer> policy : POLICIES) {
             final Intent intent = policy.first;
             String test = intent.getStringExtra(PolicyTransparencyTestActivity.EXTRA_TEST);
@@ -182,8 +177,6 @@
             return "DO_" + title;
         } else if (mMode == MODE_MANAGED_PROFILE) {
             return "MP_" + title;
-        } else if (mMode == MODE_COMP){
-            return "COMP_" + title;
         } else if (mMode == MODE_MANAGED_USER) {
             return "MU_" + title;
         }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
index 53b1bf3..5328109 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/managedprovisioning/UserRestrictions.java
@@ -207,8 +207,6 @@
                 }
             }
             return result;
-        } else if (mode == PolicyTransparencyTestListActivity.MODE_COMP) {
-            return Arrays.asList(UserManager.DISALLOW_REMOVE_MANAGED_PROFILE);
         } else if (mode == PolicyTransparencyTestListActivity.MODE_MANAGED_PROFILE) {
             return ALSO_VALID_FOR_MANAGED_PROFILE_POLICY_TRANSPARENCY;
         } else if (mode == PolicyTransparencyTestListActivity.MODE_MANAGED_USER) {
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
index 8760ef8..18f82b7 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/notifications/BubblesVerifierActivity.java
@@ -16,6 +16,7 @@
 package com.android.cts.verifier.notifications;
 
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
+import static android.content.Intent.ACTION_VIEW;
 import static android.view.View.INVISIBLE;
 import static android.view.View.VISIBLE;
 
@@ -25,12 +26,18 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.Person;
+import android.app.RemoteInput;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.ShortcutInfo;
+import android.content.pm.ShortcutManager;
 import android.graphics.Color;
 import android.graphics.drawable.Icon;
 import android.os.Bundle;
+import android.os.SystemClock;
 import android.provider.Settings;
+import android.util.ArraySet;
 import android.view.ViewGroup;
 import android.widget.Button;
 import android.widget.TextView;
@@ -39,6 +46,8 @@
 import com.android.cts.verifier.R;
 
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Set;
 
 /**
  * Bubble notification tests: This test checks the behaviour of notifications that have a bubble.
@@ -47,8 +56,12 @@
 
     private static final String CHANNEL_ID = "BubblesVerifierChannel";
     private static final int NOTIFICATION_ID = 1;
+    private static final String SHORTCUT_ID = "BubblesVerifierShortcut";
+    private static final String SHORTCUT_CATEGORY =
+            "com.android.cts.verifier.notifications.SHORTCUT_CATEGORY";
 
     private NotificationManager mNotificationManager;
+    private ShortcutManager mShortcutManager;
 
     private TextView mTestTitle;
     private TextView mTestDescription;
@@ -133,6 +146,9 @@
         mNotificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
         mNotificationManager.createNotificationChannel(
                 new NotificationChannel(CHANNEL_ID, CHANNEL_ID, IMPORTANCE_DEFAULT));
+
+        createShortcut();
+
         runNextTestOrShowSummary();
     }
 
@@ -189,6 +205,32 @@
         }
     }
 
+    /** Creates a shortcut to use for the notifications to be considered conversations */
+    private void createShortcut() {
+        Context context = getApplicationContext();
+        Intent intent = new Intent(context, BubbleActivity.class);
+        intent.setAction(ACTION_VIEW);
+        Person person = new Person.Builder()
+                .setBot(false)
+                .setIcon(Icon.createWithResource(context, R.drawable.ic_android))
+                .setName("BubbleBot")
+                .setImportant(true)
+                .build();
+        Set<String> categorySet = new ArraySet<>();
+        categorySet.add(SHORTCUT_CATEGORY);
+        ShortcutInfo shortcut = new ShortcutInfo.Builder(context, SHORTCUT_ID)
+                .setShortLabel("BubbleChat")
+                .setLongLived(true)
+                .setPerson(person)
+                .setCategories(categorySet)
+                .setIcon(Icon.createWithResource(context, R.drawable.ic_android))
+                .setIntent(intent)
+                .build();
+        ShortcutManager shortcutManager = (ShortcutManager) getSystemService(
+                Context.SHORTCUT_SERVICE);
+        shortcutManager.addDynamicShortcuts(Arrays.asList(shortcut));
+    }
+
     private class EnableBubbleTest extends BubblesTestStep {
 
         @Override
@@ -241,8 +283,8 @@
         @Override
         public void performTestAction() {
             Notification.Builder builder =
-                    getBasicNotifBuilder("Bubble notification", "1: SendBubbleTest");
-            builder.setBubbleMetadata(getBasicBubbleBuilder().build());
+                    getConversationNotif("Bubble notification", "1: SendBubbleTest");
+            builder.setBubbleMetadata(getIntentBubble().build());
 
             mNotificationManager.notify(NOTIFICATION_ID, builder.build());
         }
@@ -268,9 +310,9 @@
         @Override
         public void performTestAction() {
             Notification.Builder builder =
-                    getBasicNotifBuilder("Bubble notification", "2: SuppressNotifTest");
+                    getConversationNotif("Bubble notification", "2: SuppressNotifTest");
 
-            Notification.BubbleMetadata metadata = getBasicBubbleBuilder()
+            Notification.BubbleMetadata metadata = getIntentBubble()
                     .setSuppressNotification(true)
                     .build();
             builder.setBubbleMetadata(metadata);
@@ -299,9 +341,9 @@
         @Override
         public void performTestAction() {
             Notification.Builder builder =
-                    getBasicNotifBuilder("Bubble notification", "3: AddNotifTest");
+                    getConversationNotif("Bubble notification", "3: AddNotifTest");
 
-            Notification.BubbleMetadata metadata = getBasicBubbleBuilder()
+            Notification.BubbleMetadata metadata = getIntentBubble()
                     .setSuppressNotification(false)
                     .build();
             builder.setBubbleMetadata(metadata);
@@ -330,7 +372,7 @@
         @Override
         public void performTestAction() {
             Notification.Builder builder =
-                    getBasicNotifBuilder("Bubble notification", "4: RemoveMetadataTest");
+                    getConversationNotif("Bubble notification", "4: RemoveMetadataTest");
             mNotificationManager.notify(NOTIFICATION_ID, builder.build());
         }
     }
@@ -355,9 +397,9 @@
         @Override
         public void performTestAction() {
             Notification.Builder builder =
-                    getBasicNotifBuilder("Bubble notification", "5: AddMetadataTest");
+                    getConversationNotif("Bubble notification", "5: AddMetadataTest");
 
-            Notification.BubbleMetadata metadata = getBasicBubbleBuilder().build();
+            Notification.BubbleMetadata metadata = getIntentBubble().build();
             builder.setBubbleMetadata(metadata);
 
             mNotificationManager.notify(NOTIFICATION_ID, builder.build());
@@ -395,9 +437,9 @@
         @Override
         public void performTestAction() {
             Notification.Builder builder =
-                    getBasicNotifBuilder("Bubble notification", "7: DismissBubbleTest");
+                    getConversationNotif("Bubble notification", "7: DismissBubbleTest");
 
-            Notification.BubbleMetadata metadata = getBasicBubbleBuilder().build();
+            Notification.BubbleMetadata metadata = getIntentBubble().build();
             builder.setBubbleMetadata(metadata);
 
             mNotificationManager.notify(NOTIFICATION_ID, builder.build());
@@ -423,10 +465,10 @@
         @Override
         public void performTestAction() {
             Notification.Builder builder =
-                    getBasicNotifBuilder("Bubble notification",
+                    getConversationNotif("Bubble notification",
                             "8: DismissNotificationTest: Dismiss me!!");
 
-            Notification.BubbleMetadata metadata = getBasicBubbleBuilder().build();
+            Notification.BubbleMetadata metadata = getIntentBubble().build();
             builder.setBubbleMetadata(metadata);
 
             mNotificationManager.notify(NOTIFICATION_ID, builder.build());
@@ -452,10 +494,10 @@
         @Override
         public void performTestAction() {
             Notification.Builder builder =
-                    getBasicNotifBuilder("Bubble notification", "9: Auto expanded bubble");
+                    getConversationNotif("Bubble notification", "9: Auto expanded bubble");
 
             Notification.BubbleMetadata metadata =
-                    getBasicBubbleBuilder().setAutoExpandBubble(true).build();
+                    getIntentBubble().setAutoExpandBubble(true).build();
             builder.setBubbleMetadata(metadata);
 
             mNotificationManager.notify(NOTIFICATION_ID, builder.build());
@@ -481,37 +523,56 @@
         @Override
         public void performTestAction() {
             Notification.Builder builder =
-                    getBasicNotifBuilder("Bubble notification", "Low ram test");
-            builder.setBubbleMetadata(getBasicBubbleBuilder().build());
+                    getConversationNotif("Bubble notification", "Low ram test");
+            builder.setBubbleMetadata(getIntentBubble().build());
 
             mNotificationManager.notify(NOTIFICATION_ID, builder.build());
         }
     }
 
     /** Creates a minimally filled out {@link android.app.Notification.BubbleMetadata.Builder} */
-    private Notification.BubbleMetadata.Builder getBasicBubbleBuilder() {
+    private Notification.BubbleMetadata.Builder getIntentBubble() {
         Context context = getApplicationContext();
         Intent intent = new Intent(context, BubbleActivity.class);
         final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
 
-        return new Notification.BubbleMetadata.Builder()
-                .setIcon(Icon.createWithResource(getApplicationContext(),
-                        R.drawable.ic_android))
-                .setIntent(pendingIntent);
+        return new Notification.BubbleMetadata.Builder(pendingIntent,
+                Icon.createWithResource(getApplicationContext(),
+                        R.drawable.ic_android));
     }
 
-    /** Creates a minimally filled out {@link Notification.Builder} with provided text. */
-    private Notification.Builder getBasicNotifBuilder(@NonNull CharSequence title,
+    /** Creates a {@link Notification.Builder} that is a conversation. */
+    private Notification.Builder getConversationNotif(@NonNull CharSequence title,
             @NonNull CharSequence content) {
         Context context = getApplicationContext();
         Intent intent = new Intent(context, BubbleActivity.class);
         final PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
 
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
+        PendingIntent inputIntent = PendingIntent.getActivity(getApplicationContext(), 0,
+                new Intent(), 0);
+        Icon icon = Icon.createWithResource(getApplicationContext(), R.drawable.ic_android);
+        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
+                inputIntent).addRemoteInput(remoteInput)
+                .build();
+
         return new Notification.Builder(context, CHANNEL_ID)
                 .setSmallIcon(R.drawable.ic_android)
                 .setContentTitle(title)
                 .setContentText(content)
                 .setColor(Color.GREEN)
-                .setContentIntent(pendingIntent);
+                .setShortcutId(SHORTCUT_ID)
+                .setContentIntent(pendingIntent)
+                .setActions(replyAction)
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person)
+                );
     }
 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/CAInstallNotificationVerifierActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/CAInstallNotificationVerifierActivity.java
index ea7b991..ee992e9 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/CAInstallNotificationVerifierActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/CAInstallNotificationVerifierActivity.java
@@ -20,7 +20,7 @@
 import android.content.ActivityNotFoundException;
 import android.content.Context;
 import android.content.Intent;
-import android.security.KeyChain;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.View;
 import android.widget.Button;
@@ -55,9 +55,9 @@
     @Override
     protected void setupTests(final ArrayTestListAdapter testAdapter) {
         testAdapter.add(new InstallCertItem(this,
-                R.string.cacert_install_cert,
+                R.string.cacert_install_cert_title,
                 "install_cert",
-                KeyChain.createInstallIntent()));
+                new Intent(Settings.ACTION_SECURITY_SETTINGS)));
         testAdapter.add(new DialogTestListItem(this,
                 R.string.cacert_check_cert_in_settings,
                 "check_cert",
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/CANotifyOnBootActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/CANotifyOnBootActivity.java
index a4f63ad..9c799ab 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/CANotifyOnBootActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/CANotifyOnBootActivity.java
@@ -4,6 +4,7 @@
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
 import android.os.Bundle;
+import android.provider.Settings;
 import android.util.Log;
 import android.view.View;
 import android.view.View.OnClickListener;
@@ -81,7 +82,7 @@
             }
 
             try {
-                startActivity(new Intent("android.credentials.INSTALL"));
+                startActivity(new Intent(Settings.ACTION_SECURITY_SETTINGS));
             } catch (ActivityNotFoundException e) {
                 // do nothing
             }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/IncomingCallTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/IncomingCallTestActivity.java
index d80924c..f17c9ee 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/IncomingCallTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/IncomingCallTestActivity.java
@@ -24,6 +24,7 @@
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.util.Log;
 import android.view.View;
 import android.widget.Button;
 import android.widget.ImageView;
@@ -38,6 +39,7 @@
  * able to be answered.
  */
 public class IncomingCallTestActivity extends PassFailButtons.Activity {
+    private static final String TAG = "TelecomIncomingCall";
 
     private Button mRegisterAndEnablePhoneAccount;
     private Button mConfirmPhoneAccountEnabled;
@@ -77,6 +79,7 @@
 
                 mConfirmPhoneAccountEnabled.setEnabled(true);
             } else {
+                Log.w(TAG, "Step 1 fail - couldn't register phone account");
                 mStep1Status.setImageResource(R.drawable.fs_error);
             }
         });
@@ -91,9 +94,11 @@
             PhoneAccount account = PhoneAccountUtils.getPhoneAccount(this);
             if (account != null && account.isEnabled()) {
                 getPassButton().setEnabled(true);
+                Log.i(TAG, "Step 1 pass - account is enabled.");
                 mStep1Status.setImageResource(R.drawable.fs_good);
                 mConfirmPhoneAccountEnabled.setEnabled(false);
             } else {
+                Log.w(TAG, "Step 1 fail - account is not enabled.");
                 mStep1Status.setImageResource(R.drawable.fs_error);
             }
         });
@@ -112,9 +117,11 @@
             TelecomManager telecomManager =
                     (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
             if (telecomManager == null) {
+                Log.w(TAG, "Step 2 fail - telecom service is null");
                 mStep2Status.setImageResource(R.drawable.fs_error);
                 return;
             }
+            Log.i(TAG, "Step 2 pass - adding new incoming call");
             telecomManager.addNewIncomingCall(PhoneAccountUtils.TEST_PHONE_ACCOUNT_HANDLE, extras);
             mStep2Status.setImageResource(R.drawable.fs_good);
         });
@@ -126,8 +133,10 @@
         }
         mConfirmIncomingCallAnswered.setOnClickListener(v -> {
             if (confirmIncomingCall()) {
+                Log.i(TAG, "Step 3 pass - new incoming call answered");
                 mStep3Status.setImageResource(R.drawable.fs_good);
             } else {
+                Log.w(TAG, "Step 3 fail - failed to answer new incoming call");
                 mStep3Status.setImageResource(R.drawable.fs_error);
             }
             PhoneAccountUtils.unRegisterTestPhoneAccount(this);
@@ -148,13 +157,16 @@
         List<CtsConnection> ongoingConnections =
                 CtsConnectionService.getConnectionService().getConnections();
         if (ongoingConnections == null || ongoingConnections.size() != 1) {
+            Log.w(TAG, "Step 3 fail - no ongoing call found");
             return false;
         }
         CtsConnection incomingConnection = ongoingConnections.get(0);
         if (!incomingConnection.isIncomingCall()) {
+            Log.w(TAG, "Step 3 fail - ongoing call isn't incoming");
             return false;
         }
         if (incomingConnection.getState() != Connection.STATE_ACTIVE) {
+            Log.w(TAG, "Step 3 fail - ongoing call is not active");
             return false;
         }
         incomingConnection.onDisconnect();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/OutgoingCallTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/OutgoingCallTestActivity.java
index 5f5c605..9fc0381 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/OutgoingCallTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/OutgoingCallTestActivity.java
@@ -22,6 +22,7 @@
 import android.telecom.PhoneAccount;
 import android.telecom.PhoneAccountHandle;
 import android.telecom.TelecomManager;
+import android.util.Log;
 import android.view.View;
 import android.widget.Button;
 import android.widget.ImageView;
@@ -36,6 +37,7 @@
  * the CtsConnectionService.
  */
 public class OutgoingCallTestActivity extends PassFailButtons.Activity {
+    private final static String TAG = "TelecomOutgoingCall";
 
     private Button mRegisterAndEnablePhoneAccount;
     private Button mConfirmPhoneAccountEnabled;
@@ -71,9 +73,10 @@
                 // the default.
                 Intent intent = new Intent(TelecomManager.ACTION_CHANGE_PHONE_ACCOUNTS);
                 startActivity(intent);
-
+                Log.i(TAG, "Step 1 - phone account registered");
                 mConfirmPhoneAccountEnabled.setEnabled(true);
             } else {
+                Log.w(TAG, "Step 1 fail - phone account registration failed");
                 mStep1Status.setImageResource(R.drawable.fs_error);
             }
         });
@@ -90,9 +93,11 @@
             if (account != null && account.isEnabled() &&
                     PhoneAccountUtils.TEST_PHONE_ACCOUNT_HANDLE.equals(defaultOutgoingAccount)) {
                 getPassButton().setEnabled(true);
+                Log.i(TAG, "Step 1 pass - phone account registration confirmed");
                 mStep1Status.setImageResource(R.drawable.fs_good);
                 mConfirmPhoneAccountEnabled.setEnabled(false);
             } else {
+                Log.w(TAG, "Step 1 fail - phone account registration failed");
                 mStep1Status.setImageResource(R.drawable.fs_error);
             }
         });
@@ -106,6 +111,7 @@
             Intent intent = new Intent(Intent.ACTION_DIAL);
             intent.setData(TEST_DIAL_NUMBER);
             startActivity(intent);
+            Log.i(TAG, "Step 1 pass - dial intent sent");
             mStep2Status.setImageResource(R.drawable.fs_good);
         });
 
@@ -115,8 +121,10 @@
         }
         mConfirmOutgoingCall.setOnClickListener(v -> {
             if (confirmOutgoingCall()) {
+                Log.i(TAG, "Step 3 pass - call confirmed");
                 mStep3Status.setImageResource(R.drawable.fs_good);
             } else {
+                Log.w(TAG, "Step 3 fail - failed to confirm call");
                 mStep3Status.setImageResource(R.drawable.fs_error);
             }
             PhoneAccountUtils.unRegisterTestPhoneAccount(this);
@@ -137,10 +145,12 @@
         List<CtsConnection> ongoingConnections =
                 CtsConnectionService.getConnectionService().getConnections();
         if (ongoingConnections == null || ongoingConnections.size() != 1) {
+            Log.w(TAG, "Step 3 fail - no ongoing connections");
             return false;
         }
         CtsConnection outgoingConnection = ongoingConnections.get(0);
         if (outgoingConnection.isIncomingCall()) {
+            Log.w(TAG, "Step 3 fail - call is not outgoing");
             return false;
         }
         outgoingConnection.onDisconnect();
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/SelfManagedIncomingCallTestActivity.java b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/SelfManagedIncomingCallTestActivity.java
index a8c2caa..e273616 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/telecom/SelfManagedIncomingCallTestActivity.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/telecom/SelfManagedIncomingCallTestActivity.java
@@ -23,6 +23,7 @@
 import android.telecom.Connection;
 import android.telecom.PhoneAccount;
 import android.telecom.TelecomManager;
+import android.util.Log;
 import android.view.View;
 import android.widget.Button;
 import android.widget.ImageView;
@@ -37,6 +38,7 @@
  * call or when there is an ongoing self-managed call in another app.
  */
 public class SelfManagedIncomingCallTestActivity extends PassFailButtons.Activity {
+    private static final String TAG = "SelfManagedIncomingCall";
     private Uri TEST_DIAL_NUMBER_1 = Uri.fromParts("tel", "6505551212", null);
     private Uri TEST_DIAL_NUMBER_2 = Uri.fromParts("tel", "4085551212", null);
 
@@ -51,6 +53,7 @@
         @Override
         void onShowIncomingCallUi(CtsConnection connection) {
             // The system should have displayed the incoming call UI; this is a fail.
+            Log.w(TAG, "Step 3 fail - got unexpected onShowIncomingCallUi");
             mStep3Status.setImageResource(R.drawable.fs_error);
             getPassButton().setEnabled(false);
         };
@@ -58,6 +61,7 @@
         @Override
         void onAnswer(CtsConnection connection, int videoState) {
             // Call was answered, so disconnect it now.
+            Log.i(TAG, "Step 3 - Incoming call answered.");
             connection.onDisconnect();
         };
 
@@ -71,10 +75,11 @@
                     (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
             if (telecomManager == null || !telecomManager.isInManagedCall()) {
                 // Should still be in a managed call; only one would need to be disconnected.
+                Log.w(TAG, "Step 3 fail - not in managed call as expected.");
                 mStep3Status.setImageResource(R.drawable.fs_error);
                 return;
             }
-
+            Log.i(TAG, "Step 3 pass - call disconnected");
             mStep3Status.setImageResource(R.drawable.fs_good);
             getPassButton().setEnabled(true);
         }
@@ -100,8 +105,10 @@
                     account.hasCapabilities(PhoneAccount.CAPABILITY_SELF_MANAGED)) {
                 mRegisterPhoneAccount.setEnabled(false);
                 mVerifyCall.setEnabled(true);
+                Log.i(TAG, "Step 1 pass - account registered");
                 mStep1Status.setImageResource(R.drawable.fs_good);
             } else {
+                Log.w(TAG, "Step 1 fail - account not registered");
                 mStep1Status.setImageResource(R.drawable.fs_error);
             }
         });
@@ -112,10 +119,12 @@
             TelecomManager telecomManager =
                     (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
             if (telecomManager == null || !telecomManager.isInManagedCall()) {
+                Log.w(TAG, "Step 2 fail - expected to be in a managed call");
                 mStep2Status.setImageResource(R.drawable.fs_error);
                 mPlaceCall.setEnabled(false);
             } else {
                 mStep2Status.setImageResource(R.drawable.fs_good);
+                Log.i(TAG, "Step 2 pass - device in a managed call");
                 mVerifyCall.setEnabled(false);
                 mPlaceCall.setEnabled(true);
             }
@@ -135,6 +144,7 @@
                         TelecomManager telecomManager =
                                 (TelecomManager) getSystemService(Context.TELECOM_SERVICE);
                         if (telecomManager == null) {
+                            Log.w(TAG, "Step 2 fail - telecom manager null");
                             mStep2Status.setImageResource(R.drawable.fs_error);
                             return new Throwable("Could not get telecom service.");
                         }
@@ -144,12 +154,14 @@
                         CtsConnectionService ctsConnectionService =
                                 CtsConnectionService.waitForAndGetConnectionService();
                         if (ctsConnectionService == null) {
+                            Log.w(TAG, "Step 2 fail - ctsConnectionService null");
                             mStep2Status.setImageResource(R.drawable.fs_error);
                             return new Throwable("Could not get connection service.");
                         }
 
                         CtsConnection connection = ctsConnectionService.waitForAndGetConnection();
                         if (connection == null) {
+                            Log.w(TAG, "Step 2 fail - could not get connection");
                             mStep2Status.setImageResource(R.drawable.fs_error);
                             return new Throwable("Could not get connection.");
                         }
@@ -167,6 +179,7 @@
                         int capabilities = connection.getConnectionCapabilities();
                         capabilities &= ~Connection.CAPABILITY_HOLD;
                         connection.setConnectionCapabilities(capabilities);
+                        Log.w(TAG, "Step 2 - connection added");
                         return null;
                     } catch (Throwable t) {
                         return t;
@@ -179,6 +192,7 @@
                         mStep2Status.setImageResource(R.drawable.fs_good);
                         mPlaceCall.setEnabled(false);
                     } else {
+                        Log.i(TAG, "Step 2 pass - connection added");
                         mStep2Status.setImageResource(R.drawable.fs_error);
                     }
                 }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
index 68d3bb5..3d5e07a 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifi/testcase/NetworkSuggestionTestCase.java
@@ -101,7 +101,7 @@
         mSetBssid = setBssid;
         mSetRequiresAppInteraction = setRequiresAppInteraction;
         mSimulateConnectionFailure = simulateConnectionFailure;
-        mSetMeteredPostConnection = true;
+        mSetMeteredPostConnection = setMeteredPostConnection;
     }
 
     // Create a network specifier based on the test type.
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathOutOfBandTestCase.java b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathOutOfBandTestCase.java
index 1c35d2a..7dc3f05 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathOutOfBandTestCase.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/wifiaware/testcase/DataPathOutOfBandTestCase.java
@@ -288,7 +288,11 @@
         mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_network_requested));
         if (DBG) Log.d(TAG, "executeTestResponder: requested network");
         Pair<Network, NetworkCapabilities> info = networkCb.waitForNetworkCapabilities();
+
+        // 7. Sleep for 5 second for Initiator to get NetworkCapabilities.
+        Thread.sleep(5000);
         cm.unregisterNetworkCallback(networkCb);
+
         if (info == null) {
             setFailureReason(mContext.getString(R.string.aware_status_network_failed));
             Log.e(TAG, "executeTestResponder: network request rejected - ON_UNAVAILABLE");
@@ -429,7 +433,11 @@
         mListener.onTestMsgReceived(mContext.getString(R.string.aware_status_network_requested));
         if (DBG) Log.d(TAG, "executeTestInitiator: requested network");
         Pair<Network, NetworkCapabilities> info = networkCb.waitForNetworkCapabilities();
+
+        // 9. Sleep for 5 second for Responder to get NetworkCapabilities.
+        Thread.sleep(5000);
         cm.unregisterNetworkCallback(networkCb);
+
         if (info == null) {
             setFailureReason(mContext.getString(R.string.aware_status_network_failed));
             Log.e(TAG, "executeTestInitiator: network request rejected - ON_UNAVAILABLE");
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java
index 65b8d75..df3109f 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/PackageDeviceInfo.java
@@ -15,6 +15,10 @@
  */
 package com.android.compatibility.common.deviceinfo;
 
+import android.annotation.TargetApi;
+import android.app.admin.DevicePolicyManager;
+import android.content.ComponentName;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
@@ -26,11 +30,15 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * PackageDeviceInfo collector.
  */
+@TargetApi(Build.VERSION_CODES.N)
 public class PackageDeviceInfo extends DeviceInfo {
 
     private static final String PACKAGE = "package";
@@ -55,12 +63,28 @@
 
     private static final String SHA256_CERT = "sha256_cert";
 
+    private static final String CONFIG_NOTIFICATION_ACCESS = "config_defaultListenerAccessPackages";
+    private static final String HAS_DEFAULT_NOTIFICATION_ACCESS = "has_default_notification_access";
+
+    private static final String UID = "uid";
+    private static final String IS_ACTIVE_ADMIN = "is_active_admin";
+
+    private static final String CONFIG_ACCESSIBILITY_SERVICE = "config_defaultAccessibilityService";
+    private static final String DEFAULT_ACCESSIBILITY_SERVICE = "is_default_accessibility_service";
+
+
     @Override
     protected void collectDeviceInfo(DeviceInfoStore store) throws Exception {
         final PackageManager pm = getContext().getPackageManager();
 
         final List<PackageInfo> allPackages =
                 pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
+        final Set<String> defaultNotificationListeners =
+                getColonSeparatedPackageList(CONFIG_NOTIFICATION_ACCESS);
+
+        final Set<String> deviceAdminPackages = getActiveDeviceAdminPackages();
+
+        final ComponentName defaultAccessibilityComponent = getDefaultAccessibilityComponent();
 
         store.startArray(PACKAGE);
         for (PackageInfo pkg : allPackages) {
@@ -68,43 +92,69 @@
             store.addResult(NAME, pkg.packageName);
             store.addResult(VERSION_NAME, pkg.versionName);
 
-            store.startArray(REQUESTED_PERMISSIONS);
-            if (pkg.requestedPermissions != null && pkg.requestedPermissions.length > 0) {
-                for (String permission : pkg.requestedPermissions) {
-                    try {
-                        final PermissionInfo pi = pm.getPermissionInfo(permission, 0);
+            collectPermissions(store, pm, pkg);
+            collectionApplicationInfo(store, pm, pkg);
 
-                        store.startGroup();
-                        store.addResult(PERMISSION_NAME, permission);
-                        writePermissionsDetails(pi, store);
-                        store.endGroup();
-                    } catch (PackageManager.NameNotFoundException e) {
-                        // ignore unrecognized permission and continue
-                    }
-                }
-            }
-            store.endArray();
+            store.addResult(HAS_DEFAULT_NOTIFICATION_ACCESS,
+                    defaultNotificationListeners.contains(pkg.packageName));
 
-            final ApplicationInfo appInfo = pkg.applicationInfo;
-            if (appInfo != null) {
-                String dir = appInfo.sourceDir;
-                store.addResult(SYSTEM_PRIV, dir != null && dir.startsWith(PRIV_APP_DIR));
+            store.addResult(IS_ACTIVE_ADMIN, deviceAdminPackages.contains(pkg.packageName));
 
-                store.addResult(MIN_SDK, appInfo.minSdkVersion);
-                store.addResult(TARGET_SDK, appInfo.targetSdkVersion);
+            final boolean isDefaultAccessibilityComponent = pkg.packageName.equals(
+                    defaultAccessibilityComponent.getPackageName()
+            );
+            store.addResult(DEFAULT_ACCESSIBILITY_SERVICE, isDefaultAccessibilityComponent);
 
-                store.addResult(HAS_SYSTEM_UID, appInfo.uid < Process.FIRST_APPLICATION_UID);
-
-                final boolean canInstall = sharesUidWithInstallerPackage(pm, appInfo.uid);
-                store.addResult(SHARES_INSTALL_PERMISSION, canInstall);
-            }
             String sha256_cert = PackageUtil.computePackageSignatureDigest(pkg.packageName);
             store.addResult(SHA256_CERT, sha256_cert);
+
             store.endGroup();
         }
         store.endArray(); // "package"
     }
 
+    private static void collectPermissions(DeviceInfoStore store,
+                                           PackageManager pm,
+                                           PackageInfo pkg) throws IOException
+    {
+        store.startArray(REQUESTED_PERMISSIONS);
+        if (pkg.requestedPermissions != null && pkg.requestedPermissions.length > 0) {
+            for (String permission : pkg.requestedPermissions) {
+                try {
+                    final PermissionInfo pi = pm.getPermissionInfo(permission, 0);
+
+                    store.startGroup();
+                    store.addResult(PERMISSION_NAME, permission);
+                    writePermissionsDetails(pi, store);
+                    store.endGroup();
+                } catch (PackageManager.NameNotFoundException e) {
+                    // ignore unrecognized permission and continue
+                }
+            }
+        }
+        store.endArray();
+    }
+
+    private static void collectionApplicationInfo(DeviceInfoStore store,
+                                                  PackageManager pm,
+                                                  PackageInfo pkg) throws IOException {
+        final ApplicationInfo appInfo = pkg.applicationInfo;
+        if (appInfo != null) {
+            String dir = appInfo.sourceDir;
+            store.addResult(SYSTEM_PRIV, dir != null && dir.startsWith(PRIV_APP_DIR));
+
+            store.addResult(MIN_SDK, appInfo.minSdkVersion);
+            store.addResult(TARGET_SDK, appInfo.targetSdkVersion);
+
+            store.addResult(HAS_SYSTEM_UID, appInfo.uid < Process.FIRST_APPLICATION_UID);
+
+            final boolean canInstall = sharesUidWithInstallerPackage(pm, appInfo.uid);
+            store.addResult(SHARES_INSTALL_PERMISSION, canInstall);
+
+            store.addResult(UID, appInfo.uid);
+        }
+    }
+
     private static boolean sharesUidWithInstallerPackage(PackageManager pm, int uid) {
         final String[] sharesUidWith = pm.getPackagesForUid(uid);
 
@@ -156,5 +206,51 @@
                     pi.protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE);
         }
     }
+
+    private Set<String> getActiveDeviceAdminPackages() {
+        final DevicePolicyManager dpm = (DevicePolicyManager)
+                getContext().getSystemService(Context.DEVICE_POLICY_SERVICE);
+
+        final List<ComponentName> components = dpm.getActiveAdmins();
+        if (components == null) {
+            return new HashSet<>(0);
+        }
+
+        final HashSet<String> packages = new HashSet<>(components.size());
+        for (ComponentName component : components) {
+            packages.add(component.getPackageName());
+        }
+
+        return packages;
+    }
+
+    private ComponentName getDefaultAccessibilityComponent() {
+        final String defaultAccessibilityServiceComponent =
+                getRawDeviceConfig(CONFIG_ACCESSIBILITY_SERVICE);
+        return ComponentName.unflattenFromString(defaultAccessibilityServiceComponent);
+    }
+
+    /**
+     * Parses and returns a set of package ids from a configuration value
+     * e.g config_defaultListenerAccessPackages
+     **/
+    private Set<String> getColonSeparatedPackageList(String name) {
+        String raw = getRawDeviceConfig(name);
+        String[] packages = raw.split(":");
+        return new HashSet<>(Arrays.asList(packages));
+    }
+
+    /** Returns the value of a device configuration setting available in android.internal.R.* **/
+    private String getRawDeviceConfig(String name) {
+        return getContext()
+                .getResources()
+                .getString(getDeviceResourcesIdentifier(name, "string"));
+    }
+
+    private int getDeviceResourcesIdentifier(String name, String type) {
+        return getContext()
+                .getResources()
+                .getIdentifier(name, type, "android");
+    }
 }
 
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/RequiredFeatureRule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/RequiredFeatureRule.java
index 44571d1..e80d69f 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/RequiredFeatureRule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/RequiredFeatureRule.java
@@ -16,16 +16,22 @@
 
 package com.android.compatibility.common.util;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.util.Log;
 
 import androidx.test.InstrumentationRegistry;
 
+import org.junit.AssumptionViolatedException;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
 /**
  * Custom JUnit4 rule that does not run a test case if the device does not have a given feature.
+ *
+ * <p>The tests are skipped by throwing a {@link AssumptionViolatedException}.  CTS test runners
+ * will report this as a {@code ASSUMPTION_FAILED}.
  */
 public class RequiredFeatureRule implements TestRule {
     private static final String TAG = "RequiredFeatureRule";
@@ -48,6 +54,8 @@
                     Log.d(TAG, "skipping "
                             + description.getClassName() + "#" + description.getMethodName()
                             + " because device does not have feature '" + mFeature + "'");
+                    assumeTrue("Device does not have feature '" + mFeature + "'",
+                            mHasFeature);
                     return;
                 }
                 base.evaluate();
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/RequiredServiceRule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/RequiredServiceRule.java
index bbfa2db..2055327 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/RequiredServiceRule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/RequiredServiceRule.java
@@ -16,17 +16,26 @@
 
 package com.android.compatibility.common.util;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 
+import org.junit.AssumptionViolatedException;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
 
 /**
  * Custom JUnit4 rule that does not run a test case if the device does not have a given service.
+ *
+ * <p>The tests are skipped by throwing a {@link AssumptionViolatedException}.  CTS test runners
+ * will report this as a {@code ASSUMPTION_FAILED}.
+ *
+ * <p><b>NOTE:</b> it must be used as {@code Rule}, not {@code ClassRule}.
+ *
  */
 public class RequiredServiceRule implements TestRule {
     private static final String TAG = "RequiredServiceRule";
@@ -52,6 +61,8 @@
                     Log.d(TAG, "skipping "
                             + description.getClassName() + "#" + description.getMethodName()
                             + " because device does not have service '" + mService + "'");
+                    assumeTrue("Device does not have service '" + mService + "'",
+                            mHasService);
                     return;
                 }
                 base.evaluate();
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/RequiredSystemResourceRule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/RequiredSystemResourceRule.java
index ead59943..0ddefa1 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/RequiredSystemResourceRule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/RequiredSystemResourceRule.java
@@ -16,6 +16,8 @@
 
 package com.android.compatibility.common.util;
 
+import static org.junit.Assume.assumeTrue;
+
 import android.content.res.Resources;
 import android.text.TextUtils;
 import android.util.Log;
@@ -23,6 +25,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import org.junit.AssumptionViolatedException;
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
@@ -30,6 +33,9 @@
 /**
  * Custom JUnit4 rule that does not run a test case if the device does not define the given system
  * resource.
+ *
+ * <p>The tests are skipped by throwing a {@link AssumptionViolatedException}.  CTS test runners
+ * will report this as a {@code ASSUMPTION_FAILED}.
  */
 public class RequiredSystemResourceRule implements TestRule {
 
@@ -59,6 +65,8 @@
                     Log.d(TAG, "skipping "
                             + description.getClassName() + "#" + description.getMethodName()
                             + " because device does not have system resource '" + mName + "'");
+                    assumeTrue("Device does not have system resource '" + mName + "'",
+                            mHasResource);
                     return;
                 }
                 base.evaluate();
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
index 0fc2560..2935cb4 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/SystemUtil.java
@@ -290,6 +290,47 @@
         }
     }
 
+    /**
+     * Make sure that a {@link Callable} eventually finishes without throwing a {@link
+     * Exception}.
+     *
+     * @param c The {@link Callable} to run.
+     *
+     * @return The return value of {@code c}
+     */
+    public static <T> T getEventually(@NonNull Callable<T> c) throws Exception {
+        return getEventually(c, TIMEOUT_MILLIS);
+    }
+
+    /**
+     * Make sure that a {@link Callable} eventually finishes without throwing a {@link
+     * Exception}.
+     *
+     * @param c The {@link Callable} to run.
+     * @param timeoutMillis The number of milliseconds to wait for r to not throw
+     *
+     * @return The return value of {@code c}
+     */
+    public static <T> T getEventually(@NonNull Callable<T> c, long timeoutMillis) throws Exception {
+        long start = System.currentTimeMillis();
+
+        while (true) {
+            try {
+                return c.call();
+            } catch (Throwable e) {
+                if (System.currentTimeMillis() - start < timeoutMillis) {
+                    try {
+                        Thread.sleep(100);
+                    } catch (InterruptedException ignored) {
+                        throw new RuntimeException(e);
+                    }
+                } else {
+                    throw e;
+                }
+            }
+        }
+    }
+
     public interface ThrowingRunnable extends Runnable {
         void runOrThrow() throws Exception;
 
diff --git a/helpers/default/src/com/android/cts/helpers/aosp/DefaultCtsPrintHelper.java b/helpers/default/src/com/android/cts/helpers/aosp/DefaultCtsPrintHelper.java
index 2940789..e55e1f0 100644
--- a/helpers/default/src/com/android/cts/helpers/aosp/DefaultCtsPrintHelper.java
+++ b/helpers/default/src/com/android/cts/helpers/aosp/DefaultCtsPrintHelper.java
@@ -398,4 +398,30 @@
                 Until.findObject(By.res("com.android.printspooler:id/more_info")),
                 OPERATION_TIMEOUT_MILLIS).click();
     }
+
+    @Override
+    public void closePrinterList() {
+        mDevice.pressBack();
+    }
+
+    @Override
+    public void closeCustomPrintOptions() {
+        mDevice.pressBack();
+    }
+
+    @Override
+    public void closePrintOptions() {
+        mDevice.pressBack();
+    }
+
+    @Override
+    public void cancelPrinting() throws TestHelperException {
+        try {
+            mDevice.wakeUp();
+            mDevice.pressBack();
+            mDevice.waitForIdle();
+        } catch (RemoteException e) {
+            throw new TestHelperException("Failed to cancel printing", e);
+        }
+    }
 }
diff --git a/hostsidetests/accounts/src/android/host/accounts/AccountManagerXUserTest.java b/hostsidetests/accounts/src/android/host/accounts/AccountManagerXUserTest.java
index 3589f8c..03b3e31 100644
--- a/hostsidetests/accounts/src/android/host/accounts/AccountManagerXUserTest.java
+++ b/hostsidetests/accounts/src/android/host/accounts/AccountManagerXUserTest.java
@@ -72,7 +72,7 @@
     public void setUp() throws Exception {
         super.setUp();
 
-        assumeTrue(mSupportsMultiUser);
+        assumeTrue(mSupportsMultiUser && mSupportsManagedUsers);
 
         mOldVerifierValue =
                 getDevice().executeShellCommand("settings get global package_verifier_enable");
diff --git a/hostsidetests/accounts/src/android/host/accounts/BaseMultiUserTest.java b/hostsidetests/accounts/src/android/host/accounts/BaseMultiUserTest.java
index fa343b9..db8a703 100644
--- a/hostsidetests/accounts/src/android/host/accounts/BaseMultiUserTest.java
+++ b/hostsidetests/accounts/src/android/host/accounts/BaseMultiUserTest.java
@@ -38,9 +38,11 @@
      * https://source.android.com/compatibility/android-cdd#2_5_automotive_requirements
      */
     private static final String FEATURE_AUTOMOTIVE = "feature:android.hardware.type.automotive";
+    private static final String FEATURE_MANAGED_USERS = "android.software.managed_users";
 
     /** Whether multi-user is supported. */
     protected boolean mSupportsMultiUser;
+    protected boolean mSupportsManagedUsers;
     protected int mInitialUserId;
     protected int mPrimaryUserId;
 
@@ -52,6 +54,7 @@
     @Before
     public void setUp() throws Exception {
         mSupportsMultiUser = getDevice().getMaxNumberOfUsersSupported() > 1;
+        mSupportsManagedUsers = getDevice().hasFeature(FEATURE_MANAGED_USERS);
 
         mInitialUserId = getDevice().getCurrentUser();
         mPrimaryUserId = getDevice().getPrimaryUserId();
diff --git a/hostsidetests/apex/src/android/apex/cts/ApexTest.java b/hostsidetests/apex/src/android/apex/cts/ApexTest.java
index 9bb09d8..081da9b 100644
--- a/hostsidetests/apex/src/android/apex/cts/ApexTest.java
+++ b/hostsidetests/apex/src/android/apex/cts/ApexTest.java
@@ -37,7 +37,11 @@
     return systemProduct.equals("aosp_arm")
       || systemProduct.equals("aosp_arm64")
       || systemProduct.equals("aosp_x86")
-      || systemProduct.equals("aosp_x86_64");
+      || systemProduct.equals("aosp_x86_64")
+      || systemProduct.equals("aosp_arm_ab") // _ab for Legacy GSI
+      || systemProduct.equals("aosp_arm64_ab")
+      || systemProduct.equals("aosp_x86_ab")
+      || systemProduct.equals("aosp_x86_64_ab");
   }
 
   /**
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
index b4ead04..0161c90 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ExternalStorageHostTest.java
@@ -16,6 +16,7 @@
 
 package android.appsecurity.cts;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -23,11 +24,18 @@
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.ddmlib.Log;
+import com.android.server.role.RoleManagerServiceDumpProto;
+import com.android.server.role.RoleProto;
+import com.android.server.role.RoleUserStateProto;
+import com.android.tradefed.device.CollectingByteOutputReceiver;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
 import com.android.tradefed.util.AbiUtils;
 
+import com.google.protobuf.MessageLite;
+import com.google.protobuf.Parser;
+
 import org.junit.After;
 import org.junit.Assume;
 import org.junit.Before;
@@ -38,7 +46,6 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.List;
 
 /**
@@ -662,6 +669,51 @@
         }
     }
 
+    private <T extends MessageLite> T getDump(Parser<T> parser, String command) throws Exception {
+        final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        getDevice().executeShellCommand(command, receiver);
+        return parser.parseFrom(receiver.getOutput());
+    }
+
+    private List<RoleUserStateProto> getAllUsersRoleStates() throws Exception {
+        final RoleManagerServiceDumpProto dumpProto =
+                getDump(RoleManagerServiceDumpProto.parser(), "dumpsys role --proto");
+        final List<RoleUserStateProto> res = new ArrayList<>();
+        for (RoleUserStateProto userState : dumpProto.getUserStatesList()) {
+            for (int i : mUsers) {
+                if (i == userState.getUserId()) {
+                    res.add(userState);
+                    break;
+                }
+            }
+        }
+        return res;
+    }
+
+    @Test
+    public void testSystemGalleryExists() throws Exception {
+        final List<RoleUserStateProto> usersRoleStates = getAllUsersRoleStates();
+
+        assertEquals("Unexpected number of users returned by dumpsys role",
+                mUsers.length, usersRoleStates.size());
+
+        for (RoleUserStateProto userState : usersRoleStates) {
+            final List<RoleProto> roles = userState.getRolesList();
+            boolean systemGalleryRoleFound = false;
+
+            // Iterate through the roles until we find the System Gallery role
+            for (RoleProto roleProto : roles) {
+                if ("android.app.role.SYSTEM_GALLERY".equals(roleProto.getName())) {
+                    assertEquals(1, roleProto.getHoldersList().size());
+                    systemGalleryRoleFound = true;
+                    break;
+                }
+            }
+            assertTrue("SYSTEM_GALLERY not defined for user " + userState.getUserId(),
+                    systemGalleryRoleFound);
+        }
+    }
+
     private boolean access(String path) throws DeviceNotAvailableException {
         final long nonce = System.nanoTime();
         return getDevice().executeShellCommand("ls -la " + path + " && echo " + nonce)
diff --git a/hostsidetests/appsecurity/test-apps/AccessSerialModern/src/android/os/cts/AccessSerialModernTest.java b/hostsidetests/appsecurity/test-apps/AccessSerialModern/src/android/os/cts/AccessSerialModernTest.java
index 0cb9136..bd17748 100644
--- a/hostsidetests/appsecurity/test-apps/AccessSerialModern/src/android/os/cts/AccessSerialModernTest.java
+++ b/hostsidetests/appsecurity/test-apps/AccessSerialModern/src/android/os/cts/AccessSerialModernTest.java
@@ -35,14 +35,7 @@
     public void testAccessSerialPermissionNeeded() throws Exception {
         // Build.SERIAL should not provide the device serial for modern apps.
         // We don't know the serial but know that it should be the dummy
-        // value returned to unauthorized callers, so make sure that value
-        assertTrue("Build.SERIAL must not work for modern apps",
-                Build.UNKNOWN.equals(Build.SERIAL));
-
-        // Now grant ourselves READ_PHONE_STATE
-        grantReadPhoneStatePermission();
-
-        // Build.SERIAL should not provide the device serial for modern apps.
+        // value returned to unauthorized callers, so make sure that value is returned.
         assertTrue("Build.SERIAL must not work for modern apps",
                 Build.UNKNOWN.equals(Build.SERIAL));
 
@@ -60,10 +53,4 @@
                     + e);
         }
     }
-
-    private void grantReadPhoneStatePermission() {
-        InstrumentationRegistry.getInstrumentation().getUiAutomation().grantRuntimePermission(
-                InstrumentationRegistry.getContext().getPackageName(),
-                android.Manifest.permission.READ_PHONE_STATE);
-    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.bp b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.bp
index 953eddf..e41d1d6 100644
--- a/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.bp
+++ b/hostsidetests/appsecurity/test-apps/MultiUserStorageApp/Android.bp
@@ -16,6 +16,7 @@
     name: "CtsMultiUserStorageApp",
     defaults: ["cts_support_defaults"],
     sdk_version: "current",
+    target_sdk_version: "29",
     static_libs: [
         "androidx.test.rules",
         "CtsExternalStorageTestLib",
diff --git a/hostsidetests/car/TEST_MAPPING b/hostsidetests/car/TEST_MAPPING
new file mode 100644
index 0000000..f456a54
--- /dev/null
+++ b/hostsidetests/car/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "auto-presubmit": [
+    {
+      "name": "CtsCarHostTestCases"
+    }
+  ]
+}
diff --git a/hostsidetests/content/Android.bp b/hostsidetests/content/Android.bp
index cf1206e..0f0e03e 100644
--- a/hostsidetests/content/Android.bp
+++ b/hostsidetests/content/Android.bp
@@ -23,6 +23,7 @@
         "cts-tradefed",
         "tradefed",
         "compatibility-host-util",
+        "device-policy-log-verifier-util",
     ],
     test_suites: [
         "cts",
diff --git a/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java b/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java
index 509bf75..f02e901 100644
--- a/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java
+++ b/hostsidetests/content/src/android/content/cts/ContextCrossProfileHostTest.java
@@ -16,13 +16,19 @@
 
 package android.content.cts;
 
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.assertMetricsNotLogged;
+import static com.android.cts.devicepolicy.metrics.DevicePolicyEventLogVerifier.isStatsdEnabled;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
 
 import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.SystemUserOnly;
+import android.stats.devicepolicy.EventId;
 
 import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.cts.devicepolicy.metrics.DevicePolicyEventWrapper;
 import com.android.tradefed.build.IBuildInfo;
 import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
 import com.android.tradefed.testtype.IBuildReceiver;
@@ -394,6 +400,110 @@
     }
 
     @Test
+    public void testBindServiceAsUser_sameProfileGroup_reportsMetric()
+            throws Exception {
+        if (!isStatsdEnabled(getDevice())) {
+            return;
+        }
+        int userInSameProfileGroup = createProfile(mParentUserId);
+        getDevice().startUser(userInSameProfileGroup, /* waitFlag= */ true);
+        mTestArgs.put("testUser", Integer.toString(userInSameProfileGroup));
+        getDevice().installPackageForUser(
+                mApkFile,
+                /* reinstall= */ true,
+                /* grantPermissions= */ true,
+                userInSameProfileGroup,
+                /* extraArgs= */ "-t",
+                /* extraArgs= */ "--force-queryable");
+
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        File testServiceApkFile = buildHelper.getTestFile(TEST_SERVICE_WITH_PERMISSION_APK);
+        getDevice().installPackageForUser(
+                testServiceApkFile,
+                /* reinstall= */ true,
+                /* grantPermissions= */ true,
+                userInSameProfileGroup,
+                /* extraArgs= */ "-t",
+                /* extraArgs= */ "--force-queryable");
+
+        assertMetricsLogged(getDevice(), () -> {
+            runDeviceTests(
+                    getDevice(),
+                    TEST_WITH_PERMISSION_PKG,
+                    ".ContextCrossProfileDeviceTest",
+                    "testBindServiceAsUser_withInteractAcrossProfilePermission_noAsserts",
+                    mParentUserId,
+                    mTestArgs,
+                    /* timeout= */ 60L,
+                    TimeUnit.SECONDS);
+        }, new DevicePolicyEventWrapper.Builder(EventId.BIND_CROSS_PROFILE_SERVICE_VALUE)
+                .setStrings(TEST_WITH_PERMISSION_PKG)
+                .build());
+    }
+
+    @Test
+    public void testBindServiceAsUser_differentProfileGroup_doesNotReportMetric()
+            throws Exception {
+        if (!isStatsdEnabled(getDevice())) {
+            return;
+        }
+        int userInDifferentProfileGroup = createUser();
+        getDevice().startUser(userInDifferentProfileGroup, /* waitFlag= */ true);
+        mTestArgs.put("testUser", Integer.toString(userInDifferentProfileGroup));
+        getDevice().installPackageForUser(
+                mApkFile, /* reinstall= */ true, /* grantPermissions= */ true,
+                userInDifferentProfileGroup, /* extraArgs= */ "-t",
+                /* extraArgs= */ "--force-queryable");
+
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mCtsBuild);
+        File testServiceApkFile = buildHelper.getTestFile(TEST_SERVICE_WITH_PERMISSION_APK);
+        getDevice().installPackageForUser(
+                testServiceApkFile,
+                /* reinstall= */ true,
+                /* grantPermissions= */ true,
+                userInDifferentProfileGroup,
+                /* extraArgs= */ "-t",
+                /* extraArgs= */ "--force-queryable");
+
+        assertMetricsNotLogged(getDevice(), () -> {
+            runDeviceTests(
+                    getDevice(),
+                    TEST_WITH_PERMISSION_PKG,
+                    ".ContextCrossProfileDeviceTest",
+                    "testBindServiceAsUser_withInteractAcrossUsersFullPermission_noAsserts",
+                    mParentUserId,
+                    mTestArgs,
+                    /* timeout= */ 60L,
+                    TimeUnit.SECONDS);
+        }, new DevicePolicyEventWrapper.Builder(EventId.BIND_CROSS_PROFILE_SERVICE_VALUE)
+                .setStrings(TEST_WITH_PERMISSION_PKG)
+                .build());
+    }
+
+    @Test
+    public void testBindServiceAsUser_sameUser_doesNotReportMetric()
+            throws Exception {
+        if (!isStatsdEnabled(getDevice())) {
+            return;
+        }
+        mTestArgs.put("testUser", Integer.toString(mParentUserId));
+
+        assertMetricsNotLogged(getDevice(), () -> {
+            runDeviceTests(
+                    getDevice(),
+                    TEST_WITH_PERMISSION_PKG,
+                    ".ContextCrossProfileDeviceTest",
+                    "testBindServiceAsUser_withInteractAcrossProfilePermission_noAsserts",
+                    mParentUserId,
+                    mTestArgs,
+                    /* timeout= */ 60L,
+                    TimeUnit.SECONDS);
+        }, new DevicePolicyEventWrapper.Builder(EventId.BIND_CROSS_PROFILE_SERVICE_VALUE)
+                .setStrings(TEST_WITH_PERMISSION_PKG)
+                .build());
+    }
+
+    @Test
     public void testCreateContextAsUser_sameProfileGroup_withInteractAcrossProfilesPermission_throwsException()
             throws Exception {
         int userInSameProfileGroup = createProfile(mParentUserId);
diff --git a/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileApp/src/com/android/cts/context/ContextCrossProfileDeviceTest.java b/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileApp/src/com/android/cts/context/ContextCrossProfileDeviceTest.java
index d69a7cd..23c7730 100644
--- a/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileApp/src/com/android/cts/context/ContextCrossProfileDeviceTest.java
+++ b/hostsidetests/content/test-apps/ContextCrossProfileApps/ContextCrossProfileApp/src/com/android/cts/context/ContextCrossProfileDeviceTest.java
@@ -41,6 +41,8 @@
  */
 @RunWith(JUnit4.class)
 public class ContextCrossProfileDeviceTest {
+    private static final String INTERACT_ACROSS_USERS_FULL_PERMISSION =
+            "android.permission.INTERACT_ACROSS_USERS_FULL";
     private static final String INTERACT_ACROSS_USERS_PERMISSION =
             "android.permission.INTERACT_ACROSS_USERS";
     private static final String INTERACT_ACROSS_PROFILES_PERMISSION =
@@ -60,7 +62,6 @@
     public static final ComponentName TEST_SERVICE_IN_SAME_PKG_COMPONENT_NAME =
             new ComponentName(InstrumentationRegistry.getContext().getPackageName(),
                     TEST_SERVICE_IN_SAME_PKG_CLASS);
-    
     @After
     public void tearDown() throws Exception {
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
@@ -228,7 +229,7 @@
         uiAutomation.adoptShellPermissionIdentity(INTERACT_ACROSS_USERS_PERMISSION);
         try {
             Intent bindIntent = new Intent();
-            bindIntent.setComponent(TEST_SERVICE_IN_DIFFERENT_PKG_COMPONENT_NAME);
+            bindIntent.setComponent(TEST_SERVICE_IN_SAME_PKG_COMPONENT_NAME);
 
             context.bindServiceAsUser(
                     bindIntent, new ContextCrossProfileTestConnection(), Context.BIND_AUTO_CREATE,
@@ -249,7 +250,7 @@
         uiAutomation.adoptShellPermissionIdentity(INTERACT_ACROSS_PROFILES_PERMISSION);
         try {
             Intent bindIntent = new Intent();
-            bindIntent.setComponent(TEST_SERVICE_IN_DIFFERENT_PKG_COMPONENT_NAME);
+            bindIntent.setComponent(TEST_SERVICE_IN_SAME_PKG_COMPONENT_NAME);
 
             context.bindServiceAsUser(
                     bindIntent, new ContextCrossProfileTestConnection(), Context.BIND_AUTO_CREATE,
@@ -274,7 +275,7 @@
         UserHandle otherUserHandle = UserHandle.of(otherUserId);
         try {
             Intent bindIntent = new Intent();
-            bindIntent.setComponent(TEST_SERVICE_IN_DIFFERENT_PKG_COMPONENT_NAME);
+            bindIntent.setComponent(TEST_SERVICE_IN_SAME_PKG_COMPONENT_NAME);
 
             context.bindServiceAsUser(
                     bindIntent, new ContextCrossProfileTestConnection(),
@@ -292,7 +293,7 @@
         UserHandle otherProfileHandle = UserHandle.of(otherProfileId);
         try {
             Intent bindIntent = new Intent();
-            bindIntent.setComponent(TEST_SERVICE_IN_DIFFERENT_PKG_COMPONENT_NAME);
+            bindIntent.setComponent(TEST_SERVICE_IN_SAME_PKG_COMPONENT_NAME);
 
             context.bindServiceAsUser(
                     bindIntent, new ContextCrossProfileTestConnection(), Context.BIND_AUTO_CREATE,
@@ -304,6 +305,44 @@
     }
 
     @Test
+    public void testBindServiceAsUser_withInteractAcrossProfilePermission_noAsserts() {
+        final Context context = InstrumentationRegistry.getContext();
+        final UiAutomation uiAutomation =
+                InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        final AppOpsManager appOpsManager = context.getSystemService(AppOpsManager.class);
+        uiAutomation.adoptShellPermissionIdentity(MANAGE_APP_OPS_MODE);
+        appOpsManager.setMode(AppOpsManager.permissionToOp(INTERACT_ACROSS_PROFILES_PERMISSION),
+                Binder.getCallingUid(), context.getPackageName(), AppOpsManager.MODE_DEFAULT);
+        uiAutomation.dropShellPermissionIdentity();
+        int otherProfileId = getTestUser();
+        UserHandle otherProfileHandle = UserHandle.of(otherProfileId);
+        uiAutomation.adoptShellPermissionIdentity(INTERACT_ACROSS_PROFILES_PERMISSION);
+        Intent bindIntent = new Intent();
+        bindIntent.setComponent(TEST_SERVICE_IN_SAME_PKG_COMPONENT_NAME);
+
+        context.bindServiceAsUser(
+                bindIntent, new ContextCrossProfileTestConnection(),
+                Context.BIND_AUTO_CREATE, otherProfileHandle);
+    }
+
+    @Test
+    public void testBindServiceAsUser_withInteractAcrossUsersFullPermission_noAsserts() {
+        final Context context = InstrumentationRegistry.getContext();
+        final UiAutomation uiAutomation =
+                InstrumentationRegistry.getInstrumentation().getUiAutomation();
+        int otherProfileId = getTestUser();
+        UserHandle otherProfileHandle = UserHandle.of(otherProfileId);
+        uiAutomation.adoptShellPermissionIdentity(
+                INTERACT_ACROSS_USERS_FULL_PERMISSION, INTERACT_ACROSS_USERS_PERMISSION);
+        Intent bindIntent = new Intent();
+        bindIntent.setComponent(TEST_SERVICE_IN_SAME_PKG_COMPONENT_NAME);
+
+        context.bindServiceAsUser(
+                bindIntent, new ContextCrossProfileTestConnection(),
+                Context.BIND_AUTO_CREATE, otherProfileHandle);
+    }
+
+    @Test
     public void testCreateContextAsUser_sameProfileGroup_withInteractAcrossProfilesPermission_throwsException() {
         final Context context = InstrumentationRegistry.getContext();
         final UiAutomation uiAutomation =
diff --git a/hostsidetests/devicepolicy/OWNERS b/hostsidetests/devicepolicy/OWNERS
index 12341eb..f88e5d9 100644
--- a/hostsidetests/devicepolicy/OWNERS
+++ b/hostsidetests/devicepolicy/OWNERS
@@ -1,2 +1,5 @@
-# Bug component: 100560
-include /tests/admin/OWNERS
+# Bug template url: https://b.corp.google.com/issues/new?component=100560&template=63204
+alexkershaw@google.com
+eranm@google.com
+rubinxu@google.com
+sandness@google.com
diff --git a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
index 8dc1d69..c9576f7 100644
--- a/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
+++ b/hostsidetests/devicepolicy/app/DeviceAndProfileOwner/src/com/android/cts/deviceandprofileowner/DevicePolicyLoggingTest.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.READ_CONTACTS;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FEATURES_NONE;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
+import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_TRUST_AGENTS;
 import static android.app.admin.DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED;
 import static android.app.admin.DevicePolicyManager.PERMISSION_GRANT_STATE_DEFAULT;
@@ -77,6 +78,11 @@
                 KEYGUARD_DISABLE_FEATURES_NONE);
     }
 
+    public void testSetKeyguardDisabledSecureCameraLogged() {
+        mDevicePolicyManager.setKeyguardDisabledFeatures(
+                ADMIN_RECEIVER_COMPONENT, KEYGUARD_DISABLE_SECURE_CAMERA);
+    }
+
     public void testSetUserRestrictionLogged() {
         mDevicePolicyManager.addUserRestriction(ADMIN_RECEIVER_COMPONENT,
                 UserManager.DISALLOW_CONFIG_LOCATION);
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
index da99b21..673e166 100644
--- a/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/AndroidManifest.xml
@@ -33,6 +33,7 @@
     <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
     <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
     <application
         android:testOnly="true"
diff --git a/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DefaultSmsApplicationTest.java b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DefaultSmsApplicationTest.java
new file mode 100644
index 0000000..e9d65a0
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/DeviceOwner/src/com/android/cts/deviceowner/DefaultSmsApplicationTest.java
@@ -0,0 +1,41 @@
+/*
+ * 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.deviceowner;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.provider.Telephony;
+
+public class DefaultSmsApplicationTest extends BaseDeviceOwnerTest {
+
+    public void testSetDefaultSmsApplication() {
+        String previousSmsAppName = Telephony.Sms.getDefaultSmsPackage(mContext);
+        String newSmsAppName = "android.telephony.cts.sms.simplesmsapp";
+
+        mDevicePolicyManager.setDefaultSmsApplication(getWho(), newSmsAppName);
+        String defaultSmsApp = Telephony.Sms.getDefaultSmsPackage(mContext);
+        assertThat(defaultSmsApp).isNotNull();
+        assertThat(defaultSmsApp).isEqualTo(newSmsAppName);
+
+        // Restore previous default sms application
+        mDevicePolicyManager.setDefaultSmsApplication(getWho(), previousSmsAppName);
+        defaultSmsApp = Telephony.Sms.getDefaultSmsPackage(mContext);
+        assertThat(defaultSmsApp).isNotNull();
+        assertThat(defaultSmsApp).isEqualTo(previousSmsAppName);
+    }
+
+}
diff --git a/hostsidetests/devicepolicy/app/PackageInstaller/AndroidManifest.xml b/hostsidetests/devicepolicy/app/PackageInstaller/AndroidManifest.xml
index f4dfcaf..d81cd43 100644
--- a/hostsidetests/devicepolicy/app/PackageInstaller/AndroidManifest.xml
+++ b/hostsidetests/devicepolicy/app/PackageInstaller/AndroidManifest.xml
@@ -20,6 +20,7 @@
     <uses-sdk android:minSdkVersion="21"/>
 
     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
 
     <application
         android:testOnly="true">
diff --git a/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp b/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp
new file mode 100644
index 0000000..ab82993
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleSmsApp/Android.bp
@@ -0,0 +1,28 @@
+// 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: "SimpleSmsApp",
+    sdk_version: "test_current",
+
+    srcs: ["src/**/*.kt", "src/**/*.java"],
+
+    static_libs: [
+        "compatibility-device-util-axt",
+    ],
+
+    test_suites: [
+        "cts",
+    ],
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/SimpleSmsApp/AndroidManifest.xml b/hostsidetests/devicepolicy/app/SimpleSmsApp/AndroidManifest.xml
new file mode 100644
index 0000000..6cd7616
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleSmsApp/AndroidManifest.xml
@@ -0,0 +1,75 @@
+<?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.telephony.cts.sms.simplesmsapp">
+
+    <uses-permission android:name="android.permission.READ_SMS"/>
+
+    <application android:label="SimpleSmsApp">
+        <activity
+            android:name="android.app.Activity"
+            android:exported="true"/>
+
+        <!-- BroadcastReceiver that listens for incoming SMS messages -->
+        <receiver android:name="android.telephony.cts.sms.SmsReceiver"
+                  android:permission="android.permission.BROADCAST_SMS">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.SMS_DELIVER" />
+            </intent-filter>
+        </receiver>
+
+        <!-- BroadcastReceiver that listens for incoming MMS messages -->
+        <receiver android:name="android.telephony.cts.sms.MmsReceiver"
+                  android:permission="android.permission.BROADCAST_WAP_PUSH">
+            <intent-filter>
+                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER" />
+                <data android:mimeType="application/vnd.wap.mms-message" />
+            </intent-filter>
+        </receiver>
+
+        <!-- Activity that allows the user to send new SMS/MMS messages -->
+        <activity android:name="android.app.Activity" >
+            <intent-filter>
+                <action android:name="android.intent.action.SEND" />
+                <action android:name="android.intent.action.SENDTO" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <category android:name="android.intent.category.BROWSABLE" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+        </activity>
+
+        <!-- Service that delivers messages from the phone "quick response" -->
+        <service android:name="android.telephony.cts.sms.HeadlessSmsSendService"
+                 android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE"
+                 android:exported="true" >
+            <intent-filter>
+                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE" />
+                <category android:name="android.intent.category.DEFAULT" />
+                <data android:scheme="sms" />
+                <data android:scheme="smsto" />
+                <data android:scheme="mms" />
+                <data android:scheme="mmsto" />
+            </intent-filter>
+        </service>
+
+    </application>
+</manifest>
+
diff --git a/hostsidetests/devicepolicy/app/SimpleSmsApp/src/android/telephony/cts/sms/HeadlessSmsSendService.java b/hostsidetests/devicepolicy/app/SimpleSmsApp/src/android/telephony/cts/sms/HeadlessSmsSendService.java
new file mode 100644
index 0000000..c97c3ee
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleSmsApp/src/android/telephony/cts/sms/HeadlessSmsSendService.java
@@ -0,0 +1,40 @@
+/*
+ * 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.telephony.cts.sms;
+
+import android.app.IntentService;
+import android.content.Intent;
+
+/**
+ * SmsReceiver, MmsReceiver, ComposeSmsActivity, HeadlessSmsSendService together make
+ * this a valid SmsApplication (that can be set as the default SMS app). Although some of these
+ * classes don't do anything, they are needed to make this a valid candidate for default SMS
+ * app.
+ */
+public class HeadlessSmsSendService extends IntentService {
+    /**
+     * @param name Used to name the worker thread, important only for debugging.
+     */
+    public HeadlessSmsSendService(String name) {
+        super(name);
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/SimpleSmsApp/src/android/telephony/cts/sms/MmsReceiver.java b/hostsidetests/devicepolicy/app/SimpleSmsApp/src/android/telephony/cts/sms/MmsReceiver.java
new file mode 100644
index 0000000..200ee6d
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleSmsApp/src/android/telephony/cts/sms/MmsReceiver.java
@@ -0,0 +1,33 @@
+/*
+ * 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.telephony.cts.sms;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * SmsReceiver, MmsReceiver, ComposeSmsActivity, HeadlessSmsSendService together make
+ * this a valid SmsApplication (that can be set as the default SMS app). Although some of these
+ * classes don't do anything, they are needed to make this a valid candidate for default SMS
+ * app.
+ */
+public class MmsReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/app/SimpleSmsApp/src/android/telephony/cts/sms/SmsReceiver.java b/hostsidetests/devicepolicy/app/SimpleSmsApp/src/android/telephony/cts/sms/SmsReceiver.java
new file mode 100644
index 0000000..3145067
--- /dev/null
+++ b/hostsidetests/devicepolicy/app/SimpleSmsApp/src/android/telephony/cts/sms/SmsReceiver.java
@@ -0,0 +1,33 @@
+/*
+ * 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.telephony.cts.sms;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+
+/**
+ * SmsReceiver, MmsReceiver, ComposeSmsActivity, HeadlessSmsSendService together make
+ * this a valid SmsApplication (that can be set as the default SMS app). Although some of these
+ * classes don't do anything, they are needed to make this a valid candidate for default SMS
+ * app.
+ */
+public class SmsReceiver extends BroadcastReceiver {
+    @Override
+    public void onReceive(Context context, Intent intent) {
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
index ef05138..d0e2deb 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceAndProfileOwnerTest.java
@@ -1800,6 +1800,21 @@
     }
 
     @Test
+    public void testSetKeyguardDisabledSecureCameraLogged() throws Exception {
+        if (!mHasFeature || !isStatsdEnabled(getDevice())) {
+            return;
+        }
+        assertMetricsLogged(getDevice(), () -> {
+            executeDeviceTestMethod(
+                    ".DevicePolicyLoggingTest", "testSetKeyguardDisabledSecureCameraLogged");
+        }, new DevicePolicyEventWrapper.Builder(EventId.SET_KEYGUARD_DISABLED_FEATURES_VALUE)
+                .setAdminPackageName(DEVICE_ADMIN_PKG)
+                .setInt(KEYGUARD_DISABLE_SECURE_CAMERA)
+                .setStrings(NOT_CALLED_FROM_PARENT)
+                .build());
+    }
+
+    @Test
     public void testSetKeyguardDisabledFeatures() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
index 0ef7cd3..9b313a0 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/DeviceOwnerTest.java
@@ -66,6 +66,9 @@
     private static final String SIMPLE_APP_PKG = "com.android.cts.launcherapps.simpleapp";
     private static final String SIMPLE_APP_ACTIVITY = SIMPLE_APP_PKG + ".SimpleActivity";
 
+    protected static final String SIMPLE_SMS_APP_PKG = "android.telephony.cts.sms.simplesmsapp";
+    protected static final String SIMPLE_SMS_APP_APK = "SimpleSmsApp.apk";
+
     private static final String WIFI_CONFIG_CREATOR_PKG =
             "com.android.cts.deviceowner.wificonfigcreator";
     private static final String WIFI_CONFIG_CREATOR_APK = "CtsWifiConfigCreator.apk";
@@ -897,6 +900,19 @@
     }
 
     @Test
+    public void testDefaultSmsApplication() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
+        installAppAsUser(SIMPLE_SMS_APP_APK, mPrimaryUserId);
+
+        executeDeviceTestMethod(".DefaultSmsApplicationTest", "testSetDefaultSmsApplication");
+
+        getDevice().uninstallPackage(SIMPLE_SMS_APP_PKG);
+    }
+
+    @Test
     public void testNoHiddenActivityFoundTest() throws Exception {
         if (!mHasFeature) {
             return;
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
index 1389abd..bf32483 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/ManagedProfilePasswordTest.java
@@ -33,7 +33,7 @@
 
 public class ManagedProfilePasswordTest extends BaseManagedProfileTest {
     private static final String USER_STATE_LOCKED = "RUNNING_LOCKED";
-    private static final long TIMEOUT_USER_LOCKED_MILLIS = TimeUnit.MINUTES.toMillis(2);
+    private static final long TIMEOUT_USER_LOCKED_MILLIS = TimeUnit.MINUTES.toMillis(3);
     // Password needs to be in sync with ResetPasswordWithTokenTest.PASSWORD1
     private static final String RESET_PASSWORD_TEST_DEFAULT_PASSWORD = "123456";
 
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
index 7a61083..8551757 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/MixedManagedProfileOwnerTest.java
@@ -263,6 +263,12 @@
                 mUserId);
     }
 
+    @Test
+    @Override
+    public void testSetKeyguardDisabledSecureCameraLogged() {
+        // Managed profiles are not allowed to set keyguard disabled secure camera
+    }
+
     @FlakyTest
     @Override
     @Test
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
index 754b571..b0d86c8 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/OrgOwnedProfileOwnerTest.java
@@ -420,6 +420,10 @@
 
     @Test
     public void testPersonalAppsSuspensionNormalApp() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
         installAppAsUser(DEVICE_ADMIN_APK, mPrimaryUserId);
         // Initially the app should be launchable.
         assertCanStartPersonalApp(DEVICE_ADMIN_PKG, true);
@@ -433,6 +437,10 @@
 
     @Test
     public void testPersonalAppsSuspensionInstalledApp() throws Exception {
+        if (!mHasFeature) {
+            return;
+        }
+
         setPersonalAppsSuspended(true);
 
         installAppAsUser(DUMMY_IME_APK, mPrimaryUserId);
diff --git a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
index 16e9e7f..c1cb1ee 100644
--- a/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
+++ b/hostsidetests/devicepolicy/src/com/android/cts/devicepolicy/metrics/DevicePolicyEventLogVerifier.java
@@ -64,6 +64,34 @@
         }
     }
 
+    /**
+     * Asserts that <code>expectedLogs</code> were not logged as a result of executing
+     * <code>action</code>. Note that {@link Action#apply() } is always
+     * invoked on the <code>action</code> parameter, even if statsd expectedLogs are disabled.
+     */
+    public static void assertMetricsNotLogged(ITestDevice device, Action action,
+            DevicePolicyEventWrapper... expectedLogs) throws Exception {
+        final AtomMetricTester logVerifier = new AtomMetricTester(device);
+        if (logVerifier.isStatsdDisabled()) {
+            action.apply();
+            return;
+        }
+        try {
+            logVerifier.cleanLogs();
+            logVerifier.createAndUploadConfig(Atom.DEVICE_POLICY_EVENT_FIELD_NUMBER);
+            action.apply();
+
+            Thread.sleep(WAIT_TIME_SHORT);
+
+            final List<EventMetricData> data = logVerifier.getEventMetricDataList();
+            for (DevicePolicyEventWrapper expectedLog : expectedLogs) {
+                assertExpectedMetricNotLogged(data, expectedLog);
+            }
+        } finally {
+            logVerifier.cleanLogs();
+        }
+    }
+
     public static boolean isStatsdEnabled(ITestDevice device) throws DeviceNotAvailableException {
         final AtomMetricTester logVerifier = new AtomMetricTester(device);
         return !logVerifier.isStatsdDisabled();
@@ -71,6 +99,18 @@
 
     private static void assertExpectedMetricLogged(List<EventMetricData> data,
             DevicePolicyEventWrapper expectedLog) {
+        assertWithMessage("Expected metric was not logged.")
+                .that(isExpectedMetricLogged(data, expectedLog)).isTrue();
+    }
+
+    private static void assertExpectedMetricNotLogged(List<EventMetricData> data,
+            DevicePolicyEventWrapper expectedLog) {
+        assertWithMessage("Expected metric was logged.")
+                .that(isExpectedMetricLogged(data, expectedLog)).isFalse();
+    }
+
+    private static boolean isExpectedMetricLogged(List<EventMetricData> data,
+            DevicePolicyEventWrapper expectedLog) {
         final List<DevicePolicyEventWrapper> closestMatches = new ArrayList<>();
         AtomMetricTester.dropWhileNot(data, atom -> {
             final DevicePolicyEventWrapper actualLog =
@@ -80,7 +120,6 @@
             }
             return Objects.equals(actualLog, expectedLog);
         });
-        assertWithMessage("Expected metric was not logged.")
-                .that(closestMatches).contains(expectedLog);
+        return closestMatches.contains(expectedLog);
     }
 }
\ No newline at end of file
diff --git a/hostsidetests/graphics/gpuprofiling/Android.bp b/hostsidetests/graphics/gpuprofiling/Android.bp
new file mode 100644
index 0000000..d3afe48
--- /dev/null
+++ b/hostsidetests/graphics/gpuprofiling/Android.bp
@@ -0,0 +1,29 @@
+// 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.
+
+java_test_host {
+    name: "CtsGpuProfilingDataTestCases",
+    srcs: ["src/**/*.java"],
+    // tag this module as a cts test artifact
+    test_suites: ["cts", "general-tests"],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "compatibility-host-util",
+    ],
+    static_libs: [
+        "perfetto_config-full",
+        "platform-test-annotations-host",
+    ],
+}
diff --git a/hostsidetests/graphics/gpuprofiling/AndroidTest.xml b/hostsidetests/graphics/gpuprofiling/AndroidTest.xml
new file mode 100644
index 0000000..6dd7410
--- /dev/null
+++ b/hostsidetests/graphics/gpuprofiling/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?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 CtsGpuProfilingDataTestCases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="graphics" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <target_preparer class="com.android.compatibility.common.tradefed.targetprep.FilePusher">
+        <option name="cleanup" value="true" />
+        <option name="push" value="ctsgraphicsgpuprofilinginit->/data/local/tmp/ctsgraphicsgpuprofilinginit" />
+        <option name="append-bitness" value="true" />
+    </target_preparer>
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsGpuProfilingDataTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/graphics/gpuprofiling/OWNERS b/hostsidetests/graphics/gpuprofiling/OWNERS
new file mode 100644
index 0000000..5920110
--- /dev/null
+++ b/hostsidetests/graphics/gpuprofiling/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 151634443
+abodnar@google.com
\ No newline at end of file
diff --git a/hostsidetests/graphics/gpuprofiling/app/Android.bp b/hostsidetests/graphics/gpuprofiling/app/Android.bp
new file mode 100644
index 0000000..134e0cc
--- /dev/null
+++ b/hostsidetests/graphics/gpuprofiling/app/Android.bp
@@ -0,0 +1,41 @@
+// 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.
+
+cc_test {
+    name: "ctsgraphicsgpuprofilinginit",
+    srcs: [
+        "android_graphics_cts_GpuProfilingData.cpp",
+    ],
+    compile_multilib: "both",
+    multilib: {
+        lib32: {
+            suffix: "32",
+        },
+        lib64: {
+            suffix: "64",
+        },
+    },
+    cflags: [
+        "-Wall",
+        "-Werror",
+    ],
+    shared_libs: [
+        "libdl",
+        "libandroid",
+        "libvulkan",
+        "liblog",
+    ],
+    stl: "c++_static",
+    sdk_version: "current",
+}
diff --git a/hostsidetests/graphics/gpuprofiling/app/android_graphics_cts_GpuProfilingData.cpp b/hostsidetests/graphics/gpuprofiling/app/android_graphics_cts_GpuProfilingData.cpp
new file mode 100644
index 0000000..19439c0
--- /dev/null
+++ b/hostsidetests/graphics/gpuprofiling/app/android_graphics_cts_GpuProfilingData.cpp
@@ -0,0 +1,204 @@
+/*
+ * 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.
+ *
+ */
+
+#define LOG_TAG "GpuProfilingData"
+
+#include <chrono>
+#include <csignal>
+#include <string>
+#include <thread>
+#include <unistd.h>
+#include <vector>
+
+#include <android/log.h>
+#include <dlfcn.h>
+#include <vulkan/vulkan.h>
+
+#define ALOGI(msg, ...)                                                        \
+  __android_log_print(ANDROID_LOG_INFO, LOG_TAG, (msg), __VA_ARGS__)
+#define ALOGE(msg, ...)                                                        \
+  __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, (msg), __VA_ARGS__)
+#define ALOGD(msg, ...)                                                        \
+  __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, (msg), __VA_ARGS__)
+#define REQUIRE_SUCCESS(fn, name)                                              \
+  do {                                                                         \
+    if (VK_SUCCESS != fn) {                                                    \
+      ALOGE("Vulkan Error in %s", name);                                       \
+      return -1;                                                               \
+    }                                                                          \
+  } while (0)
+
+namespace {
+
+typedef void (*FN_PTR)(void);
+
+/**
+ * Load the vendor provided counter producer library.
+ * startCounterProducer is a thin rewrite of the same producer loading logic in
+ * github.com/google/agi
+ */
+
+int startCounterProducer() {
+  ALOGI("%s", "Loading producer library");
+  char *error;
+  std::string libDir = sizeof(void *) == 8 ? "lib64" : "lib";
+  std::string producerPath = "/vendor/" + libDir + "/libgpudataproducer.so";
+
+  ALOGI("Trying %s", producerPath.c_str());
+  void *handle = dlopen(producerPath.c_str(), RTLD_GLOBAL);
+  if ((error = dlerror()) != nullptr || handle == nullptr) {
+    ALOGE("Error loading lib: %s", error);
+    return -1;
+  }
+
+  FN_PTR startFunc = (FN_PTR)dlsym(handle, "start");
+  if ((error = dlerror()) != nullptr) {
+    ALOGE("Error looking for start symbol: %s", error);
+    dlclose(handle);
+    return -1;
+  }
+
+  if (startFunc == nullptr) {
+    ALOGE("Did not find the producer library %s", producerPath.c_str());
+    ALOGE("LD_LIBRARY_PATH=%s", getenv("LD_LIBRARY_PATH"));
+    return -1;
+  }
+
+  ALOGI("Calling start at %p", startFunc);
+  (*startFunc)();
+  ALOGI("Producer %s has exited.", producerPath.c_str());
+  dlclose(handle);
+  return 0;
+}
+
+int initVulkan(VkDevice &device) {
+  std::string result = "";
+
+  const VkApplicationInfo appInfo = {
+      VK_STRUCTURE_TYPE_APPLICATION_INFO,
+      nullptr,            // pNext
+      "GpuProfilingData", // app name
+      0,                  // app version
+      nullptr,            // engine name
+      0,                  // engine version
+      VK_API_VERSION_1_0,
+  };
+  const VkInstanceCreateInfo instanceInfo = {
+      VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
+      nullptr, // pNext
+      0,       // flags
+      &appInfo,
+      0,       // layer count
+      nullptr, // layers
+      0,       // extension count
+      nullptr, // extensions
+  };
+  VkInstance instance;
+  REQUIRE_SUCCESS(vkCreateInstance(&instanceInfo, nullptr, &instance),
+                  "vkCreateInstance");
+
+  VkPhysicalDevice physicalDevice = {};
+  uint32_t nPhysicalDevices;
+  REQUIRE_SUCCESS(
+      vkEnumeratePhysicalDevices(instance, &nPhysicalDevices, nullptr),
+      "vkEnumeratePhysicalDevices");
+  std::vector<VkPhysicalDevice> physicalDevices(nPhysicalDevices);
+
+  REQUIRE_SUCCESS(vkEnumeratePhysicalDevices(instance, &nPhysicalDevices,
+                                             physicalDevices.data()),
+                  "vkEnumeratePhysicalDevices");
+
+  uint32_t queueFamilyIndex = static_cast<uint32_t>(-1);
+  uint32_t i;
+  for (i = 0; i < nPhysicalDevices; ++i) {
+    uint32_t nQueueProperties = 0;
+    vkGetPhysicalDeviceQueueFamilyProperties(physicalDevices[i],
+                                             &nQueueProperties, nullptr);
+    std::vector<VkQueueFamilyProperties> queueProperties(nQueueProperties);
+    vkGetPhysicalDeviceQueueFamilyProperties(
+        physicalDevices[i], &nQueueProperties, queueProperties.data());
+    for (uint32_t j = 0; j < nQueueProperties; ++j) {
+      if (queueProperties[j].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
+        queueFamilyIndex = j;
+        break;
+      }
+    }
+    if (queueFamilyIndex != static_cast<uint32_t>(-1)) {
+      break;
+    }
+  }
+  if (i == nPhysicalDevices) {
+    ALOGE("%s",
+          "Could not find a physical device that supports a graphics queue");
+    return -1;
+  }
+  physicalDevice = physicalDevices[i];
+
+  float priority = 1.0f;
+  VkDeviceQueueCreateInfo queueCreateInfo{
+      VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
+      nullptr, // pNext
+      0,       // flags
+      queueFamilyIndex,
+      1,
+      &priority,
+  };
+
+  VkDeviceCreateInfo deviceCreateInfo{
+      VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
+      nullptr, // pNext
+      0,       // flags
+      1,
+      &queueCreateInfo,
+      0,
+      nullptr,
+      0,
+      nullptr,
+      nullptr,
+  };
+
+  REQUIRE_SUCCESS(
+      vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device),
+      "vkCreateDevice");
+
+  return 0;
+}
+
+volatile std::sig_atomic_t done = 0;
+
+} // anonymous namespace
+
+int main() {
+  ALOGI("%s", "Creating Vulkan device");
+  VkDevice device;
+  std::signal(SIGTERM, [](int /*signal*/) {
+    ALOGI("%s", "SIGTERM received");
+    done = 1;
+  });
+  int result = initVulkan(device);
+  ALOGI("%s %d", "initVulkan returned", result);
+  std::thread dummy([&]() {
+    result = startCounterProducer();
+    ALOGI("%s %d", "startCounterProducer returned", result);
+  });
+  ALOGI("%s", "Waiting for host");
+  while (!done) {
+    std::this_thread::sleep_for(std::chrono::milliseconds(100));
+  }
+  vkDestroyDevice(device, nullptr);
+  return 0;
+}
diff --git a/hostsidetests/graphics/gpuprofiling/src/android/graphics/gpuprofiling/cts/CtsGpuProfilingDataTest.java b/hostsidetests/graphics/gpuprofiling/src/android/graphics/gpuprofiling/cts/CtsGpuProfilingDataTest.java
new file mode 100644
index 0000000..ed2aa3f8
--- /dev/null
+++ b/hostsidetests/graphics/gpuprofiling/src/android/graphics/gpuprofiling/cts/CtsGpuProfilingDataTest.java
@@ -0,0 +1,121 @@
+package android.graphics.gpuprofiling.cts;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import perfetto.protos.PerfettoConfig.TracingServiceState;
+import perfetto.protos.PerfettoConfig.TracingServiceState.DataSource;
+import perfetto.protos.PerfettoConfig.DataSourceDescriptor;
+
+import java.util.Base64;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests that ensure Perfetto producers exist for GPU profiling when the device claims to support profilng.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class CtsGpuProfilingDataTest extends BaseHostJUnit4Test {
+    public static final String TAG = "GpuProfilingDataDeviceActivity";
+
+    // This test ensures that if a device reports ro.hardware.gpu.profiler.support if reports the correct perfetto producers
+    //
+    // Positive tests
+    // - Ensure the perfetto producers for render stages, counters, and ftrace gpu frequency are available
+
+    private static final String BIN_NAME = "ctsgraphicsgpuprofilinginit";
+    private static final String DEVICE_BIN_PATH = "/data/local/tmp/" + BIN_NAME;
+    private static final String COUNTERS_SOURCE_NAME = "gpu.counters";
+    private static final String STAGES_SOURCE_NAME = "gpu.renderstages";
+    private static final String PROFILING_PROPERTY = "ro.hardware.gpu.profiler.support";
+    private static int MAX_RETRIES = 5;
+
+    private class ShellThread extends Thread {
+
+        private String mCmd;
+
+        public ShellThread(String cmd) throws Exception {
+            super("ShellThread");
+            mCmd = cmd;
+        }
+
+        @Override
+        public void run() {
+            try {
+                CommandResult activityStatus = getDevice().executeShellV2Command(mCmd);
+            } catch (Exception e) {
+                // TODO Do something here?
+            }
+        }
+    }
+
+    /**
+     * Kill the native process after each test
+     */
+    @After
+    public void cleanup() throws Exception {
+        // TODO figure out how to unregister the producers
+        getDevice().executeShellV2Command("killall " + BIN_NAME);
+    }
+
+    /**
+     * Clean up before starting any tests.
+     */
+    @Before
+    public void init() throws Exception {
+        cleanup();
+    }
+
+    /**
+     * This is the primary test of the feature. We check that gpu.counters and gpu.renderstages sources are available.
+     */
+    @Test
+    public void testProfilingDataProducersAvailable() throws Exception {
+        String profilingSupport = getDevice().getProperty(PROFILING_PROPERTY);
+
+        if (profilingSupport != null && profilingSupport.equals("true")) {
+            // Spin up a new thread to avoid blocking the main thread while the native process waits to be killed.
+            ShellThread shellThread = new ShellThread(DEVICE_BIN_PATH);
+            shellThread.start();
+            boolean countersSourceFound = false;
+            boolean stagesSourceFound = false;
+            for(int i = 0; i < MAX_RETRIES; i++) {
+                CommandResult queryStatus = getDevice().executeShellV2Command("perfetto --query-raw | base64");
+                Assert.assertEquals(CommandStatus.SUCCESS, queryStatus.getStatus());
+                byte[] decodedBytes = Base64.getMimeDecoder().decode(queryStatus.getStdout());
+                TracingServiceState state = TracingServiceState.parseFrom(decodedBytes);
+                int count = state.getDataSourcesCount();
+                Assert.assertTrue("No sources found", count > 0);
+                for (int j = 0; j < count; j++) {
+                    DataSource source = state.getDataSources(j);
+                    DataSourceDescriptor descriptor = source.getDsDescriptor();
+                    if (descriptor != null) {
+                        if (descriptor.getName().equals(COUNTERS_SOURCE_NAME)) {
+                            countersSourceFound = true;
+                        }
+                        if (descriptor.getName().equals(STAGES_SOURCE_NAME)) {
+                            stagesSourceFound = true;
+                        }
+                        if (countersSourceFound && stagesSourceFound) {
+                            break;
+                        }
+                    }
+                }
+                if (countersSourceFound && stagesSourceFound) {
+                    break;
+                }
+                Thread.sleep(1000);
+            }
+
+            Assert.assertTrue("Producer " + STAGES_SOURCE_NAME + " not found", stagesSourceFound);
+            Assert.assertTrue("Producer " + COUNTERS_SOURCE_NAME + " not found", countersSourceFound);
+        }
+    }
+}
\ No newline at end of file
diff --git a/hostsidetests/incrementalinstall/Android.bp b/hostsidetests/incrementalinstall/Android.bp
index c3fce03..fbc9dba 100644
--- a/hostsidetests/incrementalinstall/Android.bp
+++ b/hostsidetests/incrementalinstall/Android.bp
@@ -27,4 +27,5 @@
     test_suites: [
         "cts",
     ],
+    data: [":IncrementalTestAppRule"],
 }
diff --git a/hostsidetests/incrementalinstall/OWNERS b/hostsidetests/incrementalinstall/OWNERS
index df031dd..d16fb6c 100644
--- a/hostsidetests/incrementalinstall/OWNERS
+++ b/hostsidetests/incrementalinstall/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 147373090
+# Bug component: 554432
 dimuthu@google.com
 alexbuy@google.com
 schfan@google.com
diff --git a/hostsidetests/incrementalinstall/app/Android.bp b/hostsidetests/incrementalinstall/app/Android.bp
index 535ac0b..132ea73 100644
--- a/hostsidetests/incrementalinstall/app/Android.bp
+++ b/hostsidetests/incrementalinstall/app/Android.bp
@@ -12,9 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+// v1 implementation of test app built with v1 manifest.
 android_test_helper_app {
     name: "IncrementalTestApp",
-    srcs: ["src/**/*.java"],
+    srcs: ["v1/src/**/*.java"],
     dex_preopt: {
         enabled: false,
     },
@@ -31,4 +32,77 @@
     sdk_version: "test_current",
     export_package_resources: true,
     aapt_include_all_resources: true,
-}
\ No newline at end of file
+    manifest: "AndroidManifestV1.xml",
+}
+
+// v2 implementation of test app built with v1 manifest for zero version update test.
+android_test_helper_app {
+    name: "IncrementalTestApp2_v1",
+    srcs: ["v2/src/**/*.java"],
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    test_suites: [
+        "cts",
+    ],
+    v4_signature: true,
+    static_libs: [
+        "incremental-install-common-lib",
+    ],
+    sdk_version: "test_current",
+    export_package_resources: true,
+    aapt_include_all_resources: true,
+    manifest: "AndroidManifestV1.xml",
+}
+
+// v2 implementation of test app built with v2 manifest for version update test.
+android_test_helper_app {
+    name: "IncrementalTestApp2_v2",
+    srcs: ["v2/src/**/*.java"],
+    dex_preopt: {
+        enabled: false,
+    },
+    optimize: {
+        enabled: false,
+    },
+    test_suites: [
+        "cts",
+    ],
+    v4_signature: true,
+    static_libs: [
+        "incremental-install-common-lib",
+    ],
+    sdk_version: "test_current",
+    export_package_resources: true,
+    aapt_include_all_resources: true,
+    manifest: "AndroidManifestV2.xml",
+}
+
+genrule {
+  // copy apks and id sig to gendir.
+  name: "IncrementalTestAppRule",
+  srcs: [":IncrementalTestApp",
+         ":IncrementalTestApp2_v1",
+         ":IncrementalTestApp2_v2",
+         ":IncrementalTestAppDynamicAsset",
+         ":IncrementalTestAppDynamicCode",
+         ":IncrementalTestAppCompressedNativeLib",
+         ":IncrementalTestAppUncompressedNativeLib",],
+  out: ["IncrementalTestApp.apk.idsig",
+        "IncrementalTestApp2_v1.apk.idsig",
+        "IncrementalTestApp2_v2.apk.idsig",
+        "IncrementalTestAppDynamicAsset.apk", "IncrementalTestAppDynamicAsset.apk.idsig",
+        "IncrementalTestAppDynamicCode.apk", "IncrementalTestAppDynamicCode.apk.idsig",
+        "IncrementalTestAppCompressedNativeLib.apk", "IncrementalTestAppCompressedNativeLib.apk.idsig",
+        "IncrementalTestAppUncompressedNativeLib.apk", "IncrementalTestAppUncompressedNativeLib.apk.idsig",],
+  cmd: "cp $(locations :IncrementalTestApp) $(genDir)" +
+       " && cp $(locations :IncrementalTestApp2_v1) $(genDir)" +
+       " && cp $(locations :IncrementalTestApp2_v2) $(genDir)" +
+       " && cp $(locations :IncrementalTestAppDynamicAsset) $(genDir)" +
+       " && cp $(locations :IncrementalTestAppDynamicCode) $(genDir)" +
+       " && cp $(locations :IncrementalTestAppCompressedNativeLib) $(genDir)" +
+       " && cp $(locations :IncrementalTestAppUncompressedNativeLib) $(genDir)",
+  }
\ No newline at end of file
diff --git a/hostsidetests/incrementalinstall/app/AndroidManifest.xml b/hostsidetests/incrementalinstall/app/AndroidManifestV1.xml
similarity index 96%
rename from hostsidetests/incrementalinstall/app/AndroidManifest.xml
rename to hostsidetests/incrementalinstall/app/AndroidManifestV1.xml
index a1d900c..68d6d3d 100644
--- a/hostsidetests/incrementalinstall/app/AndroidManifest.xml
+++ b/hostsidetests/incrementalinstall/app/AndroidManifestV1.xml
@@ -18,7 +18,7 @@
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.incrementalinstall.incrementaltestapp"
           android:versionCode="1"
-          android:versionName="1.0" >
+          android:versionName="1.0">
 
     <application android:label="IncrementalTestApp">
         <activity android:name=".MainActivity">
diff --git a/hostsidetests/incrementalinstall/app/AndroidManifest.xml b/hostsidetests/incrementalinstall/app/AndroidManifestV2.xml
similarity index 93%
copy from hostsidetests/incrementalinstall/app/AndroidManifest.xml
copy to hostsidetests/incrementalinstall/app/AndroidManifestV2.xml
index a1d900c..27cfbb9 100644
--- a/hostsidetests/incrementalinstall/app/AndroidManifest.xml
+++ b/hostsidetests/incrementalinstall/app/AndroidManifestV2.xml
@@ -17,8 +17,8 @@
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="android.incrementalinstall.incrementaltestapp"
-          android:versionCode="1"
-          android:versionName="1.0" >
+          android:versionCode="2"
+          android:versionName="2.0">
 
     <application android:label="IncrementalTestApp">
         <activity android:name=".MainActivity">
diff --git a/hostsidetests/incrementalinstall/app/src/android/incrementalinstall/incrementaltestapp/MainActivity.java b/hostsidetests/incrementalinstall/app/v1/src/android/incrementalinstall/incrementaltestapp/MainActivity.java
similarity index 97%
rename from hostsidetests/incrementalinstall/app/src/android/incrementalinstall/incrementaltestapp/MainActivity.java
rename to hostsidetests/incrementalinstall/app/v1/src/android/incrementalinstall/incrementaltestapp/MainActivity.java
index 980757d..86a9acd 100644
--- a/hostsidetests/incrementalinstall/app/src/android/incrementalinstall/incrementaltestapp/MainActivity.java
+++ b/hostsidetests/incrementalinstall/app/v1/src/android/incrementalinstall/incrementaltestapp/MainActivity.java
@@ -43,6 +43,7 @@
         PACKAGE_NAME = getApplicationContext().getPackageName();
 
         broadcastStatus(Consts.SupportedComponents.ON_CREATE_COMPONENT, true);
+        broadcastStatus(Consts.SupportedComponents.ON_CREATE_COMPONENT_2, false);
         loadDynamicAsset();
         loadDynamicCode();
         loadCompressedNativeLib();
diff --git a/hostsidetests/incrementalinstall/app/src/android/incrementalinstall/incrementaltestapp/dynamiccode/DynamicCodeShim.java b/hostsidetests/incrementalinstall/app/v1/src/android/incrementalinstall/incrementaltestapp/dynamiccode/DynamicCodeShim.java
similarity index 100%
rename from hostsidetests/incrementalinstall/app/src/android/incrementalinstall/incrementaltestapp/dynamiccode/DynamicCodeShim.java
rename to hostsidetests/incrementalinstall/app/v1/src/android/incrementalinstall/incrementaltestapp/dynamiccode/DynamicCodeShim.java
diff --git a/hostsidetests/incrementalinstall/app/src/android/incrementalinstall/incrementaltestapp/dynamiccode/IDynamicCode.java b/hostsidetests/incrementalinstall/app/v1/src/android/incrementalinstall/incrementaltestapp/dynamiccode/IDynamicCode.java
similarity index 100%
rename from hostsidetests/incrementalinstall/app/src/android/incrementalinstall/incrementaltestapp/dynamiccode/IDynamicCode.java
rename to hostsidetests/incrementalinstall/app/v1/src/android/incrementalinstall/incrementaltestapp/dynamiccode/IDynamicCode.java
diff --git a/hostsidetests/incrementalinstall/app/src/android/incrementalinstall/incrementaltestapp/nativelib/CompressedNativeLib.java b/hostsidetests/incrementalinstall/app/v1/src/android/incrementalinstall/incrementaltestapp/nativelib/CompressedNativeLib.java
similarity index 100%
rename from hostsidetests/incrementalinstall/app/src/android/incrementalinstall/incrementaltestapp/nativelib/CompressedNativeLib.java
rename to hostsidetests/incrementalinstall/app/v1/src/android/incrementalinstall/incrementaltestapp/nativelib/CompressedNativeLib.java
diff --git a/hostsidetests/incrementalinstall/app/src/android/incrementalinstall/incrementaltestapp/nativelib/UncompressedNativeLib.java b/hostsidetests/incrementalinstall/app/v1/src/android/incrementalinstall/incrementaltestapp/nativelib/UncompressedNativeLib.java
similarity index 100%
rename from hostsidetests/incrementalinstall/app/src/android/incrementalinstall/incrementaltestapp/nativelib/UncompressedNativeLib.java
rename to hostsidetests/incrementalinstall/app/v1/src/android/incrementalinstall/incrementaltestapp/nativelib/UncompressedNativeLib.java
diff --git a/hostsidetests/incrementalinstall/app/v2/src/android/incrementalinstall/incrementaltestapp/MainActivity.java b/hostsidetests/incrementalinstall/app/v2/src/android/incrementalinstall/incrementaltestapp/MainActivity.java
new file mode 100644
index 0000000..0ae6138
--- /dev/null
+++ b/hostsidetests/incrementalinstall/app/v2/src/android/incrementalinstall/incrementaltestapp/MainActivity.java
@@ -0,0 +1,45 @@
+/*
+ * 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.incrementalinstall.incrementaltestapp;
+
+import static android.incrementalinstall.common.Consts.COMPONENT_STATUS_KEY;
+import static android.incrementalinstall.common.Consts.INCREMENTAL_TEST_APP_STATUS_RECEIVER_ACTION;
+import static android.incrementalinstall.common.Consts.TARGET_COMPONENT_KEY;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.incrementalinstall.common.Consts;
+import android.os.Bundle;
+
+/** A second implementation of MainActivity to verify version updates. */
+public class MainActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        for (String component : Consts.SupportedComponents.getAllComponents()) {
+            broadcastStatus(component,
+                    component.equals(Consts.SupportedComponents.ON_CREATE_COMPONENT_2));
+        }
+    }
+
+    private void broadcastStatus(String component, boolean status) {
+        Intent intent = new Intent(INCREMENTAL_TEST_APP_STATUS_RECEIVER_ACTION);
+        intent.putExtra(TARGET_COMPONENT_KEY, component);
+        intent.putExtra(COMPONENT_STATUS_KEY, status);
+        sendBroadcast(intent);
+    }
+}
diff --git a/hostsidetests/incrementalinstall/appvalidator/src/android/incrementalinstall/inrementaltestappvalidation/AppValidationTest.java b/hostsidetests/incrementalinstall/appvalidator/src/android/incrementalinstall/inrementaltestappvalidation/AppValidationTest.java
index 1338fe6..6654d5b 100644
--- a/hostsidetests/incrementalinstall/appvalidator/src/android/incrementalinstall/inrementaltestappvalidation/AppValidationTest.java
+++ b/hostsidetests/incrementalinstall/appvalidator/src/android/incrementalinstall/inrementaltestappvalidation/AppValidationTest.java
@@ -17,6 +17,7 @@
 package android.incrementalinstall.inrementaltestappvalidation;
 
 import static android.incrementalinstall.common.Consts.INCREMENTAL_TEST_APP_STATUS_RECEIVER_ACTION;
+import static android.incrementalinstall.common.Consts.INSTALLED_VERSION_CODE_TAG;
 import static android.incrementalinstall.common.Consts.IS_INCFS_INSTALLATION_TAG;
 import static android.incrementalinstall.common.Consts.LOADED_COMPONENTS_TAG;
 import static android.incrementalinstall.common.Consts.NOT_LOADED_COMPONENTS_TAG;
@@ -77,19 +78,44 @@
     }
 
     @Test
-    public void testInstallationType() throws Exception{
+    public void testInstallationTypeAndVersion() throws Exception {
         boolean isIncfsInstallation = Boolean.parseBoolean(InstrumentationRegistry.getArguments()
                 .getString(IS_INCFS_INSTALLATION_TAG));
-        assertEquals(isIncfsInstallation, new PathChecker().isIncFsPath(getAppSourceDir()));
+        int versionCode = Integer.parseInt(InstrumentationRegistry.getArguments()
+                .getString(INSTALLED_VERSION_CODE_TAG));
+        InstalledAppInfo installedAppInfo = getInstalledAppInfo();
+        assertEquals(isIncfsInstallation,
+                new PathChecker().isIncFsPath(installedAppInfo.installationPath));
+        assertEquals(versionCode, installedAppInfo.versionCode);
     }
 
-    private void launchTestApp() throws Exception{
+    private void launchTestApp() throws Exception {
         mDevice.executeShellCommand(String.format("am start %s/.MainActivity", mPackageToLaunch));
     }
 
-    private String getAppSourceDir() throws Exception{
-        String output = mDevice.executeShellCommand("pm list packages -f " + mPackageToLaunch);
-        // Output of the command is package:<path>/apk=<package_name>, we just need the <path>.
-        return output.substring("package:".length(), output.lastIndexOf("/"));
+    private InstalledAppInfo getInstalledAppInfo() throws Exception {
+        // Output of the command is package:<path>/apk=<package_name> versionCode:<version>, we
+        // just need the <path> and <version>.
+        String output = mDevice.executeShellCommand(
+                "pm list packages -f --show-versioncode " + mPackageToLaunch);
+        // outputSplits[0] will contain path information and outputSplits[1] will contain
+        // versionCode.
+        String[] outputSplits = output.split(" ");
+        String installationPath = outputSplits[0].trim().substring("package:".length(),
+                output.lastIndexOf("/"));
+        int versionCode = Integer.parseInt(
+                outputSplits[1].trim().substring("versionCode:".length()));
+        return new InstalledAppInfo(installationPath, versionCode);
+    }
+
+    private class InstalledAppInfo {
+
+        private final String installationPath;
+        private final int versionCode;
+
+        InstalledAppInfo(String installedPath, int versionCode) {
+            this.installationPath = installedPath;
+            this.versionCode = versionCode;
+        }
     }
 }
diff --git a/hostsidetests/incrementalinstall/common/src/android/incrementalinstall/common/Consts.java b/hostsidetests/incrementalinstall/common/src/android/incrementalinstall/common/Consts.java
index 30ffb3a..e185e6f 100644
--- a/hostsidetests/incrementalinstall/common/src/android/incrementalinstall/common/Consts.java
+++ b/hostsidetests/incrementalinstall/common/src/android/incrementalinstall/common/Consts.java
@@ -27,9 +27,12 @@
     // Tag for the components that should not be loaded, sent from Host to app validator.
     public static final String NOT_LOADED_COMPONENTS_TAG = "NOT_LOADED_COMPONENTS";
 
-    // Tag for the components that should not be loaded, sent from Host to app validator.
+    // Tag for installation type (incremental or not), sent from Host to app validator.
     public static final String IS_INCFS_INSTALLATION_TAG = "IS_INCFS";
 
+    // Tag for the version of the installed app, sent from Host to app validator.
+    public static final String INSTALLED_VERSION_CODE_TAG = "VERSION_CODE";
+
     // Action broadcast from test app after attempting to load a component.
     public static final String INCREMENTAL_TEST_APP_STATUS_RECEIVER_ACTION =
             "android.incrementalinstall.incrementaltestapp.INCREMENTAL_TEST_APP_RECEIVER_ACTION";
@@ -43,13 +46,14 @@
     public static class SupportedComponents {
 
         public static final String ON_CREATE_COMPONENT = "onCreate";
+        public static final String ON_CREATE_COMPONENT_2 = "onCreate2";
         public static final String DYNAMIC_ASSET_COMPONENT = "dynamicAsset";
         public static final String DYNAMIC_CODE_COMPONENT = "dynamicCode";
         public static final String COMPRESSED_NATIVE_COMPONENT = "compressedNative";
         public static final String UNCOMPRESSED_NATIVE_COMPONENT = "unCompressedNative";
 
         public static String[] getAllComponents() {
-            return new String[]{ON_CREATE_COMPONENT, DYNAMIC_ASSET_COMPONENT,
+            return new String[]{ON_CREATE_COMPONENT, ON_CREATE_COMPONENT_2, DYNAMIC_ASSET_COMPONENT,
                     DYNAMIC_CODE_COMPONENT, COMPRESSED_NATIVE_COMPONENT,
                     UNCOMPRESSED_NATIVE_COMPONENT};
         }
diff --git a/hostsidetests/incrementalinstall/src/android/incrementalinstall/cts/IncrementalInstallTest.java b/hostsidetests/incrementalinstall/src/android/incrementalinstall/cts/IncrementalInstallTest.java
index 3e769e4..5f24e6c 100644
--- a/hostsidetests/incrementalinstall/src/android/incrementalinstall/cts/IncrementalInstallTest.java
+++ b/hostsidetests/incrementalinstall/src/android/incrementalinstall/cts/IncrementalInstallTest.java
@@ -20,6 +20,7 @@
 import static android.incrementalinstall.common.Consts.SupportedComponents.DYNAMIC_ASSET_COMPONENT;
 import static android.incrementalinstall.common.Consts.SupportedComponents.DYNAMIC_CODE_COMPONENT;
 import static android.incrementalinstall.common.Consts.SupportedComponents.ON_CREATE_COMPONENT;
+import static android.incrementalinstall.common.Consts.SupportedComponents.ON_CREATE_COMPONENT_2;
 import static android.incrementalinstall.common.Consts.SupportedComponents.UNCOMPRESSED_NATIVE_COMPONENT;
 
 import static org.junit.Assert.assertFalse;
@@ -70,11 +71,15 @@
     private static final String VALIDATION_HELPER_CLASS =
             VALIDATION_HELPER_PKG + ".AppValidationTest";
     private static final String VALIDATION_HELPER_METHOD = "testAppComponentsInvoked";
-    private static final String INSTALLATION_TYPE_HELPER_METHOD = "testInstallationType";
+    private static final String INSTALLATION_TYPE_HELPER_METHOD = "testInstallationTypeAndVersion";
 
     private static final String TEST_APP_PACKAGE_NAME =
             "android.incrementalinstall.incrementaltestapp";
     private static final String TEST_APP_BASE_APK_NAME = "IncrementalTestApp.apk";
+    // apk for zero version update test (has version 1).
+    private static final String TEST_APP_BASE_APK_2_V1_NAME = "IncrementalTestApp2_v1.apk";
+    // apk for version update test (has version 2).
+    private static final String TEST_APP_BASE_APK_2_V2_NAME = "IncrementalTestApp2_v2.apk";
     private static final String TEST_APP_DYNAMIC_ASSET_NAME = "IncrementalTestAppDynamicAsset.apk";
     private static final String TEST_APP_DYNAMIC_CODE_NAME = "IncrementalTestAppDynamicCode.apk";
     private static final String TEST_APP_COMPRESSED_NATIVE_NAME =
@@ -84,10 +89,10 @@
 
     private static final String SIG_SUFFIX = ".idsig";
     private static final String INSTALL_SUCCESS_OUTPUT = "Success";
-
     private static final long DEFAULT_TEST_TIMEOUT_MS = 60 * 1000L;
     private static final long DEFAULT_MAX_TIMEOUT_TO_OUTPUT_MS = 60 * 1000L; // 1min
-
+    private final int TEST_APP_V1_VERSION = 1;
+    private final int TEST_APP_V2_VERSION = 2;
     private CompatibilityBuildHelper mBuildHelper;
 
     @Before
@@ -109,16 +114,17 @@
         assertTrue(
                 installWithAdbInstaller(TEST_APP_BASE_APK_NAME).contains(INSTALL_SUCCESS_OUTPUT));
         verifyPackageInstalled(TEST_APP_PACKAGE_NAME);
-        verifyInstallationType(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true);
+        verifyInstallationTypeAndVersion(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true,
+                TEST_APP_V1_VERSION);
         validateAppLaunch(TEST_APP_PACKAGE_NAME, ON_CREATE_COMPONENT);
     }
 
     @Test
     public void testBaseApkAdbUninstall() throws Exception {
-        assertTrue(
-                installWithAdbInstaller(TEST_APP_BASE_APK_NAME).contains(INSTALL_SUCCESS_OUTPUT));
+        verifyInstallCommandSuccess(installWithAdbInstaller(TEST_APP_BASE_APK_NAME));
         verifyPackageInstalled(TEST_APP_PACKAGE_NAME);
-        verifyInstallationType(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true);
+        verifyInstallationTypeAndVersion(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true,
+                TEST_APP_V1_VERSION);
         validateAppLaunch(TEST_APP_PACKAGE_NAME, ON_CREATE_COMPONENT);
         uninstallApp(TEST_APP_PACKAGE_NAME);
         verifyPackageNotInstalled(TEST_APP_PACKAGE_NAME);
@@ -130,7 +136,7 @@
         // Create a copy of original apk but not its idsig.
         copyTestFile(TEST_APP_BASE_APK_NAME, newApkName);
         String output = installWithAdbInstaller(newApkName);
-        assertFalse(output.contains(INSTALL_SUCCESS_OUTPUT));
+        verifyInstallCommandFailure(output);
         assertTrue(output.contains(String.format("Failed to stat signature file %s",
                 getFilePathFromBuildInfo(newApkName) + SIG_SUFFIX)));
 
@@ -153,25 +159,27 @@
             raf.seek(byteToContaminate);
             raf.writeByte((byte) (~raf.readByte()));
         }
-        assertFalse(installWithAdbInstaller(newApkName).contains(INSTALL_SUCCESS_OUTPUT));
+        verifyInstallCommandFailure(installWithAdbInstaller(newApkName));
         verifyPackageNotInstalled(TEST_APP_PACKAGE_NAME);
     }
 
     @Test
     public void testDynamicAssetMultiSplitAdbInstall() throws Exception {
-        assertTrue(installWithAdbInstaller(TEST_APP_BASE_APK_NAME,
-                TEST_APP_DYNAMIC_ASSET_NAME).contains(INSTALL_SUCCESS_OUTPUT));
+        verifyInstallCommandSuccess(
+                installWithAdbInstaller(TEST_APP_BASE_APK_NAME, TEST_APP_DYNAMIC_ASSET_NAME));
         verifyPackageInstalled(TEST_APP_PACKAGE_NAME);
-        verifyInstallationType(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true);
+        verifyInstallationTypeAndVersion(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true,
+                TEST_APP_V1_VERSION);
         validateAppLaunch(TEST_APP_PACKAGE_NAME, ON_CREATE_COMPONENT, DYNAMIC_ASSET_COMPONENT);
     }
 
     @Test
     public void testDynamicCodeMultiSplitAdbInstall() throws Exception {
-        assertTrue(installWithAdbInstaller(TEST_APP_BASE_APK_NAME,
-                TEST_APP_DYNAMIC_CODE_NAME).contains(INSTALL_SUCCESS_OUTPUT));
+        verifyInstallCommandSuccess(
+                installWithAdbInstaller(TEST_APP_BASE_APK_NAME, TEST_APP_DYNAMIC_CODE_NAME));
         verifyPackageInstalled(TEST_APP_PACKAGE_NAME);
-        verifyInstallationType(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true);
+        verifyInstallationTypeAndVersion(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true,
+                TEST_APP_V1_VERSION);
         validateAppLaunch(TEST_APP_PACKAGE_NAME, ON_CREATE_COMPONENT, DYNAMIC_CODE_COMPONENT);
     }
 
@@ -179,10 +187,11 @@
     public void testCompressedNativeLibMultiSplitAdbInstall() throws Exception {
         assertTrue(checkNativeLibInApkCompression(TEST_APP_COMPRESSED_NATIVE_NAME,
                 "libcompressednativeincrementaltest.so", true));
-        assertTrue(installWithAdbInstaller(TEST_APP_BASE_APK_NAME,
-                TEST_APP_COMPRESSED_NATIVE_NAME).contains(INSTALL_SUCCESS_OUTPUT));
+        verifyInstallCommandSuccess(
+                installWithAdbInstaller(TEST_APP_BASE_APK_NAME, TEST_APP_COMPRESSED_NATIVE_NAME));
         verifyPackageInstalled(TEST_APP_PACKAGE_NAME);
-        verifyInstallationType(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true);
+        verifyInstallationTypeAndVersion(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true,
+                TEST_APP_V1_VERSION);
         validateAppLaunch(TEST_APP_PACKAGE_NAME, ON_CREATE_COMPONENT, COMPRESSED_NATIVE_COMPONENT);
     }
 
@@ -190,38 +199,69 @@
     public void testUncompressedNativeLibMultiSplitAdbInstall() throws Exception {
         assertTrue(checkNativeLibInApkCompression(TEST_APP_COMPRESSED_NATIVE_NAME,
                 "libuncompressednativeincrementaltest.so", false));
-        assertTrue(installWithAdbInstaller(TEST_APP_BASE_APK_NAME,
-                TEST_APP_UNCOMPRESSED_NATIVE_NAME).contains(INSTALL_SUCCESS_OUTPUT));
+        verifyInstallCommandSuccess(
+                installWithAdbInstaller(TEST_APP_BASE_APK_NAME, TEST_APP_UNCOMPRESSED_NATIVE_NAME));
         verifyPackageInstalled(TEST_APP_PACKAGE_NAME);
-        verifyInstallationType(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true);
+        verifyInstallationTypeAndVersion(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true,
+                TEST_APP_V1_VERSION);
         validateAppLaunch(TEST_APP_PACKAGE_NAME, ON_CREATE_COMPONENT,
                 UNCOMPRESSED_NATIVE_COMPONENT);
     }
 
     @Test
     public void testAddSplitToExistingInstallNonIfsMigration() throws Exception {
-        assertTrue(
-                installWithAdbInstaller(TEST_APP_BASE_APK_NAME).contains(INSTALL_SUCCESS_OUTPUT));
+        verifyInstallCommandSuccess(installWithAdbInstaller(TEST_APP_BASE_APK_NAME));
         verifyPackageInstalled(TEST_APP_PACKAGE_NAME);
-        verifyInstallationType(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true);
+        verifyInstallationTypeAndVersion(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true,
+                TEST_APP_V1_VERSION);
         validateAppLaunch(TEST_APP_PACKAGE_NAME, ON_CREATE_COMPONENT);
         // Adb cannot add a split to an existing install, so we'll use pm to install just the
-        // dynamic code
-        // split.
-        String deviceLocalPath = "data/local/tmp/";
+        // dynamic code split.
+        String deviceLocalPath = "/data/local/tmp/";
         getDevice().executeAdbCommand("push", getFilePathFromBuildInfo(TEST_APP_DYNAMIC_CODE_NAME),
                 deviceLocalPath);
         getDevice().executeShellCommand(String.format("pm install -p %s %s", TEST_APP_PACKAGE_NAME,
                 deviceLocalPath + TEST_APP_DYNAMIC_CODE_NAME));
         // Verify IFS->NonIFS migration.
-        verifyInstallationType(TEST_APP_PACKAGE_NAME, /* isIncfs= */ false);
+        verifyInstallationTypeAndVersion(TEST_APP_PACKAGE_NAME, /* isIncfs= */ false,
+                TEST_APP_V1_VERSION);
         validateAppLaunch(TEST_APP_PACKAGE_NAME, ON_CREATE_COMPONENT, DYNAMIC_CODE_COMPONENT);
     }
 
-    private void verifyInstallationType(String packageName, boolean isIncfs) throws Exception {
+    @Test
+    public void testZeroVersionUpdateAdbInstall() throws Exception {
+        verifyInstallCommandSuccess(installWithAdbInstaller(TEST_APP_BASE_APK_NAME));
+        verifyPackageInstalled(TEST_APP_PACKAGE_NAME);
+        verifyInstallationTypeAndVersion(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true,
+                TEST_APP_V1_VERSION);
+        validateAppLaunch(TEST_APP_PACKAGE_NAME, ON_CREATE_COMPONENT);
+        // Install second implementation of app with the same version code.
+        verifyInstallCommandSuccess(installWithAdbInstaller(TEST_APP_BASE_APK_2_V1_NAME));
+        verifyInstallationTypeAndVersion(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true,
+                TEST_APP_V1_VERSION);
+        validateAppLaunch(TEST_APP_PACKAGE_NAME, ON_CREATE_COMPONENT_2);
+    }
+
+    @Test
+    public void testVersionUpdateAdbInstall() throws Exception {
+        verifyInstallCommandSuccess(installWithAdbInstaller(TEST_APP_BASE_APK_NAME));
+        verifyPackageInstalled(TEST_APP_PACKAGE_NAME);
+        verifyInstallationTypeAndVersion(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true,
+                TEST_APP_V1_VERSION);
+        validateAppLaunch(TEST_APP_PACKAGE_NAME, ON_CREATE_COMPONENT);
+        // Install second implementation of app with the same version code.
+        verifyInstallCommandSuccess(installWithAdbInstaller(TEST_APP_BASE_APK_2_V2_NAME));
+        verifyInstallationTypeAndVersion(TEST_APP_PACKAGE_NAME, /* isIncfs= */ true,
+                TEST_APP_V2_VERSION);
+        validateAppLaunch(TEST_APP_PACKAGE_NAME, ON_CREATE_COMPONENT_2);
+    }
+
+    private void verifyInstallationTypeAndVersion(String packageName, boolean isIncfs,
+            int versionCode) throws Exception {
         Map<String, String> args = new HashMap<>();
         args.put(Consts.PACKAGE_TO_LAUNCH_TAG, packageName);
         args.put(Consts.IS_INCFS_INSTALLATION_TAG, Boolean.toString(isIncfs));
+        args.put(Consts.INSTALLED_VERSION_CODE_TAG, Integer.toString(versionCode));
         boolean result = runDeviceTests(
                 getDevice(), TEST_RUNNER, VALIDATION_HELPER_PKG, VALIDATION_HELPER_CLASS,
                 INSTALLATION_TYPE_HELPER_METHOD,
@@ -339,6 +379,26 @@
         return conformsToCompressionStatus;
     }
 
+    private void verifyInstallCommandSuccess(String adbOutput) {
+        logInstallCommandOutput(adbOutput);
+        assertTrue(adbOutput.contains(INSTALL_SUCCESS_OUTPUT));
+    }
+
+    private void verifyInstallCommandFailure(String adbOutput) {
+        logInstallCommandOutput(adbOutput);
+        assertFalse(adbOutput.contains(INSTALL_SUCCESS_OUTPUT));
+    }
+
+    private void logInstallCommandOutput(String adbOutput) {
+        // Get calling method for logging
+        StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
+        StackTraceElement e = stacktrace[3]; //test method
+        String methodName = e.getMethodName();
+        LogUtil.CLog.logAndDisplay(Log.LogLevel.INFO,
+                String.format("CtsIncrementalInstallHostTestCases#%s: adb install output: %s",
+                        methodName, adbOutput));
+    }
+
     private boolean hasIncrementalFeature() throws Exception {
         return hasDeviceFeature(FEATURE_INCREMENTAL_DELIVERY);
     }
diff --git a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
index efba5c3..52845e8 100644
--- a/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
+++ b/hostsidetests/inputmethodservice/deviceside/lib/src/android/inputmethodservice/cts/ime/CtsBaseInputMethod.java
@@ -84,6 +84,27 @@
     }
 
     @Override
+    public boolean onEvaluateFullscreenMode() {
+        // Opt-out the fullscreen mode regardless of the existence of the hardware keyboard
+        // and screen rotation.  Otherwise test scenarios become unpredictable.
+        return false;
+    }
+
+    @Override
+    public boolean onEvaluateInputViewShown() {
+        // Always returns true regardless of the existence of the hardware keyboard.
+        // Otherwise test scenarios become unpredictable.
+        return true;
+    }
+
+    @Override
+    public boolean onShowInputRequested(int flags, boolean configChange) {
+        // Always returns true regardless of the existence of the hardware keyboard.
+        // Otherwise test scenarios become unpredictable.
+        return true;
+    }
+
+    @Override
     public void onStartInput(EditorInfo editorInfo, boolean restarting) {
         if (DEBUG) {
             Log.d(mLogTag, "onStartInput:"
diff --git a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
index 1d8a13e..fe23555 100644
--- a/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
+++ b/hostsidetests/multiuser/src/android/host/multiuser/CreateUsersNoAppCrashesTest.java
@@ -81,7 +81,7 @@
     private void assertSwitchToNewUser(int toUserId) throws Exception {
         final String exitString = "Finished processing BOOT_COMPLETED for u" + toUserId;
         final Set<String> appErrors = new LinkedHashSet<>();
-        getDevice().executeAdbCommand("logcat", "-c"); // Reset log
+        getDevice().executeAdbCommand("logcat", "-b", "all", "-c"); // Reset log
         assertTrue("Couldn't switch to user " + toUserId, getDevice().switchUser(toUserId));
         final boolean result = waitForUserSwitchComplete(appErrors, toUserId, exitString);
         assertTrue("Didn't receive BOOT_COMPLETED delivered notification. appErrors="
@@ -92,10 +92,9 @@
     }
 
     private void assertSwitchToUser(int fromUserId, int toUserId) throws Exception {
-        final String exitString = "Continue user switch oldUser #" + fromUserId + ", newUser #"
-                + toUserId;
+        final String exitString = "uc_continue_user_switch: [" + fromUserId + "," + toUserId + "]";
         final Set<String> appErrors = new LinkedHashSet<>();
-        getDevice().executeAdbCommand("logcat", "-c"); // Reset log
+        getDevice().executeAdbCommand("logcat", "-b", "all", "-c"); // Reset log
         assertTrue("Couldn't switch to user " + toUserId, getDevice().switchUser(toUserId));
         final boolean result = waitForUserSwitchComplete(appErrors, toUserId, exitString);
         assertTrue("Didn't reach \"Continue user switch\" stage. appErrors=" + appErrors, result);
@@ -109,8 +108,8 @@
         boolean mExitFound = false;
         long ti = System.currentTimeMillis();
         while (System.currentTimeMillis() - ti < USER_SWITCH_COMPLETE_TIMEOUT_MS) {
-            String logs = getDevice().executeAdbCommand("logcat", "-v", "brief", "-d",
-                    "ActivityManager:D", "AndroidRuntime:E", "*:S");
+            String logs = getDevice().executeAdbCommand("logcat", "-b", "all", "-d",
+                    "ActivityManager:D", "AndroidRuntime:E", "*:I");
             Scanner in = new Scanner(logs);
             while (in.hasNextLine()) {
                 String line = in.nextLine();
diff --git a/hostsidetests/net/AndroidTest.xml b/hostsidetests/net/AndroidTest.xml
index 7cc0dd1..b7fefaf 100644
--- a/hostsidetests/net/AndroidTest.xml
+++ b/hostsidetests/net/AndroidTest.xml
@@ -20,6 +20,7 @@
     <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.compatibility.common.tradefed.targetprep.LocationCheck" />
     <target_preparer class="com.android.cts.net.NetworkPolicyTestsPreparer" />
 
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
index 21212af..29ba68c 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/AbstractRestrictBackgroundNetworkTestCase.java
@@ -126,12 +126,10 @@
     protected Context mContext;
     protected Instrumentation mInstrumentation;
     protected ConnectivityManager mCm;
-    protected WifiManager mWfm;
     protected int mUid;
     private int mMyUid;
     private MyServiceClient mServiceClient;
     private String mDeviceIdleConstantsSetting;
-    private boolean mIsLocationOn;
 
     @Rule
     public final RuleChain mRuleChain = RuleChain.outerRule(new RequiredPropertiesRule())
@@ -144,16 +142,11 @@
         mInstrumentation = getInstrumentation();
         mContext = getContext();
         mCm = getConnectivityManager();
-        mWfm = getWifiManager();
         mUid = getUid(TEST_APP2_PKG);
         mMyUid = getUid(mContext.getPackageName());
         mServiceClient = new MyServiceClient(mContext);
         mServiceClient.bind();
         mDeviceIdleConstantsSetting = "device_idle_constants";
-        mIsLocationOn = isLocationOn();
-        if (!mIsLocationOn) {
-            enableLocation();
-        }
         executeShellCommand("cmd netpolicy start-watching " + mUid);
         setAppIdle(false);
 
@@ -164,33 +157,9 @@
 
     protected void tearDown() throws Exception {
         executeShellCommand("cmd netpolicy stop-watching");
-        if (!mIsLocationOn) {
-            disableLocation();
-        }
         mServiceClient.unbind();
     }
 
-    private void enableLocation() throws Exception {
-        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.LOCATION_MODE,
-                Settings.Secure.LOCATION_MODE_SENSORS_ONLY);
-        assertEquals(Settings.Secure.LOCATION_MODE_SENSORS_ONLY,
-                Settings.Secure.getInt(mContext.getContentResolver(),
-                        Settings.Secure.LOCATION_MODE));
-    }
-
-    private void disableLocation() throws Exception {
-        Settings.Secure.putInt(mContext.getContentResolver(), Settings.Secure.LOCATION_MODE,
-                Settings.Secure.LOCATION_MODE_OFF);
-        assertEquals(Settings.Secure.LOCATION_MODE_OFF,
-                Settings.Secure.getInt(mContext.getContentResolver(),
-                        Settings.Secure.LOCATION_MODE));
-    }
-
-    private boolean isLocationOn() throws Exception {
-        return Settings.Secure.getInt(mContext.getContentResolver(),
-                Settings.Secure.LOCATION_MODE) != Settings.Secure.LOCATION_MODE_OFF;
-    }
-
     protected int getUid(String packageName) throws Exception {
         return mContext.getPackageManager().getPackageUid(packageName, 0);
     }
diff --git a/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java b/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
index ca2864c..3807d79 100644
--- a/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
+++ b/hostsidetests/net/app/src/com/android/cts/net/hostside/NetworkPolicyTestUtils.java
@@ -27,17 +27,20 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
 import android.app.ActivityManager;
 import android.app.Instrumentation;
 import android.content.Context;
+import android.location.LocationManager;
 import android.net.ConnectivityManager;
 import android.net.ConnectivityManager.NetworkCallback;
 import android.net.Network;
 import android.net.NetworkCapabilities;
 import android.net.wifi.WifiManager;
+import android.os.Process;
 import android.text.TextUtils;
 import android.util.Log;
 import android.util.Pair;
@@ -113,6 +116,20 @@
         return am.isLowRamDevice();
     }
 
+    public static boolean isLocationEnabled() {
+        final LocationManager lm = (LocationManager) getContext().getSystemService(
+                Context.LOCATION_SERVICE);
+        return lm.isLocationEnabled();
+    }
+
+    public static void setLocationEnabled(boolean enabled) {
+        final LocationManager lm = (LocationManager) getContext().getSystemService(
+                Context.LOCATION_SERVICE);
+        lm.setLocationEnabledForUser(enabled, Process.myUserHandle());
+        assertEquals("Couldn't change location enabled state", lm.isLocationEnabled(), enabled);
+        Log.d(TAG, "Changed location enabled state to " + enabled);
+    }
+
     public static boolean isActiveNetworkMetered(boolean metered) {
         return getConnectivityManager().isActiveNetworkMetered() == metered;
     }
@@ -128,9 +145,21 @@
         if (isActiveNetworkMetered(metered)) {
             return null;
         }
-        final String ssid = unquoteSSID(getWifiManager().getConnectionInfo().getSSID());
-        setWifiMeteredStatus(ssid, metered);
-        return Pair.create(ssid, !metered);
+        final boolean isLocationEnabled = isLocationEnabled();
+        try {
+            if (!isLocationEnabled) {
+                setLocationEnabled(true);
+            }
+            final String ssid = unquoteSSID(getWifiManager().getConnectionInfo().getSSID());
+            assertNotEquals(WifiManager.UNKNOWN_SSID, ssid);
+            setWifiMeteredStatus(ssid, metered);
+            return Pair.create(ssid, !metered);
+        } finally {
+            // Reset the location enabled state
+            if (!isLocationEnabled) {
+                setLocationEnabled(false);
+            }
+        }
     }
 
     public static void resetMeteredNetwork(String ssid, boolean metered) throws Exception {
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.bp b/hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.bp
index 0f298fb..e6fd1de 100644
--- a/hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-137282168/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Android Open Source Project
+// 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.
@@ -14,27 +14,13 @@
 
 cc_test {
     name: "Bug-137282168",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
     srcs: ["poc.cpp"],
-    compile_multilib: "both",
-    multilib: {
-        lib32: {
-            suffix: "32",
-        },
-        lib64: {
-            suffix: "64",
-        },
-    },
-    test_suites: ["cts"],
     shared_libs: [
         "libbinder",
         "liblog",
         "libutils",
     ],
-    arch: {
-        arm: {
-            instruction_set: "arm",
-        },
-    },
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/hostsidetests/securitybulletin/securityPatch/Bug-137878930/Android.bp b/hostsidetests/securitybulletin/securityPatch/Bug-137878930/Android.bp
index d82e171..700999c 100644
--- a/hostsidetests/securitybulletin/securityPatch/Bug-137878930/Android.bp
+++ b/hostsidetests/securitybulletin/securityPatch/Bug-137878930/Android.bp
@@ -1,4 +1,4 @@
-// Copyright (C) 2019 The Android Open Source Project
+// 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.
@@ -14,17 +14,8 @@
 
 cc_test {
     name: "Bug-137878930",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
     srcs: ["poc.cpp"],
-    compile_multilib: "both",
-    multilib: {
-        lib32: {
-            suffix: "32",
-        },
-        lib64: {
-            suffix: "64",
-        },
-    },
-    test_suites: ["cts"],
     shared_libs: [
         "android.hardware.drm@1.0",
         "android.hardware.drm@1.1",
@@ -32,11 +23,6 @@
         "liblog",
         "libutils",
     ],
-    arch: {
-        arm: {
-            instruction_set: "arm",
-        },
-    },
     cflags: [
         "-Wall",
         "-Werror",
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9536/Android.bp b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9536/Android.bp
new file mode 100644
index 0000000..16f9474
--- /dev/null
+++ b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9536/Android.bp
@@ -0,0 +1,27 @@
+// 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.
+
+cc_test {
+    name: "CVE-2018-9536",
+    defaults: ["cts_hostsidetests_securitybulletin_defaults"],
+    srcs: ["poc.cpp"],
+    include_dirs: [
+        "external/aac/libFDK/include",
+        "external/aac/libSYS/include",
+        "cts/hostsidetests/securitybulletin/securityPatch/includes",
+    ],
+    shared_libs: [
+        "libbluetooth"
+    ],
+}
diff --git a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9536/Android.mk b/hostsidetests/securitybulletin/securityPatch/CVE-2018-9536/Android.mk
deleted file mode 100644
index 541c961..0000000
--- a/hostsidetests/securitybulletin/securityPatch/CVE-2018-9536/Android.mk
+++ /dev/null
@@ -1,34 +0,0 @@
-# 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.
-
-LOCAL_PATH := $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := CVE-2018-9536
-LOCAL_MULTILIB := both
-LOCAL_MODULE_STEM_32 := $(LOCAL_MODULE)32
-LOCAL_MODULE_STEM_64 := $(LOCAL_MODULE)64
-LOCAL_SRC_FILES := poc.cpp
-LOCAL_C_INCLUDES := $(TOP)/external/aac/libFDK/include
-LOCAL_C_INCLUDES += $(TOP)/external/aac/libSYS/include
-LOCAL_C_INCLUDES += $(TOP)/cts/hostsidetests/securitybulletin/securityPatch/includes
-LOCAL_SHARED_LIBRARIES := libbluetooth
-
-# Tag this module as a cts test artifact
-LOCAL_COMPATIBILITY_SUITE := cts sts
-LOCAL_CTS_TEST_PACKAGE := android.security.cts
-
-LOCAL_ARM_MODE := arm
-LOCAL_CPPFLAGS += -Wall -Werror
-include $(BUILD_CTS_EXECUTABLE)
diff --git a/hostsidetests/stagedinstall/Android.bp b/hostsidetests/stagedinstall/Android.bp
index 68dce98..57b8403 100644
--- a/hostsidetests/stagedinstall/Android.bp
+++ b/hostsidetests/stagedinstall/Android.bp
@@ -82,119 +82,340 @@
 
 prebuilt_apex {
     name: "ApexKeyRotationTestV2_SignedBob",
-    src: "testdata/apex/com.android.apex.cts.shim.v2_signed_bob.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_signed_bob.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_signed_bob.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_signed_bob.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_signed_bob.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v2_signed_bob.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "ApexKeyRotationTestV2_SignedBobRot",
-    src: "testdata/apex/com.android.apex.cts.shim.v2_signed_bob_rot.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_signed_bob_rot.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_signed_bob_rot.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_signed_bob_rot.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_signed_bob_rot.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v2_signed_bob_rot.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "ApexKeyRotationTestV2_SignedBobRotRollback",
-    src: "testdata/apex/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "ApexKeyRotationTestV3_SignedBob",
-    src: "testdata/apex/com.android.apex.cts.shim.v3_signed_bob.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v3_signed_bob.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v3_signed_bob.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v3_signed_bob.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v3_signed_bob.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v3_signed_bob.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "ApexKeyRotationTestV3_SignedBobRot",
-    src: "testdata/apex/com.android.apex.cts.shim.v3_signed_bob_rot.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v3_signed_bob_rot.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v3_signed_bob_rot.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v3_signed_bob_rot.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v3_signed_bob_rot.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v3_signed_bob_rot.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "StagedInstallTestApexV1",
-    src: "testdata/apex/com.android.apex.cts.shim.v1.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v1.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v1.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v1.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v1.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v1.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "StagedInstallTestApexV2",
-    src: "testdata/apex/com.android.apex.cts.shim.v2.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v2.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "StagedInstallTestApexV3",
-    src: "testdata/apex/com.android.apex.cts.shim.v3.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v3.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v3.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v3.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v3.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v3.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "StagedInstallTestApexV2_AdditionalFile",
-    src: "testdata/apex/com.android.apex.cts.shim.v2_additional_file.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_additional_file.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_additional_file.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_additional_file.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_additional_file.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v2_additional_file.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "StagedInstallTestApexV2_AdditionalFolder",
-    src: "testdata/apex/com.android.apex.cts.shim.v2_additional_folder.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_additional_folder.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_additional_folder.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_additional_folder.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_additional_folder.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v2_additional_folder.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "StagedInstallTestApexV2_WithPostInstallHook",
-    src: "testdata/apex/com.android.apex.cts.shim.v2_with_post_install_hook.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_with_post_install_hook.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_with_post_install_hook.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_with_post_install_hook.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_with_post_install_hook.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v2_with_post_install_hook.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "StagedInstallTestApexV2_WithPreInstallHook",
-    src: "testdata/apex/com.android.apex.cts.shim.v2_with_pre_install_hook.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_with_pre_install_hook.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_with_pre_install_hook.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_with_pre_install_hook.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_with_pre_install_hook.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v2_with_pre_install_hook.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "StagedInstallTestApexV2_WrongSha",
-    src: "testdata/apex/com.android.apex.cts.shim.v2_wrong_sha.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_wrong_sha.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_wrong_sha.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_wrong_sha.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_wrong_sha.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v2_wrong_sha.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "StagedInstallTestApexV1_NotPreInstalled",
-    src: "testdata/apex/com.android.apex.cts.shim_not_pre_installed.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim_not_pre_installed.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim_not_pre_installed.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim_not_pre_installed.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim_not_pre_installed.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim_not_pre_installed.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "StagedInstallTestApexV2_DifferentCertificate",
-    src: "testdata/apex/com.android.apex.cts.shim.v2_different_certificate.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_different_certificate.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_different_certificate.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_different_certificate.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_different_certificate.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v2_different_certificate.apex",
     installable: false,
 }
 
 prebuilt_apex {
     name: "StagedInstallTestApexV2_NoHashtree",
-    src: "testdata/apex/com.android.apex.cts.shim.v2_no_hashtree.apex",
+    arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_no_hashtree.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_no_hashtree.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_no_hashtree.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_no_hashtree.apex",
+        },
+    },
     filename: "com.android.apex.cts.shim.v2_no_hashtree.apex",
     installable: false,
 }
 
 prebuilt_apex {
   name: "StagedInstallTestApexV2_SdkTargetP",
-  src: "testdata/apex/com.android.apex.cts.shim.v2_sdk_target_p.apex",
+  arch: {
+        arm: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_sdk_target_p.apex",
+        },
+        arm64: {
+              src: "testdata/apex/arm/com.android.apex.cts.shim.v2_sdk_target_p.apex",
+        },
+        x86: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_sdk_target_p.apex",
+        },
+        x86_64: {
+              src: "testdata/apex/x86/com.android.apex.cts.shim.v2_sdk_target_p.apex",
+        },
+    },
   filename: "com.android.apex.cts.shim.v2_sdk_target_p.apex",
   installable: false,
 }
diff --git a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
index d365c45..96e5d80 100644
--- a/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
+++ b/hostsidetests/stagedinstall/src/com/android/tests/stagedinstall/host/StagedInstallTest.java
@@ -70,16 +70,27 @@
                 phase)).isTrue();
     }
 
+    // We do not assert the success of cleanup phase since it might fail due to flaky reasons.
+    private void cleanUp() throws Exception {
+        try {
+            runDeviceTests(PACKAGE_NAME,
+                    "com.android.tests.stagedinstall.StagedInstallTest",
+                    "cleanUp");
+        } catch (AssertionError e) {
+            Log.e(TAG, e);
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
-        runPhase("cleanUp");
+        cleanUp();
         uninstallShimApexIfNecessary();
         storeDefaultLauncher();
     }
 
     @After
     public void tearDown() throws Exception {
-        runPhase("cleanUp");
+        cleanUp();
         uninstallShimApexIfNecessary();
         setDefaultLauncher(mDefaultLauncher);
     }
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v1.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v1.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v1.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v1.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_additional_file.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_additional_file.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_additional_file.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_additional_file.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_additional_folder.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_additional_folder.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_additional_folder.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_additional_folder.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_different_certificate.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_different_certificate.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_different_certificate.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_different_certificate.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_no_hashtree.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_no_hashtree.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_no_hashtree.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_no_hashtree.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_sdk_target_p.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_sdk_target_p.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_sdk_target_p.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_sdk_target_p.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_signed_bob.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_signed_bob.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_signed_bob.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_signed_bob.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_signed_bob_rot.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_signed_bob_rot.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_signed_bob_rot.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_signed_bob_rot.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_with_post_install_hook.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_with_post_install_hook.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_with_post_install_hook.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_with_post_install_hook.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_with_pre_install_hook.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_with_pre_install_hook.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_with_pre_install_hook.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_with_pre_install_hook.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_wrong_sha.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_wrong_sha.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_wrong_sha.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v2_wrong_sha.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v3.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v3.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v3.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v3.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v3_signed_bob.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v3_signed_bob.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v3_signed_bob.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v3_signed_bob.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v3_signed_bob_rot.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v3_signed_bob_rot.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v3_signed_bob_rot.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim.v3_signed_bob_rot.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim_not_pre_installed.apex b/hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim_not_pre_installed.apex
similarity index 100%
rename from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim_not_pre_installed.apex
rename to hostsidetests/stagedinstall/testdata/apex/arm/com.android.apex.cts.shim_not_pre_installed.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v1.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v1.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v1.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v1.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_additional_file.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_additional_file.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_additional_file.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_additional_file.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_additional_folder.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_additional_folder.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_additional_folder.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_additional_folder.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_different_certificate.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_different_certificate.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_different_certificate.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_different_certificate.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_no_hashtree.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_no_hashtree.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_no_hashtree.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_no_hashtree.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_sdk_target_p.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_sdk_target_p.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_sdk_target_p.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_sdk_target_p.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_signed_bob.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_signed_bob.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_signed_bob.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_signed_bob.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_signed_bob_rot.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_signed_bob_rot.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_signed_bob_rot.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_signed_bob_rot.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_signed_bob_rot_rollback.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_with_post_install_hook.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_with_post_install_hook.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_with_post_install_hook.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_with_post_install_hook.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_with_pre_install_hook.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_with_pre_install_hook.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_with_pre_install_hook.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_with_pre_install_hook.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_wrong_sha.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_wrong_sha.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v2_wrong_sha.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v2_wrong_sha.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v3.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v3.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v3.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v3.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v3_signed_bob.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v3_signed_bob.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v3_signed_bob.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v3_signed_bob.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v3_signed_bob_rot.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v3_signed_bob_rot.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim.v3_signed_bob_rot.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim.v3_signed_bob_rot.apex
Binary files differ
diff --git a/hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim_not_pre_installed.apex b/hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim_not_pre_installed.apex
similarity index 100%
copy from hostsidetests/stagedinstall/testdata/apex/com.android.apex.cts.shim_not_pre_installed.apex
copy to hostsidetests/stagedinstall/testdata/apex/x86/com.android.apex.cts.shim_not_pre_installed.apex
Binary files differ
diff --git a/hostsidetests/statsd/Android.bp b/hostsidetests/statsd/Android.bp
index d918213..b76c8d0 100644
--- a/hostsidetests/statsd/Android.bp
+++ b/hostsidetests/statsd/Android.bp
@@ -39,5 +39,6 @@
     data: [
         "**/*.pbtxt",
         ":CtsStatsdApp",
+        ":CtsStatsdEmptyApp",
     ],
 }
diff --git a/hostsidetests/statsd/apps/emptyapp/Android.bp b/hostsidetests/statsd/apps/emptyapp/Android.bp
new file mode 100644
index 0000000..74adb96
--- /dev/null
+++ b/hostsidetests/statsd/apps/emptyapp/Android.bp
@@ -0,0 +1,20 @@
+// 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: "CtsStatsdEmptyApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    v4_signature: true,
+}
diff --git a/hostsidetests/incrementalinstall/app/AndroidManifest.xml b/hostsidetests/statsd/apps/emptyapp/AndroidManifest.xml
similarity index 61%
copy from hostsidetests/incrementalinstall/app/AndroidManifest.xml
copy to hostsidetests/statsd/apps/emptyapp/AndroidManifest.xml
index a1d900c..f40d070 100644
--- a/hostsidetests/incrementalinstall/app/AndroidManifest.xml
+++ b/hostsidetests/statsd/apps/emptyapp/AndroidManifest.xml
@@ -16,16 +16,7 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="android.incrementalinstall.incrementaltestapp"
-          android:versionCode="1"
-          android:versionName="1.0" >
-
-    <application android:label="IncrementalTestApp">
-        <activity android:name=".MainActivity">
-            <intent-filter>
-                <action android:name="android.intent.action.MAIN"/>
-                <category android:name="android.intent.category.LAUNCHER"/>
-            </intent-filter>
-        </activity>
-    </application>
+          package="com.android.cts.device.statsd.emptyapp">
+    <application android:hasCode="false" android:label="Empty Test App" />
 </manifest>
+
diff --git a/hostsidetests/statsd/apps/statsdapp/Android.bp b/hostsidetests/statsd/apps/statsdapp/Android.bp
index b24d1c3..eabea94 100644
--- a/hostsidetests/statsd/apps/statsdapp/Android.bp
+++ b/hostsidetests/statsd/apps/statsdapp/Android.bp
@@ -44,6 +44,7 @@
         "compatibility-device-util-axt",
         "androidx.legacy_legacy-support-v4",
         "androidx.test.rules",
+        "cts-net-utils",
     ],
     jni_libs: ["liblmkhelper"],
     compile_multilib: "both",
diff --git a/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
index 3d27390..d824593 100644
--- a/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
+++ b/hostsidetests/statsd/apps/statsdapp/AndroidManifest.xml
@@ -43,7 +43,8 @@
 
         <service android:name=".StatsdCtsBackgroundService" android:exported="true" />
         <activity android:name=".StatsdCtsForegroundActivity" android:exported="true" />
-        <service android:name=".StatsdCtsForegroundService" android:exported="true" />
+        <service android:name=".StatsdCtsForegroundService"
+                 android:foregroundServiceType="camera|microphone" android:exported="true" />
 
         <activity
             android:name=".VideoPlayerActivity"
diff --git a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
index d08a3af..3f1a18bf 100644
--- a/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
+++ b/hostsidetests/statsd/apps/statsdapp/src/com/android/server/cts/device/statsd/AtomTests.java
@@ -50,6 +50,11 @@
 import android.location.LocationListener;
 import android.location.LocationManager;
 import android.media.MediaPlayer;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.NetworkCapabilities;
+import android.net.NetworkRequest;
+import android.net.cts.util.CtsNetUtils;
 import android.net.wifi.WifiManager;
 import android.os.AsyncTask;
 import android.os.Bundle;
@@ -64,12 +69,15 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 
 import com.android.compatibility.common.util.ShellIdentityUtils;
 
 import org.junit.Test;
 
+import java.net.HttpURLConnection;
+import java.net.URL;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -745,6 +753,108 @@
 
     }
 
+    /**
+     * Bring up and generate some traffic on cellular data connection.
+     */
+    @Test
+    public void testGenerateMobileTraffic() throws Exception {
+        final Context context = InstrumentationRegistry.getContext();
+        doGenerateNetworkTraffic(context, NetworkCapabilities.TRANSPORT_CELLULAR);
+    }
+
+    // Constants which are locally used by doGenerateNetworkTraffic.
+    private static final int NETWORK_TIMEOUT_MILLIS = 15000;
+    private static final String HTTPS_HOST_URL =
+            "https://connectivitycheck.gstatic.com/generate_204";
+    // Minimum and Maximum of iterations of exercise host, @see #doGenerateNetworkTraffic.
+    private static final int MIN_EXERCISE_HOST_ITERATIONS = 1;
+    private static final int MAX_EXERCISE_HOST_ITERATIONS = 19;
+
+    private void doGenerateNetworkTraffic(@NonNull Context context,
+            @NetworkCapabilities.Transport int transport) throws InterruptedException {
+        final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class);
+        final NetworkRequest request = new NetworkRequest.Builder().addCapability(
+                NetworkCapabilities.NET_CAPABILITY_INTERNET).addTransportType(transport).build();
+        final CtsNetUtils.TestNetworkCallback callback = new CtsNetUtils.TestNetworkCallback();
+
+        // Request network, and make http query when the network is available.
+        cm.requestNetwork(request, callback);
+
+        // If network is not available, throws IllegalStateException.
+        final Network network = callback.waitForAvailable();
+        if (network == null) {
+            throw new IllegalStateException("network "
+                    + NetworkCapabilities.transportNameOf(transport) + " is not available.");
+        }
+
+        final long startTime = SystemClock.elapsedRealtime();
+        try {
+            // Since history of network stats only have 2 hours of resolution, when it is
+            // being queried, service will assume that history network stats has uniform
+            // distribution and return a fraction of network stats that is originally
+            // subject to 2 hours. To be specific:
+            //    <returned network stats> = <total network stats> * <duration> / 2 hour,
+            // assuming the duration can fit in a 2 hours bucket.
+            // In the other hand, in statsd, the network stats is queried since boot,
+            // that means in order to assert non-zero packet counts, either the test should
+            // be run after enough time since boot, or the packet counts generated here
+            // should be enough. That is to say:
+            //   <total packet counts> * <up time> / 2 hour >= 1,
+            // or
+            //   iterations >= 2 hour / (<up time> * <packets per iteration>)
+            // Thus, iterations can be chosen based on the factors above to make this
+            // function generate enough packets in each direction to accommodate enough
+            // packet counts for a fraction of history bucket.
+            final double iterations = (TimeUnit.HOURS.toMillis(2) / startTime / 7);
+            // While just enough iterations are going to make the test flaky, add a 20%
+            // buffer to stabilize it and make sure it's in a reasonable range, so it won't
+            // consumes more than 100kb of traffic, or generates 0 byte of traffic.
+            final int augmentedIterations =
+                    (int) Math.max(iterations * 1.2, MIN_EXERCISE_HOST_ITERATIONS);
+            if (augmentedIterations > MAX_EXERCISE_HOST_ITERATIONS) {
+                throw new IllegalStateException("Exceeded max allowed iterations"
+                        + ", iterations=" + augmentedIterations
+                        + ", uptime=" + TimeUnit.MILLISECONDS.toSeconds(startTime) + "s");
+            }
+
+            for (int i = 0; i < augmentedIterations; i++) {
+                // By observing results of "dumpsys netstats --uid", typically the single
+                // run of the https request below generates 4200/1080 rx/tx bytes with
+                // around 7/9 rx/tx packets.
+                // This blocks the thread of NetworkCallback, thus no other event
+                // can be processed before return.
+                exerciseRemoteHost(cm, network, new URL(HTTPS_HOST_URL));
+            }
+            Log.i(TAG, "exerciseRemoteHost successful in " + (SystemClock.elapsedRealtime()
+                    - startTime) + " ms with iterations=" + augmentedIterations
+                    + ", uptime=" + TimeUnit.MILLISECONDS.toSeconds(startTime) + "s");
+        } catch (Exception e) {
+            Log.e(TAG, "exerciseRemoteHost failed in " + (SystemClock.elapsedRealtime()
+                    - startTime) + " ms: " + e);
+        } finally {
+            cm.unregisterNetworkCallback(callback);
+        }
+    }
+
+    /**
+     * Generate traffic on specified network.
+     */
+    private void exerciseRemoteHost(@NonNull ConnectivityManager cm, @NonNull Network network,
+            @NonNull URL url) throws Exception {
+        cm.bindProcessToNetwork(network);
+        HttpURLConnection urlc = null;
+        try {
+            urlc = (HttpURLConnection) network.openConnection(url);
+            urlc.setConnectTimeout(NETWORK_TIMEOUT_MILLIS);
+            urlc.setUseCaches(false);
+            urlc.connect();
+        } finally {
+            if (urlc != null) {
+                urlc.disconnect();
+            }
+        }
+    }
+
     // ------- Helper methods
 
     /** Puts the current thread to sleep. */
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 890a52b..c8c02fc 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
@@ -27,25 +27,15 @@
 import android.graphics.Color;
 import android.graphics.Point;
 import android.net.ConnectivityManager;
-import android.net.Network;
-import android.net.NetworkCapabilities;
-import android.net.NetworkRequest;
 import android.os.AsyncTask;
 import android.os.Bundle;
 import android.os.RemoteException;
-import android.os.SystemClock;
 import android.util.Log;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.WindowManager;
 
-import androidx.annotation.NonNull;
-
-import java.net.HttpURLConnection;
-import java.net.URL;
-import java.util.concurrent.TimeUnit;
-
 /** An activity (to be run as a foreground process) which performs one of a number of actions. */
 public class StatsdCtsForegroundActivity extends Activity {
     private static final String TAG = StatsdCtsForegroundActivity.class.getSimpleName();
@@ -58,18 +48,11 @@
     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 String ACTION_GENERATE_MOBILE_TRAFFIC = "action.generate_mobile_traffic";
     public static final String ACTION_POLL_NETWORK_STATS = "action.poll_network_stats";
 
     public static final int SLEEP_OF_ACTION_SLEEP_WHILE_TOP = 2_000;
     public static final int SLEEP_OF_ACTION_SHOW_APPLICATION_OVERLAY = 2_000;
     public static final int LONG_SLEEP_WHILE_TOP = 60_000;
-    private static final int NETWORK_TIMEOUT_MILLIS = 15000;
-    private static final String HTTPS_HOST_URL =
-            "https://connectivitycheck.gstatic.com/generate_204";
-    // Minimum and Maximum of iterations of exercise host, @see #doGenerateNetworkTraffic.
-    private static final int MIN_EXERCISE_HOST_ITERATIONS = 1;
-    private static final int MAX_EXERCISE_HOST_ITERATIONS = 19;
 
     @Override
     public void onCreate(Bundle bundle) {
@@ -106,9 +89,6 @@
             case ACTION_CREATE_CHANNEL_GROUP:
                 doCreateChannelGroup();
                 break;
-            case ACTION_GENERATE_MOBILE_TRAFFIC:
-                doGenerateNetworkTraffic(NetworkCapabilities.TRANSPORT_CELLULAR);
-                break;
             case ACTION_POLL_NETWORK_STATS:
                 doPollNetworkStats();
                 break;
@@ -194,87 +174,6 @@
         finish();
     }
 
-    private void doGenerateNetworkTraffic(@NetworkCapabilities.Transport int transport) {
-        final ConnectivityManager cm = getSystemService(ConnectivityManager.class);
-        final NetworkRequest request = new NetworkRequest.Builder().addCapability(
-                NetworkCapabilities.NET_CAPABILITY_INTERNET).addTransportType(transport).build();
-        final ConnectivityManager.NetworkCallback cb = new ConnectivityManager.NetworkCallback() {
-            @Override
-            public void onAvailable(@NonNull Network network) {
-                final long startTime = SystemClock.elapsedRealtime();
-                try {
-                    // Since history of network stats only have 2 hours of resolution, when it is
-                    // being queried, service will assume that history network stats has uniform
-                    // distribution and return a fraction of network stats that is originally
-                    // subject to 2 hours. To be specific:
-                    //    <returned network stats> = <total network stats> * <duration> / 2 hour,
-                    // assuming the duration can fit in a 2 hours bucket.
-                    // In the other hand, in statsd, the network stats is queried since boot,
-                    // that means in order to assert non-zero packet counts, either the test should
-                    // be run after enough time since boot, or the packet counts generated here
-                    // should be enough. That is to say:
-                    //   <total packet counts> * <up time> / 2 hour >= 1,
-                    // or
-                    //   iterations >= 2 hour / (<up time> * <packets per iteration>)
-                    // Thus, iterations can be chosen based on the factors above to make this
-                    // function generate enough packets in each direction to accommodate enough
-                    // packet counts for a fraction of history bucket.
-                    final double iterations = (TimeUnit.HOURS.toMillis(2) / startTime / 7);
-                    // While just enough iterations are going to make the test flaky, add a 20%
-                    // buffer to stabilize it and make sure it's in a reasonable range, so it won't
-                    // consumes more than 100kb of traffic, or generates 0 byte of traffic.
-                    final int augmentedIterations =
-                            (int) Math.max(iterations * 1.2, MIN_EXERCISE_HOST_ITERATIONS);
-                    if (augmentedIterations > MAX_EXERCISE_HOST_ITERATIONS) {
-                        throw new IllegalStateException("Exceeded max allowed iterations"
-                                + ", iterations=" + augmentedIterations
-                                + ", uptime=" + TimeUnit.MILLISECONDS.toSeconds(startTime) + "s");
-                    }
-
-                    for (int i = 0; i < augmentedIterations; i++) {
-                        // By observing results of "dumpsys netstats --uid", typically the single
-                        // run of the https request below generates 4200/1080 rx/tx bytes with
-                        // around 7/9 rx/tx packets.
-                        // This blocks the thread of NetworkCallback, thus no other event
-                        // can be processed before return.
-                        exerciseRemoteHost(cm, network, new URL(HTTPS_HOST_URL));
-                    }
-                    Log.i(TAG, "exerciseRemoteHost successful in " + (SystemClock.elapsedRealtime()
-                            - startTime) + " ms with iterations=" + augmentedIterations
-                            + ", uptime=" + TimeUnit.MILLISECONDS.toSeconds(startTime) + "s");
-                } catch (Exception e) {
-                    Log.e(TAG, "exerciseRemoteHost failed in " + (SystemClock.elapsedRealtime()
-                            - startTime) + " ms: " + e);
-                } finally {
-                    cm.unregisterNetworkCallback(this);
-                    finish();
-                }
-            }
-        };
-
-        // Request network, and make http query when the network is available.
-        cm.requestNetwork(request, cb);
-    }
-
-    /**
-     * Generate traffic on specified network.
-     */
-    private void exerciseRemoteHost(@NonNull ConnectivityManager cm, @NonNull Network network,
-            @NonNull URL url) throws Exception {
-        cm.bindProcessToNetwork(network);
-        HttpURLConnection urlc = null;
-        try {
-            urlc = (HttpURLConnection) network.openConnection(url);
-            urlc.setConnectTimeout(NETWORK_TIMEOUT_MILLIS);
-            urlc.setUseCaches(false);
-            urlc.connect();
-        } finally {
-            if (urlc != null) {
-                urlc.disconnect();
-            }
-        }
-    }
-
     // Trigger force poll on NetworkStatsService to make sure the service get most updated network
     // stats from lower layer on subsequent verifications.
     private void doPollNetworkStats() {
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
index 9fe9f70..b85741c 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/AtomTestCase.java
@@ -108,6 +108,8 @@
     public static final String FEATURE_TELEPHONY = "android.hardware.telephony";
     public static final String FEATURE_WATCH = "android.hardware.type.watch";
     public static final String FEATURE_WIFI = "android.hardware.wifi";
+    public static final String FEATURE_INCREMENTAL_DELIVERY =
+            "android.software.incremental_delivery";
 
     protected static final int WAIT_TIME_SHORT = 500;
     protected static final int WAIT_TIME_LONG = 2_000;
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
index 6429570..a074c959 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/BaseTestCase.java
@@ -133,9 +133,11 @@
      * @param pkgName Test package name, such as "com.android.server.cts.netstats".
      * @param testClassName Test class name; either a fully qualified name, or "." + a class name.
      * @param testMethodName Test method name.
+     * @return {@link TestRunResult} of this invocation.
      * @throws DeviceNotAvailableException
      */
-    protected void runDeviceTests(@Nonnull String pkgName,
+    @Nonnull
+    protected TestRunResult runDeviceTests(@Nonnull String pkgName,
             @Nullable String testClassName, @Nullable String testMethodName)
             throws DeviceNotAvailableException {
         if (testClassName != null && testClassName.startsWith(".")) {
@@ -175,6 +177,8 @@
             }
             throw new AssertionError(errorBuilder.toString());
         }
+
+        return result;
     }
 
     protected boolean statsdDisabled() throws DeviceNotAvailableException {
diff --git a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
index 8eb20a5..5203da2 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/atom/UidAtomTests.java
@@ -26,6 +26,7 @@
 import android.server.ErrorSource;
 import android.telephony.NetworkTypeEnum;
 
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 import com.android.compatibility.common.util.PropertyUtil;
 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
 import com.android.os.AtomsProto;
@@ -63,6 +64,7 @@
 import com.android.os.AtomsProto.ProcessMemorySnapshot;
 import com.android.os.AtomsProto.ProcessMemoryState;
 import com.android.os.AtomsProto.ScheduledJobStateChanged;
+import com.android.os.AtomsProto.SettingSnapshot;
 import com.android.os.AtomsProto.SyncStateChanged;
 import com.android.os.AtomsProto.TestAtomReported;
 import com.android.os.AtomsProto.VibratorStateChanged;
@@ -78,11 +80,11 @@
 import com.google.common.collect.Range;
 import com.google.protobuf.Descriptors;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
-import java.util.Objects;
 import java.util.Set;
 
 /**
@@ -99,6 +101,11 @@
     private static final int NUM_APP_OPS = AttributedAppOps.getDefaultInstance().getOp().
             getDescriptorForType().getValues().size() - 1;
 
+    private static final String TEST_INSTALL_APK = "CtsStatsdEmptyApp.apk";
+    private static final String TEST_INSTALL_PACKAGE =
+            "com.android.cts.device.statsd.emptyapp";
+    private static final String TEST_REMOTE_DIR = "/data/local/tmp/statsd";
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -1306,7 +1313,7 @@
           }
 
           assertThat(snapshot.getPid()).isGreaterThan(0);
-          assertThat(snapshot.getAnonRssAndSwapInKilobytes()).isGreaterThan(0);
+          assertThat(snapshot.getAnonRssAndSwapInKilobytes()).isAtLeast(0);
           assertThat(snapshot.getAnonRssAndSwapInKilobytes()).isEqualTo(
                   snapshot.getAnonRssInKilobytes() + snapshot.getSwapInKilobytes());
           assertThat(snapshot.getRssInKilobytes()).isAtLeast(0);
@@ -1864,6 +1871,63 @@
         assertThat(n.getNumPeople()).isEqualTo(0);
     }
 
+    public void testSettingsStatsReported() throws Exception {
+        if (statsdDisabled()) {
+            return;
+        }
+        // Base64 encoded proto com.android.service.nano.StringListParamProto,
+        // which contains two strings "font_scale" and "screen_auto_brightness_adj".
+        final String ENCODED = "ChpzY3JlZW5fYXV0b19icmlnaHRuZXNzX2FkagoKZm9udF9zY2FsZQ";
+        final String FONT_SCALE = "font_scale";
+        SettingSnapshot snapshot = null;
+
+        // Set whitelist through device config.
+        Thread.sleep(WAIT_TIME_SHORT);
+        getDevice().executeShellCommand(
+                "device_config put settings_stats SystemFeature__float_whitelist " + ENCODED);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Get SettingSnapshot as a simple gauge metric.
+        StatsdConfig.Builder config = createConfigBuilder();
+        addGaugeAtomWithDimensions(config, Atom.SETTING_SNAPSHOT_FIELD_NUMBER, null);
+        uploadConfig(config);
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        // Start test app and trigger a pull while it is running.
+        try (AutoCloseable a = withActivity("StatsdCtsForegroundActivity", "action",
+                "action.show_notification")) {
+            Thread.sleep(WAIT_TIME_SHORT);
+            // Trigger a pull and wait for new pull before killing the process.
+            setAppBreadcrumbPredicate();
+            Thread.sleep(WAIT_TIME_LONG);
+        }
+
+        // Test the size of atoms. It should contain at least "font_scale" and
+        // "screen_auto_brightness_adj" two setting values.
+        List<Atom> atoms = getGaugeMetricDataList();
+        assertThat(atoms.size()).isAtLeast(2);
+        for (Atom atom : atoms) {
+            SettingSnapshot settingSnapshot = atom.getSettingSnapshot();
+            if (FONT_SCALE.equals(settingSnapshot.getName())) {
+                snapshot = settingSnapshot;
+                break;
+            }
+        }
+        // Get font_scale value.
+        final float fontScale = Float.parseFloat(
+                getDevice().executeShellCommand("settings get system font_scale"));
+        Thread.sleep(WAIT_TIME_SHORT);
+        // Test the data of atom.
+        assertNotNull(snapshot);
+        assertThat(snapshot.getType()).isEqualTo(
+                SettingSnapshot.SettingsValueType.ASSIGNED_FLOAT_TYPE);
+        assertThat(snapshot.getBoolValue()).isEqualTo(false);
+        assertThat(snapshot.getIntValue()).isEqualTo(0);
+        assertThat(snapshot.getFloatValue()).isEqualTo(fontScale);
+        assertThat(snapshot.getStrValue()).isEqualTo("");
+        assertThat(snapshot.getUserId()).isEqualTo(0);
+    }
+
     public void testIntegrityCheckAtomReportedDuringInstall() throws Exception {
         if (statsdDisabled()) {
             return;
@@ -1955,7 +2019,7 @@
         Thread.sleep(WAIT_TIME_SHORT);
 
         // Generate some traffic on mobile network.
-        runActivity("StatsdCtsForegroundActivity", "action", "action.generate_mobile_traffic");
+        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testGenerateMobileTraffic");
         Thread.sleep(WAIT_TIME_SHORT);
 
         // Force polling NetworkStatsService to get most updated network stats from lower layer.
@@ -1967,6 +2031,7 @@
         Thread.sleep(WAIT_TIME_SHORT);
 
         final List<Atom> atoms = getGaugeMetricDataList();
+
         assertThat(atoms.size()).isAtLeast(1);
 
         boolean foundAppStats = false;
@@ -1983,4 +2048,42 @@
     private interface ThrowingPredicate<S, T extends Throwable> {
         boolean accept(S s) throws T;
     }
+
+    public void testPackageInstallerV2MetricsReported() throws Throwable {
+        if (statsdDisabled()) {
+            return;
+        }
+        if (!hasFeature(FEATURE_INCREMENTAL_DELIVERY, true)) return;
+        createAndUploadConfig(Atom.PACKAGE_INSTALLER_V2_REPORTED_FIELD_NUMBER);
+        Thread.sleep(WAIT_TIME_SHORT);
+        installPackageUsingIncremental(TEST_INSTALL_APK, TEST_REMOTE_DIR);
+        assertTrue(getDevice().isPackageInstalled(TEST_INSTALL_PACKAGE));
+        Thread.sleep(WAIT_TIME_SHORT);
+
+        List<AtomsProto.PackageInstallerV2Reported> reports = new ArrayList<>();
+        for(EventMetricData data : getEventMetricDataList()) {
+            if (data.getAtom().hasPackageInstallerV2Reported()) {
+                reports.add(data.getAtom().getPackageInstallerV2Reported());
+            }
+        }
+        assertEquals(1, reports.size());
+        final AtomsProto.PackageInstallerV2Reported report = reports.get(0);
+        assertTrue(report.getIsIncremental());
+        assertEquals(TEST_INSTALL_PACKAGE, report.getPackageName());
+        assertEquals(1, report.getReturnCode());
+        assertTrue(report.getDurationMillis() > 0);
+
+        getDevice().uninstallPackage(TEST_INSTALL_PACKAGE);
+    }
+
+    private void installPackageUsingIncremental(String apkName, String remoteDirPath)
+            throws Exception {
+        getDevice().executeShellCommand("mkdir " + remoteDirPath);
+        CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(getBuild());
+        final File apk = buildHelper.getTestFile(apkName);
+        assertNotNull(apk);
+        final String remoteApkPath = remoteDirPath + "/" + apk.getName();
+        assertTrue(getDevice().pushFile(apk, remoteApkPath));
+        getDevice().executeShellCommand("pm install-incremental -t -g " + remoteApkPath);
+    }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java b/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
index f239f0e..31401ca 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/subscriber/ShellSubscriberTest.java
@@ -16,10 +16,12 @@
 package android.cts.statsd.subscriber;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import com.android.compatibility.common.util.CpuFeatures;
 import com.android.internal.os.StatsdConfigProto;
-import com.android.os.AtomsProto;
+import com.android.os.AtomsProto.Atom;
+import com.android.os.AtomsProto.SystemUptime;
 import com.android.os.ShellConfig;
 import com.android.os.statsd.ShellDataProto;
 import com.android.tradefed.device.CollectingByteOutputReceiver;
@@ -34,6 +36,7 @@
 import java.nio.ByteBuffer;
 import java.nio.ByteOrder;
 import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Statsd shell data subscription test.
@@ -47,6 +50,34 @@
         sizetBytes = getSizetBytes();
     }
 
+    public void testShellSubscription() {
+        if (sizetBytes < 0) {
+            return;
+        }
+
+        ShellConfig.ShellSubscription config = createConfig();
+        CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+        startSubscription(config, receiver, /*maxTimeoutForCommandSec=*/5,
+                /*subscriptionTimeSec=*/5);
+        checkOutput(receiver);
+    }
+
+    public void testShellSubscriptionReconnect() {
+        if (sizetBytes < 0) {
+            return;
+        }
+
+        ShellConfig.ShellSubscription config = createConfig();
+        for (int i = 0; i < 5; i++) {
+            CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
+            // A subscription time of -1 means that statsd will not impose a timeout on the
+            // subscription. Thus, the client will exit before statsd ends the subscription.
+            startSubscription(config, receiver, /*maxTimeoutForCommandSec=*/5,
+                    /*subscriptionTimeSec=*/-1);
+            checkOutput(receiver);
+        }
+    }
+
     private int getSizetBytes() {
         try {
             ITestDevice device = getDevice();
@@ -62,59 +93,27 @@
         }
     }
 
-    // Tests that anomaly detection for count works.
-    // Also tests that anomaly detection works when spanning multiple buckets.
-    public void testShellSubscription() {
-        if (sizetBytes < 0) {
-            return;
-        }
-        // choose a pulled atom that is likely to be supported on all devices (SYSTEM_UPTIME).
-        // Testing pushed atom is a little trickier, because the executeShellCommand() is blocking
-        // and we cannot push a breadcrumb event at the same time when the shell subscription is
-        // running. So test pulled atom instead.
-        ShellConfig.ShellSubscription config = ShellConfig.ShellSubscription.newBuilder()
-                .addPulled(ShellConfig.PulledAtomSubscription.newBuilder().setMatcher(
-                        StatsdConfigProto.SimpleAtomMatcher.newBuilder()
-                                .setAtomId(AtomsProto.Atom.SYSTEM_UPTIME_FIELD_NUMBER).build())
-                        .setFreqMillis(2000).build()).build();
-        CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
-        startSubscription(config, receiver, 10);
-        byte[] output = receiver.getOutput();
-        // There should be at lease some data returned.
-        assertThat(output.length).isGreaterThan(sizetBytes);
-
-        int atomCount = 0;
-        int i = 0;
-        while (output.length > i + sizetBytes) {
-            int len = 0;
-            for (int j = 0; j < sizetBytes; j++) {
-                len += ((int) output[i + j] & 0xffL) << (sizetBytes * j);
-            }
-            LogUtil.CLog.d("received : " + output.length + " bytes, size : " + len);
-
-            if (output.length < i + sizetBytes + len) {
-                fail("Bad data received.");
-            }
-
-            try {
-                ShellDataProto.ShellData data =
-                        ShellDataProto.ShellData.parseFrom(
-                                Arrays.copyOfRange(output, i + sizetBytes, i + sizetBytes + len));
-                assertThat(data.getAtomCount()).isGreaterThan(0);
-                assertThat(data.getAtom(0).hasSystemUptime()).isTrue();
-                atomCount++;
-                LogUtil.CLog.d("Received " + data.toString());
-            } catch (InvalidProtocolBufferException e) {
-                fail("Failed to parse proto");
-            }
-            i += (sizetBytes + len);
-        }
-
-        assertThat(atomCount).isGreaterThan(0);
+    // Choose a pulled atom that is likely to be supported on all devices (SYSTEM_UPTIME). Testing
+    // pushed atoms is trickier because executeShellCommand() is blocking, so we cannot push a
+    // breadcrumb event while the shell subscription is running.
+    private ShellConfig.ShellSubscription createConfig() {
+        return ShellConfig.ShellSubscription.newBuilder()
+                .addPulled(ShellConfig.PulledAtomSubscription.newBuilder()
+                        .setMatcher(StatsdConfigProto.SimpleAtomMatcher.newBuilder()
+                                .setAtomId(Atom.SYSTEM_UPTIME_FIELD_NUMBER))
+                        .setFreqMillis(2000))
+                .build();
     }
 
-    private void startSubscription(ShellConfig.ShellSubscription config,
-                                   CollectingByteOutputReceiver receiver, int waitTimeSec) {
+    /**
+     * @param maxTimeoutForCommandSec maximum time imposed by adb that the command will run
+     * @param subscriptionTimeSec maximum time imposed by statsd that the subscription will last
+     */
+    private void startSubscription(
+            ShellConfig.ShellSubscription config,
+            CollectingByteOutputReceiver receiver,
+            int maxTimeoutForCommandSec,
+            int subscriptionTimeSec) {
         LogUtil.CLog.d("Uploading the following config:\n" + config.toString());
         try {
             File configFile = File.createTempFile("shellconfig", ".config");
@@ -130,19 +129,65 @@
             getDevice().pushFile(configFile, remotePath);
             LogUtil.CLog.d("waiting....................");
 
-            getDevice().executeShellCommand(
-                    String.join(" ", "cat", remotePath, "|", "cmd stats data-subscribe ",
-                            String.valueOf(waitTimeSec)), receiver);
+            String cmd = String.join(" ", "cat", remotePath, "|", "cmd stats data-subscribe",
+                  String.valueOf(subscriptionTimeSec));
+
+
+            getDevice().executeShellCommand(cmd, receiver, maxTimeoutForCommandSec,
+                    /*maxTimeToOutputShellResponse=*/maxTimeoutForCommandSec, TimeUnit.SECONDS,
+                    /*retryAttempts=*/0);
             getDevice().executeShellCommand("rm " + remotePath);
         } catch (Exception e) {
             fail(e.getMessage());
         }
     }
 
-    byte[] IntToByteArrayLittleEndian(int length) {
+    private byte[] IntToByteArrayLittleEndian(int length) {
         ByteBuffer b = ByteBuffer.allocate(sizetBytes);
         b.order(ByteOrder.LITTLE_ENDIAN);
         b.putInt(length);
         return b.array();
     }
+
+    // We do not know how much data will be returned, but we can check the data format.
+    private void checkOutput(CollectingByteOutputReceiver receiver) {
+        int atomCount = 0;
+        int startIndex = 0;
+
+        byte[] output = receiver.getOutput();
+        assertThat(output.length).isGreaterThan(0);
+        while (output.length > startIndex) {
+            assertThat(output.length).isAtLeast(startIndex + sizetBytes);
+            int dataLength = readSizetFromByteArray(output, startIndex);
+            assertThat(output.length).isAtLeast(startIndex + sizetBytes + dataLength);
+
+            ShellDataProto.ShellData data = null;
+            try {
+                int dataStart = startIndex + sizetBytes;
+                int dataEnd = dataStart + dataLength;
+                data = ShellDataProto.ShellData.parseFrom(
+                        Arrays.copyOfRange(output, dataStart, dataEnd));
+            } catch (InvalidProtocolBufferException e) {
+                fail("Failed to parse proto");
+            }
+
+            assertThat(data.getAtomCount()).isEqualTo(1);
+            assertThat(data.getAtom(0).hasSystemUptime()).isTrue();
+            assertThat(data.getAtom(0).getSystemUptime().getUptimeMillis()).isGreaterThan(0L);
+            atomCount++;
+            startIndex += sizetBytes + dataLength;
+        }
+        assertThat(atomCount).isGreaterThan(0);
+    }
+
+    // Converts the bytes in range [startIndex, startIndex + sizetBytes) from a little-endian array
+    // into an integer. Even though sizetBytes could be greater than 4, we assume that the result
+    // will fit within an int.
+    private int readSizetFromByteArray(byte[] arr, int startIndex) {
+        int value = 0;
+        for (int j = 0; j < sizetBytes; j++) {
+            value += ((int) arr[j + startIndex] & 0xffL) << (8 * j);
+        }
+        return value;
+    }
 }
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
index e489149..3862e6b 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/ProcStatsValidationTests.java
@@ -233,7 +233,7 @@
         assertThat(statsdData).isNotEmpty();
         assertThat(
                 statsdData.get(0).getProcStatsPkgProc().getProcStatsSection()
-                        .getProcessStatsList()
+                        .getPackageStatsList()
         ).isNotEmpty();
 
         // We pull directly from ProcessStatsService, so not necessary to compare every field.
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java
new file mode 100644
index 0000000..7fac92e
--- /dev/null
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingBaseTest.java
@@ -0,0 +1,59 @@
+/*
+ * 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.tagging;
+
+import android.compat.cts.CompatChangeGatingTestCase;
+
+import java.io.FileWriter;
+
+public class TaggingBaseTest extends CompatChangeGatingTestCase {
+
+    protected static final long NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID = 135754954;
+
+    protected boolean supportsTaggedPointers = false;
+
+    private boolean supportsTaggedPointers() throws Exception {
+        if (runCommand("grep 'Processor.*aarch64' /proc/cpuinfo").isEmpty()) {
+            return false;
+        }
+
+        String kernelVersion = runCommand("uname -r");
+        String kernelVersionSplit[] = kernelVersion.split("\\.");
+        if (kernelVersionSplit.length < 2) {
+            return false;
+        }
+
+        int major = Integer.parseInt(kernelVersionSplit[0]);
+        if (major > 4) {
+            return true;
+        } else if (major < 4) {
+            return false;
+        }
+
+        // Major version is 4. Check that the minor is >= 14.
+        int minor = Integer.parseInt(kernelVersionSplit[1]);
+        if (minor < 14) {
+            return false;
+        }
+        return true;
+    }
+
+    @Override
+    protected void setUp() throws Exception {
+        supportsTaggedPointers = supportsTaggedPointers();
+    }
+}
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDefaultTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDefaultTest.java
index db6ef0b..6e99c4e 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDefaultTest.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDefaultTest.java
@@ -16,29 +16,43 @@
 
 package com.android.cts.tagging;
 
-import android.compat.cts.CompatChangeGatingTestCase;
+import com.android.cts.tagging.TaggingBaseTest;
 
 import com.google.common.collect.ImmutableSet;
 
-public class TaggingDefaultTest extends CompatChangeGatingTestCase {
+public class TaggingDefaultTest extends TaggingBaseTest {
 
     protected static final String TEST_APK = "CtsHostsideTaggingNoneApp.apk";
     protected static final String TEST_PKG = "android.cts.tagging.none";
 
-    private static final long NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID = 135754954;
-
     @Override
     protected void setUp() throws Exception {
+        super.setUp();
         installPackage(TEST_APK, true);
     }
 
     public void testCompatFeatureEnabled() throws Exception {
-        runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testHeapTaggingEnabled",
+        if (supportsTaggedPointers) {
+            runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testHeapTaggingEnabled",
                 /*enabledChanges*/ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
                 /*disabledChanges*/ ImmutableSet.of());
+        } else {
+            // Ensure that even if the compat flag is set to true, tagged pointers don't
+            // get enabled on incompatible devices. Ensure that we don't check the statsd
+            // report of the feature status, as it won't be present on kernel-unsupported
+            // devices.
+            runDeviceCompatTestReported(TEST_PKG, ".TaggingTest", "testHeapTaggingDisabled",
+                /*enabledChanges*/ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
+                /*disabledChanges*/ ImmutableSet.of(),
+                /*reportedEnabledChanges*/ ImmutableSet.of(),
+                /*reportedDisabledChanges*/ ImmutableSet.of());
+        }
     }
 
     public void testCompatFeatureDisabled() throws Exception {
+        if (!supportsTaggedPointers) {
+            return;
+        }
         runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testHeapTaggingDisabled",
                 /*enabledChanges*/ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID));
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDisabledTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDisabledTest.java
index 66b2978..50d6df3 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDisabledTest.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingDisabledTest.java
@@ -16,23 +16,24 @@
 
 package com.android.cts.tagging;
 
-import android.compat.cts.CompatChangeGatingTestCase;
+import com.android.cts.tagging.TaggingBaseTest;
 
 import com.google.common.collect.ImmutableSet;
 
-public class TaggingDisabledTest extends CompatChangeGatingTestCase {
+public class TaggingDisabledTest extends TaggingBaseTest {
 
     protected static final String TEST_APK = "CtsHostsideTaggingDisabledApp.apk";
     protected static final String TEST_PKG = "android.cts.tagging.disabled";
 
-    private static final long NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID = 135754954;
-
     @Override
     protected void setUp() throws Exception {
+        super.setUp();
         installPackage(TEST_APK, true);
     }
 
     public void testCompatFeatureEnabled() throws Exception {
+        // Checking for supportsTaggedPointers is unnecessary here, as we don't
+        // validate against reportDisabledChanges.
         runDeviceCompatTestReported(TEST_PKG, ".TaggingTest", "testHeapTaggingDisabled",
                 /*enabledChanges*/ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
                 /*disabledChanges*/ ImmutableSet.of(),
@@ -41,6 +42,8 @@
     }
 
     public void testCompatFeatureDisabled() throws Exception {
+        // Checking for supportsTaggedPointers is unnecessary here, as we don't
+        // validate against reportDisabledChanges.
         runDeviceCompatTestReported(TEST_PKG, ".TaggingTest", "testHeapTaggingDisabled",
                 /*enabledChanges*/ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
diff --git a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingEnabledTest.java b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingEnabledTest.java
index 085b919..6b31626 100644
--- a/hostsidetests/tagging/src/com/android/cts/tagging/TaggingEnabledTest.java
+++ b/hostsidetests/tagging/src/com/android/cts/tagging/TaggingEnabledTest.java
@@ -16,29 +16,34 @@
 
 package com.android.cts.tagging;
 
-import android.compat.cts.CompatChangeGatingTestCase;
+import com.android.cts.tagging.TaggingBaseTest;
 
 import com.google.common.collect.ImmutableSet;
 
-public class TaggingEnabledTest extends CompatChangeGatingTestCase {
+public class TaggingEnabledTest extends TaggingBaseTest {
 
     protected static final String TEST_APK = "CtsHostsideTaggingEnabledApp.apk";
     protected static final String TEST_PKG = "android.cts.tagging.enabled";
 
-    private static final long NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID = 135754954;
-
     @Override
     protected void setUp() throws Exception {
+        super.setUp();
         installPackage(TEST_APK, true);
     }
 
     public void testCompatFeatureEnabled() throws Exception {
+        if (!supportsTaggedPointers) {
+            return;
+        }
         runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testHeapTaggingEnabled",
                 /*enabledChanges*/ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID),
                 /*disabledChanges*/ ImmutableSet.of());
     }
 
     public void testCompatFeatureDisabled() throws Exception {
+        if (!supportsTaggedPointers) {
+            return;
+        }
         runDeviceCompatTest(TEST_PKG, ".TaggingTest", "testHeapTaggingDisabled",
                 /*enabledChanges*/ImmutableSet.of(),
                 /*disabledChanges*/ ImmutableSet.of(NATIVE_HEAP_POINTER_TAGGING_CHANGE_ID));
diff --git a/hostsidetests/userspacereboot/src/com/android/cts/userspacereboot/host/UserspaceRebootHostTest.java b/hostsidetests/userspacereboot/src/com/android/cts/userspacereboot/host/UserspaceRebootHostTest.java
index 0db309f..e50a8ee 100644
--- a/hostsidetests/userspacereboot/src/com/android/cts/userspacereboot/host/UserspaceRebootHostTest.java
+++ b/hostsidetests/userspacereboot/src/com/android/cts/userspacereboot/host/UserspaceRebootHostTest.java
@@ -55,7 +55,13 @@
             "com.android.cts.userspacereboot.bootcompleted";
 
     private void runDeviceTest(String pkgName, String className, String testName) throws Exception {
-        assertThat(runDeviceTests(pkgName, pkgName + "." + className, testName)).isTrue();
+        runDeviceTests(pkgName, pkgName + "." + className, testName);
+    }
+
+    private void runDeviceTest(String pkgName, String className, String testName, Duration timeout)
+            throws Exception {
+        runDeviceTests(
+                getDevice(), pkgName, pkgName + "." + className, testName, timeout.toMillis());
     }
 
     private void installApk(String apkFileName) throws Exception {
@@ -150,13 +156,10 @@
                     "prepareFile");
             rebootUserspaceAndWaitForBootComplete();
             assertUserspaceRebootSucceed();
-            // Sleep for 30s to make sure that system_server has sent out BOOT_COMPLETED broadcast.
-            Thread.sleep(Duration.ofSeconds(30).toMillis());
-            getDevice().executeShellV2Command("am wait-for-broadcast-idle");
             runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
                     "testVerifyCeStorageUnlocked");
             runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
-                    "testVerifyReceivedBootCompletedBroadcast");
+                    "testVerifyReceivedBootCompletedBroadcast", Duration.ofMinutes(6));
         } finally {
             getDevice().executeShellV2Command("cmd lock_settings clear --old 1543");
         }
@@ -175,20 +178,16 @@
             Thread.sleep(500);
             assertWithMessage("Failed to start checkpoint : %s", result.getStderr()).that(
                     result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
-            rebootUserspaceAndWaitForBootComplete();
             getDevice().executeShellV2Command("cmd lock_settings set-pin 1543");
             installApk(BOOT_COMPLETED_TEST_APP_APK);
             runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
                     "prepareFile");
             rebootUserspaceAndWaitForBootComplete();
             assertUserspaceRebootSucceed();
-            // Sleep for 30s to make sure that system_server has sent out BOOT_COMPLETED broadcast.
-            Thread.sleep(Duration.ofSeconds(30).toMillis());
-            getDevice().executeShellV2Command("am wait-for-broadcast-idle");
             runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
                     "testVerifyCeStorageUnlocked");
             runDeviceTest(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME, "BootCompletedUserspaceRebootTest",
-                    "testVerifyReceivedBootCompletedBroadcast");
+                    "testVerifyReceivedBootCompletedBroadcast", Duration.ofMinutes(6));
         } finally {
             getDevice().executeShellV2Command("cmd lock_settings clear --old 1543");
         }
@@ -210,8 +209,8 @@
                 getProperty("init.userspace_reboot.sigterm.timeoutmillis", "");
         try {
             // Explicitly set a very low value to make sure that safety mechanism kicks in.
-            getDevice().setProperty("init.userspace_reboot.sigkill.timeoutmillis", "500");
-            getDevice().setProperty("init.userspace_reboot.sigterm.timeoutmillis", "500");
+            getDevice().setProperty("init.userspace_reboot.sigkill.timeoutmillis", "10");
+            getDevice().setProperty("init.userspace_reboot.sigterm.timeoutmillis", "10");
             rebootUserspaceAndWaitForBootComplete();
             assertUserspaceRebootFailed();
             assertLastBootReasonIs("userspace_failed,shutdown_aborted");
@@ -236,7 +235,7 @@
             getDevice().setProperty("init.userspace_reboot.watchdog.timeoutmillis", "1000");
             rebootUserspaceAndWaitForBootComplete();
             assertUserspaceRebootFailed();
-            assertLastBootReasonIs("userspace_failed,watchdog_triggered");
+            assertLastBootReasonIs("userspace_failed,watchdog_triggered,failed_to_boot");
         } finally {
             getDevice().setProperty("init.userspace_reboot.watchdog.timeoutmillis", defaultValue);
         }
@@ -262,10 +261,12 @@
      * userspace reboot succeeded.
      */
     private void rebootUserspaceAndWaitForBootComplete() throws Exception {
+        Duration timeout = Duration.ofMillis(getDevice().getIntProperty(
+                "init.userspace_reboot.watchdog.timeoutmillis", 0)).plusMinutes(2);
         setProperty("test.userspace_reboot.requested", "1");
         getDevice().rebootUserspaceUntilOnline();
-        assertWithMessage("Device did not boot withing 2 minutes").that(
-                getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
+        assertWithMessage("Device did not boot within %s", timeout).that(
+                getDevice().waitForBootComplete(timeout.toMillis())).isTrue();
     }
 
     /**
diff --git a/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/Android.bp b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/Android.bp
index 76357a6..76fa942 100644
--- a/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/Android.bp
+++ b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/Android.bp
@@ -19,8 +19,9 @@
     static_libs: [
         "androidx.test.runner",
         "androidx.test.core",
+        "compatibility-device-util-axt",
         "testng",
         "truth-prebuilt",
     ],
     min_sdk_version: "30",
-}
\ No newline at end of file
+}
diff --git a/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/src/com/android/cts/userspacereboot/bootcompleted/BootCompletedUserspaceRebootTest.java b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/src/com/android/cts/userspacereboot/bootcompleted/BootCompletedUserspaceRebootTest.java
index bfd8773..6942bf8 100644
--- a/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/src/com/android/cts/userspacereboot/bootcompleted/BootCompletedUserspaceRebootTest.java
+++ b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/src/com/android/cts/userspacereboot/bootcompleted/BootCompletedUserspaceRebootTest.java
@@ -26,13 +26,17 @@
 import android.os.UserManager;
 import android.util.Log;
 
+import com.android.compatibility.common.util.TestUtils;
+
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.JUnit4;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.time.Duration;
 import java.util.Scanner;
 
 /**
@@ -49,6 +53,8 @@
 
     private static final String RECEIVED_BROADCASTS_FILE = "received_broadcasts.txt";
 
+    private static final Duration BOOT_TIMEOUT = Duration.ofMinutes(6);
+
     private final Context mCeContext =
             getInstrumentation().getContext().createCredentialProtectedStorageContext();
     private final Context mDeContext =
@@ -87,6 +93,11 @@
      */
     @Test
     public void testVerifyReceivedBootCompletedBroadcast() throws Exception {
+        final File probe = new File(mDeContext.getFilesDir(), RECEIVED_BROADCASTS_FILE);
+        TestUtils.waitUntil(
+                "Failed to stat " + probe.getAbsolutePath() + " in " + BOOT_TIMEOUT,
+                (int) BOOT_TIMEOUT.getSeconds(),
+                probe::exists);
         try (Scanner scanner = new Scanner(mDeContext.openFileInput(RECEIVED_BROADCASTS_FILE))) {
             final String intent = scanner.nextLine();
             assertThat(intent).isEqualTo(Intent.ACTION_BOOT_COMPLETED);
diff --git a/libs/helpers/interfaces/src/com/android/cts/helpers/ICtsPrintHelper.java b/libs/helpers/interfaces/src/com/android/cts/helpers/ICtsPrintHelper.java
index 687a9fa..e5fc609 100644
--- a/libs/helpers/interfaces/src/com/android/cts/helpers/ICtsPrintHelper.java
+++ b/libs/helpers/interfaces/src/com/android/cts/helpers/ICtsPrintHelper.java
@@ -183,4 +183,34 @@
      * Display the "more info" activity for the currently selected printer.
      */
     void displayMoreInfo();
+
+    /**
+     * Close the printer list that was previously opened with {@link #displayPrinterList()}.
+     */
+    void closePrinterList();
+
+    /**
+     * Close the custom print options that were previously opened with
+     * {@link #openCustomPrintOptions()}.
+     *
+     * This call must be properly nested with other calls to openPrintOptions/closePrintOptions and
+     * print/cancelPrinting.
+     */
+    void closeCustomPrintOptions();
+
+    /**
+     * Close the basic print options that were previously opened with {@link #openPrintOptions()}.
+     *
+     * This call must be properly nested with other calls to
+     * openCustomPrintOptions/closeCustomPrintOptions and print/cancelPrinting.
+     */
+    void closePrintOptions();
+
+    /**
+     * Close the main print UI that as previously opened with {@link PrintManager.print()}.
+     *
+     * This call must be properly nested with other calls to
+     * openCustomPrintOptions/closeCustomPrintOptions and openPrintOptions/closePrintOptions.
+     */
+    void cancelPrinting();
 }
diff --git a/libs/install/Android.bp b/libs/install/Android.bp
index e3b28fe..0e1ebb0 100644
--- a/libs/install/Android.bp
+++ b/libs/install/Android.bp
@@ -93,7 +93,7 @@
 java_library {
     name: "cts-install-lib",
     srcs: ["src/**/*.java"],
-    static_libs: ["androidx.test.rules", "truth-prebuilt"],
+    static_libs: ["androidx.test.rules", "compatibility-device-util-axt", "truth-prebuilt"],
     sdk_version: "test_current",
     java_resources: [
         ":TestAppAv1",
diff --git a/libs/install/src/com/android/cts/install/lib/Install.java b/libs/install/src/com/android/cts/install/lib/Install.java
index 7fec1dc..cd18906 100644
--- a/libs/install/src/com/android/cts/install/lib/Install.java
+++ b/libs/install/src/com/android/cts/install/lib/Install.java
@@ -21,6 +21,8 @@
 import android.content.Intent;
 import android.content.pm.PackageInstaller;
 
+import com.android.compatibility.common.util.SystemUtil;
+
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -203,22 +205,32 @@
      */
     private int createEmptyInstallSession(boolean multiPackage, boolean isApex)
             throws IOException {
-        PackageInstaller.SessionParams params = new PackageInstaller.SessionParams(mSessionMode);
-        if (multiPackage) {
-            params.setMultiPackage();
-        }
-        if (isApex) {
-            params.setInstallAsApex();
-        }
         if (mIsStaged) {
-            params.setStaged();
+            SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check true");
         }
-        params.setRequestDowngrade(mIsDowngrade);
-        params.setEnableRollback(mEnableRollback, mRollbackDataPolicy);
-        if (mInstallFlags != 0) {
-            InstallUtils.mutateInstallFlags(params, mInstallFlags);
+        try {
+            PackageInstaller.SessionParams params =
+                    new PackageInstaller.SessionParams(mSessionMode);
+            if (multiPackage) {
+                params.setMultiPackage();
+            }
+            if (isApex) {
+                params.setInstallAsApex();
+            }
+            if (mIsStaged) {
+                params.setStaged();
+            }
+            params.setRequestDowngrade(mIsDowngrade);
+            params.setEnableRollback(mEnableRollback, mRollbackDataPolicy);
+            if (mInstallFlags != 0) {
+                InstallUtils.mutateInstallFlags(params, mInstallFlags);
+            }
+            return InstallUtils.getPackageInstaller().createSession(params);
+        } finally {
+            if (mIsStaged) {
+                SystemUtil.runShellCommandForNoOutput("pm bypass-staged-installer-check false");
+            }
         }
-        return InstallUtils.getPackageInstaller().createSession(params);
     }
 
     /**
diff --git a/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java b/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
index 08ba87a..4d8a3b9 100644
--- a/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
+++ b/libs/rollback/src/com/android/cts/rollback/lib/RollbackUtils.java
@@ -186,6 +186,22 @@
                 "Rollback did not become unavailable");
     }
 
+    private static boolean hasRollbackInclude(List<RollbackInfo> rollbacks, String packageName) {
+        return rollbacks.stream().anyMatch(
+                ri -> ri.getPackages().stream().anyMatch(
+                        pri -> packageName.equals(pri.getPackageName())));
+    }
+
+    /**
+     * Retries until all rollbacks including {@code packageName} are gone. An assertion is raised if
+     * this does not occur after a certain number of checks.
+     */
+    public static void waitForRollbackGone(
+            Supplier<List<RollbackInfo>> supplier, String packageName) throws InterruptedException {
+        retry(supplier, rollbacks -> !hasRollbackInclude(rollbacks, packageName),
+                "Rollback containing " + packageName + " did not go away");
+    }
+
     private static <T> T retry(Supplier<T> supplier, Predicate<T> predicate, String message)
             throws InterruptedException {
         for (int i = 0; i < RETRY_MAX_INTERVALS; i++) {
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
index d891e54..3974c38 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/BatteryConstraintTest.java
@@ -152,13 +152,6 @@
         assertJobNotReady(BATTERY_JOB_ID);
     }
 
-    static void waitFor(long waitMillis) throws Exception {
-        final long deadline = SystemClock.uptimeMillis() + waitMillis;
-        do {
-            Thread.sleep(500L);
-        } while (SystemClock.uptimeMillis() < deadline);
-    }
-
     // --------------------------------------------------------------------------------------------
     // Positives - schedule jobs under conditions that require them to pass.
     // --------------------------------------------------------------------------------------------
@@ -187,7 +180,7 @@
      */
     public void testBatteryNotLowConstraintExecutes_withPower() throws Exception {
         setBatteryState(true, 100);
-        waitFor(2_000);
+        Thread.sleep(2_000);
         verifyChargingState(true);
         verifyBatteryNotLowState(true);
 
@@ -212,7 +205,7 @@
         }
 
         setBatteryState(false, 100);
-        waitFor(2_000);
+        Thread.sleep(2_000);
         verifyChargingState(false);
         verifyBatteryNotLowState(true);
 
@@ -283,9 +276,9 @@
         if (!hasBattery()) {
             return;
         }
-        if(getInstrumentation().getContext().getPackageManager().hasSystemFeature(FEATURE_WATCH) &&
-               getInstrumentation().getContext().getPackageManager().hasSystemFeature(
-               TWM_HARDWARE_FEATURE)) {
+        if (getInstrumentation().getContext().getPackageManager().hasSystemFeature(FEATURE_WATCH)
+                && getInstrumentation().getContext().getPackageManager().hasSystemFeature(
+                TWM_HARDWARE_FEATURE)) {
             return;
         }
 
@@ -293,7 +286,7 @@
         // setBatteryState() waited for the charging/not-charging state to formally settle,
         // but battery level reporting lags behind that.  wait a moment to let that happen
         // before proceeding.
-        waitFor(2_000);
+        Thread.sleep(2_000);
         verifyChargingState(false);
         verifyBatteryNotLowState(false);
 
@@ -314,7 +307,7 @@
         kTestEnvironment.setExpectedWaitForRun();
         kTestEnvironment.setContinueAfterStart();
         setBatteryState(false, 50);
-        waitFor(2_000);
+        Thread.sleep(2_000);
         verifyChargingState(false);
         verifyBatteryNotLowState(true);
         kTestEnvironment.setExpectedStopped();
@@ -326,7 +319,7 @@
         // And check that the job is stopped if battery goes low again.
         setBatteryState(false, 5);
         setBatteryState(false, 4);
-        waitFor(2_000);
+        Thread.sleep(2_000);
         verifyChargingState(false);
         verifyBatteryNotLowState(false);
         assertTrue("Job with not low constraint did not stop when battery went low.",
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
index 788deb9..f02ceaa 100644
--- a/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/ConnectivityConstraintTest.java
@@ -492,7 +492,7 @@
      */
     static void setWifiState(final boolean enable,
             final ConnectivityManager cm, final WifiManager wm) throws InterruptedException {
-        if (enable != wm.isWifiEnabled()) {
+        if (enable != isWiFiConnected(cm, wm)) {
             NetworkRequest nr = new NetworkRequest.Builder().addCapability(
                     NetworkCapabilities.NET_CAPABILITY_NOT_METERED).build();
             NetworkTracker tracker = new NetworkTracker(false, enable, cm);
@@ -503,14 +503,21 @@
             } else {
                 SystemUtil.runShellCommand("svc wifi disable");
             }
+
+            tracker.waitForStateChange();
+
             assertTrue("Wifi must be " + (enable ? "connected to" : "disconnected from")
                             + " an access point for this test.",
-                    tracker.waitForStateChange() || enable == wm.isWifiEnabled());
+                    enable == isWiFiConnected(cm, wm));
 
             cm.unregisterNetworkCallback(tracker);
         }
     }
 
+    private static boolean isWiFiConnected(final ConnectivityManager cm, final WifiManager wm) {
+        return wm.isWifiEnabled() && cm.getActiveNetwork() != null && !cm.isActiveNetworkMetered();
+    }
+
     /**
      * Disconnect from WiFi in an attempt to connect to cellular data. Worth noting that this is
      * best effort - there are no public APIs to force connecting to cell data. We disable WiFi
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java
deleted file mode 100644
index 0e6fadc..0000000
--- a/tests/JobScheduler/src/android/jobscheduler/cts/DeviceStatesTest.java
+++ /dev/null
@@ -1,206 +0,0 @@
-/*
- * Copyright (C) 2018 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.jobscheduler.cts;
-
-import android.annotation.TargetApi;
-import android.app.job.JobInfo;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.support.test.uiautomator.UiDevice;
-
-import androidx.test.InstrumentationRegistry;
-
-/**
- * Make sure the state of {@link android.app.job.JobScheduler} is correct.
- */
-@TargetApi(28)
-public class DeviceStatesTest extends BaseJobSchedulerTest {
-    /** Unique identifier for the job scheduled by this suite of tests. */
-    public static final int STATE_JOB_ID = DeviceStatesTest.class.hashCode();
-    private static final String TAG = "DeviceStatesTest";
-
-    private PowerManager.WakeLock mWakeLock;
-    private JobInfo.Builder mBuilder;
-    private UiDevice mUiDevice;
-
-    @Override
-    public void setUp() throws Exception {
-        super.setUp();
-        mBuilder = new JobInfo.Builder(STATE_JOB_ID, kJobServiceComponent);
-        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
-        PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
-        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
-        mWakeLock.acquire();
-    }
-
-    @Override
-    public void tearDown() throws Exception {
-        mJobScheduler.cancel(STATE_JOB_ID);
-        // Put device back in to normal operation.
-        toggleScreenOn(true /* screen on */);
-        if (mWakeLock != null && mWakeLock.isHeld()) {
-            mWakeLock.release();
-        }
-
-        super.tearDown();
-    }
-
-    void assertJobReady() throws Exception {
-        assertJobReady(STATE_JOB_ID);
-    }
-
-    void assertJobWaiting() throws Exception {
-        assertJobWaiting(STATE_JOB_ID);
-    }
-
-    void assertJobNotReady() throws Exception {
-        assertJobNotReady(STATE_JOB_ID);
-    }
-
-    static void waitFor(long waitMillis) throws Exception {
-        final long deadline = SystemClock.uptimeMillis() + waitMillis;
-        do {
-             Thread.sleep(500L);
-        } while (SystemClock.uptimeMillis() < deadline);
-    }
-
-    /**
-     * Toggle device is dock idle or dock active.
-     */
-    private void toggleFakeDeviceDockState(final boolean idle) throws Exception {
-        mUiDevice.executeShellCommand("cmd jobscheduler trigger-dock-state "
-                + (idle ? "idle" : "active"));
-        // Wait a moment to let that happen before proceeding.
-        waitFor(2_000);
-    }
-
-    /**
-     * Make sure the screen state.
-     */
-    private void toggleScreenOn(final boolean screenon) throws Exception {
-        if (screenon) {
-            mUiDevice.executeShellCommand("input keyevent KEYCODE_WAKEUP");
-        } else {
-            mUiDevice.executeShellCommand("input keyevent KEYCODE_SLEEP");
-        }
-        // Since the screen on/off intent is ordered, they will not be sent right now.
-        waitFor(2_000);
-    }
-
-    /**
-     * Simulated for idle, and then perform idle maintenance now.
-     */
-    private void triggerIdleMaintenance() throws Exception {
-        mUiDevice.executeShellCommand("cmd activity idle-maintenance");
-        // Wait a moment to let that happen before proceeding.
-        waitFor(2_000);
-    }
-
-    /**
-     * Schedule a job that requires the device is idle, and assert it fired to make
-     * sure the device is idle.
-     */
-    void verifyIdleState() throws Exception {
-        kTestEnvironment.setExpectedExecutions(1);
-        kTestEnvironment.setExpectedWaitForRun();
-        mJobScheduler.schedule(mBuilder.setRequiresDeviceIdle(true).build());
-        assertJobReady();
-        kTestEnvironment.readyToRun();
-
-        assertTrue("Job with idle constraint did not fire on idle",
-                kTestEnvironment.awaitExecution());
-    }
-
-    /**
-     * Schedule a job that requires the device is idle, and assert it failed to make
-     * sure the device is active.
-     */
-    void verifyActiveState() throws Exception {
-        kTestEnvironment.setExpectedExecutions(0);
-        kTestEnvironment.setExpectedWaitForRun();
-        mJobScheduler.schedule(mBuilder.setRequiresDeviceIdle(true).build());
-        assertJobWaiting();
-        assertJobNotReady();
-        kTestEnvironment.readyToRun();
-
-        assertFalse("Job with idle constraint fired while not on idle.",
-                kTestEnvironment.awaitExecution(250));
-    }
-
-    /**
-     * Ensure that device can switch state normally.
-     */
-    public void testDeviceChangeIdleActiveState() throws Exception {
-        toggleScreenOn(true /* screen on */);
-        verifyActiveState();
-
-        // Assert device is idle when screen is off for a while.
-        toggleScreenOn(false /* screen off */);
-        triggerIdleMaintenance();
-        verifyIdleState();
-
-        // Assert device is back to active when screen is on.
-        toggleScreenOn(true /* screen on */);
-        verifyActiveState();
-    }
-
-    /**
-     * Check if dock state is supported.
-     */
-    private boolean isDockStateSupported() {
-        // Car does not support dock state.
-        return !getContext().getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUTOMOTIVE);
-    }
-
-    /**
-     * Ensure that device can switch state on dock normally.
-     */
-    public void testScreenOnDeviceOnDockChangeState() throws Exception {
-        if (!isDockStateSupported()) {
-            return;
-        }
-        toggleScreenOn(true /* screen on */);
-        verifyActiveState();
-
-        // Assert device go to idle if user doesn't interact with device for a while.
-        toggleFakeDeviceDockState(true /* idle */);
-        triggerIdleMaintenance();
-        verifyIdleState();
-
-        // Assert device go back to active if user interacts with device.
-        toggleFakeDeviceDockState(false /* active */);
-        verifyActiveState();
-    }
-
-    /**
-     *  Ensure that ignores this dock intent during screen off.
-     */
-    public void testScreenOffDeviceOnDockNoChangeState() throws Exception {
-        if (!isDockStateSupported()) {
-            return;
-        }
-        toggleScreenOn(false /* screen off */);
-        triggerIdleMaintenance();
-        verifyIdleState();
-
-        toggleFakeDeviceDockState(false /* active */);
-        verifyIdleState();
-    }
-}
diff --git a/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java b/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java
new file mode 100644
index 0000000..6d7d95c
--- /dev/null
+++ b/tests/JobScheduler/src/android/jobscheduler/cts/IdleConstraintTest.java
@@ -0,0 +1,301 @@
+/*
+ * 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.jobscheduler.cts;
+
+import static com.android.compatibility.common.util.TestUtils.waitUntil;
+
+import android.annotation.TargetApi;
+import android.app.UiModeManager;
+import android.app.job.JobInfo;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.support.test.uiautomator.UiDevice;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.BatteryUtils;
+
+/**
+ * Make sure the state of {@link android.app.job.JobScheduler} is correct.
+ */
+public class IdleConstraintTest extends BaseJobSchedulerTest {
+    /** Unique identifier for the job scheduled by this suite of tests. */
+    private static final int STATE_JOB_ID = IdleConstraintTest.class.hashCode();
+    private static final String TAG = "IdleConstraintTest";
+
+    private PowerManager mPowerManager;
+    private JobInfo.Builder mBuilder;
+    private UiDevice mUiDevice;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        mBuilder = new JobInfo.Builder(STATE_JOB_ID, kJobServiceComponent);
+        mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        mJobScheduler.cancel(STATE_JOB_ID);
+        // Put device back in to normal operation.
+        toggleScreenOn(true);
+        setCarMode(false);
+
+        super.tearDown();
+    }
+
+    void assertJobReady() throws Exception {
+        assertJobReady(STATE_JOB_ID);
+    }
+
+    void assertJobWaiting() throws Exception {
+        assertJobWaiting(STATE_JOB_ID);
+    }
+
+    void assertJobNotReady() throws Exception {
+        assertJobNotReady(STATE_JOB_ID);
+    }
+
+    /**
+     * Toggle device is dock idle or dock active.
+     */
+    private void toggleFakeDeviceDockState(final boolean idle) throws Exception {
+        mUiDevice.executeShellCommand("cmd jobscheduler trigger-dock-state "
+                + (idle ? "idle" : "active"));
+        // Wait a moment to let that happen before proceeding.
+        Thread.sleep(2_000);
+    }
+
+    /**
+     * Set the screen state.
+     */
+    private void toggleScreenOn(final boolean screenon) throws Exception {
+        BatteryUtils.turnOnScreen(screenon);
+        // Wait a little bit for the broadcasts to be processed.
+        Thread.sleep(2_000);
+    }
+
+    /**
+     * Simulated for idle, and then perform idle maintenance now.
+     */
+    private void triggerIdleMaintenance() throws Exception {
+        mUiDevice.executeShellCommand("cmd activity idle-maintenance");
+        // Wait a moment to let that happen before proceeding.
+        Thread.sleep(2_000);
+    }
+
+    /**
+     * Schedule a job that requires the device is idle, and assert it fired to make
+     * sure the device is idle.
+     */
+    void verifyIdleState() throws Exception {
+        kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWaitForRun();
+        mJobScheduler.schedule(mBuilder.setRequiresDeviceIdle(true).build());
+        assertJobReady();
+        kTestEnvironment.readyToRun();
+        runSatisfiedJob();
+
+        assertTrue("Job with idle constraint did not fire on idle",
+                kTestEnvironment.awaitExecution());
+    }
+
+    /**
+     * Schedule a job that requires the device is idle, and assert it failed to make
+     * sure the device is active.
+     */
+    void verifyActiveState() throws Exception {
+        kTestEnvironment.setExpectedExecutions(0);
+        kTestEnvironment.setExpectedWaitForRun();
+        mJobScheduler.schedule(mBuilder.setRequiresDeviceIdle(true).build());
+        assertJobWaiting();
+        assertJobNotReady();
+        kTestEnvironment.readyToRun();
+        runSatisfiedJob();
+
+        assertFalse("Job with idle constraint fired while not on idle.",
+                kTestEnvironment.awaitExecution(250));
+    }
+
+    /**
+     * Ensure that device can switch state normally.
+     */
+    public void testDeviceChangeIdleActiveState() throws Exception {
+        toggleScreenOn(true);
+        verifyActiveState();
+
+        // Assert device is idle when screen is off for a while.
+        toggleScreenOn(false);
+        triggerIdleMaintenance();
+        verifyIdleState();
+
+        // Assert device is back to active when screen is on.
+        toggleScreenOn(true);
+        verifyActiveState();
+    }
+
+    /**
+     * Check if dock state is supported.
+     */
+    private boolean isDockStateSupported() {
+        // Car does not support dock state.
+        return !getContext().getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_AUTOMOTIVE);
+    }
+
+    /**
+     * Ensure that device can switch state on dock normally.
+     */
+    @TargetApi(28)
+    public void testScreenOnDeviceOnDockChangeState() throws Exception {
+        if (!isDockStateSupported()) {
+            return;
+        }
+        toggleScreenOn(true);
+        verifyActiveState();
+
+        // Assert device go to idle if user doesn't interact with device for a while.
+        toggleFakeDeviceDockState(true /* idle */);
+        triggerIdleMaintenance();
+        verifyIdleState();
+
+        // Assert device go back to active if user interacts with device.
+        toggleFakeDeviceDockState(false /* active */);
+        verifyActiveState();
+    }
+
+    /**
+     *  Ensure that the tracker ignores this dock intent during screen off.
+     */
+    @TargetApi(28)
+    public void testScreenOffDeviceOnDockNoChangeState() throws Exception {
+        if (!isDockStateSupported()) {
+            return;
+        }
+        toggleScreenOn(false);
+        triggerIdleMaintenance();
+        verifyIdleState();
+
+        toggleFakeDeviceDockState(false /* active */);
+        verifyIdleState();
+    }
+
+    private void setCarMode(boolean on) throws Exception {
+        UiModeManager uiModeManager = getContext().getSystemService(UiModeManager.class);
+        final boolean wasScreenOn = mPowerManager.isInteractive();
+        if (on) {
+            uiModeManager.enableCarMode(0);
+            waitUntil("UI mode didn't change to " + Configuration.UI_MODE_TYPE_CAR,
+                    () -> Configuration.UI_MODE_TYPE_CAR ==
+                            (getContext().getResources().getConfiguration().uiMode
+                                    & Configuration.UI_MODE_TYPE_MASK));
+        } else {
+            uiModeManager.disableCarMode(0);
+            waitUntil("UI mode didn't change from " + Configuration.UI_MODE_TYPE_CAR,
+                    () -> Configuration.UI_MODE_TYPE_CAR !=
+                            (getContext().getResources().getConfiguration().uiMode
+                                    & Configuration.UI_MODE_TYPE_MASK));
+        }
+        Thread.sleep(2_000);
+        if (mPowerManager.isInteractive() != wasScreenOn) {
+            // Apparently setting the car mode can change the screen state >.<
+            Log.d(TAG, "Screen state changed");
+            toggleScreenOn(wasScreenOn);
+        }
+    }
+
+    /**
+     * Ensure car mode is considered active.
+     */
+    public void testCarModePreventsIdle() throws Exception {
+        toggleScreenOn(false);
+
+        setCarMode(true);
+        triggerIdleMaintenance();
+        verifyActiveState();
+
+        setCarMode(false);
+        triggerIdleMaintenance();
+        verifyIdleState();
+    }
+
+    private void runIdleJobStartsOnlyWhenIdle() throws Exception {
+        toggleScreenOn(true);
+
+        kTestEnvironment.setExpectedExecutions(0);
+        kTestEnvironment.setExpectedWaitForRun();
+        mJobScheduler.schedule(mBuilder.setRequiresDeviceIdle(true).build());
+        triggerIdleMaintenance();
+        assertJobWaiting();
+        assertJobNotReady();
+        kTestEnvironment.readyToRun();
+        runSatisfiedJob();
+        assertFalse("Job fired when the device was active.", kTestEnvironment.awaitExecution(500));
+
+        kTestEnvironment.setExpectedExecutions(0);
+        kTestEnvironment.setExpectedWaitForRun();
+        setCarMode(true);
+        toggleScreenOn(false);
+        triggerIdleMaintenance();
+        assertJobWaiting();
+        assertJobNotReady();
+        kTestEnvironment.readyToRun();
+        runSatisfiedJob();
+        assertFalse("Job fired when the device was active.", kTestEnvironment.awaitExecution(500));
+
+        kTestEnvironment.setExpectedExecutions(1);
+        kTestEnvironment.setExpectedWaitForRun();
+        kTestEnvironment.setContinueAfterStart();
+        kTestEnvironment.setExpectedStopped();
+        setCarMode(false);
+        triggerIdleMaintenance();
+        assertJobReady();
+        kTestEnvironment.readyToRun();
+        runSatisfiedJob();
+        assertTrue("Job didn't fire when the device became idle.",
+                kTestEnvironment.awaitExecution());
+    }
+
+    public void testIdleJobStartsOnlyWhenIdle_carEndsIdle() throws Exception {
+        runIdleJobStartsOnlyWhenIdle();
+
+        setCarMode(true);
+        assertTrue("Job didn't stop when the device became active.",
+                kTestEnvironment.awaitStopped());
+    }
+
+    public void testIdleJobStartsOnlyWhenIdle_screenEndsIdle() throws Exception {
+        runIdleJobStartsOnlyWhenIdle();
+
+        toggleScreenOn(true);
+        assertTrue("Job didn't stop when the device became active.",
+                kTestEnvironment.awaitStopped());
+    }
+
+    /** Asks (not forces) JobScheduler to run the job if constraints are met. */
+    private void runSatisfiedJob() throws Exception {
+        mUiDevice.executeShellCommand("cmd jobscheduler run -s"
+                + " -u " + UserHandle.myUserId()
+                + " " + kJobServiceComponent.getPackageName()
+                + " " + STATE_JOB_ID);
+    }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTakeScreenshotTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTakeScreenshotTest.java
index ce04787..c6b8ea9 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTakeScreenshotTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityTakeScreenshotTest.java
@@ -16,8 +16,12 @@
 
 package android.accessibilityservice.cts;
 
+import static android.accessibilityservice.cts.utils.AccessibilityEventFilterUtils.filterWindowsChangedWithChangeTypes;
 import static android.accessibilityservice.cts.utils.ActivityLaunchUtils.launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen;
 import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession;
+import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
+import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+import static android.view.accessibility.AccessibilityEvent.WINDOWS_CHANGE_ADDED;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
@@ -36,12 +40,16 @@
 import android.app.UiAutomation;
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.graphics.ColorSpace;
 import android.graphics.Point;
+import android.graphics.drawable.ColorDrawable;
 import android.hardware.HardwareBuffer;
 import android.os.SystemClock;
 import android.view.Display;
+import android.view.View;
 import android.view.WindowManager;
+import android.widget.ImageView;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -67,6 +75,7 @@
      * The timeout for waiting screenshot had been taken done.
      */
     private static final long TIMEOUT_TAKE_SCREENSHOT_DONE_MILLIS = 1000;
+    public static final int SECURE_WINDOW_CONTENT_COLOR = Color.BLUE;
 
     private static Instrumentation sInstrumentation;
     private static UiAutomation sUiAutomation;
@@ -183,6 +192,66 @@
         }
     }
 
+    @Test
+    public void testTakeScreenshotWithSecureWindow_GetScreenshotAndVerifyBitmap() throws Throwable {
+        final Activity activity = launchActivityOnSpecifiedDisplayAndWaitForItToBeOnscreen(
+                sInstrumentation, sUiAutomation, AccessibilityWindowQueryActivity.class,
+                Display.DEFAULT_DISPLAY);
+
+        final ImageView image = new ImageView(activity);
+        image.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE
+                | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+                | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
+                | View.SYSTEM_UI_FLAG_FULLSCREEN);
+        image.setImageDrawable(new ColorDrawable(SECURE_WINDOW_CONTENT_COLOR));
+
+        final WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+        params.width = WindowManager.LayoutParams.MATCH_PARENT;
+        params.height = WindowManager.LayoutParams.MATCH_PARENT;
+        params.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
+        params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
+                | WindowManager.LayoutParams.FLAG_SECURE;
+
+        sUiAutomation.executeAndWaitForEvent(() -> sInstrumentation.runOnMainSync(
+                () -> {
+                    activity.getWindowManager().addView(image, params);
+                }),
+                filterWindowsChangedWithChangeTypes(WINDOWS_CHANGE_ADDED),
+                DEFAULT_TIMEOUT_MS);
+        takeScreenshot(Display.DEFAULT_DISPLAY);
+
+        verify(mCallback, timeout(TIMEOUT_TAKE_SCREENSHOT_DONE_MILLIS)).onSuccess(
+                mSuccessResultArgumentCaptor.capture());
+
+        final AccessibilityService.ScreenshotResult newScreenshot =
+                mSuccessResultArgumentCaptor.getValue();
+        final Bitmap bitmap = Bitmap.wrapHardwareBuffer(newScreenshot.getHardwareBuffer(),
+                newScreenshot.getColorSpace());
+
+        assertTrue(doesBitmapDisplaySecureContent(activity, bitmap, SECURE_WINDOW_CONTENT_COLOR));
+    }
+
+    private boolean doesBitmapDisplaySecureContent(Activity activity, Bitmap screenshot, int color) {
+        final Display display = activity.getWindowManager().getDefaultDisplay();
+        final Point displaySize = new Point();
+        display.getRealSize(displaySize);
+
+        final int[] pixels = new int[displaySize.x * displaySize.y];
+        final Bitmap bitmap = screenshot.copy(Bitmap.Config.ARGB_8888, false);
+        bitmap.getPixels(pixels, 0, displaySize.x, 0, 0, displaySize.x,
+                displaySize.y);
+        for (int pixel : pixels) {
+            if ((Color.red(pixel) == Color.red(color))
+                    && (Color.green(pixel) == Color.green(color))
+                    && (Color.blue(pixel) == Color.blue(color))) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
     private void takeScreenshot(int displayId) {
         mStartTestingTime = SystemClock.uptimeMillis();
         mService.takeScreenshot(displayId, mContext.getMainExecutor(),
diff --git a/tests/app/app/AndroidManifest.xml b/tests/app/app/AndroidManifest.xml
index c73b7d1..a6fe151 100644
--- a/tests/app/app/AndroidManifest.xml
+++ b/tests/app/app/AndroidManifest.xml
@@ -399,14 +399,11 @@
 
         <activity android:name="android.app.stubs.SendBubbleActivity"
                   android:turnScreenOn="true">
-            <!-- for testing shortcut based bubbles. -->
-            <meta-data android:name="android.app.shortcuts"
-                       android:resource="@xml/shortcuts" />
-            <!-- needs action launcher to be able to have xml shortcuts. -->
             <intent-filter>
-                <action android:name="android.intent.action.MAIN" />
                 <action android:name="android.intent.action.VIEW" />
-                <category android:name="android.intent.category.LAUNCHER" />
+                <action android:name="android.intent.action.SEND" />
+                <data android:mimeType="text/plain" />
+                <data android:mimeType="image/*" />
             </intent-filter>
         </activity>
 
@@ -426,6 +423,9 @@
                 <action android:name="android.intent.action.MAIN" />
                 <category android:name="android.intent.category.LAUNCHER" />
             </intent-filter>
+
+            <meta-data android:name="android.app.shortcuts"
+                       android:resource="@xml/shortcuts" />
         </activity>
 
     </application>
diff --git a/tests/app/app/res/xml/shortcuts.xml b/tests/app/app/res/xml/shortcuts.xml
index 926d22a3..75b48ec 100644
--- a/tests/app/app/res/xml/shortcuts.xml
+++ b/tests/app/app/res/xml/shortcuts.xml
@@ -16,18 +16,9 @@
   -->
 
 <shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
-    <!-- Keep shortcutId in sync with BUBBLE_SHORTCUT_ID_MANIFEST
-         in NotificationManagerTest.java. -->
-    <shortcut
-        android:shortcutId="bubbleShortcutIdManifest"
-        android:enabled="true"
-        android:icon="@drawable/icon_black"
-        android:shortcutShortLabel="@string/bubble_shortcut_label">
-        <intent
-            android:action="android.intent.action.VIEW"
-            android:targetPackage="android.app.stubs"
-            android:targetClass="android.app.stubs.SendBubbleActivity" />
-        <categories android:name="android.shortcut.conversation" />
-    </shortcut>
-
+    <share-target android:targetClass="android.app.stubs.SendBubbleActivity">
+        <data android:mimeType="text/plain" />
+        <data android:mimeType="image/*" />
+        <category android:name="android.app.stubs.SHARE_SHORTCUT_CATEGORY" />
+    </share-target>
 </shortcuts>
\ No newline at end of file
diff --git a/tests/app/app/src/android/app/stubs/BubblesTestService.java b/tests/app/app/src/android/app/stubs/BubblesTestService.java
index 3be1439..fef0e68 100644
--- a/tests/app/app/src/android/app/stubs/BubblesTestService.java
+++ b/tests/app/app/src/android/app/stubs/BubblesTestService.java
@@ -21,12 +21,12 @@
 import android.app.Notification;
 import android.app.PendingIntent;
 import android.app.Person;
-import android.app.RemoteInput;
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.os.IBinder;
+import android.os.SystemClock;
 
 /**
  * Used by NotificationManagerTest for testing policy around bubbles.
@@ -35,6 +35,7 @@
 
     // Should be same as wht NotificationManagerTest is using
     private static final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
+    private static final String BUBBLE_SHORTCUT_ID_DYNAMIC = "bubbleShortcutIdDynamic";
 
     // Must configure foreground service notification in different ways for different tests
     public static final String EXTRA_TEST_CASE =
@@ -57,37 +58,30 @@
         return null;
     }
 
-    private Notification getNotificationForTest(final int testCase, final Context context) {
+    private Notification getNotificationForTest(int testCase, Context context) {
         final Intent intent = new Intent(context, SendBubbleActivity.class);
         final PendingIntent pendingIntent =
                 PendingIntent.getActivity(getApplicationContext(), 0, intent, 0);
-        Notification.Builder nb = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
-                .setContentTitle("foofoo")
-                .setContentIntent(pendingIntent)
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
         Person person = new Person.Builder()
                 .setName("bubblebot")
                 .build();
-        Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder()
-                .setIcon(Icon.createWithResource(context, R.drawable.black))
-                .setIntent(pendingIntent)
-                .build();
-        nb.addPerson(person);
+        Notification.Builder nb = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
+                .setContentTitle("foofoo")
+                .setContentIntent(pendingIntent)
+                .setSmallIcon(android.R.drawable.sym_def_app_icon)
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person));
+        Notification.BubbleMetadata data = new Notification.BubbleMetadata.Builder(pendingIntent,
+                Icon.createWithResource(context, R.drawable.black)).build();
         if (testCase != TEST_MESSAGING) {
-            nb.setCategory(CATEGORY_CALL);
+            nb.setCategory(Notification.CATEGORY_CALL);
         }
+        nb.setShortcutId(BUBBLE_SHORTCUT_ID_DYNAMIC);
         nb.setBubbleMetadata(data);
-        if (testCase == TEST_MESSAGING) {
-            // For messaging policy we need inline reply action
-            RemoteInput remoteInput =
-                    new RemoteInput.Builder("reply_key").setLabel("reply").build();
-            PendingIntent inputIntent = PendingIntent.getActivity(context, 0, new Intent(), 0);
-            Icon icon = Icon.createWithResource(context, android.R.drawable.sym_def_app_icon);
-            Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                    inputIntent).addRemoteInput(remoteInput)
-                    .build();
-            nb.setActions(replyAction);
-        }
         return nb.build();
     }
 }
diff --git a/tests/app/app/src/android/app/stubs/SendBubbleActivity.java b/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
index 223f885..1cbd70f 100644
--- a/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
+++ b/tests/app/app/src/android/app/stubs/SendBubbleActivity.java
@@ -22,7 +22,6 @@
 import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.Person;
-import android.app.RemoteInput;
 import android.content.Context;
 import android.content.Intent;
 import android.graphics.drawable.Icon;
@@ -36,8 +35,9 @@
 public class SendBubbleActivity extends Activity {
     final String TAG = SendBubbleActivity.class.getSimpleName();
 
-    // Should be same as wht NotificationManagerTest is using
+    // Should be same as what NotificationManagerTest is using
     private static final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
+    private static final String SHARE_SHORTCUT_ID = "shareShortcut";
 
     public static final String BUBBLE_ACTIVITY_OPENED =
             "android.app.stubs.BUBBLE_ACTIVITY_OPENED";
@@ -83,17 +83,11 @@
         Person person = new Person.Builder()
                 .setName("bubblebot")
                 .build();
-        // It needs remote input to be bubble-able
-        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel("reply").build();
-        PendingIntent inputIntent = PendingIntent.getActivity(context, 0, new Intent(), 0);
-        Icon icon = Icon.createWithResource(context, android.R.drawable.sym_def_app_icon);
-        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                inputIntent).addRemoteInput(remoteInput)
-                .build();
         // Make it messaging style
         Notification n = new Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
                 .setSmallIcon(R.drawable.black)
                 .setContentTitle("Bubble Chat")
+                .setShortcutId(SHARE_SHORTCUT_ID)
                 .setStyle(new Notification.MessagingStyle(person)
                         .setConversationTitle("Bubble Chat")
                         .addMessage("Hello?",
@@ -101,7 +95,6 @@
                         .addMessage("Is it me you're looking for?",
                                 SystemClock.currentThreadTimeMillis(), person)
                 )
-                .setActions(replyAction)
                 .setBubbleMetadata(getBubbleMetadata(autoExpand, suppressNotification))
                 .build();
 
@@ -118,9 +111,8 @@
         final PendingIntent pendingIntent =
                 PendingIntent.getActivity(context, 0, intent, 0);
 
-        return new Notification.BubbleMetadata.Builder()
-                .setIcon(Icon.createWithResource(context, R.drawable.black))
-                .setIntent(pendingIntent)
+        return new Notification.BubbleMetadata.Builder(pendingIntent,
+                Icon.createWithResource(context, R.drawable.black))
                 .setAutoExpandBubble(autoExpand)
                 .setSuppressNotification(suppressNotification)
                 .build();
diff --git a/tests/app/src/android/app/cts/BaseProcessInstrumentation.java b/tests/app/src/android/app/cts/BaseProcessInstrumentation.java
index 344747a..3ab4337 100644
--- a/tests/app/src/android/app/cts/BaseProcessInstrumentation.java
+++ b/tests/app/src/android/app/cts/BaseProcessInstrumentation.java
@@ -23,7 +23,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
-import android.util.Log;
+import android.os.Handler;
+import android.os.Looper;
 
 public class BaseProcessInstrumentation extends Instrumentation {
     final String mMainProc;
@@ -63,6 +64,17 @@
             // We are running in a secondary proc, just report it.
             //Log.i("xxx", "Instrumentation adding result in " + proc);
             addResults(result);
+            // Just in case something messes up, if it takes too long for this instrumentation
+            // process to finish, then we will force it to finish.
+            new Handler(Looper.myLooper()).postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    Bundle result = new Bundle();
+                    result.putString("FAILURE",
+                            "Timed out waiting for sub-instrumentation to complete");
+                    finish(Activity.RESULT_CANCELED, result);
+                }
+            }, 20 * 1000);
         }
     }
 }
diff --git a/tests/app/src/android/app/cts/NotificationChannelTest.java b/tests/app/src/android/app/cts/NotificationChannelTest.java
index 5727237..bdbb0b7 100644
--- a/tests/app/src/android/app/cts/NotificationChannelTest.java
+++ b/tests/app/src/android/app/cts/NotificationChannelTest.java
@@ -59,7 +59,7 @@
         assertEquals(Notification.AUDIO_ATTRIBUTES_DEFAULT, channel.getAudioAttributes());
         assertEquals(null, channel.getGroup());
         assertTrue(channel.getLightColor() == 0);
-        assertTrue(channel.canBubble());
+        assertFalse(channel.canBubble());
         assertFalse(channel.isImportanceLockedByOEM());
         assertEquals(IMPORTANCE_UNSPECIFIED, channel.getOriginalImportance());
         assertNull(channel.getConversationId());
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index 0951ba0..d8b9e2a 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -65,7 +65,6 @@
 import android.app.NotificationManager.Policy;
 import android.app.PendingIntent;
 import android.app.Person;
-import android.app.RemoteInput;
 import android.app.UiAutomation;
 import android.app.stubs.AutomaticZenRuleActivity;
 import android.app.stubs.BubbledActivity;
@@ -108,6 +107,7 @@
 import android.service.notification.StatusBarNotification;
 import android.service.notification.ZenPolicy;
 import android.test.AndroidTestCase;
+import android.util.ArraySet;
 import android.util.Log;
 import android.widget.RemoteViews;
 
@@ -129,6 +129,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
@@ -138,15 +139,16 @@
 public class NotificationManagerTest extends AndroidTestCase {
     final String TAG = NotificationManagerTest.class.getSimpleName();
     final boolean DEBUG = false;
-    final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
+    static final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
 
     private static final String DELEGATOR = "com.android.test.notificationdelegator";
     private static final String DELEGATE_POST_CLASS = DELEGATOR + ".NotificationDelegateAndPost";
     private static final String REVOKE_CLASS = DELEGATOR + ".NotificationRevoker";
     private static final long SHORT_WAIT_TIME = 100;
     private static final long MAX_WAIT_TIME = 2000;
-    private static final String BUBBLE_SHORTCUT_ID_MANIFEST = "bubbleShortcutIdManifest";
-    private static final String BUBBLE_SHORTCUT_ID_DYNAMIC = "bubbleShortcutIdDynamic";
+    private static final String SHARE_SHORTCUT_ID = "shareShortcut";
+    private static final String SHARE_SHORTCUT_CATEGORY =
+            "android.app.stubs.SHARE_SHORTCUT_CATEGORY";
 
     private PackageManager mPackageManager;
     private AudioManager mAudioManager;
@@ -215,6 +217,10 @@
             mNotificationManager.deleteNotificationChannel(nc.getId());
         }
 
+        // Unsuspend package if it was suspended in the test
+        suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
+                false);
+
         toggleListenerAccess(TestNotificationListener.getId(),
                 InstrumentationRegistry.getInstrumentation(), false);
         toggleNotificationPolicyAccess(mContext.getPackageName(),
@@ -227,14 +233,7 @@
         }
 
         // Restore bubbles setting
-        toggleBubbleSetting(mBubblesEnabledSettingToRestore);
-    }
-
-    private void toggleBubbleSetting(boolean enabled) throws InterruptedException {
-        SystemUtil.runWithShellPermissionIdentity(() ->
-                Settings.Global.putInt(mContext.getContentResolver(),
-                        Settings.Global.NOTIFICATION_BUBBLES, enabled ? 1 : 0));
-        Thread.sleep(500); // wait for ranking update
+        setBubblesGlobal(mBubblesEnabledSettingToRestore);
     }
 
     private boolean isNotificationCancelled(int id, boolean all) {
@@ -627,6 +626,35 @@
                 nm.isNotificationListenerAccessGranted(listenerComponent) == on);
     }
 
+    private void setBubblesGlobal(boolean enabled)
+            throws InterruptedException {
+        SystemUtil.runWithShellPermissionIdentity(() ->
+                Settings.Global.putInt(mContext.getContentResolver(),
+                        Settings.Global.NOTIFICATION_BUBBLES, enabled ? 1 : 0));
+        Thread.sleep(500); // wait for ranking update
+    }
+
+    private void setBubblesAppPref(int pref) throws Exception {
+        int userId = mContext.getUser().getIdentifier();
+        String pkg = mContext.getPackageName();
+        String command = " cmd notification set_bubbles " + pkg
+                + " " + Integer.toString(pref)
+                + " " + userId;
+        runCommand(command, InstrumentationRegistry.getInstrumentation());
+        Thread.sleep(500); // wait for ranking update
+    }
+
+    private void setBubblesChannelAllowed(boolean allowed) throws Exception {
+        int userId = mContext.getUser().getIdentifier();
+        String pkg = mContext.getPackageName();
+        String command = " cmd notification set_bubbles_channel " + pkg
+                + " " + NOTIFICATION_CHANNEL_ID
+                + " " + Boolean.toString(allowed)
+                + " " + userId;
+        runCommand(command, InstrumentationRegistry.getInstrumentation());
+        Thread.sleep(500); // wait for ranking update
+    }
+
     private void runCommand(String command, Instrumentation instrumentation) throws IOException {
         UiAutomation uiAutomation = instrumentation.getUiAutomation();
         // Execute command
@@ -684,6 +712,62 @@
         assertEquals(expectedState, mNotificationManager.getCurrentInterruptionFilter());
     }
 
+    /** Creates a dynamic, longlived, sharing shortcut. Call {@link #deleteShortcuts()} after. */
+    private void createDynamicShortcut() {
+        Person person = new Person.Builder()
+                .setBot(false)
+                .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
+                .setName("BubbleBot")
+                .setImportant(true)
+                .build();
+
+        Set<String> categorySet = new ArraySet<>();
+        categorySet.add(SHARE_SHORTCUT_CATEGORY);
+        Intent shortcutIntent = new Intent(mContext, SendBubbleActivity.class);
+        shortcutIntent.setAction(Intent.ACTION_VIEW);
+
+        ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, SHARE_SHORTCUT_ID)
+                .setShortLabel(SHARE_SHORTCUT_ID)
+                .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
+                .setIntent(shortcutIntent)
+                .setPerson(person)
+                .setCategories(categorySet)
+                .setLongLived(true)
+                .build();
+
+        ShortcutManager scManager =
+                (ShortcutManager) mContext.getSystemService(Context.SHORTCUT_SERVICE);
+        scManager.addDynamicShortcuts(Arrays.asList(shortcut));
+    }
+
+    private void deleteShortcuts() {
+        ShortcutManager scManager =
+                (ShortcutManager) mContext.getSystemService(Context.SHORTCUT_SERVICE);
+        scManager.removeAllDynamicShortcuts();
+    }
+
+    /**
+     * Notification fulfilling conversation policy; for the shortcut to be valid
+     * call {@link #createDynamicShortcut()}
+     */
+    private Notification.Builder getConversationNotification() {
+        Person person = new Person.Builder()
+                .setName("bubblebot")
+                .build();
+        Notification.Builder nb = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+                .setContentTitle("foo")
+                .setShortcutId(SHARE_SHORTCUT_ID)
+                .setStyle(new Notification.MessagingStyle(person)
+                        .setConversationTitle("Bubble Chat")
+                        .addMessage("Hello?",
+                                SystemClock.currentThreadTimeMillis() - 300000, person)
+                        .addMessage("Is it me you're looking for?",
+                                SystemClock.currentThreadTimeMillis(), person)
+                )
+                .setSmallIcon(android.R.drawable.sym_def_app_icon);
+        return nb;
+    }
+
     /**
      * Starts an activity that is able to send a bubble; also handles unlocking the device.
      * Any tests that use this method should be sure to call {@link #cleanupSendBubbleActivity()}
@@ -1384,7 +1468,7 @@
         }
 
         // turn on bubbles globally
-        toggleBubbleSetting(true);
+        setBubblesGlobal(true);
 
         assertEquals(1, Settings.Global.getInt(
                 mContext.getContentResolver(), Settings.Global.NOTIFICATION_BUBBLES));
@@ -1404,13 +1488,13 @@
         for (String key : rankingMap.getOrderedKeys()) {
             if (key.contains(mListener.getPackageName())) {
                 rankingMap.getRanking(key, outRanking);
-                // by default everything can bubble
-                assertTrue(outRanking.canBubble());
+                // by default nothing can bubble
+                assertFalse(outRanking.canBubble());
             }
         }
 
         // turn off bubbles globally
-        toggleBubbleSetting(false);
+        setBubblesGlobal(false);
 
         rankingMap = mListener.mRankingMap;
         outRanking = new NotificationListenerService.Ranking();
@@ -2479,7 +2563,7 @@
     }
 
     public void testAreBubblesAllowed() {
-        assertTrue(mNotificationManager.areBubblesAllowed());
+        assertFalse(mNotificationManager.areBubblesAllowed());
     }
 
     public void testNotificationIcon() {
@@ -2777,71 +2861,26 @@
                 badNumberString);
     }
 
-    public void testNotificationManagerBubblePolicy_flagForMessage_passesNoRemoteInput()
-            throws InterruptedException {
+    public void testNotificationManagerBubblePolicy_flag_intentBubble()
+            throws Exception {
         if (FeatureUtil.isAutomotive()) {
             // Automotive does not support notification bubbles.
             return;
         }
-        // turn on bubbles globally
-        toggleBubbleSetting(true);
+        try {
+            setBubblesGlobal(true);
+            setBubblesAppPref(1 /* all */);
+            createDynamicShortcut();
 
-        Person person = new Person.Builder()
-                .setName("bubblebot")
-                .build();
-        Notification.Builder nb = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
-                .setContentTitle("foo")
-                .setStyle(new Notification.MessagingStyle(person)
-                        .setConversationTitle("Bubble Chat")
-                        .addMessage("Hello?",
-                                SystemClock.currentThreadTimeMillis() - 300000, person)
-                        .addMessage("Is it me you're looking for?",
-                                SystemClock.currentThreadTimeMillis(), person)
-                )
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
-        boolean shouldBeBubble = !mActivityManager.isLowRamDevice();
-        sendAndVerifyBubble(1, nb, null /* use default metadata */, shouldBeBubble);
-    }
-
-    public void testNotificationManagerBubblePolicy_flagForMessage_succeeds()
-            throws InterruptedException {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
-            return;
+            Notification.Builder nb = getConversationNotification();
+            boolean shouldBeBubble = !mActivityManager.isLowRamDevice();
+            sendAndVerifyBubble(1, nb, null /* use default metadata */, shouldBeBubble);
+        } finally {
+            deleteShortcuts();
         }
-
-        // turn on bubbles globally
-        toggleBubbleSetting(true);
-
-        Person person = new Person.Builder()
-                .setName("bubblebot")
-                .build();
-
-        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel(
-                "reply").build();
-        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
-        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                inputIntent).addRemoteInput(remoteInput)
-                .build();
-
-        Notification.Builder nb = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
-                .setContentTitle("foo")
-                .setStyle(new Notification.MessagingStyle(person)
-                        .setConversationTitle("Bubble Chat")
-                        .addMessage("Hello?",
-                                SystemClock.currentThreadTimeMillis() - 300000, person)
-                        .addMessage("Is it me you're looking for?",
-                                SystemClock.currentThreadTimeMillis(), person)
-                )
-                .setActions(replyAction)
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
-        boolean shouldBeBubble = !mActivityManager.isLowRamDevice();
-        sendAndVerifyBubble(1, nb, null /* use default metadata */, shouldBeBubble);
     }
 
-    public void testNotificationManagerBubblePolicy_flagForMessagingWithService_fails()
+    public void testNotificationManagerBubblePolicy_noFlag_service()
             throws Exception {
         if (FeatureUtil.isAutomotive()) {
             // Automotive does not support notification bubbles.
@@ -2849,23 +2888,24 @@
         }
         Intent serviceIntent = new Intent(mContext, BubblesTestService.class);
         serviceIntent.putExtra(EXTRA_TEST_CASE, TEST_MESSAGING);
-
         try {
-            // turn on bubbles globally
-            toggleBubbleSetting(true);
-
+            setBubblesGlobal(true);
+            setBubblesAppPref(1 /* all */);
+            createDynamicShortcut();
             setUpNotifListener();
 
             mContext.startService(serviceIntent);
 
+            // No services in R (allowed in Q)
             verifyNotificationBubbleState(BUBBLE_NOTIF_ID, false /* shouldBeBubble */);
         } finally {
+            deleteShortcuts();
             mContext.stopService(serviceIntent);
         }
     }
 
-    public void testNotificationManagerBubblePolicy_noflagForPhonecall()
-            throws InterruptedException {
+    public void testNotificationManagerBubblePolicy_noFlag_phonecall()
+            throws Exception {
         if (FeatureUtil.isAutomotive()) {
             // Automotive does not support notification bubbles.
             return;
@@ -2874,52 +2914,55 @@
         serviceIntent.putExtra(EXTRA_TEST_CASE, TEST_CALL);
 
         try {
-            // turn on bubbles globally
-            toggleBubbleSetting(true);
-
+            setBubblesGlobal(true);
+            setBubblesAppPref(1 /* all */);
+            createDynamicShortcut();
             setUpNotifListener();
 
             mContext.startService(serviceIntent);
 
-            verifyNotificationBubbleState(BUBBLE_NOTIF_ID, false);
+            // No phonecalls in R (allowed in Q)
+            verifyNotificationBubbleState(BUBBLE_NOTIF_ID, false /* shouldBeBubble */);
         } finally {
+            deleteShortcuts();
             mContext.stopService(serviceIntent);
         }
     }
 
-    public void testNotificationManagerBubblePolicy_flagForAppForeground_fails() throws Exception {
+    public void testNotificationManagerBubblePolicy_noFlag_foreground() throws Exception {
         if (FeatureUtil.isAutomotive()) {
             // Automotive does not support notification bubbles.
             return;
         }
         try {
-            // turn on bubbles globally
-            toggleBubbleSetting(true);
-
+            setBubblesGlobal(true);
+            setBubblesAppPref(1 /* all */);
+            createDynamicShortcut();
             setUpNotifListener();
 
             // Start & get the activity
             SendBubbleActivity a = startSendBubbleActivity();
-
             // Send a bubble that doesn't fulfill policy from foreground
             a.sendInvalidBubble(false /* autoExpand */);
 
-            // Just because app is foreground, doesn't mean they get to bubble
+            // No foreground bubbles that don't fulfill policy in R (allowed in Q)
             verifyNotificationBubbleState(BUBBLE_NOTIF_ID, false /* shouldBeBubble */);
         } finally {
+            deleteShortcuts();
             cleanupSendBubbleActivity();
         }
     }
 
-    public void testNotificationManagerBubble_ensureFlaggedDocumentLaunchMode() throws Exception {
+    public void testNotificationManagerBubble_checkActivityFlagsDocumentLaunchMode()
+            throws Exception {
         if (FeatureUtil.isAutomotive()) {
             // Automotive does not support notification bubbles.
             return;
         }
         try {
-            // turn on bubbles globally
-            toggleBubbleSetting(true);
-
+            setBubblesGlobal(true);
+            setBubblesAppPref(1 /* all */);
+            createDynamicShortcut();
             setUpNotifListener();
 
             // make ourselves foreground so we can auto-expand the bubble & check the intent flags
@@ -2944,171 +2987,203 @@
             assertTrue((activity.getIntent().getFlags() & FLAG_ACTIVITY_NEW_DOCUMENT) != 0);
             assertTrue((activity.getIntent().getFlags() & FLAG_ACTIVITY_MULTIPLE_TASK) != 0);
         } finally {
+            deleteShortcuts();
             cleanupSendBubbleActivity();
         }
     }
 
-    public void testNotificationManagerBubblePolicy_flagForShortcut_manifest_fails()
+    public void testNotificationManagerBubblePolicy_flag_shortcutBubble()
             throws Exception {
         if (FeatureUtil.isAutomotive()) {
             // Automotive does not support notification bubbles.
             return;
         }
-        // turn on bubbles globally
-        toggleBubbleSetting(true);
-
-        // Message notif
-        Person person = new Person.Builder()
-                .setName("bubblebot")
-                .build();
-
-        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel(
-                "reply").build();
-        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
-        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                inputIntent).addRemoteInput(remoteInput)
-                .build();
-
-        Notification.Builder nb = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
-                .setContentTitle("foo")
-                .setStyle(new Notification.MessagingStyle(person)
-                        .setConversationTitle("Bubble Chat")
-                        .addMessage("Hello?",
-                                SystemClock.currentThreadTimeMillis() - 300000, person)
-                        .addMessage("Is it me you're looking for?",
-                                SystemClock.currentThreadTimeMillis(), person)
-                )
-                .setActions(replyAction)
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
-        // BubbleMetadata with manifest shortcut
-        Notification.BubbleMetadata data =
-                new Notification.BubbleMetadata.Builder(BUBBLE_SHORTCUT_ID_MANIFEST).build();
-
-        sendAndVerifyBubble(1, nb, data, false /* shouldBeBubble */);
-    }
-
-    public void testNotificationManagerBubblePolicy_flagForShortcut_dynamic_succeeds()
-            throws Exception {
-        if (FeatureUtil.isAutomotive()) {
-            // Automotive does not support notification bubbles.
-            return;
-        }
-
-        ShortcutManager scmanager = mContext.getSystemService(ShortcutManager.class);
-
         try {
-            // turn on bubbles globally
-            toggleBubbleSetting(true);
+            setBubblesGlobal(true);
+            setBubblesAppPref(1 /* all */);
+            createDynamicShortcut();
 
-            // Make dynamic shortcut
-            Intent shortcutIntent = new Intent(mContext, SendBubbleActivity.class);
-            shortcutIntent.setAction(Intent.ACTION_VIEW);
-            ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, BUBBLE_SHORTCUT_ID_DYNAMIC)
-                    .setShortLabel(BUBBLE_SHORTCUT_ID_DYNAMIC)
-                    .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
-                    .setIntent(shortcutIntent)
-                    .setLongLived(true)
-                    .build();
-            scmanager.addDynamicShortcuts(Arrays.asList(shortcut));
-
-            // Message notif
-            Person person = new Person.Builder()
-                    .setName("bubblebot")
-                    .build();
-
-            RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel(
-                    "reply").build();
-            PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-            Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
-            Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                    inputIntent).addRemoteInput(remoteInput)
-                    .build();
-
-            Notification.Builder nb = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
-                    .setContentTitle("foo")
-                    .setStyle(new Notification.MessagingStyle(person)
-                            .setConversationTitle("Bubble Chat")
-                            .addMessage("Hello?",
-                                    SystemClock.currentThreadTimeMillis() - 300000, person)
-                            .addMessage("Is it me you're looking for?",
-                                    SystemClock.currentThreadTimeMillis(), person)
-                    )
-                    .setActions(replyAction)
-                    .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
-            // BubbleMetadata with our dynamic shortcut ic
+            Notification.Builder nb = getConversationNotification();
             Notification.BubbleMetadata data =
-                    new Notification.BubbleMetadata.Builder(BUBBLE_SHORTCUT_ID_DYNAMIC)
+                    new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID)
                             .build();
 
             boolean shouldBeBubble = !mActivityManager.isLowRamDevice();
             sendAndVerifyBubble(1, nb, data, shouldBeBubble);
         } finally {
-            // remove the shortcut
-            scmanager.removeAllDynamicShortcuts();
+            deleteShortcuts();
         }
     }
 
-    public void testNotificationManagerBubblePolicy_flagForShortcut_fails_invalidShortcut()
+    public void testNotificationManagerBubblePolicy_noFlag_invalidShortcut()
             throws Exception {
         if (FeatureUtil.isAutomotive()) {
             // Automotive does not support notification bubbles.
             return;
         }
-        // turn on bubbles globally
-        toggleBubbleSetting(true);
+        try {
+            setBubblesGlobal(true);
+            setBubblesAppPref(1 /* all */);
+            createDynamicShortcut();
 
-        // Message notif
-        Person person = new Person.Builder()
-                .setName("bubblebot")
-                .build();
+            Notification.Builder nb = getConversationNotification();
+            nb.setShortcutId("invalid");
+            Notification.BubbleMetadata data =
+                    new Notification.BubbleMetadata.Builder("invalid")
+                            .build();
 
-        RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel(
-                "reply").build();
-        PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-        Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
-        Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                inputIntent).addRemoteInput(remoteInput)
-                .build();
-
-        Notification.Builder nb = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
-                .setContentTitle("foo")
-                .setStyle(new Notification.MessagingStyle(person)
-                        .setConversationTitle("Bubble Chat")
-                        .addMessage("Hello?",
-                                SystemClock.currentThreadTimeMillis() - 300000, person)
-                        .addMessage("Is it me you're looking for?",
-                                SystemClock.currentThreadTimeMillis(), person)
-                )
-                .setActions(replyAction)
-                .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
-        // BubbleMetadata with shortcut that doesn't exist
-        Notification.BubbleMetadata data =
-                new Notification.BubbleMetadata.Builder("shortcutDoesntExist")
-                        .build();
-
-        sendAndVerifyBubble(1, nb, data, false);
+            sendAndVerifyBubble(1, nb, data, false);
+        } finally {
+            deleteShortcuts();
+        }
     }
 
-    public void testNotificationManagerBubblePolicy_flagForShortcut_fails_invalidNotif()
+    public void testNotificationManagerBubblePolicy_noFlag_invalidNotif()
             throws Exception {
         if (FeatureUtil.isAutomotive()) {
             // Automotive does not support notification bubbles.
             return;
         }
-        // turn on bubbles globally
-        toggleBubbleSetting(true);
+        try {
+            setBubblesGlobal(true);
+            setBubblesAppPref(1 /* all */);
+            createDynamicShortcut();
 
-        // BubbleMetadata with manifest shortcut
-        Notification.BubbleMetadata data =
-                new Notification.BubbleMetadata.Builder(BUBBLE_SHORTCUT_ID_MANIFEST)
-                        .build();
+            Notification.BubbleMetadata data =
+                    new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID)
+                            .build();
 
-        sendAndVerifyBubble(1, null /* use default notif builder */, data,
-                false /* shouldBeBubble */);
+            sendAndVerifyBubble(1, null /* use default notif builder */, data,
+                    false /* shouldBeBubble */);
+        } finally {
+            deleteShortcuts();
+        }
+    }
+
+    public void testNotificationManagerBubblePolicy_appAll_globalOn() throws Exception {
+        if (FeatureUtil.isAutomotive()) {
+            // Automotive does not support notification bubbles.
+            return;
+        }
+        try {
+            setBubblesGlobal(true);
+            setBubblesAppPref(1 /* all */);
+            createDynamicShortcut();
+            Notification.BubbleMetadata data =
+                    new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID)
+                            .build();
+            Notification.Builder nb = getConversationNotification();
+
+            boolean shouldBeBubble = !mActivityManager.isLowRamDevice();
+            sendAndVerifyBubble(1, nb, data, shouldBeBubble);
+        } finally {
+            deleteShortcuts();
+        }
+    }
+
+    public void testNotificationManagerBubblePolicy_appAll_globalOff() throws Exception {
+        if (FeatureUtil.isAutomotive()) {
+            // Automotive does not support notification bubbles.
+            return;
+        }
+        try {
+            setBubblesGlobal(false);
+            setBubblesAppPref(1 /* all */);
+            createDynamicShortcut();
+            Notification.BubbleMetadata data =
+                    new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID)
+                            .build();
+            Notification.Builder nb = getConversationNotification();
+
+            sendAndVerifyBubble(1, nb, data, false);
+        } finally {
+            deleteShortcuts();
+        }
+    }
+
+    public void testNotificationManagerBubblePolicy_appAll_channelNo() throws Exception {
+        if (FeatureUtil.isAutomotive()) {
+            // Automotive does not support notification bubbles.
+            return;
+        }
+        try {
+            setBubblesGlobal(true);
+            setBubblesAppPref(1 /* all */);
+            setBubblesChannelAllowed(false);
+            createDynamicShortcut();
+            Notification.BubbleMetadata data =
+                    new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID)
+                            .build();
+            Notification.Builder nb = getConversationNotification();
+
+            sendAndVerifyBubble(1, nb, data, false);
+        } finally {
+            deleteShortcuts();
+        }
+    }
+
+    public void testNotificationManagerBubblePolicy_appSelected_channelNo() throws Exception {
+        if (FeatureUtil.isAutomotive()) {
+            // Automotive does not support notification bubbles.
+            return;
+        }
+        try {
+            setBubblesGlobal(true);
+            setBubblesAppPref(2 /* selected */);
+            setBubblesChannelAllowed(false);
+            createDynamicShortcut();
+            Notification.BubbleMetadata data =
+                    new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID)
+                            .build();
+            Notification.Builder nb = getConversationNotification();
+
+            sendAndVerifyBubble(1, nb, data, false);
+        } finally {
+            deleteShortcuts();
+        }
+    }
+
+    public void testNotificationManagerBubblePolicy_appSelected_channelYes() throws Exception {
+        if (FeatureUtil.isAutomotive()) {
+            // Automotive does not support notification bubbles.
+            return;
+        }
+        try {
+            setBubblesGlobal(true);
+            setBubblesAppPref(2 /* selected */);
+            setBubblesChannelAllowed(true);
+            createDynamicShortcut();
+            Notification.BubbleMetadata data =
+                    new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID)
+                            .build();
+            Notification.Builder nb = getConversationNotification();
+
+            boolean shouldBeBubble = !mActivityManager.isLowRamDevice();
+            sendAndVerifyBubble(1, nb, data, shouldBeBubble);
+        } finally {
+            deleteShortcuts();
+        }
+    }
+
+    public void testNotificationManagerBubblePolicy_appNone_channelNo() throws Exception {
+        if (FeatureUtil.isAutomotive()) {
+            // Automotive does not support notification bubbles.
+            return;
+        }
+        try {
+            setBubblesGlobal(true);
+            setBubblesAppPref(0 /* none */);
+            setBubblesChannelAllowed(false);
+            createDynamicShortcut();
+            Notification.BubbleMetadata data =
+                    new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID)
+                            .build();
+            Notification.Builder nb = getConversationNotification();
+
+            sendAndVerifyBubble(1, nb, data, false);
+        } finally {
+            deleteShortcuts();
+        }
     }
 
     public void testNotificationManagerBubblePolicy_noFlag_shortcutRemoved()
@@ -3118,68 +3193,24 @@
             return;
         }
 
-        ShortcutManager scmanager = mContext.getSystemService(ShortcutManager.class);
-
         try {
-            // turn on bubbles globally
-            toggleBubbleSetting(true);
-
-            setUpNotifListener();
-
-            // Make dynamic shortcut
-            Intent shortcutIntent = new Intent(mContext, SendBubbleActivity.class);
-            shortcutIntent.setAction(Intent.ACTION_VIEW);
-            ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, BUBBLE_SHORTCUT_ID_DYNAMIC)
-                    .setShortLabel(BUBBLE_SHORTCUT_ID_DYNAMIC)
-                    .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
-                    .setIntent(shortcutIntent)
-                    .setLongLived(true)
-                    .build();
-            scmanager.addDynamicShortcuts(Arrays.asList(shortcut));
-
-            // Message notif
-            Person person = new Person.Builder()
-                    .setName("bubblebot")
-                    .build();
-
-            RemoteInput remoteInput = new RemoteInput.Builder("reply_key").setLabel(
-                    "reply").build();
-            PendingIntent inputIntent = PendingIntent.getActivity(mContext, 0, new Intent(), 0);
-            Icon icon = Icon.createWithResource(mContext, android.R.drawable.sym_def_app_icon);
-            Notification.Action replyAction = new Notification.Action.Builder(icon, "Reply",
-                    inputIntent).addRemoteInput(remoteInput)
-                    .build();
-
-            Notification.Builder nb = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
-                    .setContentTitle("foo")
-                    .setStyle(new Notification.MessagingStyle(person)
-                            .setConversationTitle("Bubble Chat")
-                            .addMessage("Hello?",
-                                    SystemClock.currentThreadTimeMillis() - 300000, person)
-                            .addMessage("Is it me you're looking for?",
-                                    SystemClock.currentThreadTimeMillis(), person)
-                    )
-                    .setActions(replyAction)
-                    .setSmallIcon(android.R.drawable.sym_def_app_icon);
-
-            // BubbleMetadata with our dynamic shortcut
+            setBubblesGlobal(true);
+            setBubblesAppPref(1 /* all */);
+            createDynamicShortcut();
             Notification.BubbleMetadata data =
-                    new Notification.BubbleMetadata.Builder(BUBBLE_SHORTCUT_ID_DYNAMIC)
+                    new Notification.BubbleMetadata.Builder(SHARE_SHORTCUT_ID)
                             .build();
+            Notification.Builder nb = getConversationNotification();
 
             boolean shouldBeBubble = !mActivityManager.isLowRamDevice();
             sendAndVerifyBubble(1, nb, data, shouldBeBubble);
-
             mListener.resetData();
 
-            // Now lets delete the shortcut and make sure the notif has been updated to not
-            // be a bubble.
-            scmanager.removeAllDynamicShortcuts();
+            deleteShortcuts();
 
             verifyNotificationBubbleState(1, false /* should be bubble */);
         } finally {
-            // remove the shortcut
-            scmanager.removeAllDynamicShortcuts();
+            deleteShortcuts();
         }
     }
 
@@ -3189,9 +3220,9 @@
             return;
         }
         try {
-            // turn on bubbles globally
-            toggleBubbleSetting(true);
-
+            setBubblesGlobal(true);
+            setBubblesAppPref(1 /* all */);
+            createDynamicShortcut();
             setUpNotifListener();
 
             // make ourselves foreground so we can specify suppress notification flag
@@ -3226,11 +3257,7 @@
             assertFalse(metadata.isNotificationSuppressed());
         } finally {
             cleanupSendBubbleActivity();
-
-            // turn off bubbles globally
-            toggleBubbleSetting(false);
-
-            mListener.resetData();
+            deleteShortcuts();
         }
     }
 
diff --git a/tests/app/src/android/app/cts/WallpaperManagerTest.java b/tests/app/src/android/app/cts/WallpaperManagerTest.java
index 6f0fd4e..5b0f9a1 100644
--- a/tests/app/src/android/app/cts/WallpaperManagerTest.java
+++ b/tests/app/src/android/app/cts/WallpaperManagerTest.java
@@ -51,8 +51,6 @@
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.compatibility.common.util.SystemUtil;
-
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
@@ -104,7 +102,6 @@
 
     @After
     public void tearDown() throws Exception {
-        mWallpaperManager.clear();
         mContext.unregisterReceiver(mBroadcastReceiver);
     }
 
@@ -320,16 +317,13 @@
 
     @Test
     public void highRatioWallpaper_largeWidth() throws Exception {
-        final String sysuiPid = getSysuiPid();
-        Bitmap highRatioWallpaper = Bitmap.createBitmap(800, 8000, Bitmap.Config.ARGB_8888);
+        Bitmap highRatioWallpaper = Bitmap.createBitmap(8000, 800, Bitmap.Config.ARGB_8888);
         Canvas canvas = new Canvas(highRatioWallpaper);
         canvas.drawColor(Color.RED);
 
         try {
             mWallpaperManager.setBitmap(highRatioWallpaper);
-
-            Assert.assertTrue(mCountDownLatch.await(5, TimeUnit.SECONDS));
-            Assert.assertTrue(sysuiPid.contentEquals(getSysuiPid()));
+            assertBitmapDimensions(mWallpaperManager.getBitmap());
         } finally {
             highRatioWallpaper.recycle();
         }
@@ -337,16 +331,13 @@
 
     @Test
     public void highRatioWallpaper_largeHeight() throws Exception {
-        final String sysuiPid = getSysuiPid();
-        Bitmap highRatioWallpaper = Bitmap.createBitmap(8000, 800, Bitmap.Config.ARGB_8888);
+        Bitmap highRatioWallpaper = Bitmap.createBitmap(800, 8000, Bitmap.Config.ARGB_8888);
         Canvas canvas = new Canvas(highRatioWallpaper);
         canvas.drawColor(Color.RED);
 
         try {
             mWallpaperManager.setBitmap(highRatioWallpaper);
-
-            Assert.assertTrue(mCountDownLatch.await(5, TimeUnit.SECONDS));
-            Assert.assertTrue(sysuiPid.contentEquals(getSysuiPid()));
+            assertBitmapDimensions(mWallpaperManager.getBitmap());
         } finally {
             highRatioWallpaper.recycle();
         }
@@ -354,16 +345,13 @@
 
     @Test
     public void highResolutionWallpaper() throws Exception {
-        final String sysuiPid = getSysuiPid();
         Bitmap highResolutionWallpaper = Bitmap.createBitmap(10000, 10000, Bitmap.Config.ARGB_8888);
         Canvas canvas = new Canvas(highResolutionWallpaper);
         canvas.drawColor(Color.BLUE);
 
         try {
             mWallpaperManager.setBitmap(highResolutionWallpaper);
-
-            Assert.assertTrue(mCountDownLatch.await(5, TimeUnit.SECONDS));
-            Assert.assertTrue(sysuiPid.contentEquals(getSysuiPid()));
+            assertBitmapDimensions(mWallpaperManager.getBitmap());
         } finally {
             highResolutionWallpaper.recycle();
         }
@@ -414,10 +402,13 @@
         }
     }
 
-    private static String getSysuiPid() {
-        final String sysuiPkgName = "com.android.systemui";
-        final String sysuiPid = "pidof " + sysuiPkgName;
-        return SystemUtil.runShellCommand(sysuiPid);
+    private void assertBitmapDimensions(Bitmap bitmap) {
+        int maxSize = getMaxTextureSize();
+        boolean safe = false;
+        if (bitmap != null) {
+            safe = bitmap.getWidth() <= maxSize && bitmap.getHeight() <= maxSize;
+        }
+        assertThat(safe).isTrue();
     }
 
     private void assertDesiredDimension(Point suggestedSize, Point expectedSize) {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryCommonTestCase.java
index 7504705..931193f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryCommonTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/FillEventHistoryCommonTestCase.java
@@ -271,6 +271,7 @@
         waitUntilConnected();
         sReplier.getNextFillRequest();
         mUiBot.selectDataset("dataset1");
+        mUiBot.waitForIdleSync();
         mActivity.assertAutoFilled();
 
         {
@@ -285,6 +286,7 @@
         // Second request
         sReplier.addResponse(NO_RESPONSE);
         mActivity.onPassword(View::requestFocus);
+        mUiBot.waitForIdleSync();
         sReplier.getNextFillRequest();
         mUiBot.assertNoDatasets();
         waitUntilDisconnected();
@@ -307,9 +309,11 @@
 
         // Trigger autofill and IME.
         mUiBot.focusByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
         waitUntilConnected();
         sReplier.getNextFillRequest();
         mUiBot.selectDataset("dataset1");
+        mUiBot.waitForIdleSync();
         mActivity.assertAutoFilled();
 
         {
@@ -324,9 +328,9 @@
         // Second request
         sReplier.addResponse(new CannedFillResponse.Builder().returnFailure("D'OH!").build());
         mActivity.onPassword(View::requestFocus);
+        mUiBot.waitForIdleSync();
         sReplier.getNextFillRequest();
         mUiBot.assertNoDatasets();
-        mUiBot.assertNoSuggestionStripEver();
         waitUntilDisconnected();
 
         InstrumentedAutoFillService.assertNoFillEventHistory();
diff --git a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
index 8fa55d0..9b49073 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/InstrumentedAutoFillService.java
@@ -48,6 +48,7 @@
 import android.service.autofill.FillResponse;
 import android.service.autofill.SaveCallback;
 import android.util.Log;
+import android.view.inputmethod.InlineSuggestionsRequest;
 
 import androidx.annotation.Nullable;
 
@@ -234,7 +235,8 @@
         }
         mHandler.post(
                 () -> sReplier.onFillRequest(request.getFillContexts(), request.getClientState(),
-                        cancellationSignal, callback, request.getFlags()));
+                        cancellationSignal, callback, request.getFlags(),
+                        request.getInlineSuggestionsRequest()));
     }
 
     @Override
@@ -362,15 +364,18 @@
         public final CancellationSignal cancellationSignal;
         public final FillCallback callback;
         public final int flags;
+        public final InlineSuggestionsRequest inlineRequest;
 
         private FillRequest(List<FillContext> contexts, Bundle data,
-                CancellationSignal cancellationSignal, FillCallback callback, int flags) {
+                CancellationSignal cancellationSignal, FillCallback callback, int flags,
+                InlineSuggestionsRequest inlineRequest) {
             this.contexts = contexts;
             this.data = data;
             this.cancellationSignal = cancellationSignal;
             this.callback = callback;
             this.flags = flags;
             this.structure = contexts.get(contexts.size() - 1).getStructure();
+            this.inlineRequest = inlineRequest;
         }
 
         @Override
@@ -602,7 +607,8 @@
         }
 
         private void onFillRequest(List<FillContext> contexts, Bundle data,
-                CancellationSignal cancellationSignal, FillCallback callback, int flags) {
+                CancellationSignal cancellationSignal, FillCallback callback, int flags,
+                InlineSuggestionsRequest inlineRequest) {
             try {
                 CannedFillResponse response = null;
                 try {
@@ -676,7 +682,7 @@
                 addException(t);
             } finally {
                 Helper.offer(mFillRequests, new FillRequest(contexts, data, cancellationSignal,
-                        callback, flags), CONNECTION_TIMEOUT.ms());
+                        callback, flags, inlineRequest), CONNECTION_TIMEOUT.ms());
             }
         }
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityCommonTestCase.java
index e200cb8..48a2be9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/LoginActivityCommonTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/LoginActivityCommonTestCase.java
@@ -148,6 +148,11 @@
         final FillContext fillContext = request.contexts.get(request.contexts.size() - 1);
         assertThat(fillContext.getFocusedId())
                 .isEqualTo(findAutofillIdByResourceId(fillContext, ID_USERNAME));
+        if (isInlineMode()) {
+            assertThat(request.inlineRequest).isNotNull();
+        } else {
+            assertThat(request.inlineRequest).isNull();
+        }
 
         // Make sure initial focus was properly set.
         assertWithMessage("Username node is not focused").that(
diff --git a/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java b/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
index e671551..b84a96e 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/Timeouts.java
@@ -52,7 +52,7 @@
     /**
      * Timeout to get the expected number of fill events.
      */
-    static final Timeout FILL_EVENTS_TIMEOUT = new Timeout("FILL_EVENTS_TIMEOUT",
+    public static final Timeout FILL_EVENTS_TIMEOUT = new Timeout("FILL_EVENTS_TIMEOUT",
             ONE_TIMEOUT_TO_RULE_THEN_ALL_MS, 2F, ONE_TIMEOUT_TO_RULE_THEN_ALL_MS);
 
     /**
@@ -112,7 +112,7 @@
      * Timeout used when the dataset picker is not expected to be shown - test will sleep for that
      * amount of time as there is no callback that be received to assert it's not shown.
      */
-    static final long DATASET_PICKER_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
+    public static final long DATASET_PICKER_NOT_SHOWN_NAPTIME_MS = ONE_NAPTIME_TO_RULE_THEN_ALL_MS;
 
     /**
      * Timeout (in milliseconds) for an activity to be brought out to top.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
index b04315e..d904add 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/UiBot.java
@@ -70,7 +70,6 @@
 
 import com.android.compatibility.common.util.RetryableException;
 import com.android.compatibility.common.util.Timeout;
-import com.android.cts.mockime.MockIme;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -95,8 +94,6 @@
     private static final String RESOURCE_ID_SAVE_BUTTON_NO = "autofill_save_no";
     private static final String RESOURCE_ID_SAVE_BUTTON_YES = "autofill_save_yes";
     private static final String RESOURCE_ID_OVERFLOW = "overflow";
-    //TODO: Change magic constant
-    private static final String RESOURCE_ID_SUGGESTION_STRIP = "message";
 
     private static final String RESOURCE_STRING_SAVE_TITLE = "autofill_save_title";
     private static final String RESOURCE_STRING_SAVE_TITLE_WITH_TYPE =
@@ -135,8 +132,6 @@
     private static final BySelector SAVE_UI_SELECTOR = By.res("android", RESOURCE_ID_SAVE_SNACKBAR);
     private static final BySelector DATASET_HEADER_SELECTOR =
             By.res("android", RESOURCE_ID_DATASET_HEADER);
-    private static final BySelector SUGGESTION_STRIP_SELECTOR =
-            By.res("android", RESOURCE_ID_SUGGESTION_STRIP);
 
     // TODO: figure out a more reliable solution that does not depend on SystemUI resources.
     private static final String SPLIT_WINDOW_DIVIDER_ID =
@@ -270,14 +265,6 @@
     }
 
     /**
-     * Asserts the suggestion strip was never shown.
-     */
-    public void assertNoSuggestionStripEver() throws Exception {
-        assertNeverShown("suggestion strip", SUGGESTION_STRIP_SELECTOR,
-                DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
-    }
-
-    /**
      * Asserts the dataset chooser is shown and contains exactly the given datasets.
      *
      * @return the dataset picker object.
@@ -336,32 +323,6 @@
     }
 
     /**
-     * Asserts the suggestion strip on the {@link MockIme} is shown and contains the given number
-     * of child suggestions.
-     *
-     * @param childrenCount the expected number of children.
-     *
-     * @return the suggestion strip object
-     */
-    public UiObject2 assertSuggestionStrip(int childrenCount) throws Exception {
-        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
-        assertThat(strip.getChildCount()).isEqualTo(childrenCount);
-        return strip;
-    }
-
-    /**
-     * Selects the suggestion in the {@link MockIme}'s suggestion strip at the given index.
-     *
-     * @param index the index of the suggestion to select.
-     */
-    public void selectSuggestion(int index) throws Exception {
-        final UiObject2 strip = findSuggestionStrip(UI_TIMEOUT);
-        assertThat(index).isAtLeast(0);
-        assertThat(index).isLessThan(strip.getChildCount());
-        strip.getChildren().get(index).click();
-    }
-
-    /**
      * Gets the text of this object children.
      */
     public List<String> getChildrenAsText(UiObject2 object) {
@@ -1067,10 +1028,6 @@
         return picker;
     }
 
-    protected UiObject2 findSuggestionStrip(Timeout timeout) throws Exception {
-        return waitForObject(SUGGESTION_STRIP_SELECTOR, timeout);
-    }
-
     /**
      * Asserts a given object has the expected accessibility title.
      */
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillAutoActivityLaunchTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillAutoActivityLaunchTestCase.java
index 023a5cd..d0d61ec 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillAutoActivityLaunchTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/AugmentedAutofillAutoActivityLaunchTestCase.java
@@ -20,6 +20,7 @@
 
 import android.autofillservice.cts.AbstractAutoFillActivity;
 import android.autofillservice.cts.AutoFillServiceTestCase;
+import android.autofillservice.cts.UiBot;
 import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedReplier;
 import android.content.AutofillOptions;
 import android.view.autofill.AutofillManager;
@@ -53,6 +54,12 @@
             .outerRule(sRequiredFeatureRule)
             .around(sRequiredResource);
 
+    public AugmentedAutofillAutoActivityLaunchTestCase() {}
+
+    public AugmentedAutofillAutoActivityLaunchTestCase(UiBot uiBot) {
+        super(uiBot);
+    }
+
     @BeforeClass
     public static void allowAugmentedAutofill() {
         sContext.getApplicationContext()
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/CannedAugmentedFillResponse.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/CannedAugmentedFillResponse.java
index 5a312ef..a8a26a3 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/CannedAugmentedFillResponse.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/CannedAugmentedFillResponse.java
@@ -19,6 +19,7 @@
 
 import android.autofillservice.cts.R;
 import android.content.Context;
+import android.os.Bundle;
 import android.service.autofill.InlinePresentation;
 import android.service.autofill.augmented.FillCallback;
 import android.service.autofill.augmented.FillController;
@@ -51,6 +52,9 @@
 
     private static final String TAG = CannedAugmentedFillResponse.class.getSimpleName();
 
+    public static final String CLIENT_STATE_KEY = "clientStateKey";
+    public static final String CLIENT_STATE_VALUE = "clientStateValue";
+
     private final AugmentedResponseType mResponseType;
     private final Map<AutofillId, Dataset> mDatasets;
     private long mDelay;
@@ -169,6 +173,12 @@
         TIMEOUT,
     }
 
+    private Bundle newClientState() {
+        Bundle b = new Bundle();
+        b.putString(CLIENT_STATE_KEY, CLIENT_STATE_VALUE);
+        return b;
+    }
+
     private FillResponse createResponseWithInlineSuggestion() {
         List<android.service.autofill.Dataset> list = new ArrayList<>();
         for (Dataset dataset : mInlineSuggestions) {
@@ -183,7 +193,8 @@
                 list.add(datasetBuilder.build());
             }
         }
-        return new FillResponse.Builder().setInlineSuggestions(list).build();
+        return new FillResponse.Builder().setInlineSuggestions(list).setClientState(
+                newClientState()).build();
     }
 
     public static final class Builder {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/augmented/CtsAugmentedAutofillService.java b/tests/autofillservice/src/android/autofillservice/cts/augmented/CtsAugmentedAutofillService.java
index fa2f682..3604955 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/augmented/CtsAugmentedAutofillService.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/augmented/CtsAugmentedAutofillService.java
@@ -16,6 +16,7 @@
 
 package android.autofillservice.cts.augmented;
 
+import static android.autofillservice.cts.Timeouts.FILL_EVENTS_TIMEOUT;
 import static android.autofillservice.cts.augmented.AugmentedHelper.await;
 import static android.autofillservice.cts.augmented.AugmentedHelper.getActivityName;
 import static android.autofillservice.cts.augmented.AugmentedTimeouts.AUGMENTED_CONNECTION_TIMEOUT;
@@ -23,6 +24,8 @@
 import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.AugmentedResponseType.NULL;
 import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.AugmentedResponseType.TIMEOUT;
 
+import static com.google.common.truth.Truth.assertWithMessage;
+
 import android.autofillservice.cts.Helper;
 import android.content.ComponentName;
 import android.content.Context;
@@ -30,6 +33,8 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.SystemClock;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
 import android.service.autofill.augmented.AugmentedAutofillService;
 import android.service.autofill.augmented.FillCallback;
 import android.service.autofill.augmented.FillController;
@@ -170,6 +175,25 @@
         sServiceWatcher = null;
     }
 
+    public FillEventHistory getFillEventHistory(int expectedSize) throws Exception {
+        return FILL_EVENTS_TIMEOUT.run("getFillEvents(" + expectedSize + ")", () -> {
+            final FillEventHistory history = getFillEventHistory();
+            if (history == null) {
+                return null;
+            }
+            final List<Event> events = history.getEvents();
+            if (events != null) {
+                assertWithMessage("Didn't get " + expectedSize + " events yet: " + events).that(
+                        events.size()).isEqualTo(expectedSize);
+            } else {
+                assertWithMessage("Events is null (expecting " + expectedSize + ")").that(
+                        expectedSize).isEqualTo(0);
+                return null;
+            }
+            return history;
+        });
+    }
+
     /**
      * Waits until the system calls {@link #onConnected()}.
      */
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
index 79fd26d..6a994b7 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAugmentedLoginActivityTest.java
@@ -18,24 +18,40 @@
 
 import static android.autofillservice.cts.CannedFillResponse.NO_RESPONSE;
 import static android.autofillservice.cts.Helper.ID_USERNAME;
+import static android.autofillservice.cts.Helper.NULL_DATASET_ID;
+import static android.autofillservice.cts.Helper.assertFillEventForDatasetSelected;
+import static android.autofillservice.cts.Helper.assertFillEventForDatasetShown;
 import static android.autofillservice.cts.augmented.AugmentedHelper.assertBasicRequestInfo;
+import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.CLIENT_STATE_KEY;
+import static android.autofillservice.cts.augmented.CannedAugmentedFillResponse.CLIENT_STATE_VALUE;
+
+import static com.google.common.truth.Truth.assertThat;
 
 import android.autofillservice.cts.AutofillActivityTestRule;
 import android.autofillservice.cts.augmented.AugmentedAutofillAutoActivityLaunchTestCase;
 import android.autofillservice.cts.augmented.AugmentedLoginActivity;
 import android.autofillservice.cts.augmented.CannedAugmentedFillResponse;
+import android.autofillservice.cts.augmented.CtsAugmentedAutofillService;
 import android.autofillservice.cts.augmented.CtsAugmentedAutofillService.AugmentedFillRequest;
+import android.service.autofill.FillEventHistory;
+import android.service.autofill.FillEventHistory.Event;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillValue;
 import android.widget.EditText;
 
 import org.junit.Test;
 
+import java.util.List;
+
 public class InlineAugmentedLoginActivityTest
         extends AugmentedAutofillAutoActivityLaunchTestCase<AugmentedLoginActivity> {
 
     protected AugmentedLoginActivity mActivity;
 
+    public InlineAugmentedLoginActivityTest() {
+        super(getInlineUiBot());
+    }
+
     @Override
     protected AutofillActivityTestRule<AugmentedLoginActivity> getActivityRule() {
         return new AutofillActivityTestRule<AugmentedLoginActivity>(
@@ -53,41 +69,24 @@
         enableService();
         enableAugmentedService();
 
-        // Set expectations
-        final EditText username = mActivity.getUsername();
-        final EditText password = mActivity.getPassword();
-        final AutofillId usernameId = username.getAutofillId();
-        final AutofillId passwordId = password.getAutofillId();
-        final AutofillValue usernameValue = username.getAutofillValue();
-        sReplier.addResponse(NO_RESPONSE);
-        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
-                .addInlineSuggestion(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
-                        .setField(usernameId, "dude", createInlinePresentation("dude"))
-                        .setField(passwordId, "sweet", createInlinePresentation("sweet"))
-                        .build())
-                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("req1")
-                        .build(), usernameId)
-                .build());
+        testBasicLoginAutofill();
+    }
 
-        // Trigger auto-fill
-        mUiBot.selectByRelativeId(ID_USERNAME);
-        mUiBot.waitForIdle();
-        sReplier.getNextFillRequest();
-        final AugmentedFillRequest request1 = sAugmentedReplier.getNextFillRequest();
+    @Test
+    public void testAugmentedAutofillFillHistory_oneDatasetThenFilled() throws Exception {
+        // Set services
+        enableService();
+        final CtsAugmentedAutofillService augmentedService = enableAugmentedService();
 
-        // Assert request
-        assertBasicRequestInfo(request1, mActivity, usernameId, usernameValue);
+        testBasicLoginAutofill();
 
-        // Confirm one suggestion
-        mUiBot.assertSuggestionStrip(1);
-
-        mActivity.expectAutoFill("dude", "sweet");
-
-        // Select suggestion
-        mUiBot.selectSuggestion(0);
-        mUiBot.waitForIdle();
-
-        mActivity.assertAutoFilled();
+        // Verify events history
+        final FillEventHistory history = augmentedService.getFillEventHistory(2);
+        assertThat(history).isNotNull();
+        final List<Event> events = history.getEvents();
+        assertFillEventForDatasetShown(events.get(0), CLIENT_STATE_KEY, CLIENT_STATE_VALUE);
+        assertFillEventForDatasetSelected(events.get(1), NULL_DATASET_ID, CLIENT_STATE_KEY,
+                CLIENT_STATE_VALUE);
     }
 
     @Test
@@ -126,14 +125,124 @@
         assertBasicRequestInfo(request1, mActivity, usernameId, usernameValue);
 
         // Confirm one suggestion
-        mUiBot.assertSuggestionStrip(2);
+        mUiBot.assertDatasets("dude", "DUDE");
 
         mActivity.expectAutoFill("DUDE", "SWEET");
 
         // Select suggestion
-        mUiBot.selectSuggestion(1);
+        mUiBot.selectDataset("DUDE");
         mUiBot.waitForIdle();
 
         mActivity.assertAutoFilled();
     }
+
+    private void testBasicLoginAutofill() throws Exception {
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final EditText password = mActivity.getPassword();
+        final AutofillId usernameId = username.getAutofillId();
+        final AutofillId passwordId = password.getAutofillId();
+        final AutofillValue usernameValue = username.getAutofillValue();
+        sReplier.addResponse(NO_RESPONSE);
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .addInlineSuggestion(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "dude", createInlinePresentation("dude"))
+                        .setField(passwordId, "sweet", createInlinePresentation("sweet"))
+                        .build())
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("req1")
+                        .build(), usernameId)
+                .build());
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+        final AugmentedFillRequest request1 = sAugmentedReplier.getNextFillRequest();
+
+        // Assert request
+        assertBasicRequestInfo(request1, mActivity, usernameId, usernameValue);
+
+        // Confirm two suggestion
+        mUiBot.assertDatasets("dude");
+
+        mActivity.expectAutoFill("dude", "sweet");
+
+        // Select suggestion
+        mUiBot.selectDataset("dude");
+        mUiBot.waitForIdle();
+
+        mActivity.assertAutoFilled();
+    }
+
+    @Test
+    public void testAugmentedAutoFill_selectDatasetThenHideInlineSuggestion() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final EditText password = mActivity.getPassword();
+        final AutofillId usernameId = username.getAutofillId();
+        final AutofillId passwordId = password.getAutofillId();
+        final AutofillValue usernameValue = username.getAutofillValue();
+        sReplier.addResponse(NO_RESPONSE);
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .addInlineSuggestion(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "dude", createInlinePresentation("dude"))
+                        .setField(passwordId, "sweet", createInlinePresentation("sweet"))
+                        .build())
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("req1")
+                        .build(), usernameId)
+                .build());
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+        final AugmentedFillRequest request1 = sAugmentedReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("dude");
+
+        mActivity.expectAutoFill("dude", "sweet");
+
+        mUiBot.selectDataset("dude");
+        mUiBot.waitForIdle();
+
+        mUiBot.assertNoDatasets();
+    }
+
+    @Test
+    public void testAugmentedAutoFill_startTypingThenHideInlineSuggestion() throws Exception {
+        // Set services
+        enableService();
+        enableAugmentedService();
+
+        // Set expectations
+        final EditText username = mActivity.getUsername();
+        final AutofillId usernameId = username.getAutofillId();
+        sReplier.addResponse(NO_RESPONSE);
+        sAugmentedReplier.addResponse(new CannedAugmentedFillResponse.Builder()
+                .addInlineSuggestion(new CannedAugmentedFillResponse.Dataset.Builder("Augment Me")
+                        .setField(usernameId, "dude", createInlinePresentation("dude"))
+                        .build())
+                .setDataset(new CannedAugmentedFillResponse.Dataset.Builder("req1")
+                        .build(), usernameId)
+                .build());
+
+        // Trigger auto-fill
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdle();
+        sReplier.getNextFillRequest();
+        sAugmentedReplier.getNextFillRequest();
+
+        mUiBot.assertDatasets("dude");
+
+        // Now pretend user typing something by updating the value in the input field.
+        mActivity.onUsername((v) -> v.setText("d"));
+        mUiBot.waitForIdle();
+
+        // Expect the inline suggestion to disappear.
+        mUiBot.assertNoDatasets();
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
index 99e49e5..43c2abc 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineAuthenticationTest.java
@@ -52,6 +52,10 @@
         BOTH
     }
 
+    public InlineAuthenticationTest() {
+        super(getInlineUiBot());
+    }
+
     @Override
     protected void enableService() {
         Helper.enableAutofillService(getContext(), SERVICE_NAME);
@@ -90,32 +94,32 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill.
-        assertSuggestionShownBySelectViewId(ID_USERNAME, /* childrenCount */ 1);
+        assertSuggestionShownBySelectViewId(ID_USERNAME, "auth");
         sReplier.getNextFillRequest();
 
         // Make sure UI is show on 2nd field as well
-        assertSuggestionShownBySelectViewId(ID_PASSWORD, /* childrenCount */ 1);
+        assertSuggestionShownBySelectViewId(ID_PASSWORD, "auth");
 
         // Now tap on 1st field to show it again...
-        assertSuggestionShownBySelectViewId(ID_USERNAME, /* childrenCount */ 1);
+        assertSuggestionShownBySelectViewId(ID_USERNAME, "auth");
 
         if (cancelFirstAttempt) {
             // Trigger the auth dialog, but emulate cancel.
             AuthenticationActivity.setResultCode(RESULT_CANCELED);
-            mUiBot.selectSuggestion(0);
+            mUiBot.selectDataset("auth");
             mUiBot.waitForIdle();
-            mUiBot.assertSuggestionStrip(1);
+            mUiBot.assertDatasets("auth");
 
             // Make sure it's still shown on other fields...
-            assertSuggestionShownBySelectViewId(ID_PASSWORD, /* childrenCount */ 1);
+            assertSuggestionShownBySelectViewId(ID_PASSWORD, "auth");
 
             // Tap on 1st field to show it again...
-            assertSuggestionShownBySelectViewId(ID_USERNAME, /* childrenCount */ 1);
+            assertSuggestionShownBySelectViewId(ID_USERNAME, "auth");
         }
 
         // ...and select it this time
         AuthenticationActivity.setResultCode(RESULT_OK);
-        mUiBot.selectSuggestion(0);
+        mUiBot.selectDataset("auth");
         mUiBot.waitForIdle();
 
         // Check the results.
@@ -148,24 +152,24 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill, make sure it's showing initially.
-        assertSuggestionShownBySelectViewId(ID_USERNAME, /* childrenCount */ 1);
+        assertSuggestionShownBySelectViewId(ID_USERNAME, "auth");
         sReplier.getNextFillRequest();
 
         // ...then type something to hide it.
         mActivity.onUsername((v) -> v.setText("a"));
         // Suggestion strip was not shown.
-        mUiBot.assertNoSuggestionStripEver();
+        mUiBot.assertNoDatasetsEver();
         mUiBot.waitForIdle();
 
         // ...now type something again to show it, as the input will have 2 chars.
         mActivity.onUsername((v) -> v.setText("aa"));
         mUiBot.waitForIdle();
-        mUiBot.assertSuggestionStrip(1);
+        mUiBot.assertDatasets("auth");
 
         // ...and select it
-        mUiBot.selectSuggestion(0);
+        mUiBot.selectDataset("auth");
         mUiBot.waitForIdle();
-        mUiBot.assertNoSuggestionStripEver();
+        mUiBot.assertNoDatasets();
 
         // Check the results.
         mActivity.assertAutoFilled();
@@ -220,11 +224,11 @@
         mActivity.expectAutoFill("dude", "sweet");
 
         // Trigger auto-fill, make sure it's showing initially.
-        assertSuggestionShownBySelectViewId(ID_USERNAME, /* childrenCount */ 1);
+        assertSuggestionShownBySelectViewId(ID_USERNAME, "auth");
         sReplier.getNextFillRequest();
 
         // Tap authentication request.
-        mUiBot.selectSuggestion(0);
+        mUiBot.selectDataset("auth");
         mUiBot.waitForIdle();
 
         // Check the results.
@@ -257,10 +261,10 @@
         Helper.assertAuthenticationClientState("on save", saveRequest.data, "CSI", expectedValue);
     }
 
-    private void assertSuggestionShownBySelectViewId(String id, int childrenCount)
+    private void assertSuggestionShownBySelectViewId(String id, String...names)
             throws Exception {
         mUiBot.selectByRelativeId(id);
         mUiBot.waitForIdle();
-        mUiBot.assertSuggestionStrip(childrenCount);
+        mUiBot.assertDatasets(names);
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
index ce10f15..feba57c 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFillEventHistoryTest.java
@@ -83,11 +83,10 @@
         sReplier.getNextFillRequest();
 
         // Suggestion strip was shown.
-        mUiBot.assertSuggestionStrip(1);
+        mUiBot.assertDatasets("Dataset");
+        mUiBot.selectDataset("Dataset");
         mUiBot.waitForIdle();
 
-        mUiBot.selectSuggestion(0);
-
         // Change username and password
         mActivity.syncRunOnUiThread(() ->  mActivity.onUsername((v) -> v.setText("ID")));
         mActivity.syncRunOnUiThread(() ->  mActivity.onPassword((v) -> v.setText("PASS")));
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java
index a99a418..6ea4607 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineFilteringTest.java
@@ -35,6 +35,10 @@
 
     private static final String TAG = "InlineLoginActivityTest";
 
+    public InlineFilteringTest() {
+        super(getInlineUiBot());
+    }
+
     @Override
     protected void enableService() {
         Helper.enableAutofillService(getContext(), SERVICE_NAME);
@@ -64,25 +68,25 @@
         // Trigger autofill, then make sure it's showing initially.
         mUiBot.selectByRelativeId(ID_USERNAME);
         mUiBot.waitForIdleSync();
-        mUiBot.assertSuggestionStrip(2);
+        mUiBot.assertDatasets("The Dude", "Second Dude");
         sReplier.getNextFillRequest();
 
         // Filter out one of the datasets.
         mActivity.onUsername((v) -> v.setText("t"));
         mUiBot.waitForIdleSync();
-        mUiBot.assertSuggestionStrip(1);
+        mUiBot.assertDatasets("Second Dude");
 
         // Filter out both datasets.
         mActivity.onUsername((v) -> v.setText("ta"));
         mUiBot.waitForIdleSync();
-        mUiBot.assertNoSuggestionStripEver();
+        mUiBot.assertNoDatasets();
 
         // Backspace to bring back one dataset.
         mActivity.onUsername((v) -> v.setText("t"));
         mUiBot.waitForIdleSync();
-        mUiBot.assertSuggestionStrip(1);
+        mUiBot.assertDatasets("Second Dude");
 
-        mUiBot.selectSuggestion(0);
+        mUiBot.selectDataset("Second Dude");
         mUiBot.waitForIdleSync();
         mActivity.assertAutoFilled();
     }
@@ -103,26 +107,26 @@
         mUiBot.selectByRelativeId(ID_USERNAME);
         mActivity.onUsername((v) -> v.setText("s"));
         mUiBot.waitForIdleSync();
-        mUiBot.assertSuggestionStrip(1);
+        mUiBot.assertDatasets("sergey");
         sReplier.getNextFillRequest();
 
         // Enter the wrong second char - filters out dataset.
         mActivity.onUsername((v) -> v.setText("sa"));
         mUiBot.waitForIdleSync();
-        mUiBot.assertNoSuggestionStripEver();
+        mUiBot.assertNoDatasets();
 
         // Backspace to bring back the dataset.
         mActivity.onUsername((v) -> v.setText("s"));
         mUiBot.waitForIdleSync();
-        mUiBot.assertSuggestionStrip(1);
+        mUiBot.assertDatasets("sergey");
 
         // Enter the correct second char, then check that suggestions are no longer shown.
         mActivity.onUsername((v) -> v.setText("se"));
         mUiBot.waitForIdleSync();
-        mUiBot.assertNoSuggestionStripEver();
+        mUiBot.assertNoDatasets();
         mActivity.onUsername((v) -> v.setText(""));
         mUiBot.waitForIdleSync();
-        mUiBot.assertNoSuggestionStripEver();
+        mUiBot.assertNoDatasetsEver();
     }
 
     /**
@@ -145,7 +149,7 @@
         mUiBot.selectByRelativeId(ID_USERNAME);
         mActivity.onUsername((v) -> v.setText("ser"));
         mUiBot.waitForIdleSync();
-        mUiBot.assertSuggestionStrip(1);
+        mUiBot.assertDatasets("sergey");
         sReplier.getNextFillRequest();
 
         // Enter a couple different strings, then check that suggestions are no longer shown.
@@ -153,10 +157,10 @@
         mActivity.onUsername((v) -> v.setText("bbb"));
         mActivity.onUsername((v) -> v.setText("ser"));
         mUiBot.waitForIdleSync();
-        mUiBot.assertNoSuggestionStripEver();
+        mUiBot.assertNoDatasets();
         mActivity.onUsername((v) -> v.setText(""));
         mUiBot.waitForIdleSync();
-        mUiBot.assertNoSuggestionStripEver();
+        mUiBot.assertNoDatasetsEver();
     }
 
     @Test
@@ -174,7 +178,7 @@
         mUiBot.selectByRelativeId(ID_USERNAME);
         mActivity.onUsername((v) -> v.setText("aaa"));
         mUiBot.waitForIdleSync();
-        mUiBot.assertSuggestionStrip(1);
+        mUiBot.assertDatasets("pinned");
         sReplier.getNextFillRequest();
     }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
index 3be0ce6..108ae74 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineLoginActivityTest.java
@@ -115,4 +115,45 @@
         assertWithMessage("Password node is focused").that(
                 findNodeByResourceId(request.structure, ID_PASSWORD).isFocused()).isFalse();
     }
+
+    @Test
+    public void testAutofill_selectDatasetThenHideInlineSuggestion() throws Exception {
+        // Set service.
+        enableService();
+
+        final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+                .addDataset(new CannedFillResponse.CannedDataset.Builder()
+                        .setField(ID_USERNAME, "dude")
+                        .setPresentation(createPresentation("The Username"))
+                        .setInlinePresentation(createInlinePresentation("The Username"))
+                        .build());
+
+        sReplier.addResponse(builder.build());
+        mActivity.expectAutoFill("dude");
+
+        // Trigger auto-fill.
+        mUiBot.selectByRelativeId(ID_USERNAME);
+        mUiBot.waitForIdleSync();
+
+        mUiBot.assertDatasets("The Username");
+
+        mUiBot.selectDataset("The Username");
+        mUiBot.waitForIdleSync();
+
+        mUiBot.assertNoDatasets();
+
+        // Make sure input was sanitized.
+        final InstrumentedAutoFillService.FillRequest request = sReplier.getNextFillRequest();
+        assertWithMessage("CancelationSignal is null").that(request.cancellationSignal).isNotNull();
+        assertTextIsSanitized(request.structure, ID_PASSWORD);
+        final FillContext fillContext = request.contexts.get(request.contexts.size() - 1);
+        assertThat(fillContext.getFocusedId())
+                .isEqualTo(findAutofillIdByResourceId(fillContext, ID_USERNAME));
+
+        // Make sure initial focus was properly set.
+        assertWithMessage("Username node is not focused").that(
+                findNodeByResourceId(request.structure, ID_USERNAME).isFocused()).isTrue();
+        assertWithMessage("Password node is focused").that(
+                findNodeByResourceId(request.structure, ID_PASSWORD).isFocused()).isFalse();
+    }
 }
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
index 17bcc2f..c2b5515 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineSimpleSaveActivityTest.java
@@ -43,6 +43,10 @@
     private static final String TAG = "InlineSimpleSaveActivityTest";
     protected SimpleSaveActivity mActivity;
 
+    public InlineSimpleSaveActivityTest() {
+        super(getInlineUiBot());
+    }
+
     @Override
     protected void enableService() {
         Helper.enableAutofillService(getContext(), SERVICE_NAME);
@@ -76,7 +80,7 @@
         sReplier.getNextFillRequest();
 
         // Suggestion strip was never shown.
-        mUiBot.assertNoSuggestionStripEver();
+        mUiBot.assertNoDatasetsEver();
 
         // Change input
         mActivity.syncRunOnUiThread(() -> mActivity.getInput().setText("ID"));
@@ -120,10 +124,10 @@
         sReplier.getNextFillRequest();
 
         // Confirm one suggestion
-        mUiBot.assertSuggestionStrip(1);
+        mUiBot.assertDatasets("YO");
 
         // Select suggestion
-        mUiBot.selectSuggestion(0);
+        mUiBot.selectDataset("YO");
         mUiBot.waitForIdle();
 
         // Check the results.
diff --git a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineUiBot.java b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineUiBot.java
index a2645ec1..13f7ce8 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/inline/InlineUiBot.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/inline/InlineUiBot.java
@@ -16,10 +16,12 @@
 
 package android.autofillservice.cts.inline;
 
+import static android.autofillservice.cts.Timeouts.DATASET_PICKER_NOT_SHOWN_NAPTIME_MS;
 import static android.autofillservice.cts.Timeouts.UI_TIMEOUT;
 
 import android.autofillservice.cts.UiBot;
 import android.support.test.uiautomator.By;
+import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiObject2;
 
 import com.android.compatibility.common.util.Timeout;
@@ -31,6 +33,11 @@
 public final class InlineUiBot extends UiBot {
 
     private static final String TAG = "AutoFillInlineCtsUiBot";
+    //TODO: Change magic constant
+    private static final String RESOURCE_ID_SUGGESTION_STRIP = "message";
+
+    private static final BySelector SUGGESTION_STRIP_SELECTOR =
+            By.res("android", RESOURCE_ID_SUGGESTION_STRIP);
 
     public InlineUiBot() {
         this(UI_TIMEOUT);
@@ -42,12 +49,13 @@
 
     @Override
     public void assertNoDatasets() throws Exception {
-        assertNoSuggestionStripEver();
+        assertNoDatasetsEver();
     }
 
     @Override
     public void assertNoDatasetsEver() throws Exception {
-        assertNoSuggestionStripEver();
+        assertNeverShown("suggestion strip", SUGGESTION_STRIP_SELECTOR,
+                DATASET_PICKER_NOT_SHOWN_NAPTIME_MS);
     }
 
     /**
@@ -72,4 +80,8 @@
         final UiObject2 picker = findSuggestionStrip(UI_TIMEOUT);
         return assertDatasets(picker, names);
     }
+
+    private UiObject2 findSuggestionStrip(Timeout timeout) throws Exception {
+        return waitForObject(SUGGESTION_STRIP_SELECTOR, timeout);
+    }
 }
diff --git a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
index afa68b1..80a2fe95 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -241,6 +241,11 @@
                                 physicalStaticInfo.getCharacteristics().get(
                                         CameraCharacteristics.SCALER_MANDATORY_STREAM_COMBINATIONS);
 
+                        if (phyCombinations == null) {
+                            Log.i(TAG, "No mandatory stream combinations for physical camera device: " + id + " skip test");
+                            continue;
+                        }
+
                         for (MandatoryStreamCombination combination : phyCombinations) {
                             if (!combination.isReprocessable()) {
                                 testMandatoryStreamCombination(id, physicalStaticInfo,
@@ -2138,14 +2143,18 @@
 
         public MaxStreamSizes(StaticMetadata sm, String cameraId, Context context) {
             Size[] privSizes = sm.getAvailableSizesForFormatChecked(ImageFormat.PRIVATE,
-                    StaticMetadata.StreamDirection.Output);
+                    StaticMetadata.StreamDirection.Output, /*fastSizes*/true, /*slowSizes*/false);
             Size[] yuvSizes = sm.getAvailableSizesForFormatChecked(ImageFormat.YUV_420_888,
-                    StaticMetadata.StreamDirection.Output);
+                    StaticMetadata.StreamDirection.Output, /*fastSizes*/true, /*slowSizes*/false);
+
             Size[] y8Sizes = sm.getAvailableSizesForFormatChecked(ImageFormat.Y8,
-                    StaticMetadata.StreamDirection.Output);
-            Size[] jpegSizes = sm.getJpegOutputSizesChecked();
-            Size[] rawSizes = sm.getRawOutputSizesChecked();
-            Size[] heicSizes = sm.getHeicOutputSizesChecked();
+                    StaticMetadata.StreamDirection.Output, /*fastSizes*/true, /*slowSizes*/false);
+            Size[] jpegSizes = sm.getAvailableSizesForFormatChecked(ImageFormat.JPEG,
+                    StaticMetadata.StreamDirection.Output, /*fastSizes*/true, /*slowSizes*/false);
+            Size[] rawSizes = sm.getAvailableSizesForFormatChecked(ImageFormat.RAW_SENSOR,
+                    StaticMetadata.StreamDirection.Output, /*fastSizes*/true, /*slowSizes*/false);
+            Size[] heicSizes = sm.getAvailableSizesForFormatChecked(ImageFormat.HEIC,
+                    StaticMetadata.StreamDirection.Output, /*fastSizes*/true, /*slowSizes*/false);
 
             Size maxPreviewSize = getMaxPreviewSize(context, cameraId);
 
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java
index f5dedba..394f346 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestRule.java
@@ -189,7 +189,7 @@
     }
 
     private String[] deriveCameraIdsUnderTest() throws Exception {
-        String[] idsUnderTest = mCameraManager.getCameraIdListNoLazy();
+        String[] idsUnderTest = mCameraManager.getCameraIdList();
         assertNotNull("Camera ids shouldn't be null", idsUnderTest);
         if (mOverrideCameraId != null) {
             if (Arrays.asList(idsUnderTest).contains(mOverrideCameraId)) {
diff --git a/tests/controls/Android.bp b/tests/controls/Android.bp
new file mode 100644
index 0000000..b0346be
--- /dev/null
+++ b/tests/controls/Android.bp
@@ -0,0 +1,38 @@
+// 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: "CtsControlsDeviceTestCases",
+    defaults: ["cts_defaults"],
+
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "androidx.test.uiautomator_uiautomator",
+        "compatibility-device-util-axt",
+     ],
+
+    libs: [
+        "android.test.runner.stubs",
+        "android.test.base.stubs",
+    ],
+
+    srcs: ["src/**/*.java"],
+    sdk_version: "test_current",
+}
\ No newline at end of file
diff --git a/tests/controls/AndroidManifest.xml b/tests/controls/AndroidManifest.xml
new file mode 100644
index 0000000..4ce024e
--- /dev/null
+++ b/tests/controls/AndroidManifest.xml
@@ -0,0 +1,35 @@
+<?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.controls.cts">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+        <activity android:name="CtsControlsDeviceActivity" >
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+    </application>
+
+    <!--  self-instrumenting test package. -->
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.controls.cts" >
+    </instrumentation>
+</manifest>
diff --git a/tests/controls/AndroidTest.xml b/tests/controls/AndroidTest.xml
new file mode 100644
index 0000000..7141031
--- /dev/null
+++ b/tests/controls/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?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 CtsControlsDeviceTestCases">
+    <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="CtsControlsDeviceTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.controls.cts" />
+    </test>
+</configuration>
diff --git a/tests/controls/OWNERS b/tests/controls/OWNERS
new file mode 100644
index 0000000..813d05f
--- /dev/null
+++ b/tests/controls/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 802986
+mpietal@google.com
+kozynski@google.com
+nesciosquid@google.com
+ethibodeau@google.com
+dupin@google.com
\ No newline at end of file
diff --git a/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java b/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java
new file mode 100644
index 0000000..2c0da02
--- /dev/null
+++ b/tests/controls/src/android/controls/cts/CtsControlTemplateTest.java
@@ -0,0 +1,216 @@
+/*
+ * 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.controls.cts;
+
+import static junit.framework.Assert.assertTrue;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Parcel;
+import android.service.controls.Control;
+import android.service.controls.templates.ControlButton;
+import android.service.controls.templates.ControlTemplate;
+import android.service.controls.templates.RangeTemplate;
+import android.service.controls.templates.StatelessTemplate;
+import android.service.controls.templates.TemperatureControlTemplate;
+import android.service.controls.templates.ToggleRangeTemplate;
+import android.service.controls.templates.ToggleTemplate;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class CtsControlTemplateTest {
+
+    private static final String TEST_ID = "TEST_ID";
+    private static final CharSequence TEST_ACTION_DESCRIPTION = "TEST_ACTION_DESCRIPTION";
+    private ControlButton mControlButton;
+
+    private PendingIntent mPendingIntent;
+
+    @Before
+    public void setUp() {
+        Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        mControlButton = new ControlButton(true, TEST_ACTION_DESCRIPTION);
+        mPendingIntent = PendingIntent.getActivity(context, 1, new Intent(), 0);
+    }
+
+    @Test
+    public void testUnparcelingCorrectClass_none() {
+        ControlTemplate toParcel = ControlTemplate.getNoTemplateObject();
+
+        ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
+
+        assertEquals(ControlTemplate.getNoTemplateObject(), fromParcel);
+    }
+
+    @Test
+    public void testUnparcelingCorrectClass_toggle() {
+        ControlTemplate toParcel = new ToggleTemplate(TEST_ID, mControlButton);
+
+        ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
+
+        assertEquals(ControlTemplate.TYPE_TOGGLE, fromParcel.getTemplateType());
+        assertTrue(fromParcel instanceof ToggleTemplate);
+    }
+
+    @Test
+    public void testUnparcelingCorrectClass_range() {
+        ControlTemplate toParcel = new RangeTemplate(TEST_ID, 0, 2, 1, 1, "%f");
+
+        ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
+
+        assertEquals(ControlTemplate.TYPE_RANGE, fromParcel.getTemplateType());
+        assertTrue(fromParcel instanceof RangeTemplate);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRangeParameters_minMax() {
+        new RangeTemplate(TEST_ID, 2, 0, 1, 1, "%f");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRangeParameters_minCurrent() {
+        new RangeTemplate(TEST_ID, 0, 2, -1, 1, "%f");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRangeParameters_maxCurrent() {
+        new RangeTemplate(TEST_ID, 0, 2, 3, 1, "%f");
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testRangeParameters_negativeStep() {
+        new RangeTemplate(TEST_ID, 0, 2, 1, -1, "%f");
+    }
+
+    @Test
+    public void testUnparcelingCorrectClass_toggleRange() {
+        ControlTemplate toParcel = new ToggleRangeTemplate(TEST_ID, mControlButton,
+                new RangeTemplate(TEST_ID, 0, 2, 1, 1, "%f"));
+
+        ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
+
+        assertEquals(ControlTemplate.TYPE_TOGGLE_RANGE, fromParcel.getTemplateType());
+        assertTrue(fromParcel instanceof ToggleRangeTemplate);
+    }
+
+    @Test
+    public void testUnparcelingCorrectClass_stateless() {
+        ControlTemplate toParcel = new StatelessTemplate(TEST_ID);
+
+        ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
+
+        assertEquals(ControlTemplate.TYPE_STATELESS, fromParcel.getTemplateType());
+        assertTrue(fromParcel instanceof StatelessTemplate);
+    }
+
+    @Test
+    public void testUnparcelingCorrectClass_thermostat() {
+        ControlTemplate toParcel = new TemperatureControlTemplate(
+                TEST_ID,
+                new ToggleTemplate("", mControlButton),
+                TemperatureControlTemplate.MODE_OFF,
+                TemperatureControlTemplate.MODE_OFF,
+                TemperatureControlTemplate.FLAG_MODE_OFF);
+
+        ControlTemplate fromParcel = parcelAndUnparcel(toParcel);
+
+        assertEquals(ControlTemplate.TYPE_TEMPERATURE, fromParcel.getTemplateType());
+        assertTrue(fromParcel instanceof TemperatureControlTemplate);
+    }
+
+    @Test
+    public void testThermostatParams_wrongMode() {
+        TemperatureControlTemplate thermostat = new TemperatureControlTemplate(
+                TEST_ID,
+                ControlTemplate.getNoTemplateObject(),
+                -1,
+                TemperatureControlTemplate.MODE_OFF,
+                TemperatureControlTemplate.FLAG_MODE_OFF);
+        assertEquals(TemperatureControlTemplate.MODE_UNKNOWN, thermostat.getCurrentMode());
+
+        thermostat = new TemperatureControlTemplate(
+                TEST_ID,
+                ControlTemplate.getNoTemplateObject(),
+                100,
+                TemperatureControlTemplate.MODE_OFF,
+                TemperatureControlTemplate.FLAG_MODE_OFF);
+        assertEquals(TemperatureControlTemplate.MODE_UNKNOWN, thermostat.getCurrentMode());
+    }
+
+    @Test
+    public void testThermostatParams_wrongActiveMode() {
+        TemperatureControlTemplate thermostat = new TemperatureControlTemplate(
+                TEST_ID,
+                ControlTemplate.getNoTemplateObject(),
+                TemperatureControlTemplate.MODE_OFF,
+                -1,
+                TemperatureControlTemplate.FLAG_MODE_OFF);
+        assertEquals(TemperatureControlTemplate.MODE_UNKNOWN, thermostat.getCurrentActiveMode());
+
+        thermostat = new TemperatureControlTemplate(
+                TEST_ID,
+                ControlTemplate.getNoTemplateObject(),
+                TemperatureControlTemplate.MODE_OFF,
+                100,
+                TemperatureControlTemplate.FLAG_MODE_OFF);
+        assertEquals(TemperatureControlTemplate.MODE_UNKNOWN, thermostat.getCurrentActiveMode());
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testThermostatParams_wrongFlags_currentMode() {
+        new TemperatureControlTemplate(
+                TEST_ID,
+                ControlTemplate.getNoTemplateObject(),
+                TemperatureControlTemplate.MODE_HEAT,
+                TemperatureControlTemplate.MODE_OFF,
+                TemperatureControlTemplate.FLAG_MODE_OFF);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testThermostatParams_wrongFlags_currentActiveMode() {
+        new TemperatureControlTemplate(TEST_ID,
+                ControlTemplate.getNoTemplateObject(),
+                TemperatureControlTemplate.MODE_HEAT,
+                TemperatureControlTemplate.MODE_OFF,
+                TemperatureControlTemplate.FLAG_MODE_HEAT);
+    }
+
+    private ControlTemplate parcelAndUnparcel(ControlTemplate toParcel) {
+        Parcel parcel = Parcel.obtain();
+        assertNotNull(parcel);
+
+        parcel.setDataPosition(0);
+        Control control = new Control.StatefulBuilder("1", mPendingIntent)
+                .setControlTemplate(toParcel)
+                .build();
+        control.writeToParcel(parcel, 0);
+        parcel.setDataPosition(0);
+
+        return Control.CREATOR.createFromParcel(parcel).getControlTemplate();
+    }
+}
diff --git a/tests/controls/src/android/controls/cts/CtsControlsDeviceActivity.java b/tests/controls/src/android/controls/cts/CtsControlsDeviceActivity.java
new file mode 100644
index 0000000..dcf7c32
--- /dev/null
+++ b/tests/controls/src/android/controls/cts/CtsControlsDeviceActivity.java
@@ -0,0 +1,29 @@
+/*
+ * 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.controls.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+public class CtsControlsDeviceActivity extends Activity {
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        finish();
+    }
+}
diff --git a/tests/controls/src/android/controls/cts/CtsControlsPublisher.java b/tests/controls/src/android/controls/cts/CtsControlsPublisher.java
new file mode 100644
index 0000000..643d80e
--- /dev/null
+++ b/tests/controls/src/android/controls/cts/CtsControlsPublisher.java
@@ -0,0 +1,69 @@
+/*
+ * 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.controls.cts;
+
+import android.service.controls.Control;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Flow.Publisher;
+import java.util.concurrent.Flow.Subscriber;
+import java.util.concurrent.Flow.Subscription;
+
+/**
+ * Simplified Publisher for use with CTS testing only. Assumes all Controls are added
+ * to the Publisher ahead of a subscribe request. Assumes only one request() call.
+ */
+public class CtsControlsPublisher implements Publisher<Control> {
+
+    private final List<Control> mControls = new ArrayList<>();
+    private Subscriber<? super Control> mSubscriber;
+
+    public CtsControlsPublisher(List<Control> controls) {
+        if (controls != null) {
+            mControls.addAll(controls);
+        }
+    }
+
+    public void subscribe​(Subscriber<? super Control> subscriber) {
+        mSubscriber = subscriber;
+        mSubscriber.onSubscribe(new Subscription() {
+                public void request(long n) {
+                    int i = 0;
+                    while (i < n && i < mControls.size()) {
+                        subscriber.onNext(mControls.get(i));
+                        i++;
+                    }
+
+                    if (i == mControls.size()) {
+                        subscriber.onComplete();
+                    }
+                }
+
+                public void cancel() {
+
+                }
+            });
+    }
+
+    public void onNext(Control c) {
+        if (mSubscriber == null) {
+            mControls.add(c);
+        } else {
+            mSubscriber.onNext(c);
+        }
+    }
+}
diff --git a/tests/controls/src/android/controls/cts/CtsControlsService.java b/tests/controls/src/android/controls/cts/CtsControlsService.java
new file mode 100644
index 0000000..8890664
--- /dev/null
+++ b/tests/controls/src/android/controls/cts/CtsControlsService.java
@@ -0,0 +1,324 @@
+/*
+ * 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.controls.cts;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.service.controls.Control;
+import android.service.controls.ControlsProviderService;
+import android.service.controls.DeviceTypes;
+import android.service.controls.actions.BooleanAction;
+import android.service.controls.actions.CommandAction;
+import android.service.controls.actions.ControlAction;
+import android.service.controls.actions.FloatAction;
+import android.service.controls.actions.ModeAction;
+import android.service.controls.templates.ControlButton;
+import android.service.controls.templates.ControlTemplate;
+import android.service.controls.templates.RangeTemplate;
+import android.service.controls.templates.StatelessTemplate;
+import android.service.controls.templates.TemperatureControlTemplate;
+import android.service.controls.templates.ToggleRangeTemplate;
+import android.service.controls.templates.ToggleTemplate;
+
+import androidx.test.InstrumentationRegistry;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Flow.Publisher;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+
+/**
+  * CTS Controls Service to send known controls for testing.
+  */
+public class CtsControlsService extends ControlsProviderService {
+
+    private CtsControlsPublisher mUpdatePublisher;
+    private final List<Control> mAllControls = new ArrayList<>();
+    private final Map<String, Control> mControlsById = new HashMap<>();
+    private final Context mContext;
+    private final PendingIntent mPendingIntent;
+
+    public CtsControlsService() {
+        mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+        mPendingIntent = PendingIntent.getActivity(mContext, 1, new Intent(),
+            PendingIntent.FLAG_UPDATE_CURRENT);
+        mAllControls.add(buildLight(false /* isOn */, 0.0f /* intensity */));
+        mAllControls.add(buildLock(false /* isLocked */));
+        mAllControls.add(buildRoutine());
+        mAllControls.add(buildThermostat(TemperatureControlTemplate.MODE_OFF));
+        mAllControls.add(buildMower(false /* isStarted */));
+        mAllControls.add(buildSwitch(false /* isOn */));
+        mAllControls.add(buildGate(false /* isLocked */));
+
+        for (Control c : mAllControls) {
+            mControlsById.put(c.getControlId(), c);
+        }
+    }
+
+    public Control buildLight(boolean isOn, float intensity) {
+        RangeTemplate rt = new RangeTemplate("range", 0.0f, 100.0f, intensity, 1.0f, null);
+        ControlTemplate template =
+                new ToggleRangeTemplate("toggleRange", isOn, isOn ? "On" : "Off", rt);
+        return new Control.StatefulBuilder("light", mPendingIntent)
+            .setTitle("Light Title")
+            .setSubtitle("Light Subtitle")
+            .setStatus(Control.STATUS_OK)
+            .setStatusText(isOn ? "On" : "Off")
+            .setDeviceType(DeviceTypes.TYPE_LIGHT)
+            .setStructure("Home")
+            .setControlTemplate(template)
+            .build();
+    }
+
+    public Control buildSwitch(boolean isOn) {
+        ControlButton button = new ControlButton(isOn, isOn ? "On" : "Off");
+        ControlTemplate template = new ToggleTemplate("toggle", button);
+        return new Control.StatefulBuilder("switch", mPendingIntent)
+            .setTitle("Switch Title")
+            .setSubtitle("Switch Subtitle")
+            .setStatus(Control.STATUS_OK)
+            .setStatusText(isOn ? "On" : "Off")
+            .setDeviceType(DeviceTypes.TYPE_SWITCH)
+            .setStructure("Home")
+            .setControlTemplate(template)
+            .build();
+    }
+
+
+    public Control buildMower(boolean isStarted) {
+        String desc = isStarted ? "Started" : "Stopped";
+        ControlButton button = new ControlButton(isStarted, desc);
+        ControlTemplate template = new ToggleTemplate("toggle", button);
+        return new Control.StatefulBuilder("mower", mPendingIntent)
+            .setTitle("Mower Title")
+            .setSubtitle("Mower Subtitle")
+            .setStatus(Control.STATUS_OK)
+            .setStatusText(desc)
+            .setDeviceType(DeviceTypes.TYPE_MOWER)
+            .setStructure("Vacation")
+            .setZone("Outside")
+            .setControlTemplate(template)
+            .build();
+    }
+
+    public Control buildLock(boolean isLocked) {
+        String desc = isLocked ? "Locked" : "Unlocked";
+        ControlButton button = new ControlButton(isLocked, desc);
+        ControlTemplate template = new ToggleTemplate("toggle", button);
+        return new Control.StatefulBuilder("lock", mPendingIntent)
+            .setTitle("Lock Title")
+            .setSubtitle("Lock Subtitle")
+            .setStatus(Control.STATUS_OK)
+            .setStatusText(desc)
+            .setDeviceType(DeviceTypes.TYPE_LOCK)
+            .setControlTemplate(template)
+            .build();
+    }
+
+    public Control buildGate(boolean isLocked) {
+        String desc = isLocked ? "Locked" : "Unlocked";
+        ControlButton button = new ControlButton(isLocked, desc);
+        ControlTemplate template = new ToggleTemplate("toggle", button);
+        return new Control.StatefulBuilder("gate", mPendingIntent)
+            .setTitle("Gate Title")
+            .setSubtitle("Gate Subtitle")
+            .setStatus(Control.STATUS_OK)
+            .setStatusText(desc)
+            .setDeviceType(DeviceTypes.TYPE_GATE)
+            .setControlTemplate(template)
+            .setStructure("Other home")
+            .build();
+    }
+
+    public Control buildThermostat(int mode) {
+        ControlTemplate template = new TemperatureControlTemplate("temperature",
+                    ControlTemplate.getNoTemplateObject(),
+                    mode,
+                    TemperatureControlTemplate.MODE_OFF,
+                    TemperatureControlTemplate.FLAG_MODE_HEAT
+                    | TemperatureControlTemplate.FLAG_MODE_COOL
+                    | TemperatureControlTemplate.FLAG_MODE_OFF
+                    | TemperatureControlTemplate.FLAG_MODE_ECO);
+
+        return new Control.StatefulBuilder("thermostat", mPendingIntent)
+            .setTitle("Thermostat Title")
+            .setSubtitle("Thermostat Subtitle")
+            .setStatus(Control.STATUS_OK)
+            .setStatusText("Off")
+            .setDeviceType(DeviceTypes.TYPE_THERMOSTAT)
+            .setControlTemplate(template)
+            .build();
+    }
+
+    public Control buildRoutine() {
+        ControlTemplate template = new StatelessTemplate("stateless");
+        return new Control.StatefulBuilder("routine", mPendingIntent)
+            .setTitle("Routine Title")
+            .setSubtitle("Routine Subtitle")
+            .setStatus(Control.STATUS_OK)
+            .setStatusText("Good Morning")
+            .setDeviceType(DeviceTypes.TYPE_ROUTINE)
+            .setControlTemplate(template)
+            .build();
+    }
+
+    @Override
+    public Publisher<Control> createPublisherForAllAvailable() {
+        return new CtsControlsPublisher(mAllControls.stream()
+            .map(c -> new Control.StatelessBuilder(c).build())
+            .collect(Collectors.toList()));
+    }
+
+    @Override
+    public Publisher<Control> createPublisherForSuggested() {
+        return new CtsControlsPublisher(mAllControls.stream()
+            .map(c -> new Control.StatelessBuilder(c).build())
+            .collect(Collectors.toList()));
+    }
+
+    @Override
+    public Publisher<Control> createPublisherFor(List<String> controlIds) {
+        mUpdatePublisher = new CtsControlsPublisher(null);
+
+        for (String id : controlIds) {
+            Control control = mControlsById.get(id);
+            if (control == null) continue;
+
+            mUpdatePublisher.onNext(control);
+        }
+
+        return mUpdatePublisher;
+    }
+
+    @Override
+    public void performControlAction(String controlId, ControlAction action,
+            Consumer<Integer> consumer) {
+        Control c = mControlsById.get(controlId);
+        if (c == null) return;
+
+        Control.StatefulBuilder builder = controlToBuilder(c);
+
+        // Modify the builder in order to update the Control to have predefined, verifiable behavior
+        if (action instanceof BooleanAction) {
+            BooleanAction b = (BooleanAction) action;
+
+            if (c.getDeviceType() == DeviceTypes.TYPE_LIGHT) {
+                RangeTemplate rt = new RangeTemplate("range",
+                        0.0f /* minValue */,
+                        100.0f /* maxValue */,
+                        50.0f /* currentValue */,
+                        1.0f /* step */, null);
+                String desc = b.getNewState() ? "On" : "Off";
+
+                builder.setStatusText(desc);
+                builder.setControlTemplate(new ToggleRangeTemplate("toggleRange", b.getNewState(),
+                        desc, rt));
+            } else if (c.getDeviceType() == DeviceTypes.TYPE_ROUTINE) {
+                builder.setStatusText("Running");
+                builder.setControlTemplate(new StatelessTemplate("stateless"));
+            } else if (c.getDeviceType() == DeviceTypes.TYPE_SWITCH) {
+                String desc = b.getNewState() ? "On" : "Off";
+                builder.setStatusText(desc);
+                ControlButton button = new ControlButton(b.getNewState(), desc);
+                builder.setControlTemplate(new ToggleTemplate("toggle", button));
+            } else if (c.getDeviceType() == DeviceTypes.TYPE_LOCK) {
+                String value = action.getChallengeValue();
+                if (value != null && value.equals("1234")) {
+                    String desc = b.getNewState() ? "Locked" : "Unlocked";
+                    ControlButton button = new ControlButton(b.getNewState(), desc);
+                    builder.setStatusText(desc);
+                    builder.setControlTemplate(new ToggleTemplate("toggle", button));
+                } else {
+                    consumer.accept(ControlAction.RESPONSE_CHALLENGE_PIN);
+                    return;
+                }
+            } else if (c.getDeviceType() == DeviceTypes.TYPE_GATE) {
+                String value = action.getChallengeValue();
+                if (value != null && value.equals("abc123")) {
+                    String desc = b.getNewState() ? "Locked" : "Unlocked";
+                    ControlButton button = new ControlButton(b.getNewState(), desc);
+                    builder.setStatusText(desc);
+                    builder.setControlTemplate(new ToggleTemplate("toggle", button));
+                } else {
+                    consumer.accept(ControlAction.RESPONSE_CHALLENGE_PASSPHRASE);
+                    return;
+                }
+            } else if (c.getDeviceType() == DeviceTypes.TYPE_MOWER) {
+                String value = action.getChallengeValue();
+                if (value != null && value.equals("true")) {
+                    String desc = b.getNewState() ? "Started" : "Stopped";
+                    ControlButton button = new ControlButton(b.getNewState(), desc);
+                    builder.setStatusText(desc);
+                    builder.setControlTemplate(new ToggleTemplate("toggle", button));
+                } else {
+                    consumer.accept(ControlAction.RESPONSE_CHALLENGE_ACK);
+                    return;
+                }
+            }
+        } else if (action instanceof FloatAction) {
+            FloatAction f = (FloatAction) action;
+            if (c.getDeviceType() == DeviceTypes.TYPE_LIGHT) {
+                RangeTemplate rt = new RangeTemplate("range", 0.0f, 100.0f, f.getNewValue(), 1.0f,
+                        null);
+
+                ToggleRangeTemplate trt = (ToggleRangeTemplate) c.getControlTemplate();
+                String desc = trt.getActionDescription().toString();
+                boolean state = trt.isChecked();
+
+                builder.setStatusText(desc);
+                builder.setControlTemplate(new ToggleRangeTemplate("toggleRange", state, desc, rt));
+            }
+        } else if (action instanceof ModeAction) {
+            ModeAction m = (ModeAction) action;
+            if (c.getDeviceType() == DeviceTypes.TYPE_THERMOSTAT) {
+                ControlTemplate template = new TemperatureControlTemplate("temperature",
+                        ControlTemplate.getNoTemplateObject(),
+                        m.getNewMode(),
+                        TemperatureControlTemplate.MODE_OFF,
+                        TemperatureControlTemplate.FLAG_MODE_HEAT
+                        | TemperatureControlTemplate.FLAG_MODE_COOL
+                        | TemperatureControlTemplate.FLAG_MODE_OFF
+                        | TemperatureControlTemplate.FLAG_MODE_ECO);
+
+                builder.setControlTemplate(template);
+            }
+        } else if (action instanceof CommandAction) {
+            builder.setControlTemplate(new StatelessTemplate("stateless"));
+        }
+
+        // Finally build and send the default OK status
+        Control updatedControl = builder.build();
+        mControlsById.put(controlId, updatedControl);
+        mUpdatePublisher.onNext(updatedControl);
+        consumer.accept(ControlAction.RESPONSE_OK);
+    }
+
+    private Control.StatefulBuilder controlToBuilder(Control c) {
+        return new Control.StatefulBuilder(c.getControlId(), c.getAppIntent())
+            .setTitle(c.getTitle())
+            .setSubtitle(c.getSubtitle())
+            .setStructure(c.getStructure())
+            .setDeviceType(c.getDeviceType())
+            .setZone(c.getZone())
+            .setStatus(Control.STATUS_OK)
+            .setStatusText("Refreshed");
+    }
+}
diff --git a/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java b/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java
new file mode 100644
index 0000000..97dae17
--- /dev/null
+++ b/tests/controls/src/android/controls/cts/CtsControlsServiceTest.java
@@ -0,0 +1,385 @@
+/*
+ * 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.controls.cts;
+
+import android.service.controls.Control;
+import android.service.controls.actions.BooleanAction;
+import android.service.controls.actions.CommandAction;
+import android.service.controls.actions.ControlAction;
+import android.service.controls.actions.FloatAction;
+import android.service.controls.actions.ModeAction;
+import android.service.controls.templates.ControlTemplate;
+import android.service.controls.templates.RangeTemplate;
+import android.service.controls.templates.TemperatureControlTemplate;
+import android.service.controls.templates.ToggleRangeTemplate;
+import android.service.controls.templates.ToggleTemplate;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Flow.Publisher;
+import java.util.concurrent.Flow.Subscriber;
+import java.util.concurrent.Flow.Subscription;
+import java.util.function.Consumer;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+@RunWith(AndroidJUnit4.class)
+public class CtsControlsServiceTest {
+
+    private CtsControlsService mControlsService;
+
+    @Before
+    public void setUp() {
+        mControlsService = new CtsControlsService();
+    }
+
+    @Test
+    public void testLoadAllAvailable() {
+        Publisher<Control> publisher = mControlsService.createPublisherForAllAvailable();
+        List<Control> loadedControls = new ArrayList<>();
+        subscribe(publisher, 10, loadedControls);
+
+        List<Control> expectedControls = new ArrayList<>();
+        expectedControls.add(new Control.StatelessBuilder(
+                mControlsService.buildLight(false, 0.0f)).build());
+        expectedControls.add(new Control.StatelessBuilder(
+                mControlsService.buildLock(false)).build());
+        expectedControls.add(new Control.StatelessBuilder(
+                mControlsService.buildRoutine()).build());
+        expectedControls.add(new Control.StatelessBuilder(mControlsService.buildThermostat(
+                TemperatureControlTemplate.MODE_OFF)).build());
+        expectedControls.add(new Control.StatelessBuilder(
+                mControlsService.buildMower(false)).build());
+        expectedControls.add(new Control.StatelessBuilder(
+                mControlsService.buildSwitch(false)).build());
+        expectedControls.add(new Control.StatelessBuilder(
+                mControlsService.buildGate(false)).build());
+
+        assertControlsList(loadedControls, expectedControls);
+    }
+
+    @Test
+    public void testLoadSuggested() {
+        Publisher<Control> publisher = mControlsService.createPublisherForSuggested();
+        List<Control> loadedControls = new ArrayList<>();
+        subscribe(publisher, 3, loadedControls);
+
+        List<Control> expectedControls = new ArrayList<>();
+        expectedControls.add(new Control.StatelessBuilder(
+                mControlsService.buildLight(false, 0.0f)).build());
+        expectedControls.add(new Control.StatelessBuilder(
+                mControlsService.buildLock(false)).build());
+        expectedControls.add(new Control.StatelessBuilder(
+                mControlsService.buildRoutine()).build());
+
+        assertControlsList(loadedControls, expectedControls);
+    }
+
+    @Test
+    public void testPublisherForSingleControl() {
+        List<String> idsToLoad = new ArrayList<>();
+        idsToLoad.add("mower");
+
+        Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
+        List<Control> loadedControls = new ArrayList<>();
+        subscribe(publisher, 10, loadedControls);
+
+        List<Control> expectedControls = new ArrayList<>();
+        expectedControls.add(mControlsService.buildMower(false));
+
+        assertControlsList(loadedControls, expectedControls);
+    }
+
+    @Test
+    public void testPublisherForMultipleControls() {
+        List<String> idsToLoad = new ArrayList<>();
+        idsToLoad.add("lock");
+        idsToLoad.add("light");
+
+        Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
+        List<Control> loadedControls = new ArrayList<>();
+        subscribe(publisher, 10, loadedControls);
+
+        List<Control> expectedControls = new ArrayList<>();
+        expectedControls.add(mControlsService.buildLock(false));
+        expectedControls.add(mControlsService.buildLight(false, 0.0f));
+
+        assertControlsList(loadedControls, expectedControls);
+    }
+
+    @Test
+    public void testBooleanAction() {
+        List<String> idsToLoad = new ArrayList<>();
+        idsToLoad.add("switch");
+
+        Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
+        List<Control> loadedControls = new ArrayList<>();
+        subscribe(publisher, 10, loadedControls);
+
+        mControlsService.performControlAction("switch", new BooleanAction("action", true),
+                assertConsumer(ControlAction.RESPONSE_OK));
+
+        List<Control> expectedControls = new ArrayList<>();
+        expectedControls.add(mControlsService.buildSwitch(false));
+        expectedControls.add(mControlsService.buildSwitch(true));
+
+        assertControlsList(loadedControls, expectedControls);
+    }
+
+    @Test
+    public void testFloatAction() {
+        List<String> idsToLoad = new ArrayList<>();
+        idsToLoad.add("light");
+
+        Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
+        List<Control> loadedControls = new ArrayList<>();
+        subscribe(publisher, 10, loadedControls);
+
+        mControlsService.performControlAction("light", new BooleanAction("action", true),
+                assertConsumer(ControlAction.RESPONSE_OK));
+
+        mControlsService.performControlAction("light", new FloatAction("action", 80.0f),
+                assertConsumer(ControlAction.RESPONSE_OK));
+
+        List<Control> expectedControls = new ArrayList<>();
+        expectedControls.add(mControlsService.buildLight(false, 0.0f));
+        expectedControls.add(mControlsService.buildLight(true, 50.0f));
+        expectedControls.add(mControlsService.buildLight(true, 80.0f));
+
+        assertControlsList(loadedControls, expectedControls);
+    }
+
+    @Test
+    public void testCommandAction() {
+        List<String> idsToLoad = new ArrayList<>();
+        idsToLoad.add("routine");
+
+        Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
+        List<Control> loadedControls = new ArrayList<>();
+        subscribe(publisher, 10, loadedControls);
+
+        mControlsService.performControlAction("routine", new CommandAction("action"),
+                assertConsumer(ControlAction.RESPONSE_OK));
+
+        List<Control> expectedControls = new ArrayList<>();
+        expectedControls.add(mControlsService.buildRoutine());
+        expectedControls.add(mControlsService.buildRoutine());
+
+        assertControlsList(loadedControls, expectedControls);
+    }
+
+    @Test
+    public void testBooleanActionWithPinChallenge() {
+        List<String> idsToLoad = new ArrayList<>();
+        idsToLoad.add("lock");
+
+        Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
+        List<Control> loadedControls = new ArrayList<>();
+        subscribe(publisher, 10, loadedControls);
+
+        mControlsService.performControlAction("lock", new BooleanAction("action", true),
+                assertConsumer(ControlAction.RESPONSE_CHALLENGE_PIN));
+
+        mControlsService.performControlAction("lock", new BooleanAction("action", true, "1234"),
+                assertConsumer(ControlAction.RESPONSE_OK));
+
+        List<Control> expectedControls = new ArrayList<>();
+        expectedControls.add(mControlsService.buildLock(false));
+        expectedControls.add(mControlsService.buildLock(true));
+
+        assertControlsList(loadedControls, expectedControls);
+    }
+
+    @Test
+    public void testBooleanActionWithPassphraseChallenge() {
+        List<String> idsToLoad = new ArrayList<>();
+        idsToLoad.add("gate");
+
+        Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
+        List<Control> loadedControls = new ArrayList<>();
+        subscribe(publisher, 10, loadedControls);
+
+        mControlsService.performControlAction("gate", new BooleanAction("action", true),
+                assertConsumer(ControlAction.RESPONSE_CHALLENGE_PASSPHRASE));
+
+        mControlsService.performControlAction("gate", new BooleanAction("action", true, "abc123"),
+                assertConsumer(ControlAction.RESPONSE_OK));
+
+        List<Control> expectedControls = new ArrayList<>();
+        expectedControls.add(mControlsService.buildGate(false));
+        expectedControls.add(mControlsService.buildGate(true));
+
+        assertControlsList(loadedControls, expectedControls);
+    }
+
+    @Test
+    public void testBooleanActionWithAckChallenge() {
+        List<String> idsToLoad = new ArrayList<>();
+        idsToLoad.add("mower");
+
+        Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
+        List<Control> loadedControls = new ArrayList<>();
+        subscribe(publisher, 10, loadedControls);
+
+        mControlsService.performControlAction("mower", new BooleanAction("action", true),
+                assertConsumer(ControlAction.RESPONSE_CHALLENGE_ACK));
+
+        mControlsService.performControlAction("mower", new BooleanAction("action", true, "true"),
+                assertConsumer(ControlAction.RESPONSE_OK));
+
+        List<Control> expectedControls = new ArrayList<>();
+        expectedControls.add(mControlsService.buildMower(false));
+        expectedControls.add(mControlsService.buildMower(true));
+
+        assertControlsList(loadedControls, expectedControls);
+    }
+
+    @Test
+    public void testModeAction() {
+        List<String> idsToLoad = new ArrayList<>();
+        idsToLoad.add("thermostat");
+
+        Publisher<Control> publisher = mControlsService.createPublisherFor(idsToLoad);
+        List<Control> loadedControls = new ArrayList<>();
+        subscribe(publisher, 10, loadedControls);
+
+        mControlsService.performControlAction("thermostat",
+                new ModeAction("action", TemperatureControlTemplate.MODE_COOL),
+                assertConsumer(ControlAction.RESPONSE_OK));
+
+        List<Control> expectedControls = new ArrayList<>();
+        expectedControls.add(mControlsService.buildThermostat(TemperatureControlTemplate.MODE_OFF));
+        expectedControls.add(mControlsService.buildThermostat(
+                TemperatureControlTemplate.MODE_COOL));
+
+        assertControlsList(loadedControls, expectedControls);
+    }
+
+    private void assertConsumerOk(int status) {
+        assertEquals(status, ControlAction.RESPONSE_OK);
+    }
+
+    private Consumer<Integer> assertConsumer(int expectedStatus) {
+        return (status) -> {
+            assertEquals((int) status, expectedStatus);
+        };
+    }
+
+    private void subscribe(Publisher<Control> publisher, final int request,
+            final List<Control> addToList) {
+        publisher.subscribe(new Subscriber<Control>() {
+                public void onSubscribe(Subscription s) {
+                    s.request(request);
+                }
+
+                public void onNext(Control c) {
+                    addToList.add(c);
+                }
+
+                public void onError(Throwable t) {
+                    throw new IllegalStateException("onError should not be called here");
+                }
+
+                public void onComplete() {
+
+                }
+            });
+    }
+
+    private void assertControlsList(List<Control> actualControls, List<Control> expectedControls) {
+        assertEquals(actualControls.size(), expectedControls.size());
+
+        for (int i = 0; i < actualControls.size(); i++) {
+            assertControlEquals(actualControls.get(i), expectedControls.get(i));
+        }
+    }
+
+    private void assertControlEquals(Control c1, Control c2) {
+        assertEquals(c1.getTitle(), c2.getTitle());
+        assertEquals(c1.getSubtitle(), c2.getSubtitle());
+        assertEquals(c1.getStructure(), c2.getStructure());
+        assertEquals(c1.getZone(), c2.getZone());
+        assertEquals(c1.getDeviceType(), c2.getDeviceType());
+        assertEquals(c1.getStatus(), c2.getStatus());
+        assertEquals(c1.getControlId(), c2.getControlId());
+
+        assertTemplateEquals(c1.getControlTemplate(), c2.getControlTemplate());
+    }
+
+    private void assertTemplateEquals(ControlTemplate ct1, ControlTemplate ct2) {
+        if (ct1 == null) {
+            assertNull(ct2);
+            return;
+        } else {
+            assertNotNull(ct2);
+        }
+
+        assertEquals(ct1.getTemplateType(), ct2.getTemplateType());
+        assertEquals(ct1.getTemplateId(), ct2.getTemplateId());
+
+        switch (ct1.getTemplateType()) {
+            case ControlTemplate.TYPE_TOGGLE:
+                assertToggleTemplate((ToggleTemplate) ct1, (ToggleTemplate) ct2);
+                break;
+            case ControlTemplate.TYPE_RANGE:
+                assertRangeTemplate((RangeTemplate) ct1, (RangeTemplate) ct2);
+                break;
+            case ControlTemplate.TYPE_TEMPERATURE:
+                assertTemperatureControlTemplate((TemperatureControlTemplate) ct1,
+                        (TemperatureControlTemplate) ct2);
+                break;
+            case ControlTemplate.TYPE_TOGGLE_RANGE:
+                assertToggleRangeTemplate((ToggleRangeTemplate) ct1, (ToggleRangeTemplate) ct2);
+                break;
+        }
+    }
+
+    private void assertToggleTemplate(ToggleTemplate t1, ToggleTemplate t2) {
+        assertEquals(t1.isChecked(), t2.isChecked());
+        assertEquals(t1.getContentDescription(), t2.getContentDescription());
+    }
+
+    private void assertRangeTemplate(RangeTemplate t1, RangeTemplate t2) {
+        assertEquals(t1.getMinValue(), t2.getMinValue(), 0.0f);
+        assertEquals(t1.getMaxValue(), t2.getMaxValue(), 0.0f);
+        assertEquals(t1.getCurrentValue(), t2.getCurrentValue(), 0.0f);
+        assertEquals(t1.getStepValue(), t2.getStepValue(), 0.0f);
+        assertEquals(t1.getFormatString(), t2.getFormatString());
+    }
+
+    private void assertTemperatureControlTemplate(TemperatureControlTemplate t1,
+            TemperatureControlTemplate t2) {
+        assertEquals(t1.getCurrentMode(), t2.getCurrentMode());
+        assertEquals(t1.getCurrentActiveMode(), t2.getCurrentActiveMode());
+        assertEquals(t1.getModes(), t2.getModes());
+        assertTemplateEquals(t1.getTemplate(), t2.getTemplate());
+    }
+
+    private void assertToggleRangeTemplate(ToggleRangeTemplate t1, ToggleRangeTemplate t2) {
+        assertEquals(t1.isChecked(), t2.isChecked());
+        assertEquals(t1.getActionDescription(), t2.getActionDescription());
+        assertRangeTemplate(t1.getRange(), t2.getRange());
+    }
+}
diff --git a/tests/framework/base/windowmanager/Android.bp b/tests/framework/base/windowmanager/Android.bp
index 0415b9a..cf35fd0 100644
--- a/tests/framework/base/windowmanager/Android.bp
+++ b/tests/framework/base/windowmanager/Android.bp
@@ -21,3 +21,8 @@
     name: "cts-wm-aspect-ratio-test-base",
     srcs: ["src/android/server/wm/AspectRatioTestsBase.java"],
 }
+
+filegroup {
+    name: "cts-wm-decor-inset-test-base",
+    srcs: ["src/android/server/wm/DecorInsetTestsBase.java"],
+}
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index c3d5bce..e9b1aec 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -299,6 +299,22 @@
         <activity android:name="android.server.wm.DisplayCutoutTests$TestActivity"
                   android:turnScreenOn="true"
                   android:showWhenLocked="true"/>
+
+        <activity android:name="android.server.wm.WindowInsetsAnimationSynchronicityTests$TestActivity"
+                  android:turnScreenOn="true"
+                  android:showWhenLocked="true"/>
+        <service
+            android:name="android.server.wm.WindowInsetsAnimationSynchronicityTests$SimpleIme"
+            android:label="Simple IME"
+            android:permission="android.permission.BIND_INPUT_METHOD">
+            <intent-filter>
+                <action android:name="android.view.InputMethod" />
+            </intent-filter>
+            <meta-data
+                android:name="android.view.im"
+                android:resource="@xml/simple_method" />
+        </service>
+
         <activity android:name="android.server.wm.KeyEventActivity"
                   android:exported="true"
                   android:configChanges="orientation|screenLayout"
@@ -326,6 +342,7 @@
         <activity android:name="android.server.wm.WindowInsetsLayoutTests$TestActivity" />
         <activity android:name="android.server.wm.WindowInsetsControllerTests$TestActivity" />
         <activity android:name="android.server.wm.WindowInsetsControllerTests$TestHideOnCreateActivity" />
+        <activity android:name="android.server.wm.WindowInsetsControllerTests$TestShowOnCreateActivity" />
 
         <activity android:name="android.server.wm.DragDropTest$DragDropActivity"
                   android:screenOrientation="locked"
@@ -338,6 +355,11 @@
             </intent-filter>
         </activity>
 
+        <activity
+            android:name="android.server.wm.DecorInsetTestsBase$TestActivity"
+            android:label="DecorInsetTestsBase.TestActivity"
+            android:exported="true" />
+
         <activity android:name="android.server.wm.WindowCtsActivity"
                   android:theme="@android:style/Theme.Material.NoActionBar"
                   android:screenOrientation="locked"
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java
index 6a6aac4..959c922 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/AlwaysFocusablePipActivity.java
@@ -18,6 +18,7 @@
 
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
+import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
 import android.app.Activity;
@@ -28,12 +29,22 @@
 public class AlwaysFocusablePipActivity extends Activity {
 
     static void launchAlwaysFocusablePipActivity(Activity caller, boolean newTask) {
+        launchAlwaysFocusablePipActivity(caller, newTask, false /* multiTask */);
+    }
+
+    static void launchAlwaysFocusablePipActivity(Activity caller, boolean newTask,
+            boolean multiTask) {
         final Intent intent = new Intent(caller, AlwaysFocusablePipActivity.class);
 
-        intent.setFlags(FLAG_ACTIVITY_CLEAR_TASK);
+        intent.setFlags(0);
         if (newTask) {
             intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
         }
+        if (multiTask) {
+            intent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);
+        } else {
+            intent.addFlags(FLAG_ACTIVITY_CLEAR_TASK);
+        }
 
         final ActivityOptions options = ActivityOptions.makeBasic();
         options.setLaunchBounds(new Rect(0, 0, 500, 500));
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java
index 4830e19..f2721bb 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/LaunchIntoPinnedStackPipActivity.java
@@ -22,6 +22,7 @@
     @Override
     protected void onResume() {
         super.onResume();
-        AlwaysFocusablePipActivity.launchAlwaysFocusablePipActivity(this, true /* newTask */);
+        AlwaysFocusablePipActivity.launchAlwaysFocusablePipActivity(this, true /* newTask */,
+                true /* multiTask */);
     }
 }
diff --git a/tests/framework/base/windowmanager/res/xml/simple_method.xml b/tests/framework/base/windowmanager/res/xml/simple_method.xml
new file mode 100644
index 0000000..87cb1ad
--- /dev/null
+++ b/tests/framework/base/windowmanager/res/xml/simple_method.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<!-- Configuration info for an input method -->
+<input-method xmlns:android="http://schemas.android.com/apk/res/android" />
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
new file mode 100644
index 0000000..3aa6fec
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.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.server.wm;
+
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
+import static android.server.wm.ActivityLauncher.KEY_NEW_TASK;
+import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.ActivityOptions;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.cts.R;
+import android.util.Range;
+
+import org.junit.Test;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * <p>Build/Install/Run:
+ * atest CtsWindowManagerDeviceTestCases:ActivityTransitionTests
+ */
+@Presubmit
+public class ActivityTransitionTests extends ActivityManagerTestBase {
+    @Test
+    public void testActivityTransitionDurationNoShortenAsExpected() throws Exception {
+        final long expectedDurationMs = 500L - 100L;
+        final long minDurationMs = expectedDurationMs;
+        final long maxDurationMs = expectedDurationMs + 300L;
+        final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        long[] transitionStartTime = new long[1];
+        long[] transitionEndTime = new long[1];
+
+        final ActivityOptions.OnAnimationStartedListener startedListener = () -> {
+            transitionStartTime[0] = System.currentTimeMillis();
+        };
+
+        final ActivityOptions.OnAnimationFinishedListener finishedListener = () -> {
+            transitionEndTime[0] = System.currentTimeMillis();
+            latch.countDown();
+        };
+
+        final Bundle bundle = ActivityOptions.makeCustomAnimation(mContext,
+                R.anim.alpha, 0, new Handler(Looper.getMainLooper()), startedListener,
+                finishedListener).toBundle();
+        final Intent intent = new Intent().setComponent(TEST_ACTIVITY)
+                .addFlags(FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent, bundle);
+        mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+        waitAndAssertTopResumedActivity(TEST_ACTIVITY, DEFAULT_DISPLAY,
+                "Activity must be launched");
+
+        latch.await(2, TimeUnit.SECONDS);
+        final long totalTime = transitionEndTime[0] - transitionStartTime[0];
+        assertTrue("Actual transition duration should be in the range "
+                + "<" + minDurationMs + ", " + maxDurationMs + "> ms, "
+                + "actual=" + totalTime, durationRange.contains(totalTime));
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
index a091a1f..79ce524 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
@@ -218,6 +218,8 @@
 
     @Test
     public void testTurnScreenOnActivity() {
+        assumeTrue(supportsLockScreen());
+
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         final ActivitySessionClient activityClient = createManagedActivityClientSession();
         testTurnScreenOnActivity(lockScreenSession, activityClient, true /* useWindowFlags */);
@@ -255,6 +257,7 @@
         getLaunchActivityBuilder()
                 .setTargetActivity(BROADCAST_RECEIVER_ACTIVITY)
                 .setWaitForLaunched(true)
+                .setUseInstrumentation()
                 .execute();
         mWmState.assertVisibility(BROADCAST_RECEIVER_ACTIVITY, true);
         // Launch something to fullscreen stack to make it focused.
@@ -460,6 +463,8 @@
 
     @Test
     public void testTurnScreenOnAttrNoLockScreen() {
+        assumeTrue(supportsLockScreen());
+
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         lockScreenSession.disableLockScreen().sleepDevice();
         separateTestJournal();
@@ -485,6 +490,8 @@
 
     @Test
     public void testTurnScreenOnShowOnLockAttr() {
+        assumeTrue(supportsLockScreen());
+
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         lockScreenSession.sleepDevice();
         mWmState.waitForAllStoppedActivities();
@@ -497,6 +504,8 @@
 
     @Test
     public void testTurnScreenOnAttrRemove() {
+        assumeTrue(supportsLockScreen());
+
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         lockScreenSession.sleepDevice();
         mWmState.waitForAllStoppedActivities();
@@ -518,6 +527,8 @@
 
     @Test
     public void testTurnScreenOnSingleTask() {
+        assumeTrue(supportsLockScreen());
+
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         lockScreenSession.sleepDevice();
         separateTestJournal();
@@ -543,6 +554,8 @@
 
     @Test
     public void testTurnScreenOnActivity_withRelayout() {
+        assumeTrue(supportsLockScreen());
+
         final LockScreenSession lockScreenSession = createManagedLockScreenSession();
         lockScreenSession.sleepDevice();
         launchActivity(TURN_SCREEN_ON_WITH_RELAYOUT_ACTIVITY);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AddWindowAsUserTest.java b/tests/framework/base/windowmanager/src/android/server/wm/AddWindowAsUserTest.java
new file mode 100644
index 0000000..4c33be8
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AddWindowAsUserTest.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.server.wm;
+
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.UserHandle;
+import android.platform.test.annotations.Presubmit;
+import android.view.View;
+import android.view.WindowManager;
+
+import org.junit.Test;
+
+@Presubmit
+public class AddWindowAsUserTest extends ActivityManagerTestBase  {
+    @Test
+    public void testAddWindowSecondaryUser() {
+        // Get original userId from context first, not every platform use SYSTEM as default user.
+        final int myUserId = mContext.getUserId();
+        testAddWindowWithUser(UserHandle.of(myUserId), false /* shouldCatchException */);
+
+        // Doesn't grant INTERACT_ACROSS_USERS_FULL permission, so any other user should not
+        // able to add window.
+        testAddWindowWithUser(UserHandle.ALL, true);
+    }
+
+    private void testAddWindowWithUser(UserHandle user, boolean shouldCatchException) {
+        mInstrumentation.runOnMainSync(() -> {
+            final View view = new View(mContext);
+            final WindowManager wm = getWindowManagerForUser(user);
+            boolean catchException = false;
+            try {
+                wm.addView(view, new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY));
+            } catch (WindowManager.BadTokenException exception) {
+                catchException = true;
+            } finally {
+                if (!catchException) {
+                    wm.removeViewImmediate(view);
+                }
+            }
+            if (shouldCatchException) {
+                assertTrue("Should receive exception for user " + user, catchException);
+            } else {
+                assertFalse("Shouldn't receive exception for user " + user, catchException);
+            }
+        });
+    }
+
+    private WindowManager getWindowManagerForUser(UserHandle user) {
+        final Context userContext = mContext.createContextAsUser(user, 0);
+        return userContext.getSystemService(WindowManager.class);
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AnrTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AnrTests.java
index 608a760..effa21f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AnrTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AnrTests.java
@@ -26,7 +26,9 @@
 import static android.view.Display.DEFAULT_DISPLAY;
 
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
+import android.app.ActivityTaskManager;
 import android.content.ComponentName;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
@@ -67,6 +69,8 @@
     @Before
     public void setup() throws Exception {
         super.setUp();
+        assumeTrue(mAtm.currentUiModeSupportsErrorDialogs(mContext));
+
         mLogSeparator = separateLogs(); // add a new separator for logs
         mHideDialogSetting = new SettingsSession<>(
                 Settings.Global.getUriFor(Settings.Global.HIDE_ERROR_DIALOGS),
@@ -76,7 +80,7 @@
 
     @After
     public void teardown() {
-        mHideDialogSetting.close();
+        if (mHideDialogSetting != null) mHideDialogSetting.close();
         stopTestPackage(UNRESPONSIVE_ACTIVITY.getPackageName());
         stopTestPackage(HOST_ACTIVITY.getPackageName());
     }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
index 739bfb4..868187c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/AppConfigurationTests.java
@@ -240,9 +240,6 @@
         setActivityTaskWindowingMode(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
         final SizeInfo dockedSizes = getActivityDisplaySize(activityName);
         assertSizesAreSane(initialFullscreenSizes, dockedSizes);
-        // Make sure docked stack is focused. This way when we dismiss it later fullscreen stack
-        // will come up.
-        launchActivity(activityName, WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
 
         // Resize docked stack to fullscreen size. This will trigger activity relaunch with
         // non-empty override configuration corresponding to fullscreen size.
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DecorInsetTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DecorInsetTests.java
new file mode 100644
index 0000000..8997f60
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DecorInsetTests.java
@@ -0,0 +1,94 @@
+/*
+ * 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.server.wm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.view.WindowInsets;
+
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Build/Install/Run:
+ *  atest CtsWindowManagerDeviceTestCases:DecorInsetTests
+ */
+@Presubmit
+public class DecorInsetTests extends DecorInsetTestsBase {
+
+    @Test
+    public void testDecorView_consumesAllInsets_byDefault() throws Throwable {
+        TestActivity activity = mDecorActivity.launchActivity(new Intent()
+                .putExtra(ARG_LAYOUT_STABLE, false)
+                .putExtra(ARG_LAYOUT_FULLSCREEN, false)
+                .putExtra(ARG_LAYOUT_HIDE_NAV, false)
+                .putExtra(ARG_DECOR_FITS_SYSTEM_WINDOWS, true));
+        activity.mLaidOut.await(4, TimeUnit.SECONDS);
+
+        assertNotNull("test setup failed", activity.mLastDecorInsets);
+        assertNull("unexpected content insets", activity.mLastContentInsets);
+    }
+
+    @Test
+    public void testDecorView_consumesNavBar_ifLayoutHideNavIsNotSet() throws Throwable {
+        TestActivity activity = mDecorActivity.launchActivity(new Intent()
+                .putExtra(ARG_LAYOUT_STABLE, true)
+                .putExtra(ARG_LAYOUT_FULLSCREEN, true)
+                .putExtra(ARG_LAYOUT_HIDE_NAV, false)
+                .putExtra(ARG_DECOR_FITS_SYSTEM_WINDOWS, true));
+        activity.mLaidOut.await(4, TimeUnit.SECONDS);
+
+        assertNotNull("test setup failed", activity.mLastDecorInsets);
+        assertEquals("unexpected bottom inset: ", 0, activity.mLastContentInsets.getInsets(
+                WindowInsets.Type.systemBars()).bottom);
+    }
+
+    @Test
+    public void testDecorView_doesntConsumeNavBar_ifLayoutHideNavIsSet() throws Throwable {
+        TestActivity activity = mDecorActivity.launchActivity(new Intent()
+                .putExtra(ARG_LAYOUT_STABLE, true)
+                .putExtra(ARG_LAYOUT_FULLSCREEN, false)
+                .putExtra(ARG_LAYOUT_HIDE_NAV, true)
+                .putExtra(ARG_DECOR_FITS_SYSTEM_WINDOWS, true));
+        activity.mLaidOut.await(4, TimeUnit.SECONDS);
+
+        assertNotNull("test setup failed", activity.mLastDecorInsets);
+        assertEquals("insets were unexpectedly consumed: ",
+                activity.mLastDecorInsets.getSystemWindowInsets(),
+                activity.mLastContentInsets.getSystemWindowInsets());
+    }
+
+    @Test
+    public void testDecorView_doesntConsumeNavBar_ifDecorDoesntFitSystemWindows() throws Throwable {
+        TestActivity activity = mDecorActivity.launchActivity(new Intent()
+                .putExtra(ARG_LAYOUT_STABLE, true)
+                .putExtra(ARG_LAYOUT_FULLSCREEN, false)
+                .putExtra(ARG_LAYOUT_HIDE_NAV, false)
+                .putExtra(ARG_DECOR_FITS_SYSTEM_WINDOWS, false));
+        activity.mLaidOut.await(4, TimeUnit.SECONDS);
+
+        assertNotNull("test setup failed", activity.mLastDecorInsets);
+        assertEquals("insets were unexpectedly consumed: ",
+                activity.mLastDecorInsets.getSystemWindowInsets(),
+                activity.mLastContentInsets.getSystemWindowInsets());
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DecorInsetTestsBase.java b/tests/framework/base/windowmanager/src/android/server/wm/DecorInsetTestsBase.java
new file mode 100644
index 0000000..10b3d0d
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DecorInsetTestsBase.java
@@ -0,0 +1,93 @@
+/*
+ * 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.server.wm;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.view.WindowInsets;
+
+import androidx.annotation.Nullable;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Rule;
+
+import java.util.concurrent.CountDownLatch;
+
+public class DecorInsetTestsBase {
+
+    public static final String ARG_DECOR_FITS_SYSTEM_WINDOWS = "decorFitsSystemWindows";
+    public static final String ARG_LAYOUT_STABLE = "flagLayoutStable";
+    public static final String ARG_LAYOUT_FULLSCREEN = "flagLayoutFullscreen";
+    public static final String ARG_LAYOUT_HIDE_NAV = "flagLayoutHideNav";
+
+    @Rule
+    public ActivityTestRule<TestActivity> mDecorActivity = new ActivityTestRule<>(
+            TestActivity.class, false /* initialTouchMode */,
+            false /* launchActivity */);
+
+    public static class TestActivity extends Activity {
+        WindowInsets mLastContentInsets;
+        WindowInsets mLastDecorInsets;
+        final CountDownLatch mLaidOut = new CountDownLatch(1);
+
+        @Override
+        protected void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+
+            getWindow().setDecorFitsSystemWindows(
+                    getIntent().getBooleanExtra(ARG_DECOR_FITS_SYSTEM_WINDOWS, false));
+
+            View view = new View(this);
+            view.setSystemUiVisibility(intentToSysuiVisibility(getIntent()));
+
+            view.setOnApplyWindowInsetsListener((v, wi) -> {
+                mLastContentInsets = wi;
+                return WindowInsets.CONSUMED;
+            });
+            setContentView(view);
+            getWindow().getDecorView().setOnApplyWindowInsetsListener((v, wi) -> {
+                mLastDecorInsets = wi;
+                return v.onApplyWindowInsets(wi);
+            });
+
+            view.getViewTreeObserver().addOnGlobalLayoutListener(
+                    new ViewTreeObserver.OnGlobalLayoutListener() {
+                @Override
+                public void onGlobalLayout() {
+                    view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
+                    mLaidOut.countDown();
+                }
+            });
+        }
+
+        private static int intentToSysuiVisibility(Intent intent) {
+            int vis = 0;
+            vis |= intent.getBooleanExtra(ARG_LAYOUT_HIDE_NAV, false)
+                    ? View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION : 0;
+            vis |= intent.getBooleanExtra(ARG_LAYOUT_FULLSCREEN, false)
+                    ? View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN : 0;
+            vis |= intent.getBooleanExtra(ARG_LAYOUT_STABLE, false)
+                    ? View.SYSTEM_UI_FLAG_LAYOUT_STABLE : 0;
+            return vis;
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java
index 244beb0..874b13d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTestActivity.java
@@ -192,8 +192,8 @@
     private void testWithMargins() {
         doLayoutParamTest(params -> {
             params.gravity = Gravity.LEFT | Gravity.TOP;
-            params.horizontalMargin = .25f;
-            params.verticalMargin = .35f;
+            params.horizontalMargin = .10f;
+            params.verticalMargin = .15f;
             params.width = 200;
             params.height = 200;
             params.x = 0;
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
index f0c2b6e..856b4fa 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DialogFrameTests.java
@@ -215,8 +215,8 @@
 
     @Test
     public void testMarginsArePercentagesOfContentFrame() throws Exception {
-        float horizontalMargin = .25f;
-        float verticalMargin = .35f;
+        float horizontalMargin = .10f;
+        float verticalMargin = .15f;
         doParentChildTest(TEST_WITH_MARGINS, (parent, dialog) -> {
             Rect frame = parent.getContentFrame();
             Rect expectedFrame = new Rect(
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
index 49ef515..45acc41 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DisplayCutoutTests.java
@@ -16,7 +16,13 @@
 
 package android.server.wm;
 
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.server.wm.DisplayCutoutTests.TestActivity.EXTRA_CUTOUT_MODE;
+import static android.server.wm.DisplayCutoutTests.TestActivity.EXTRA_ORIENTATION;
 import static android.server.wm.DisplayCutoutTests.TestDef.Which.DISPATCHED;
 import static android.server.wm.DisplayCutoutTests.TestDef.Which.ROOT;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
@@ -27,7 +33,6 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static org.hamcrest.Matchers.anyOf;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.everyItem;
 import static org.hamcrest.Matchers.greaterThan;
@@ -54,6 +59,7 @@
 import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowInsets;
+import android.view.WindowInsets.Type;
 
 import androidx.test.rule.ActivityTestRule;
 
@@ -66,6 +72,9 @@
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ErrorCollector;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
 
 import java.util.Arrays;
 import java.util.List;
@@ -79,12 +88,29 @@
  */
 @Presubmit
 @android.server.wm.annotation.Group3
+@RunWith(Parameterized.class)
 public class DisplayCutoutTests {
     static final String LEFT = "left";
     static final String TOP = "top";
     static final String RIGHT = "right";
     static final String BOTTOM = "bottom";
 
+    @Parameterized.Parameters(name= "{1}({0})")
+    public static Object[][] data() {
+        return new Object[][]{
+                {SCREEN_ORIENTATION_PORTRAIT, "SCREEN_ORIENTATION_PORTRAIT"},
+                {SCREEN_ORIENTATION_LANDSCAPE, "SCREEN_ORIENTATION_LANDSCAPE"},
+                {SCREEN_ORIENTATION_REVERSE_LANDSCAPE, "SCREEN_ORIENTATION_REVERSE_LANDSCAPE"},
+                {SCREEN_ORIENTATION_REVERSE_PORTRAIT, "SCREEN_ORIENTATION_REVERSE_PORTRAIT"},
+        };
+    }
+
+    @Parameter(0)
+    public int orientation;
+
+    @Parameter(1)
+    public String orientationName;
+
     @Rule
     public final ErrorCollector mErrorCollector = new ErrorCollector();
 
@@ -144,8 +170,9 @@
     }
 
     @Test
-    public void testDisplayCutout_default_portrait() {
-        runTest(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT, (activity, insets, displayCutout, which) -> {
+    public void testDisplayCutout_default() {
+        runTest(LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT,
+                (activity, insets, displayCutout, which) -> {
             if (displayCutout == null) {
                 return;
             }
@@ -160,33 +187,38 @@
     }
 
     @Test
-    public void testDisplayCutout_landscape() {
-        // TODO add landscape variants
-    }
-
-    @Test
-    public void testDisplayCutout_shortEdges_portrait() {
-        runTest(LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, (a, insets, displayCutout, which) -> {
+    public void testDisplayCutout_shortEdges() {
+        runTest(LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES, (a, insets, cutout, which) -> {
             if (which == ROOT) {
-                assertThat("cutout is either null or the short edge insets must be equal to "
-                                + "those of the Display.getCutout() and the long edge "
-                                + "side insets must be 0",
-                        safeInsets(displayCutout),
-                        anyOf(is(nullValue()), onlyHasShortEdgeInsetsAndEqualsTo(
-                                safeInsets(a.getDisplay().getCutout()))));
+                final Rect appBounds = getAppBounds(a);
+                final Insets displaySafeInsets = Insets.of(safeInsets(a.getDisplay().getCutout()));
+                final Insets expected;
+                if (appBounds.height() > appBounds.width()) {
+                    // Portrait display
+                    expected = Insets.of(0, displaySafeInsets.top, 0, displaySafeInsets.bottom);
+                } else if (appBounds.height() < appBounds.width()) {
+                    // Landscape display
+                    expected = Insets.of(displaySafeInsets.left, 0, displaySafeInsets.right, 0);
+                } else {
+                    expected = Insets.NONE;
+                }
+                assertThat("cutout must provide the display's safe insets on short edges and zero"
+                                + " on the long edges.",
+                        Insets.of(safeInsets(cutout)),
+                        equalTo(expected));
             }
         });
     }
 
     @Test
-    public void testDisplayCutout_never_portrait() {
+    public void testDisplayCutout_never() {
         runTest(LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER, (a, insets, displayCutout, which) -> {
             assertThat("must not layout in cutout area in never mode", displayCutout, nullValue());
         });
     }
 
     @Test
-    public void testDisplayCutout_always_portrait() {
+    public void testDisplayCutout_always() {
         runTest(LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS, (a, insets, displayCutout, which) -> {
             if (which == ROOT) {
                 assertThat("Display.getCutout() must equal view root cutout",
@@ -196,8 +228,12 @@
     }
 
     private void runTest(int cutoutMode, TestDef test) {
+        runTest(cutoutMode, test, orientation);
+    }
+
+    private void runTest(int cutoutMode, TestDef test, int orientation) {
         final TestActivity activity = launchAndWait(mDisplayCutoutActivity,
-                cutoutMode);
+                cutoutMode, orientation);
 
         WindowInsets insets = getOnMainSync(activity::getRootInsets);
         WindowInsets dispatchedInsets = getOnMainSync(activity::getDispatchedInsets);
@@ -213,6 +249,7 @@
                 shortEdgeAsserts(activity, insets, displayCutout);
             }
             assertCutoutsAreConsistentWithInsets(activity, displayCutout);
+            assertSafeInsetsAreConsistentWithDisplayCutoutInsets(insets);
         }
         test.run(activity, insets, displayCutout, ROOT);
 
@@ -222,10 +259,24 @@
                 shortEdgeAsserts(activity, insets, displayCutout);
             }
             assertCutoutsAreConsistentWithInsets(activity, dispatchedDisplayCutout);
+            if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
+                assertSafeInsetsAreConsistentWithDisplayCutoutInsets(dispatchedInsets);
+            }
         }
         test.run(activity, dispatchedInsets, dispatchedDisplayCutout, DISPATCHED);
     }
 
+    private void assertSafeInsetsAreConsistentWithDisplayCutoutInsets(WindowInsets insets) {
+        DisplayCutout cutout = insets.getDisplayCutout();
+        Insets safeInsets = Insets.of(safeInsets(cutout));
+        assertEquals("WindowInsets.getInsets(displayCutout()) must equal"
+                        + " DisplayCutout.getSafeInsets()",
+                safeInsets, insets.getInsets(Type.displayCutout()));
+        assertEquals("WindowInsets.getInsetsIgnoringVisibility(displayCutout()) must equal"
+                        + " DisplayCutout.getSafeInsets()",
+                safeInsets, insets.getInsetsIgnoringVisibility(Type.displayCutout()));
+    }
+
     private void commonAsserts(TestActivity activity, DisplayCutout cutout) {
         assertSafeInsetsValid(cutout);
         assertCutoutsAreWithinSafeInsets(activity, cutout);
@@ -302,16 +353,15 @@
 
     private void assertOnlyShortEdgeHasInsets(TestActivity activity,
             DisplayCutout displayCutout) {
-        final Point displaySize = new Point();
-        runOnMainSync(() -> activity.getDecorView().getDisplay().getRealSize(displaySize));
-        if (displaySize.y > displaySize.x) {
+        final Rect appBounds = getAppBounds(activity);
+        if (appBounds.height() > appBounds.width()) {
             // Portrait display
             assertThat("left edge has a cutout despite being long edge",
                     displayCutout.getSafeInsetLeft(), is(0));
             assertThat("right edge has a cutout despite being long edge",
                     displayCutout.getSafeInsetRight(), is(0));
         }
-        if (displaySize.y < displaySize.x) {
+        if (appBounds.height() < appBounds.width()) {
             // Landscape display
             assertThat("top edge has a cutout despite being long edge",
                     displayCutout.getSafeInsetTop(), is(0));
@@ -321,10 +371,8 @@
     }
 
     private void assertOnlyShortEdgeHasBounds(TestActivity activity, DisplayCutout cutout) {
-        final Point displaySize = new Point();
-        runOnMainSync(() -> activity.getDecorView().getDisplay().getRealSize(displaySize));
         final Rect appBounds = getAppBounds(activity);
-        if (displaySize.y > displaySize.x) {
+        if (appBounds.height() > appBounds.width()) {
             // Portrait display
             assertThat("left edge has a cutout despite being long edge",
                     hasBound(LEFT, cutout, appBounds), is(false));
@@ -332,7 +380,7 @@
             assertThat("right edge has a cutout despite being long edge",
                     hasBound(RIGHT, cutout, appBounds), is(false));
         }
-        if (displaySize.y < displaySize.x) {
+        if (appBounds.height() < appBounds.width()) {
             // Landscape display
             assertThat("top edge has a cutout despite being long edge",
                     hasBound(TOP, cutout, appBounds), is(false));
@@ -409,18 +457,6 @@
         };
     }
 
-    private static Matcher<Rect> onlyHasShortEdgeInsetsAndEqualsTo(Rect expect) {
-        return new CustomTypeSafeMatcher<Rect>(
-                "must be 0 on long edge insets and equal on short edge insets to "
-                        + expect) {
-            @Override
-            protected boolean matchesSafely(Rect actual) {
-                return actual != null && actual.left == 0 && actual.top == expect.top
-                        && actual.right == 0 && actual.bottom == expect.bottom;
-            }
-        };
-    }
-
     private static Matcher<Rect> intersectsWith(Rect safeRect) {
         return new CustomTypeSafeMatcher<Rect>("intersects with " + safeRect) {
             @Override
@@ -455,16 +491,27 @@
         getInstrumentation().runOnMainSync(runnable);
     }
 
-    private <T extends Activity> T launchAndWait(ActivityTestRule<T> rule, int cutoutMode) {
+    private <T extends TestActivity> T launchAndWait(ActivityTestRule<T> rule, int cutoutMode,
+            int orientation) {
         final T activity = rule.launchActivity(
-                new Intent().putExtra(EXTRA_CUTOUT_MODE, cutoutMode));
+                new Intent().putExtra(EXTRA_CUTOUT_MODE, cutoutMode)
+                        .putExtra(EXTRA_ORIENTATION, orientation));
         PollingCheck.waitFor(activity::hasWindowFocus);
+        PollingCheck.waitFor(() -> {
+            final Rect appBounds = getAppBounds(activity);
+            final Point displaySize = new Point();
+            activity.getDisplay().getRealSize(displaySize);
+            // During app launch into a different rotation, we have temporarily have the display
+            // in a different rotation than the app itself. Wait for this to settle.
+            return (appBounds.width() > appBounds.height()) == (displaySize.x > displaySize.y);
+        });
         return activity;
     }
 
     public static class TestActivity extends Activity {
 
         static final String EXTRA_CUTOUT_MODE = "extra.cutout_mode";
+        static final String EXTRA_ORIENTATION = "extra.orientation";
         private WindowInsets mDispatchedInsets;
 
         @Override
@@ -474,6 +521,8 @@
             if (getIntent() != null) {
                 getWindow().getAttributes().layoutInDisplayCutoutMode = getIntent().getIntExtra(
                         EXTRA_CUTOUT_MODE, LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT);
+                setRequestedOrientation(getIntent().getIntExtra(
+                        EXTRA_ORIENTATION, SCREEN_ORIENTATION_UNSPECIFIED));
             }
             View view = new View(this);
             view.setLayoutParams(new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
index f1d5651..b6fc590 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayPolicyTests.java
@@ -498,6 +498,8 @@
      */
     @Test
     public void testStackFocusSwitchOnStackEmptiedInSleeping() {
+        assumeTrue(supportsLockScreen());
+
         validateStackFocusSwitchOnStackEmptied(createManagedVirtualDisplaySession(),
                 createManagedLockScreenSession());
     }
@@ -582,6 +584,7 @@
     @Test
     public void testStackFocusSwitchOnTouchEventAfterKeyguard() {
         assumeFalse(perDisplayFocusEnabled());
+        assumeTrue(supportsLockScreen());
 
         // Launch something on the primary display so we know there is a resumed activity there
         launchActivity(RESIZEABLE_ACTIVITY);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
index f8f714c..bfe1757 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
@@ -52,11 +52,10 @@
 import android.content.res.Configuration;
 import android.os.Bundle;
 import android.provider.Settings;
-import android.server.wm.WindowManagerState.DisplayContent;
 import android.server.wm.CommandSession.ActivitySession;
 import android.server.wm.CommandSession.ActivitySessionClient;
+import android.server.wm.WindowManagerState.DisplayContent;
 import android.server.wm.settings.SettingsSession;
-import android.util.DisplayMetrics;
 import android.util.Pair;
 import android.util.Size;
 import android.view.WindowManager;
@@ -186,12 +185,12 @@
             if (overrideSize != null) {
                 setSize(overrideSize);
             } else {
-                executeShellCommand(WM_SIZE + " reset");
+                executeShellCommand(WM_SIZE + " reset -d " + mDisplayId);
             }
             if (overrideDensity != null) {
                 setDensity(overrideDensity);
             } else {
-                executeShellCommand(WM_DENSITY + " reset");
+                executeShellCommand(WM_DENSITY + " reset -d " + mDisplayId);
             }
         }
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
index 1f24b6b..8cd0d1d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -553,9 +553,21 @@
         // Ensure that auto-enter pip failed and that the resumed activity in the pinned stack is
         // still the first activity
         final ActivityTask pinnedStack = getPinnedStack();
-        assertEquals(1, pinnedStack.getTasks().size());
-        assertEquals(getActivityName(ALWAYS_FOCUSABLE_PIP_ACTIVITY),
-                pinnedStack.getTasks().get(0).mRealActivity);
+        assertEquals(getActivityName(ALWAYS_FOCUSABLE_PIP_ACTIVITY), pinnedStack.mRealActivity);
+    }
+
+    @Test
+    public void testDismissPipWhenLaunchNewOne() throws Exception {
+        // Launch another PIP activity
+        launchActivity(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
+        waitForEnterPip(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
+        assertPinnedStackExists();
+        final ActivityTask pinnedStack = getPinnedStack();
+
+        launchActivityInNewTask(LAUNCH_INTO_PINNED_STACK_PIP_ACTIVITY);
+        waitForEnterPip(ALWAYS_FOCUSABLE_PIP_ACTIVITY);
+
+        assertEquals(1, mWmState.countStacks(WINDOWING_MODE_PINNED, ACTIVITY_TYPE_STANDARD));
     }
 
     @Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
index c0bf81f..7075f4c 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplitScreenTests.java
@@ -17,26 +17,24 @@
 package android.server.wm;
 
 import static android.app.ActivityManager.LOCK_TASK_MODE_NONE;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT;
-import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
-import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
-import static android.server.wm.WindowManagerState.TRANSIT_WALLPAPER_OPEN;
+import static android.server.wm.WindowManagerState.STATE_STOPPED;
 import static android.server.wm.app.Components.DOCKED_ACTIVITY;
 import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
 import static android.server.wm.app.Components.NON_RESIZEABLE_ACTIVITY;
 import static android.server.wm.app.Components.NO_RELAUNCH_ACTIVITY;
-import static android.server.wm.app.Components.RESIZEABLE_ACTIVITY;
 import static android.server.wm.app.Components.SINGLE_INSTANCE_ACTIVITY;
 import static android.server.wm.app.Components.SINGLE_TASK_ACTIVITY;
 import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app.Components.TEST_ACTIVITY_WITH_SAME_AFFINITY;
+import static android.server.wm.app.Components.TRANSLUCENT_TEST_ACTIVITY;
 import static android.server.wm.app.Components.TestActivity.TEST_ACTIVITY_ACTION_FINISH_SELF;
 import static android.server.wm.app27.Components.SDK_27_LAUNCHING_ACTIVITY;
 import static android.server.wm.app27.Components.SDK_27_SEPARATE_PROCESS_ACTIVITY;
@@ -50,10 +48,8 @@
 import static org.hamcrest.Matchers.lessThan;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeTrue;
 
 import android.content.ComponentName;
@@ -99,6 +95,11 @@
                         + " task size");
     }
 
+
+// TODO: Add test to make sure you can't register to split-windowing mode organization if test
+//  doesn't support it.
+
+
     @Test
     public void testStackList() throws Exception {
         launchActivity(TEST_ACTIVITY);
@@ -133,6 +134,36 @@
     }
 
     @Test
+    public void testNonResizeableWhenAlreadyInSplitScreenPrimary() throws Exception {
+        launchActivityInSplitScreenWithRecents(SDK_27_LAUNCHING_ACTIVITY);
+        launchActivity(NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_UNDEFINED);
+
+        mWmState.assertDoesNotContainStack("Must not contain docked stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+        mWmState.assertFrontStack("Fullscreen stack must be front stack.",
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+
+        waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
+                "NON_RESIZEABLE_ACTIVITY launched on default display must be focused");
+    }
+
+    @Test
+    public void testNonResizeableWhenAlreadyInSplitScreenSecondary() throws Exception {
+        launchActivityInSplitScreenWithRecents(SDK_27_LAUNCHING_ACTIVITY);
+        // Launch home so secondary side as focus.
+        launchHomeActivity();
+        launchActivity(NON_RESIZEABLE_ACTIVITY, WINDOWING_MODE_UNDEFINED);
+
+        mWmState.assertDoesNotContainStack("Must not contain docked stack.",
+                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD);
+        mWmState.assertFrontStack("Fullscreen stack must be front stack.",
+                WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+
+        waitAndAssertTopResumedActivity(NON_RESIZEABLE_ACTIVITY, DEFAULT_DISPLAY,
+                "NON_RESIZEABLE_ACTIVITY launched on default display must be focused");
+    }
+
+    @Test
     public void testLaunchToSide() throws Exception {
         launchActivitiesInSplitScreen(
                 getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
@@ -156,7 +187,7 @@
 
         // Exit split-screen mode and ensure we only get 1 multi-window mode changed callback.
         separateTestJournal();
-        removeStacksInWindowingModes(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        SystemUtil.runWithShellPermissionIdentity(() -> mTaskOrganizer.dismissedSplitScreen());
         final ActivityLifecycleCounts lifecycleCounts = waitForOnMultiWindowModeChanged(
                 TEST_ACTIVITY);
         assertEquals(1, lifecycleCounts.getCount(ActivityCallback.ON_MULTI_WINDOW_MODE_CHANGED));
@@ -538,4 +569,38 @@
         assertThat("Recents stack should be on top of home stack",
                 recentsStackIndex, lessThan(homeStackIndex));
     }
+
+
+    /**
+     * Asserts that the activity is visible when the top opaque activity finishes and with another
+     * translucent activity on top while in split-screen-secondary task.
+     */
+    @Test
+    public void testVisibilityWithTranslucentAndTopFinishingActivity() throws Exception {
+        // Launch two activities in split-screen mode.
+        launchActivitiesInSplitScreen(
+                getLaunchActivityBuilder().setTargetActivity(LAUNCHING_ACTIVITY),
+                getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY_WITH_SAME_AFFINITY));
+
+        // Launch two more activities on a different task on top of split-screen-secondary and
+        // only the top opaque activity should be visible.
+        getLaunchActivityBuilder().setTargetActivity(TRANSLUCENT_TEST_ACTIVITY)
+                .setUseInstrumentation()
+                .setWaitForLaunched(true)
+                .execute();
+        getLaunchActivityBuilder().setTargetActivity(TEST_ACTIVITY)
+                .setUseInstrumentation()
+                .setWaitForLaunched(true)
+                .execute();
+        mWmState.assertVisibility(TEST_ACTIVITY, true);
+        mWmState.waitForActivityState(TRANSLUCENT_TEST_ACTIVITY, STATE_STOPPED);
+        mWmState.assertVisibility(TRANSLUCENT_TEST_ACTIVITY, false);
+        mWmState.assertVisibility(TEST_ACTIVITY_WITH_SAME_AFFINITY, false);
+
+        // Finish the top opaque activity and both the two activities should be visible.
+        mBroadcastActionTrigger.doAction(TEST_ACTIVITY_ACTION_FINISH_SELF);
+        mWmState.computeState(new WaitForValidActivityState(TRANSLUCENT_TEST_ACTIVITY));
+        mWmState.assertVisibility(TRANSLUCENT_TEST_ACTIVITY, true);
+        mWmState.assertVisibility(TEST_ACTIVITY_WITH_SAME_AFFINITY, true);
+    }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
index ff719ab..bcb3456 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SurfaceControlViewHostTests.java
@@ -26,10 +26,15 @@
 import static org.junit.Assert.assertTrue;
 
 import android.app.Activity;
+import android.app.ActivityManager;
 import android.app.Instrumentation;
+import android.content.Context;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.FeatureInfo;
 import android.graphics.PixelFormat;
 import android.graphics.Point;
 import android.graphics.Rect;
+import android.platform.test.annotations.RequiresDevice;
 import android.view.Gravity;
 import android.view.View;
 import android.view.ViewGroup;
@@ -150,6 +155,41 @@
         assertTrue(mClicked);
     }
 
+    private static int getGlEsVersion(Context context) {
+        ActivityManager activityManager =
+                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
+        ConfigurationInfo configInfo = activityManager.getDeviceConfigurationInfo();
+        if (configInfo.reqGlEsVersion != ConfigurationInfo.GL_ES_VERSION_UNDEFINED) {
+            return getMajorVersion(configInfo.reqGlEsVersion);
+        } else {
+            return 1; // Lack of property means OpenGL ES version 1
+        }
+    }
+
+    /** @see FeatureInfo#getGlEsVersion() */
+    private static int getMajorVersion(int glEsVersion) {
+        return ((glEsVersion & 0xffff0000) >> 16);
+    }
+
+    @Test
+    @RequiresDevice
+    @FlakyTest(bugId = 152103238)
+    public void testEmbeddedViewIsHardwareAccelerated() throws Throwable {
+        // Hardware accel may not be supported on devices without GLES 2.0
+        if (getGlEsVersion(mActivity) < 2) {
+            return;
+        }
+        mEmbeddedView = new Button(mActivity);
+        mEmbeddedView.setOnClickListener((View v) -> {
+            mClicked = true;
+        });
+
+        addSurfaceView(DEFAULT_SURFACE_VIEW_WIDTH, DEFAULT_SURFACE_VIEW_HEIGHT);
+        mInstrumentation.waitForIdleSync();
+
+        assertTrue(mEmbeddedView.isHardwareAccelerated());
+    }
+
     @Test
     public void testEmbeddedViewResizes() throws Throwable {
         mEmbeddedView = new Button(mActivity);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTests.java
index 573fc80..344c795 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowContextTests.java
@@ -22,9 +22,10 @@
 
 import android.app.Instrumentation;
 import android.content.Context;
+import android.graphics.Rect;
 import android.hardware.display.DisplayManager;
+import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
-import android.util.DisplayMetrics;
 import android.view.Display;
 import android.view.View;
 import android.view.WindowManager;
@@ -42,6 +43,7 @@
 
     @Test
     @FlakyTest(bugId = 150251036)
+    @AppModeFull
     public void testWindowContextConfigChanges() {
         final WindowManagerState.DisplayContent display =  createManagedVirtualDisplaySession()
                 .setSimulateDisplay(true).createDisplay();
@@ -51,17 +53,22 @@
             WindowManager wm = windowContext.getSystemService(WindowManager.class);
             wm.addView(view, new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY));
         });
-        mWmState.computeState();
-
         final DisplayMetricsSession displayMetricsSession =
                 createManagedDisplayMetricsSession(display.mId);
 
+        mWmState.computeState();
+
+        Rect bounds = windowContext.getSystemService(WindowManager.class).getCurrentWindowMetrics()
+                .getBounds();
+        assertBoundsEquals(displayMetricsSession.getDisplayMetrics(), bounds);
+
         displayMetricsSession.changeDisplayMetrics(1.2 /* sizeRatio */, 1.1 /* densityRatio */);
 
         mWmState.computeState();
 
-        assertDisplayMetricsEquals(displayMetricsSession.getDisplayMetrics(),
-                windowContext.getResources().getDisplayMetrics());
+        bounds = windowContext.getSystemService(WindowManager.class).getCurrentWindowMetrics()
+                .getBounds();
+        assertBoundsEquals(displayMetricsSession.getDisplayMetrics(), bounds);
     }
 
     private Context createWindowContext(int displayId) {
@@ -70,10 +77,9 @@
                 null /* options */);
     }
 
-    private void assertDisplayMetricsEquals(ReportedDisplayMetrics expectedMetrics,
-            DisplayMetrics actualMetrics) {
-        assertEquals(expectedMetrics.getSize().getWidth(), actualMetrics.widthPixels);
-        assertEquals(expectedMetrics.getSize().getHeight(), actualMetrics.heightPixels);
-        assertEquals(expectedMetrics.getDensity(), actualMetrics.densityDpi);
+    private void assertBoundsEquals(ReportedDisplayMetrics expectedMetrics,
+            Rect bounds) {
+        assertEquals(expectedMetrics.getSize().getWidth(), bounds.width());
+        assertEquals(expectedMetrics.getSize().getHeight(), bounds.height());
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationCallbackTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationCallbackTests.java
new file mode 100644
index 0000000..3edda65
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationCallbackTests.java
@@ -0,0 +1,181 @@
+/*
+ * 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.server.wm;
+
+import static android.graphics.Insets.NONE;
+import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_CONTINUE_ON_SUBTREE;
+import static android.view.WindowInsetsAnimation.Callback.DISPATCH_MODE_STOP;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import android.content.Context;
+import android.graphics.Insets;
+import android.platform.test.annotations.Presubmit;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
+import android.view.WindowInsetsAnimation.Bounds;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.List;
+
+@Presubmit
+public class WindowInsetsAnimationCallbackTests {
+
+    private static final Bounds BOUNDS = new Bounds(NONE, Insets.of(1000, 2000, 3000, 4000));
+    private static final WindowInsetsAnimation ANIMATION = new WindowInsetsAnimation(1, null, 1000);
+    private static final WindowInsets INSETS = new WindowInsets.Builder()
+            .setSystemWindowInsets(BOUNDS.getUpperBound()).build();
+    private static final List<WindowInsetsAnimation> RUNNING = Collections.singletonList(ANIMATION);
+
+    final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    private ViewGroup mRoot;
+    private ViewGroup mBlocking;
+    private ViewGroup mSibling;
+    private View mChild;
+    private RecordingCallback mRootCallback;
+    private RecordingCallback mBlockingCallback;
+    private RecordingCallback mSiblingCallback;
+    private RecordingCallback mChildCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        mRoot = new FrameLayout(mContext);
+        mBlocking = new FrameLayout(mContext);
+        mSibling = new FrameLayout(mContext);
+        mChild = new View(mContext);
+        mRoot.addView(mBlocking);
+        mRoot.addView(mSibling);
+        mBlocking.addView(mChild);
+
+        mRootCallback = new RecordingCallback(DISPATCH_MODE_CONTINUE_ON_SUBTREE);
+        mBlockingCallback = new RecordingCallback(DISPATCH_MODE_STOP);
+        mSiblingCallback = new RecordingCallback(DISPATCH_MODE_STOP);
+        mChildCallback = new RecordingCallback(DISPATCH_MODE_CONTINUE_ON_SUBTREE);
+
+        mRoot.setWindowInsetsAnimationCallback(mRootCallback);
+        mBlocking.setWindowInsetsAnimationCallback(mBlockingCallback);
+        mSibling.setWindowInsetsAnimationCallback(mSiblingCallback);
+        mChild.setWindowInsetsAnimationCallback(mChildCallback);
+    }
+
+    @Test
+    public void dispatchWindowInsetsAnimationPrepare() {
+        mRoot.dispatchWindowInsetsAnimationPrepare(ANIMATION);
+        assertSame(ANIMATION, mRootCallback.mOnPrepare);
+        assertSame(ANIMATION, mBlockingCallback.mOnPrepare);
+        assertSame(ANIMATION, mSiblingCallback.mOnPrepare);
+        assertNull(mChildCallback.mOnPrepare);
+    }
+
+    @Test
+    public void dispatchWindowInsetsAnimationStart() {
+        mRoot.dispatchWindowInsetsAnimationStart(ANIMATION, BOUNDS);
+        assertSame(ANIMATION, mRootCallback.mOnStart);
+        assertSame(ANIMATION, mBlockingCallback.mOnStart);
+        assertSame(ANIMATION, mSiblingCallback.mOnStart);
+        assertNull(mChildCallback.mOnStart);
+
+        assertSame(BOUNDS, mRootCallback.mOnStartBoundsIn);
+        assertSame(mRootCallback.mOnStartBoundsOut, mBlockingCallback.mOnStartBoundsIn);
+        assertSame(mRootCallback.mOnStartBoundsOut, mSiblingCallback.mOnStartBoundsIn);
+    }
+
+    @Test
+    public void dispatchWindowInsetsAnimationProgress() {
+        mRoot.dispatchWindowInsetsAnimationProgress(INSETS, RUNNING);
+        assertSame(RUNNING, mRootCallback.mOnProgress);
+        assertSame(RUNNING, mBlockingCallback.mOnProgress);
+        assertSame(RUNNING, mSiblingCallback.mOnProgress);
+        assertNull(mChildCallback.mOnProgress);
+
+        assertSame(INSETS, mRootCallback.mOnProgressIn);
+        assertSame(mRootCallback.mOnProgressOut, mBlockingCallback.mOnProgressIn);
+        assertSame(mRootCallback.mOnProgressOut, mSiblingCallback.mOnProgressIn);
+    }
+
+    @Test
+    public void dispatchWindowInsetsAnimationEnd() {
+        mRoot.dispatchWindowInsetsAnimationEnd(ANIMATION);
+        assertSame(ANIMATION, mRootCallback.mOnEnd);
+        assertSame(ANIMATION, mBlockingCallback.mOnEnd);
+        assertSame(ANIMATION, mSiblingCallback.mOnEnd);
+        assertNull(mChildCallback.mOnEnd);
+    }
+
+    @Test
+    public void getDispatchMode() {
+        assertEquals(DISPATCH_MODE_STOP,
+                new RecordingCallback(DISPATCH_MODE_STOP).getDispatchMode());
+        assertEquals(DISPATCH_MODE_CONTINUE_ON_SUBTREE,
+                new RecordingCallback(DISPATCH_MODE_CONTINUE_ON_SUBTREE).getDispatchMode());
+    }
+
+    public static class RecordingCallback extends WindowInsetsAnimation.Callback {
+
+        WindowInsetsAnimation mOnPrepare;
+        WindowInsetsAnimation mOnStart;
+        Bounds mOnStartBoundsIn;
+        Bounds mOnStartBoundsOut;
+        WindowInsetsAnimation mOnEnd;
+        WindowInsets mOnProgressIn;
+        WindowInsets mOnProgressOut;
+        List<WindowInsetsAnimation> mOnProgress;
+
+        public RecordingCallback(int dispatchMode) {
+            super(dispatchMode);
+        }
+
+        @Override
+        public void onPrepare(@NonNull WindowInsetsAnimation animation) {
+            mOnPrepare = animation;
+        }
+
+        @NonNull
+        @Override
+        public Bounds onStart(@NonNull WindowInsetsAnimation animation, @NonNull Bounds bounds) {
+            mOnStart = animation;
+            mOnStartBoundsIn = bounds;
+            return mOnStartBoundsOut = bounds.inset(Insets.of(1, 2, 3, 4));
+        }
+
+        @NonNull
+        @Override
+        public WindowInsets onProgress(@NonNull WindowInsets insets,
+                @NonNull List<WindowInsetsAnimation> runningAnimations) {
+            mOnProgress = runningAnimations;
+            mOnProgressIn = insets;
+            return mOnProgressOut = insets.inset(1, 2, 3, 4);
+        }
+
+        @Override
+        public void onEnd(@NonNull WindowInsetsAnimation animation) {
+            mOnEnd = animation;
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
index 85f31b4..e743aff 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
@@ -19,6 +19,7 @@
 import static android.server.wm.WindowInsetsAnimationControllerTests.ControlListener.Event.CANCELLED;
 import static android.server.wm.WindowInsetsAnimationControllerTests.ControlListener.Event.FINISHED;
 import static android.server.wm.WindowInsetsAnimationControllerTests.ControlListener.Event.READY;
+import static android.server.wm.WindowInsetsAnimationUtils.INSETS_EVALUATOR;
 import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
@@ -27,7 +28,6 @@
 
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.containsInAnyOrder;
-import static org.hamcrest.Matchers.empty;
 import static org.hamcrest.Matchers.equalTo;
 import static org.hamcrest.Matchers.hasItem;
 import static org.hamcrest.Matchers.is;
@@ -39,7 +39,6 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.animation.TypeEvaluator;
 import android.animation.ValueAnimator;
 import android.graphics.Insets;
 import android.os.CancellationSignal;
@@ -58,7 +57,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.test.filters.FlakyTest;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -323,7 +321,7 @@
     private void runTransition(boolean show) throws Throwable {
         runOnUiThread(() -> {
             mAnimator = ValueAnimator.ofObject(
-                    sInsetsEvaluator,
+                    INSETS_EVALUATOR,
                     show ? mListener.mController.getHiddenStateInsets()
                             : mListener.mController.getShownStateInsets(),
                     show ? mListener.mController.getShownStateInsets()
@@ -331,6 +329,12 @@
             );
             mAnimator.setDuration(1000);
             mAnimator.addUpdateListener((animator1) -> {
+                if (!mListener.mController.isReady()) {
+                    // Lost control - Don't crash the instrumentation below.
+                    mErrorCollector.addError(new AssertionError("Unexpectedly lost control."));
+                    mAnimator.cancel();
+                    return;
+                }
                 Insets insets = (Insets) mAnimator.getAnimatedValue();
                 mOnProgressCalled = false;
                 mListener.mController.setInsetsAndAlpha(insets, 1.0f,
@@ -394,6 +398,9 @@
             // Collect errors here and below, so we don't crash the main thread.
             mErrorCollector.checkThat(controller, notNullValue());
             mErrorCollector.checkThat(types, not(equalTo(0)));
+            mErrorCollector.checkThat("isReady", controller.isReady(), is(true));
+            mErrorCollector.checkThat("isFinished", controller.isFinished(), is(false));
+            mErrorCollector.checkThat("isCancelled", controller.isCancelled(), is(false));
             report(READY);
         }
 
@@ -401,12 +408,20 @@
         public void onFinished(@NonNull WindowInsetsAnimationController controller) {
             mErrorCollector.checkThat(controller, notNullValue());
             mErrorCollector.checkThat(controller, sameInstance(mController));
+            mErrorCollector.checkThat("isReady", controller.isReady(), is(false));
+            mErrorCollector.checkThat("isFinished", controller.isFinished(), is(true));
+            mErrorCollector.checkThat("isCancelled", controller.isCancelled(), is(false));
             report(FINISHED);
         }
 
         @Override
         public void onCancelled(@Nullable WindowInsetsAnimationController controller) {
             mErrorCollector.checkThat(controller, sameInstance(mController));
+            if (controller != null) {
+                mErrorCollector.checkThat("isReady", controller.isReady(), is(false));
+                mErrorCollector.checkThat("isFinished", controller.isFinished(), is(false));
+                mErrorCollector.checkThat("isCancelled", controller.isCancelled(), is(true));
+            }
             report(CANCELLED);
         }
 
@@ -438,13 +453,6 @@
         }
     }
 
-    private static TypeEvaluator<Insets> sInsetsEvaluator =
-            (fraction, startValue, endValue) -> Insets.of(
-                    (int) (startValue.left + fraction * (endValue.left - startValue.left)),
-                    (int) (startValue.top + fraction * (endValue.top - startValue.top)),
-                    (int) (startValue.right + fraction * (endValue.right - startValue.right)),
-                    (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
-
 
     private class VerifyingCallback extends Callback {
         private final Callback mInner;
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
new file mode 100644
index 0000000..d4e6472
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationSynchronicityTests.java
@@ -0,0 +1,328 @@
+/*
+ * 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.server.wm;
+
+import static android.server.wm.ActivityManagerTestBase.executeShellCommand;
+import static android.server.wm.WindowInsetsAnimationUtils.requestControlThenTransitionToVisibility;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.WindowInsets.Type.ime;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import android.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Insets;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.inputmethodservice.InputMethodService;
+import android.os.Bundle;
+import android.platform.test.annotations.LargeTest;
+import android.provider.Settings;
+import android.server.wm.WindowInsetsAnimationControllerTests.LimitedErrorCollector;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowInsets;
+import android.view.WindowInsetsAnimation;
+import android.view.WindowInsetsAnimation.Callback;
+import android.view.WindowInsetsController;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.FrameLayout;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.function.Supplier;
+
+@LargeTest
+public class WindowInsetsAnimationSynchronicityTests {
+    private static final int APP_COLOR = 0xff01fe10; // green
+    private static final int BEHIND_IME_COLOR = 0xfffeef00; // yellow
+    private static final int IME_COLOR = 0xfffe01fd; // pink
+
+    @Rule
+    public LimitedErrorCollector mErrorCollector = new LimitedErrorCollector();
+
+    @Rule
+    public ActivityTestRule<TestActivity> mActivityRule = new ActivityTestRule<>(
+            TestActivity.class, false, false);
+
+    private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    @Test
+    public void testShowAndHide_renderSynchronouslyBetweenImeWindowAndAppContent() throws Throwable {
+        runTest(false /* useControlApi */);
+    }
+
+    @Test
+    public void testControl_rendersSynchronouslyBetweenImeWindowAndAppContent() throws Throwable {
+        runTest(true /* useControlApi */);
+    }
+
+    private void runTest(boolean useControlApi) throws Exception {
+        try (ImeSession imeSession = new ImeSession(SimpleIme.getName(mContext))) {
+            TestActivity activity = mActivityRule.launchActivity(null);
+            activity.setUseControlApi(useControlApi);
+            PollingCheck.waitFor(activity::hasWindowFocus);
+            activity.setEvaluator(() -> {
+                // This runs from time to time on the UI thread.
+                Bitmap screenshot = getInstrumentation().getUiAutomation().takeScreenshot();
+                final int center = screenshot.getWidth() / 2;
+                int imePositionApp = lowestPixelWithColor(APP_COLOR, 1, screenshot);
+                int contentBottomMiddle = lowestPixelWithColor(APP_COLOR, center, screenshot);
+                int behindImeBottomMiddle =
+                        lowestPixelWithColor(BEHIND_IME_COLOR, center, screenshot);
+                int imePositionIme = Math.max(contentBottomMiddle, behindImeBottomMiddle);
+                if (imePositionApp != imePositionIme) {
+                    mErrorCollector.addError(new AssertionError(String.format(Locale.US,
+                            "IME is positioned at %d (max of %d, %d),"
+                                    + " app thinks it is positioned at %d",
+                            imePositionIme, contentBottomMiddle, behindImeBottomMiddle,
+                            imePositionApp)));
+                }
+            });
+            Thread.sleep(2000);
+        }
+    }
+
+    private static int lowestPixelWithColor(int color, int x, Bitmap bitmap) {
+        int[] pixels = new int[bitmap.getHeight()];
+        bitmap.getPixels(pixels, 0, 1, x, 0, 1, bitmap.getHeight());
+        for (int y = pixels.length - 1; y >= 0; y--) {
+            if (pixels[y] == color) {
+                return y;
+            }
+        }
+        return -1;
+    }
+
+    public static class TestActivity extends Activity implements
+            WindowInsetsController.OnControllableInsetsChangedListener {
+
+        private TestView mTestView;
+        private EditText mEditText;
+        private Runnable mEvaluator;
+        private boolean mUseControlApi;
+
+        @Override
+        protected void onCreate(@Nullable Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            getWindow().requestFeature(Window.FEATURE_NO_TITLE);
+            getWindow().setDecorFitsSystemWindows(false);
+            mTestView = new TestView(this);
+            mEditText = new EditText(this);
+            mTestView.addView(mEditText);
+            mTestView.mEvaluator = () -> {
+                if (mEvaluator != null) {
+                    mEvaluator.run();
+                }
+            };
+            mEditText.requestFocus();
+            setContentView(mTestView);
+            mEditText.getWindowInsetsController().addOnControllableInsetsChangedListener(this);
+        }
+
+        void setEvaluator(Runnable evaluator) {
+            mEvaluator = evaluator;
+        }
+
+        void setUseControlApi(boolean useControlApi) {
+            mUseControlApi = useControlApi;
+        }
+
+        @Override
+        public void onControllableInsetsChanged(@NonNull WindowInsetsController controller,
+                int typeMask) {
+            if ((typeMask & ime()) != 0) {
+                mEditText.getWindowInsetsController().removeOnControllableInsetsChangedListener(
+                        this);
+                showIme();
+            }
+        }
+
+        private void showIme() {
+            if (mUseControlApi) {
+                requestControlThenTransitionToVisibility(mTestView.getWindowInsetsController(),
+                        ime(), true);
+            } else {
+                mTestView.getWindowInsetsController().show(ime());
+            }
+        }
+
+        private void hideIme() {
+            if (mUseControlApi) {
+                requestControlThenTransitionToVisibility(mTestView.getWindowInsetsController(),
+                        ime(), false);
+            } else {
+                mTestView.getWindowInsetsController().hide(ime());
+            }
+        }
+
+        private static class TestView extends FrameLayout {
+            private WindowInsets mLayoutInsets;
+            private WindowInsets mAnimationInsets;
+            private final Rect mTmpRect = new Rect();
+            private final Paint mContentPaint = new Paint();
+
+            private final Callback mInsetsCallback = new Callback(Callback.DISPATCH_MODE_STOP) {
+                @NonNull
+                @Override
+                public WindowInsets onProgress(@NonNull WindowInsets insets,
+                        @NonNull List<WindowInsetsAnimation> runningAnimations) {
+                    if (runningAnimations.stream().anyMatch(TestView::isImeAnimation)) {
+                        mAnimationInsets = insets;
+                        invalidate();
+                    }
+                    return WindowInsets.CONSUMED;
+                }
+
+                @Override
+                public void onEnd(@NonNull WindowInsetsAnimation animation) {
+                    if (isImeAnimation(animation)) {
+                        mAnimationInsets = null;
+                        post(() -> {
+                            if (mLayoutInsets.isVisible(ime())) {
+                                ((TestActivity) getContext()).hideIme();
+                            } else {
+                                ((TestActivity) getContext()).showIme();
+                            }
+                        });
+
+                    }
+                }
+            };
+            private final Runnable mRunEvaluator;
+            private Runnable mEvaluator;
+
+            TestView(Context context) {
+                super(context);
+                setWindowInsetsAnimationCallback(mInsetsCallback);
+                mContentPaint.setColor(APP_COLOR);
+                mContentPaint.setStyle(Paint.Style.FILL);
+                setWillNotDraw(false);
+                mRunEvaluator = () -> {
+                    if (mEvaluator != null) {
+                        mEvaluator.run();
+                    }
+                };
+            }
+
+            @Override
+            public WindowInsets onApplyWindowInsets(WindowInsets insets) {
+                mLayoutInsets = insets;
+                return WindowInsets.CONSUMED;
+            }
+
+            private WindowInsets getEffectiveInsets() {
+                return mAnimationInsets != null ? mAnimationInsets : mLayoutInsets;
+            }
+
+            @Override
+            protected void onDraw(Canvas canvas) {
+                canvas.drawColor(BEHIND_IME_COLOR);
+                mTmpRect.set(0, 0, getWidth(), getHeight());
+                Insets insets = getEffectiveInsets().getInsets(ime());
+                insetRect(mTmpRect, insets);
+                canvas.drawRect(mTmpRect, mContentPaint);
+                removeCallbacks(mRunEvaluator);
+                post(mRunEvaluator);
+            }
+
+            private static boolean isImeAnimation(WindowInsetsAnimation animation) {
+                return (animation.getTypeMask() & ime()) != 0;
+            }
+
+            private static void insetRect(Rect rect, Insets insets) {
+                rect.left += insets.left;
+                rect.top += insets.top;
+                rect.right -= insets.right;
+                rect.bottom -= insets.bottom;
+            }
+        }
+    }
+
+    private static class ImeSession implements AutoCloseable {
+
+        private static final long TIMEOUT = 2000;
+        private final ComponentName mImeName;
+        private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+        ImeSession(ComponentName ime) throws Exception {
+            mImeName = ime;
+            executeShellCommand("ime reset");
+            executeShellCommand("ime enable " + ime.flattenToShortString());
+            executeShellCommand("ime set " + ime.flattenToShortString());
+            PollingCheck.check("Make sure that MockIME becomes available", TIMEOUT,
+                    () -> ime.equals(getCurrentInputMethodId()));
+        }
+
+        @Override
+        public void close() throws Exception {
+            executeShellCommand("ime reset");
+            PollingCheck.check("Make sure that MockIME becomes unavailable", TIMEOUT, () ->
+                    mContext.getSystemService(InputMethodManager.class)
+                            .getEnabledInputMethodList()
+                            .stream()
+                            .noneMatch(info -> mImeName.equals(info.getComponent())));
+        }
+
+        @Nullable
+        private ComponentName getCurrentInputMethodId() {
+            // TODO: Replace this with IMM#getCurrentInputMethodIdForTesting()
+            return ComponentName.unflattenFromString(
+                    Settings.Secure.getString(mContext.getContentResolver(),
+                    Settings.Secure.DEFAULT_INPUT_METHOD));
+        }
+    }
+
+    public static class SimpleIme extends InputMethodService {
+
+        public static final int HEIGHT_DP = 200;
+        public static final int SIDE_PADDING_DP = 50;
+
+        @Override
+        public View onCreateInputView() {
+            final ViewGroup view = new FrameLayout(this);
+            final View inner = new View(this);
+            final float density = getResources().getDisplayMetrics().density;
+            final int height = (int) (HEIGHT_DP * density);
+            final int sidePadding = (int) (SIDE_PADDING_DP * density);
+            view.setPadding(sidePadding, 0, sidePadding, 0);
+            view.addView(inner, new FrameLayout.LayoutParams(MATCH_PARENT,
+                    height));
+            inner.setBackgroundColor(IME_COLOR);
+            return view;
+        }
+
+        static ComponentName getName(Context context) {
+            return new ComponentName(context, SimpleIme.class);
+        }
+    }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
index 957062b..86fc5ee 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
@@ -17,6 +17,7 @@
 package android.server.wm;
 
 import static android.graphics.Insets.NONE;
+import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowInsets.Type.systemBars;
@@ -50,6 +51,7 @@
 import android.view.WindowInsetsAnimation;
 import android.view.WindowInsetsAnimation.Bounds;
 import android.view.WindowInsetsAnimation.Callback;
+import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
@@ -94,7 +96,7 @@
 
         waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone);
 
-        commonAnimationAssertions(mActivity, before, false /* show */);
+        commonAnimationAssertions(mActivity, before, false /* show */, systemBars());
     }
 
     @Test
@@ -112,7 +114,27 @@
 
         waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone);
 
-        commonAnimationAssertions(mActivity, before, true /* show */);
+        commonAnimationAssertions(mActivity, before, true /* show */, systemBars());
+    }
+
+    @Test
+    public void testImeAnimationCallbacksShowAndHide() {
+        WindowInsets before = mActivity.mLastWindowInsets;
+        getInstrumentation().runOnMainSync(
+                () -> mRootView.getWindowInsetsController().show(ime()));
+
+        waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone);
+        commonAnimationAssertions(mActivity, before, true /* show */, ime());
+        mActivity.mCallback.animationDone = false;
+
+        before = mActivity.mLastWindowInsets;
+
+        getInstrumentation().runOnMainSync(
+                () -> mRootView.getWindowInsetsController().hide(ime()));
+
+        waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone);
+
+        commonAnimationAssertions(mActivity, before, false /* show */, ime());
     }
 
     @Test
@@ -177,6 +199,67 @@
     }
 
     @Test
+    @FlakyTest(detail = "Promote once confirmed non-flaky")
+    public void testAnimationCallbacks_overlapping_opposite() {
+        WindowInsets before = mActivity.mLastWindowInsets;
+
+        MultiAnimCallback callbackInner = new MultiAnimCallback();
+        MultiAnimCallback callback = mock(MultiAnimCallback.class,
+                withSettings()
+                        .spiedInstance(callbackInner)
+                        .defaultAnswer(CALLS_REAL_METHODS)
+                        .verboseLogging());
+        mActivity.mView.setWindowInsetsAnimationCallback(callback);
+
+        getInstrumentation().runOnMainSync(
+                () -> mRootView.getWindowInsetsController().hide(navigationBars()));
+        getInstrumentation().runOnMainSync(
+                () -> mRootView.getWindowInsetsController().show(ime()));
+
+        waitForOrFail("Waiting until animation done", () -> callback.animationDone);
+
+        WindowInsets after = mActivity.mLastWindowInsets;
+
+        InOrder inOrder = inOrder(callback, mActivity.mListener);
+
+        inOrder.verify(callback).onPrepare(eq(callback.navBarAnim));
+
+        inOrder.verify(mActivity.mListener).onApplyWindowInsets(any(), argThat(
+                argument -> NONE.equals(argument.getInsets(navigationBars()))
+                        && NONE.equals(argument.getInsets(ime()))));
+
+        inOrder.verify(callback).onStart(eq(callback.navBarAnim), argThat(
+                argument -> argument.getLowerBound().equals(NONE)
+                        && argument.getUpperBound().equals(before.getInsets(navigationBars()))));
+
+        inOrder.verify(callback).onPrepare(eq(callback.imeAnim));
+        inOrder.verify(mActivity.mListener).onApplyWindowInsets(
+                any(), eq(mActivity.mLastWindowInsets));
+
+        inOrder.verify(callback).onStart(eq(callback.imeAnim), argThat(
+                argument -> argument.getLowerBound().equals(NONE)
+                        && !argument.getUpperBound().equals(NONE)));
+
+        inOrder.verify(callback).onEnd(eq(callback.navBarAnim));
+        inOrder.verify(callback).onEnd(eq(callback.imeAnim));
+
+        assertAnimationSteps(callback.navAnimSteps, false /* showAnimation */);
+        assertAnimationSteps(callback.imeAnimSteps, false /* showAnimation */);
+
+        assertEquals(before.getInsets(navigationBars()),
+                callback.navAnimSteps.get(0).insets.getInsets(navigationBars()));
+        assertEquals(after.getInsets(navigationBars()),
+                callback.navAnimSteps.get(callback.navAnimSteps.size() - 1).insets
+                        .getInsets(navigationBars()));
+
+        assertEquals(before.getInsets(ime()),
+                callback.imeAnimSteps.get(0).insets.getInsets(ime()));
+        assertEquals(after.getInsets(ime()),
+                callback.imeAnimSteps.get(callback.imeAnimSteps.size() - 1).insets
+                        .getInsets(ime()));
+    }
+
+    @Test
     public void testAnimationCallbacks_consumedByDecor() {
         getInstrumentation().runOnMainSync(() -> {
             mActivity.getWindow().setDecorFitsSystemWindows(true);
@@ -244,7 +327,7 @@
     }
 
     private void commonAnimationAssertions(TestActivity activity, WindowInsets before,
-            boolean show) {
+            boolean show, int types) {
 
         AnimCallback callback = activity.mCallback;
 
@@ -257,14 +340,19 @@
         inOrder.verify(callback).onStart(eq(callback.lastAnimation), argThat(
                 argument -> argument.getLowerBound().equals(NONE)
                         && argument.getUpperBound().equals(show
-                                ? after.getInsets(systemBars())
-                                : before.getInsets(systemBars()))));
+                                ? after.getInsets(types)
+                                : before.getInsets(types))));
 
         inOrder.verify(callback, atLeast(2)).onProgress(any(), argThat(
                 argument -> argument.size() == 1 && argument.get(0) == callback.lastAnimation));
         inOrder.verify(callback).onEnd(eq(callback.lastAnimation));
 
-        assertTrue((callback.lastAnimation.getTypeMask() & systemBars()) != 0);
+        if ((types & systemBars()) != 0) {
+            assertTrue((callback.lastAnimation.getTypeMask() & systemBars()) != 0);
+        }
+        if ((types & ime()) != 0) {
+            assertTrue((callback.lastAnimation.getTypeMask() & ime()) != 0);
+        }
         assertTrue(callback.lastAnimation.getDurationMillis() > 0);
         assertNotNull(callback.lastAnimation.getInterpolator());
         assertBeforeAfterState(callback.animationSteps, before, after);
@@ -386,9 +474,11 @@
 
         WindowInsetsAnimation statusBarAnim;
         WindowInsetsAnimation navBarAnim;
+        WindowInsetsAnimation imeAnim;
         volatile boolean animationDone;
         final ArrayList<AnimationStep> statusAnimSteps = new ArrayList<>();
         final ArrayList<AnimationStep> navAnimSteps = new ArrayList<>();
+        final ArrayList<AnimationStep> imeAnimSteps = new ArrayList<>();
         Runnable startRunnable;
         final ArraySet<WindowInsetsAnimation> runningAnims = new ArraySet<>();
 
@@ -404,6 +494,9 @@
             if ((animation.getTypeMask() & navigationBars()) != 0) {
                 navBarAnim = animation;
             }
+            if ((animation.getTypeMask() & ime()) != 0) {
+                imeAnim = animation;
+            }
         }
 
         @Override
@@ -426,6 +519,10 @@
                 navAnimSteps.add(new AnimationStep(insets, navBarAnim.getFraction(),
                         navBarAnim.getInterpolatedFraction(), navBarAnim.getAlpha()));
             }
+            if (imeAnim != null) {
+                imeAnimSteps.add(new AnimationStep(insets, imeAnim.getFraction(),
+                        imeAnim.getInterpolatedFraction(), imeAnim.getAlpha()));
+            }
 
             assertEquals(runningAnims.size(), runningAnimations.size());
             for (int i = runningAnimations.size() - 1; i >= 0; i--) {
@@ -452,6 +549,7 @@
         OnApplyWindowInsetsListener mListener;
         LinearLayout mView;
         View mChild;
+        EditText mEditor;
 
         public class InsetsListener implements OnApplyWindowInsetsListener {
 
@@ -470,12 +568,14 @@
             mView.setWindowInsetsAnimationCallback(mCallback);
             mView.setOnApplyWindowInsetsListener(mListener);
             mChild = new TextView(this);
+            mEditor = new EditText(this);
             mView.addView(mChild);
 
             getWindow().setDecorFitsSystemWindows(false);
             getWindow().getAttributes().layoutInDisplayCutoutMode =
                     LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
             setContentView(mView);
+            mEditor.requestFocus();
         }
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
index 66b1ea5..c960532 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsControllerTests.java
@@ -16,6 +16,7 @@
 
 package android.server.wm;
 
+import static android.view.WindowInsets.Type.ime;
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_BARS_BY_SWIPE;
@@ -27,6 +28,7 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assume.assumeTrue;
 
+import android.app.Activity;
 import android.app.AlertDialog;
 import android.os.Bundle;
 import android.os.SystemClock;
@@ -37,6 +39,8 @@
 import android.view.WindowInsets;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsetsAnimation;
+import android.widget.EditText;
+import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import androidx.test.filters.FlakyTest;
@@ -103,6 +107,20 @@
     }
 
     @Test
+    public void testImeShowAndHide() {
+        final TestActivity activity = startActivity(TestActivity.class);
+        final View rootView = activity.getWindow().getDecorView();
+        getInstrumentation().runOnMainSync(() -> {
+            rootView.getWindowInsetsController().show(ime());
+        });
+        PollingCheck.waitFor(TIMEOUT, () -> rootView.getRootWindowInsets().isVisible(ime()));
+        getInstrumentation().runOnMainSync(() -> {
+            rootView.getWindowInsetsController().hide(ime());
+        });
+        PollingCheck.waitFor(TIMEOUT, () -> !rootView.getRootWindowInsets().isVisible(ime()));
+    }
+
+    @Test
     public void testSetSystemBarsBehavior_showBarsByTouch() throws InterruptedException {
         final TestActivity activity = startActivity(TestActivity.class);
         final View rootView = activity.getWindow().getDecorView();
@@ -178,6 +196,15 @@
     }
 
     @Test
+    public void testShowImeOnCreate() throws Exception {
+        final TestShowOnCreateActivity activity = startActivity(TestShowOnCreateActivity.class);
+        final View rootView = activity.getWindow().getDecorView();
+        ANIMATION_CALLBACK.waitForFinishing(TIMEOUT);
+        PollingCheck.waitFor(TIMEOUT,
+                () -> rootView.getRootWindowInsets().isVisible(ime()));
+    }
+
+    @Test
     public void testInsetsDispatch() throws Exception {
         // Start an activity which hides system bars.
         final TestHideOnCreateActivity activity = startActivity(TestHideOnCreateActivity.class);
@@ -287,19 +314,48 @@
         }
     }
 
-    public static class TestActivity extends FocusableActivity { }
+    private static View setViews(Activity activity) {
+        LinearLayout layout = new LinearLayout(activity);
+        View text = new TextView(activity);
+        EditText editor = new EditText(activity);
+        layout.addView(text);
+        layout.addView(editor);
+        activity.setContentView(layout);
+        editor.requestFocus();
+        return layout;
+    }
+
+    public static class TestActivity extends FocusableActivity {
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            setViews(this);
+        }
+    }
 
     public static class TestHideOnCreateActivity extends FocusableActivity {
 
         @Override
         protected void onCreate(Bundle savedInstanceState) {
             super.onCreate(savedInstanceState);
-            View content = new TextView(this);
-            setContentView(content);
+            View layout = setViews(this);
             ANIMATION_CALLBACK.reset();
             getWindow().getDecorView().setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
             getWindow().getInsetsController().hide(statusBars());
-            content.getWindowInsetsController().hide(navigationBars());
+            layout.getWindowInsetsController().hide(navigationBars());
+        }
+    }
+
+    public static class TestShowOnCreateActivity extends FocusableActivity {
+
+        @Override
+        protected void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            View layout = setViews(this);
+            ANIMATION_CALLBACK.reset();
+            getWindow().getDecorView().setWindowInsetsAnimationCallback(ANIMATION_CALLBACK);
+            getWindow().getInsetsController().show(ime());
         }
     }
 }
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTests.java
index 28d5a76..0d7e4aa 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowMetricsTests.java
@@ -109,8 +109,15 @@
                 windowInsets.getInsetsIgnoringVisibility(navigationBars() | displayCutout());
 
         final Rect bounds = windowMetrics.getBounds();
-        final Insets result = Insets.subtract(Insets.of(bounds), insetsWithCutout);
-        return new Rect(result.left, result.top, result.right, result.bottom);
+        return inset(bounds, insetsWithCutout);
+    }
+
+    private static Rect inset(Rect original, Insets insets) {
+        final int left = original.left + insets.left;
+        final int top = original.top + insets.top;
+        final int right = original.right - insets.right;
+        final int bottom = original.bottom - insets.bottom;
+        return new Rect(left, top, right, bottom);
     }
 
     public static class MetricsActivity extends Activity implements View.OnLayoutChangeListener {
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java
index 3c86182..e2c2fe6 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowTest.java
@@ -17,6 +17,8 @@
 package android.server.wm;
 
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
+import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -650,6 +652,7 @@
             throws Throwable {
         mActivityRule.runOnUiThread(() -> {
             mWindow.getDecorView().setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
+            mWindow.getDecorView().setSystemUiVisibility(SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
         });
         mInstrumentation.waitForIdleSync();
         assertEquals(mActivity.getContentView().getRootWindowInsets().getSystemWindowInsets(),
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
index 6c337ac..9fbd1da 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleKeyguardTests.java
@@ -85,6 +85,9 @@
         assumeTrue(supportsSecureLock());
         assumeTrue(supportsSplitScreenMultiWindow());
 
+        // TODO(b/149338177): Fix test to pass with organizer API.
+        mUseTaskOrganizer = false;
+
         final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
         // Enter split screen
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
index faebece..142b8ac 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecyclePipTests.java
@@ -218,6 +218,8 @@
 
     @Test
     public void testSplitScreenBelowPip() throws Exception {
+        // TODO(b/149338177): Fix test to pass with organizer API.
+        mUseTaskOrganizer = false;
         // Launch Pip-capable activity and enter Pip immediately
         new Launcher(PipActivity.class)
                 .setExpectedState(ON_PAUSE)
@@ -253,6 +255,8 @@
 
     @Test
     public void testPipAboveSplitScreen() throws Exception {
+        // TODO(b/149338177): Fix test to pass with organizer API.
+        mUseTaskOrganizer = false;
         // Launch first activity
         final Activity firstActivity = launchActivityAndWait(FirstActivity.class);
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
index 8acde84..a9ecab2 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleSplitScreenTests.java
@@ -69,6 +69,8 @@
     public void setUp() throws Exception {
         super.setUp();
         assumeTrue(supportsSplitScreenMultiWindow());
+        // TODO(b/149338177): Fix test to pass with organizer API.
+        mUseTaskOrganizer = false;
     }
 
     @FlakyTest(bugId = 137329632)
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
index 0a2cf35..3d33cfc 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/lifecycle/ActivityLifecycleTopResumedStateTests.java
@@ -58,6 +58,7 @@
 import androidx.test.filters.FlakyTest;
 import androidx.test.filters.MediumTest;
 
+import org.junit.Before;
 import org.junit.Test;
 
 import java.util.Arrays;
@@ -74,6 +75,13 @@
 @android.server.wm.annotation.Group3
 public class ActivityLifecycleTopResumedStateTests extends ActivityLifecycleClientTestBase {
 
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+        // TODO(b/149338177): Fix test to pass with organizer API.
+        mUseTaskOrganizer = false;
+    }
+
     @Test
     public void testTopPositionAfterLaunch() throws Exception {
         launchActivityAndWait(CallbackTrackingActivity.class);
diff --git a/tests/framework/base/windowmanager/testsdk29/Android.bp b/tests/framework/base/windowmanager/testsdk29/Android.bp
new file mode 100644
index 0000000..5f8806b
--- /dev/null
+++ b/tests/framework/base/windowmanager/testsdk29/Android.bp
@@ -0,0 +1,38 @@
+//
+// 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: "CtsWindowManagerSdk29TestCases",
+    defaults: ["cts_defaults"],
+
+    srcs: [
+        "src/**/*.java",
+        ":cts-wm-decor-inset-test-base",
+    ],
+
+    // Intentionally don't set the compile SDK to 29, because we want to test
+    // usage of R APIs with 29 targetSdk behavior
+
+    static_libs: [
+        "androidx.test.rules",
+        "cts-wm-util",
+    ],
+
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+}
diff --git a/tests/framework/base/windowmanager/testsdk29/AndroidManifest.xml b/tests/framework/base/windowmanager/testsdk29/AndroidManifest.xml
new file mode 100644
index 0000000..faa11fe
--- /dev/null
+++ b/tests/framework/base/windowmanager/testsdk29/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?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.server.wm.cts.testsdk29">
+
+    <uses-sdk android:targetSdkVersion="29" />
+
+    <application android:label="CtsWindowManagerSdk28TestCases">
+        <uses-library android:name="android.test.runner" />
+
+        <activity
+            android:name="android.server.wm.DecorInsetTestsBase$TestActivity"
+            android:label="DecorInsetTestsBase.TestActivity"
+            android:exported="true" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="android.server.wm.cts.testsdk29" />
+
+</manifest>
diff --git a/tests/framework/base/windowmanager/testsdk29/AndroidTest.xml b/tests/framework/base/windowmanager/testsdk29/AndroidTest.xml
new file mode 100644
index 0000000..6d1f8d8
--- /dev/null
+++ b/tests/framework/base/windowmanager/testsdk29/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 CTS ActivityManager SDK 29 compatibility test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <!-- Instant apps and multi-abi not supported. -->
+    <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.compatibility.common.tradefed.targetprep.LocationCheck" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsWindowManagerSdk29TestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.server.wm.cts.testsdk29" />
+        <option name="runtime-hint" value="1m" />
+    </test>
+</configuration>
diff --git a/tests/framework/base/windowmanager/testsdk29/OWNERS b/tests/framework/base/windowmanager/testsdk29/OWNERS
new file mode 100644
index 0000000..f93c21b
--- /dev/null
+++ b/tests/framework/base/windowmanager/testsdk29/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 99608
+include ../../OWNERS
diff --git a/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/DecorInsetSdk29Tests.java b/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/DecorInsetSdk29Tests.java
new file mode 100644
index 0000000..6d6d93d
--- /dev/null
+++ b/tests/framework/base/windowmanager/testsdk29/src/android/server/wm/DecorInsetSdk29Tests.java
@@ -0,0 +1,94 @@
+/*
+ * 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.server.wm;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import android.content.Intent;
+import android.platform.test.annotations.Presubmit;
+import android.view.WindowInsets;
+
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Build/Install/Run:
+ *  atest CtsWindowManagerSdk29TestCases:DecorInsetSdk29Tests
+ */
+@Presubmit
+public class DecorInsetSdk29Tests extends DecorInsetTestsBase {
+
+    @Test
+    public void testDecorView_consumesAllInsets_byDefault() throws Throwable {
+        TestActivity activity = mDecorActivity.launchActivity(new Intent()
+                .putExtra(ARG_LAYOUT_STABLE, false)
+                .putExtra(ARG_LAYOUT_FULLSCREEN, false)
+                .putExtra(ARG_LAYOUT_HIDE_NAV, false)
+                .putExtra(ARG_DECOR_FITS_SYSTEM_WINDOWS, true));
+        activity.mLaidOut.await(4, TimeUnit.SECONDS);
+
+        assertNotNull("test setup failed", activity.mLastDecorInsets);
+        assertNull("unexpected content insets", activity.mLastContentInsets);
+    }
+
+    @Test
+    public void testDecorView_consumesNavBar_ifLayoutHideNavIsNotSet() throws Throwable {
+        TestActivity activity = mDecorActivity.launchActivity(new Intent()
+                .putExtra(ARG_LAYOUT_STABLE, true)
+                .putExtra(ARG_LAYOUT_FULLSCREEN, true)
+                .putExtra(ARG_LAYOUT_HIDE_NAV, false)
+                .putExtra(ARG_DECOR_FITS_SYSTEM_WINDOWS, true));
+        activity.mLaidOut.await(4, TimeUnit.SECONDS);
+
+        assertNotNull("test setup failed", activity.mLastDecorInsets);
+        assertEquals("unexpected bottom inset: ", 0, activity.mLastContentInsets.getInsets(
+                WindowInsets.Type.systemBars()).bottom);
+    }
+
+    @Test
+    public void testDecorView_doesntConsumeNavBar_ifLayoutHideNavIsSet() throws Throwable {
+        TestActivity activity = mDecorActivity.launchActivity(new Intent()
+                .putExtra(ARG_LAYOUT_STABLE, true)
+                .putExtra(ARG_LAYOUT_FULLSCREEN, false)
+                .putExtra(ARG_LAYOUT_HIDE_NAV, true)
+                .putExtra(ARG_DECOR_FITS_SYSTEM_WINDOWS, true));
+        activity.mLaidOut.await(4, TimeUnit.SECONDS);
+
+        assertNotNull("test setup failed", activity.mLastDecorInsets);
+        assertEquals("insets were unexpectedly consumed: ",
+                activity.mLastDecorInsets.getSystemWindowInsets(),
+                activity.mLastContentInsets.getSystemWindowInsets());
+    }
+
+    @Test
+    public void testDecorView_doesntConsumeNavBar_ifDecorDoesntFitSystemWindows() throws Throwable {
+        TestActivity activity = mDecorActivity.launchActivity(new Intent()
+                .putExtra(ARG_LAYOUT_STABLE, true)
+                .putExtra(ARG_LAYOUT_FULLSCREEN, false)
+                .putExtra(ARG_LAYOUT_HIDE_NAV, false)
+                .putExtra(ARG_DECOR_FITS_SYSTEM_WINDOWS, false));
+        activity.mLaidOut.await(4, TimeUnit.SECONDS);
+
+        assertNotNull("test setup failed", activity.mLastDecorInsets);
+        assertEquals("insets were unexpectedly consumed: ",
+                activity.mLastDecorInsets.getSystemWindowInsets(),
+                activity.mLastContentInsets.getSystemWindowInsets());
+    }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index 1d286fb..279f74d 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
@@ -111,6 +111,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
+import android.app.Instrumentation;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -217,6 +218,7 @@
 
     protected static final int INVALID_DEVICE_ROTATION = -1;
 
+    protected final Instrumentation mInstrumentation = getInstrumentation();
     protected final Context mContext = getInstrumentation().getContext();
     protected final ActivityManager mAm = mContext.getSystemService(ActivityManager.class);
     protected final ActivityTaskManager mAtm = mContext.getSystemService(ActivityTaskManager.class);
@@ -291,6 +293,10 @@
     }
 
     protected WindowManagerStateHelper mWmState = new WindowManagerStateHelper();
+    TestTaskOrganizer mTaskOrganizer = new TestTaskOrganizer();
+    // If the specific test should run using the task organizer or older API.
+    // TODO(b/149338177): Fix all places setting this to fail to be able to use organizer API.
+    public boolean mUseTaskOrganizer = true;
 
     public WindowManagerStateHelper getWmState() {
         return mWmState;
@@ -408,11 +414,10 @@
         private T launchActivityOnDisplay(Intent intent, int displayId) {
             final Bundle bundle = ActivityOptions.makeBasic()
                     .setLaunchDisplayId(displayId).toBundle();
-            final ActivityMonitor monitor = getInstrumentation()
-                    .addMonitor((String) null, null, false);
+            final ActivityMonitor monitor = mInstrumentation.addMonitor((String) null, null, false);
             mContext.startActivity(intent.addFlags(FLAG_ACTIVITY_NEW_TASK), bundle);
             // Wait for activity launch with timeout.
-            mTestActivity = (T) getInstrumentation().waitForMonitorWithTimeout(monitor,
+            mTestActivity = (T) mInstrumentation.waitForMonitorWithTimeout(monitor,
                     ACTIVITY_LAUNCH_TIMEOUT);
             assertNotNull(mTestActivity);
             return mTestActivity;
@@ -426,8 +431,8 @@
         }
 
         void runOnMainSyncAndWait(Runnable runnable) {
-            getInstrumentation().runOnMainSync(runnable);
-            getInstrumentation().waitForIdleSync();
+            mInstrumentation.runOnMainSync(runnable);
+            mInstrumentation.waitForIdleSync();
         }
 
         void runOnMainAndAssertWithTimeout(@NonNull BooleanSupplier condition, long timeoutMs,
@@ -475,6 +480,8 @@
     private void tearDownBase() {
         mObjectTracker.tearDown(mPostAssertionRule::addError);
 
+        SystemUtil.runWithShellPermissionIdentity(
+                () -> mTaskOrganizer.unregisterOrganizerIfNeeded());
         // Synchronous execution of removeStacksWithActivityTypes() ensures that all activities but
         // home are cleaned up from the stack at the end of each test. Am force stop shell commands
         // might be asynchronous and could interrupt the stack cleanup process if executed first.
@@ -593,7 +600,7 @@
         // This is needed after a tap in multi-display to ensure that the display focus has really
         // changed, if needed. The call to syncInputTransaction will wait until focus change has
         // propagated from WMS to native input before returning.
-        getInstrumentation().getUiAutomation().syncInputTransactions();
+        mInstrumentation.getUiAutomation().syncInputTransactions();
     }
 
     protected void tapOnCenter(Rect bounds, int displayId) {
@@ -667,7 +674,7 @@
     }
 
     protected Bitmap takeScreenshot() {
-        return getInstrumentation().getUiAutomation().takeScreenshot();
+        return mInstrumentation.getUiAutomation().takeScreenshot();
     }
 
     protected void launchActivity(final ComponentName activityName, final String... keyValuePairs) {
@@ -773,10 +780,14 @@
         SystemUtil.runWithShellPermissionIdentity(() -> {
             launchActivity(activityName);
             final int taskId = mWmState.getTaskByActivity(activityName).mTaskId;
-            mAtm.setTaskWindowingModeSplitScreenPrimary(taskId,
-                    SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
-                    true /* onTop */, false /* animate */,
-                    null /* initialBounds */, true /* showRecents */);
+            if (mUseTaskOrganizer) {
+                mTaskOrganizer.putTaskInSplitPrimary(taskId);
+            } else {
+                mAtm.setTaskWindowingModeSplitScreenPrimary(taskId,
+                        SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT,
+                        true /* onTop */, false /* animate */,
+                        null /* initialBounds */, true /* showRecents */);
+            }
 
             mWmState.waitForValidState(
                     new WaitForValidActivityState.Builder(activityName)
@@ -802,10 +813,15 @@
     public void moveTaskToPrimarySplitScreen(int taskId, boolean showSideActivity) {
         final boolean isHomeRecentsComponent = mWmState.isHomeRecentsComponent();
         SystemUtil.runWithShellPermissionIdentity(() -> {
-            mAtm.setTaskWindowingModeSplitScreenPrimary(taskId,
-                    SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true /* onTop */,
-                    false /* animate */,
-                    null /* initialBounds */, showSideActivity && !isHomeRecentsComponent);
+            if (mUseTaskOrganizer) {
+                mTaskOrganizer.putTaskInSplitPrimary(taskId);
+            } else {
+                mAtm.setTaskWindowingModeSplitScreenPrimary(taskId,
+                        SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT, true /* onTop */,
+                        false /* animate */, null /* initialBounds */,
+                        showSideActivity && !isHomeRecentsComponent);
+            }
+
             mWmState.waitForRecentsActivityVisible();
 
             if (showSideActivity) {
@@ -948,6 +964,11 @@
                 || PRETEND_DEVICE_SUPPORTS_FREEFORM;
     }
 
+    /** Whether or not the device supports lock screen. */
+    protected boolean supportsLockScreen() {
+        return supportsInsecureLock() || supportsSecureLock();
+    }
+
     /** Whether or not the device supports pin/pattern/password lock. */
     protected boolean supportsSecureLock() {
         return hasDeviceFeature(FEATURE_SECURE_LOCK_SCREEN);
@@ -1251,7 +1272,7 @@
 
             waitForDeviceIdle(3000);
             SystemUtil.runWithShellPermissionIdentity(() ->
-                    getInstrumentation().sendStringSync(LOCK_CREDENTIAL));
+                    mInstrumentation.sendStringSync(LOCK_CREDENTIAL));
             pressEnterButton();
             return this;
         }
@@ -1271,7 +1292,7 @@
             // Not all device variants lock when we go to sleep, so we need to explicitly lock the
             // device. Note that pressSleepButton() above is redundant because the action also
             // puts the device to sleep, but kept around for clarity.
-            getInstrumentation().getUiAutomation().performGlobalAction(
+            mInstrumentation.getUiAutomation().performGlobalAction(
                     AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);
             if (mAmbientDisplayConfiguration.alwaysOnEnabled(
                     android.os.Process.myUserHandle().getIdentifier())) {
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java b/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
new file mode 100644
index 0000000..5dec9e9
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/TestTaskOrganizer.java
@@ -0,0 +1,174 @@
+/*
+ * 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.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
+import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
+import static android.view.Display.DEFAULT_DISPLAY;
+
+import android.app.ActivityManager;
+import android.view.Display;
+import android.window.TaskOrganizer;
+import android.window.WindowContainerTransaction;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+class TestTaskOrganizer extends TaskOrganizer {
+
+    private boolean mRegistered;
+    final HashMap<Integer, ActivityManager.RunningTaskInfo> mKnownTasks = new HashMap<>();
+    private ActivityManager.RunningTaskInfo mRootPrimary;
+    private boolean mRootPrimaryHasChild;
+    private ActivityManager.RunningTaskInfo mRootSecondary;
+
+    private void registerOrganizerIfNeeded() {
+        if (mRegistered) return;
+
+        registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
+        registerOrganizer(WINDOWING_MODE_SPLIT_SCREEN_SECONDARY);
+        mRegistered = true;
+    }
+
+    void unregisterOrganizerIfNeeded() {
+        if (!mRegistered) return;
+        mRegistered = false;
+
+        dismissedSplitScreen();
+        super.unregisterOrganizer();
+    }
+
+    void putTaskInSplitPrimary(int taskId) {
+        registerOrganizerIfNeeded();
+        ActivityManager.RunningTaskInfo taskInfo = getTaskInfo(taskId);
+        final WindowContainerTransaction t = new WindowContainerTransaction();
+        t.reparent(taskInfo.getToken(), mRootPrimary.getToken(), true /* onTop */);
+        applyTransaction(t);
+    }
+
+    void dismissedSplitScreen() {
+        // Re-set default launch root.
+        TaskOrganizer.setLaunchRoot(Display.DEFAULT_DISPLAY, null);
+
+        // Re-parent everything back to the display from the splits so that things are as they were.
+        final List<ActivityManager.RunningTaskInfo> children = new ArrayList<>();
+        final List<ActivityManager.RunningTaskInfo> primaryChildren =
+                getChildTasks(mRootPrimary.getToken(), null /* activityTypes */);
+        if (primaryChildren != null && !primaryChildren.isEmpty()) {
+            children.addAll(primaryChildren);
+        }
+        final List<ActivityManager.RunningTaskInfo> secondaryChildren =
+                getChildTasks(mRootSecondary.getToken(), null /* activityTypes */);
+        if (secondaryChildren != null && !secondaryChildren.isEmpty()) {
+            children.addAll(secondaryChildren);
+        }
+        if (children.isEmpty()) {
+            return;
+        }
+
+        final WindowContainerTransaction t = new WindowContainerTransaction();
+        for (ActivityManager.RunningTaskInfo task : children) {
+            t.reparent(task.getToken(), null /* parent */, true /* onTop */);
+        }
+        applyTransaction(t);
+    }
+
+    /** Also completes the process of entering split mode. */
+    private void processRootPrimaryTaskInfoChanged() {
+        List<ActivityManager.RunningTaskInfo> children =
+                getChildTasks(mRootPrimary.getToken(), null /* activityTypes */);
+        final boolean hasChild = !children.isEmpty();
+        if (mRootPrimaryHasChild == hasChild) return;
+        mRootPrimaryHasChild = hasChild;
+        if (!hasChild) return;
+
+        // Finish entering split-screen mode
+
+        // Set launch root for the default display to secondary...for no good reason...
+        setLaunchRoot(DEFAULT_DISPLAY, mRootSecondary.getToken());
+
+        List<ActivityManager.RunningTaskInfo> rootTasks =
+                getRootTasks(DEFAULT_DISPLAY, null /* activityTypes */);
+        if (rootTasks.isEmpty()) return;
+        // Move all root fullscreen task to secondary split.
+        final WindowContainerTransaction t = new WindowContainerTransaction();
+        for (int i = rootTasks.size() - 1; i >= 0; --i) {
+            final ActivityManager.RunningTaskInfo task = rootTasks.get(i);
+            if (task.getConfiguration().windowConfiguration.getWindowingMode()
+                    == WINDOWING_MODE_FULLSCREEN) {
+                t.reparent(task.getToken(), mRootSecondary.getToken(), true /* onTop */);
+            }
+        }
+        // Move the secondary split-forward.
+        t.reorder(mRootSecondary.getToken(), true /* onTop */);
+        applyTransaction(t);
+    }
+
+    private ActivityManager.RunningTaskInfo getTaskInfo(int taskId) {
+        ActivityManager.RunningTaskInfo taskInfo = mKnownTasks.get(taskId);
+        if (taskInfo != null) return taskInfo;
+
+        final List<ActivityManager.RunningTaskInfo> rootTasks = getRootTasks(DEFAULT_DISPLAY, null);
+        for (ActivityManager.RunningTaskInfo info : rootTasks) {
+            addTask(info);
+        }
+
+        return mKnownTasks.get(taskId);
+    }
+
+    @Override
+    public void onTaskAppeared(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+        addTask(taskInfo);
+    }
+
+    @Override
+    public void onTaskVanished(@NonNull ActivityManager.RunningTaskInfo taskInfo) {
+        removeTask(taskInfo);
+    }
+
+    @Override
+    public void onTaskInfoChanged(ActivityManager.RunningTaskInfo taskInfo) {
+        addTask(taskInfo);
+    }
+
+    private void addTask(ActivityManager.RunningTaskInfo taskInfo) {
+        mKnownTasks.put(taskInfo.taskId, taskInfo);
+
+        final int windowingMode =
+                taskInfo.getConfiguration().windowConfiguration.getWindowingMode();
+        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
+            mRootPrimary = taskInfo;
+            processRootPrimaryTaskInfoChanged();
+        }
+
+        if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_SECONDARY) {
+            mRootSecondary = taskInfo;
+        }
+    }
+
+    private void removeTask(ActivityManager.RunningTaskInfo taskInfo) {
+        final int taskId = taskInfo.taskId;
+        mKnownTasks.remove(taskId);
+
+        if (taskId == mRootPrimary.taskId) mRootPrimary = null;
+        if (taskId == mRootSecondary.taskId) mRootSecondary = null;
+    }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowInsetsAnimationUtils.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowInsetsAnimationUtils.java
new file mode 100644
index 0000000..d288141
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowInsetsAnimationUtils.java
@@ -0,0 +1,106 @@
+/*
+ * 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.server.wm;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.graphics.Insets;
+import android.view.WindowInsetsAnimationControlListener;
+import android.view.WindowInsetsAnimationController;
+import android.view.WindowInsetsController;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class WindowInsetsAnimationUtils {
+
+    private static final int DURATION = 1000;
+
+    /** Interpolates {@link Insets} for use with {@link ValueAnimator}. */
+    public static TypeEvaluator<Insets> INSETS_EVALUATOR =
+            (fraction, startValue, endValue) -> Insets.of(
+                    (int) (startValue.left + fraction * (endValue.left - startValue.left)),
+                    (int) (startValue.top + fraction * (endValue.top - startValue.top)),
+                    (int) (startValue.right + fraction * (endValue.right - startValue.right)),
+                    (int) (startValue.bottom + fraction * (endValue.bottom - startValue.bottom)));
+
+
+    /**
+     * Requests control of the given {@code types} and animates them according to {@code show}.
+     *
+     * @param show if true, animate the inset in, otherwise animate it out.
+     */
+    public static void requestControlThenTransitionToVisibility(WindowInsetsController wic,
+            int types, boolean show) {
+        wic.controlWindowInsetsAnimation(types, -1, null,
+                null, new WindowInsetsAnimationControlListener() {
+                    Animator mAnimator;
+
+                    @Override
+                    public void onReady(@NonNull WindowInsetsAnimationController controller,
+                            int types) {
+                        mAnimator = runTransition(controller, show);
+                    }
+
+                    @Override
+                    public void onFinished(
+                            @NonNull WindowInsetsAnimationController controller) {
+                    }
+
+                    @Override
+                    public void onCancelled(
+                            @Nullable WindowInsetsAnimationController controller) {
+                        if (mAnimator != null) {
+                            mAnimator.cancel();
+                        }
+                    }
+                });
+    }
+
+    private static ValueAnimator runTransition(WindowInsetsAnimationController controller,
+            boolean show) {
+        ValueAnimator animator = ValueAnimator.ofObject(
+                INSETS_EVALUATOR,
+                show ? controller.getHiddenStateInsets()
+                        : controller.getShownStateInsets(),
+                show ? controller.getShownStateInsets()
+                        : controller.getHiddenStateInsets()
+        );
+        animator.setDuration(DURATION);
+        animator.addUpdateListener((animator1) -> {
+            if (!controller.isReady()) {
+                // Lost control
+                animator.cancel();
+                return;
+            }
+            Insets insets = (Insets) animator.getAnimatedValue();
+            controller.setInsetsAndAlpha(insets, 1.0f, animator.getAnimatedFraction());
+        });
+        animator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (!controller.isCancelled()) {
+                    controller.finish(show);
+                }
+            }
+        });
+        animator.start();
+        return animator;
+    }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
index eba9bc7..c270965 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
@@ -536,6 +536,11 @@
     }
 
     boolean containsStack(int windowingMode, int activityType) {
+        return countStacks(windowingMode, activityType) > 0;
+    }
+
+    int countStacks(int windowingMode, int activityType) {
+        int count = 0;
         for (ActivityTask stack : mRootTasks) {
             if (activityType != ACTIVITY_TYPE_UNDEFINED
                     && activityType != stack.getActivityType()) {
@@ -545,9 +550,9 @@
                     && windowingMode != stack.getWindowingMode()) {
                 continue;
             }
-            return true;
+            ++count;
         }
-        return false;
+        return count;
     }
 
     ActivityTask getRootTask(int taskId) {
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
index 67bb7a6..c1863f9 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
@@ -46,6 +46,7 @@
     private static final String WINDOW_FLAGS_MASK = "WindowFlagsMask";
     private static final String FULLSCREEN_MODE_ALLOWED = "FullscreenModeAllowed";
     private static final String INPUT_VIEW_SYSTEM_UI_VISIBILITY = "InputViewSystemUiVisibility";
+    private static final String WATERMARK_ENABLED = "WatermarkEnabled";
     private static final String HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED =
             "HardKeyboardConfigurationBehaviorAllowed";
     private static final String INLINE_SUGGESTIONS_ENABLED = "InlineSuggestionsEnabled";
@@ -107,6 +108,10 @@
         return mBundle.getInt(INPUT_VIEW_SYSTEM_UI_VISIBILITY, defaultFlags);
     }
 
+    public boolean isWatermarkEnabled(boolean defaultValue) {
+        return mBundle.getBoolean(WATERMARK_ENABLED, defaultValue);
+    }
+
     public boolean getHardKeyboardConfigurationBehaviorAllowed(boolean defaultValue) {
         return mBundle.getBoolean(HARD_KEYBOARD_CONFIGURATION_BEHAVIOR_ALLOWED, defaultValue);
     }
@@ -212,6 +217,18 @@
         }
 
         /**
+         * Sets whether a unique watermark image needs to be shown on the software keyboard or not.
+         *
+         * <p>This needs to be enabled to use</p>
+         *
+         * @param enabled {@code true} when such a watermark image is requested.
+         */
+        public Builder setWatermarkEnabled(boolean enabled) {
+            mBundle.putBoolean(WATERMARK_ENABLED, enabled);
+            return this;
+        }
+
+        /**
          * Controls whether {@link MockIme} is allowed to change the behavior based on
          * {@link android.content.res.Configuration#keyboard} and
          * {@link android.content.res.Configuration#hardKeyboardHidden}.
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index b215647..279206e 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.res.Configuration;
+import android.graphics.Bitmap;
 import android.inputmethodservice.InputMethodService;
 import android.os.AsyncTask;
 import android.os.Build;
@@ -44,7 +45,6 @@
 import android.view.Gravity;
 import android.view.KeyEvent;
 import android.view.View;
-import android.view.ViewGroup;
 import android.view.Window;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -59,27 +59,28 @@
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputContentInfo;
 import android.view.inputmethod.InputMethod;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.ScrollView;
 import android.widget.TextView;
 import android.widget.inline.InlinePresentationSpec;
 
+import androidx.annotation.AnyThread;
+import androidx.annotation.CallSuper;
+import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.WorkerThread;
+
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BooleanSupplier;
 import java.util.function.Consumer;
 import java.util.function.Supplier;
 
-import androidx.annotation.AnyThread;
-import androidx.annotation.CallSuper;
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.WorkerThread;
-
 /**
  * Mock IME for end-to-end tests.
  */
@@ -435,37 +436,51 @@
                     getResources().getColor(android.R.color.holo_orange_dark, null);
 
             final int mainSpacerHeight = mSettings.getInputViewHeight(LayoutParams.WRAP_CONTENT);
+            mLayout = new LinearLayout(getContext());
+            mLayout.setOrientation(LinearLayout.VERTICAL);
+
+            if (mSettings.getInlineSuggestionsEnabled()) {
+                final ScrollView scrollView = new ScrollView(getContext());
+                final LayoutParams scrollViewParams = new LayoutParams(MATCH_PARENT, 100);
+                scrollView.setLayoutParams(scrollViewParams);
+
+                sSuggestionView = new LinearLayout(getContext());
+                sSuggestionView.setBackgroundColor(0xFFEEEEEE);
+                //TODO: Change magic id
+                sSuggestionView.setId(0x0102000b);
+                scrollView.addView(sSuggestionView,
+                        new LayoutParams(MATCH_PARENT, MATCH_PARENT));
+
+                mLayout.addView(scrollView);
+            }
+
             {
-                mLayout = new LinearLayout(getContext());
-                mLayout.setOrientation(LinearLayout.VERTICAL);
-
-                if (mSettings.getInlineSuggestionsEnabled()) {
-                    final ScrollView scrollView = new ScrollView(getContext());
-                    final LayoutParams scrollViewParams = new LayoutParams(MATCH_PARENT, 100);
-                    scrollView.setLayoutParams(scrollViewParams);
-
-                    sSuggestionView = new LinearLayout(getContext());
-                    sSuggestionView.setBackgroundColor(0xFFEEEEEE);
-                    //TODO: Change magic id
-                    sSuggestionView.setId(0x0102000b);
-                    scrollView.addView(sSuggestionView,
-                            new LayoutParams(MATCH_PARENT, MATCH_PARENT));
-
-                    mLayout.addView(scrollView);
-                }
+                final FrameLayout secondaryLayout = new FrameLayout(getContext());
+                secondaryLayout.setForegroundGravity(Gravity.CENTER);
 
                 final TextView textView = new TextView(getContext());
-                final LayoutParams params = new LayoutParams(MATCH_PARENT, WRAP_CONTENT);
-                textView.setLayoutParams(params);
+                textView.setLayoutParams(new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
                 textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 20);
                 textView.setGravity(Gravity.CENTER);
                 textView.setText(getImeId());
-                textView.setBackgroundColor(mSettings.getBackgroundColor(defaultBackgroundColor));
-                mLayout.addView(textView);
+                textView.setBackgroundColor(
+                        mSettings.getBackgroundColor(defaultBackgroundColor));
+                secondaryLayout.addView(textView);
 
-                addView(mLayout, MATCH_PARENT, mainSpacerHeight);
+                if (mSettings.isWatermarkEnabled(true /* defaultValue */)) {
+                    final ImageView imageView = new ImageView(getContext());
+                    final Bitmap bitmap = Watermark.create();
+                    imageView.setImageBitmap(bitmap);
+                    secondaryLayout.addView(imageView,
+                            new FrameLayout.LayoutParams(bitmap.getWidth(), bitmap.getHeight(),
+                                    Gravity.CENTER));
+                }
+
+                mLayout.addView(secondaryLayout);
             }
 
+            addView(mLayout, MATCH_PARENT, mainSpacerHeight);
+
             final int systemUiVisibility = mSettings.getInputViewSystemUiVisibility(0);
             if (systemUiVisibility != 0) {
                 setSystemUiVisibility(systemUiVisibility);
@@ -663,13 +678,27 @@
     }
 
     private static LinearLayout sSuggestionView;
-    @GuardedBy("this")
-    private List<View> mSuggestionViews = new ArrayList<>();
-    @GuardedBy("this")
-    private List<Size> mSuggestionViewSizes = new ArrayList<>();
-    @GuardedBy("this")
-    private boolean mSuggestionViewVisible = false;
+    private PendingInlineSuggestions mPendingInlineSuggestions;
 
+    private static final class PendingInlineSuggestions {
+        final InlineSuggestionsResponse mResponse;
+        final int mTotalCount;
+        final View[] mViews;
+        final Size[] mViewSizes;
+        final AtomicInteger mInflatedViewCount;
+        final AtomicBoolean mValid = new AtomicBoolean(true);
+
+        PendingInlineSuggestions(InlineSuggestionsResponse response) {
+            mResponse = response;
+            mTotalCount = response.getInlineSuggestions().size();
+            mViews = new View[mTotalCount];
+            mViewSizes = new Size[mTotalCount];
+            mInflatedViewCount = new AtomicInteger(0);
+        }
+    }
+
+    @MainThread
+    @Override
     public InlineSuggestionsRequest onCreateInlineSuggestionsRequest(Bundle uiExtras) {
         Log.d(TAG, "onCreateInlineSuggestionsRequest() called");
         final ArrayList<InlinePresentationSpec> presentationSpecs = new ArrayList<>();
@@ -683,91 +712,65 @@
                 .build();
     }
 
+
+    @MainThread
     @Override
     public boolean onInlineSuggestionsResponse(InlineSuggestionsResponse response) {
-        Log.d(TAG, "onInlineSuggestionsResponse() called");
-        AsyncTask.THREAD_POOL_EXECUTOR.execute(() -> {
-            onInlineSuggestionsResponseInternal(response);
-        });
-        return true;
-    }
-
-    private synchronized void updateInlineSuggestionVisibility(boolean visible, boolean force) {
-        Log.d(TAG, "updateInlineSuggestionVisibility() called, visible=" + visible + ", force="
-                + force);
-        mMainHandler.post(() -> {
-            Log.d(TAG, "updateInlineSuggestionVisibility() running");
-            if (visible == mSuggestionViewVisible && !force) {
-                return;
-            } else if (visible) {
-                sSuggestionView.removeAllViews();
-                final int size = mSuggestionViews.size();
-                for (int i = 0; i < size; i++) {
-                    if(mSuggestionViews.get(i) == null) {
-                        continue;
-                    }
-                    ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(
-                            mSuggestionViewSizes.get(i).getWidth(),
-                            mSuggestionViewSizes.get(i).getHeight());
-                    sSuggestionView.addView(mSuggestionViews.get(i), layoutParams);
-                }
-                mSuggestionViewVisible = true;
-            } else {
-                sSuggestionView.removeAllViews();
-                mSuggestionViewVisible = false;
-            }
-        });
-    }
-
-    private void onSuggestionViewUpdated() {
-        getTracer().onSuggestionViewUpdated();
-    }
-
-    private synchronized void updateSuggestionViews(View[] suggestionViews, Size[] sizes) {
-        Log.d(TAG, "updateSuggestionViews() called on " + suggestionViews.length + " views");
-        mSuggestionViews = Arrays.asList(suggestionViews);
-        mSuggestionViewSizes = Arrays.asList(sizes);
-        final boolean visible = !mSuggestionViews.isEmpty();
-        updateInlineSuggestionVisibility(visible, true);
-        onSuggestionViewUpdated();
-    }
-
-    private void onInlineSuggestionsResponseInternal(InlineSuggestionsResponse response) {
-        if (response == null || response.getInlineSuggestions() == null) {
-            Log.w(TAG, "onInlineSuggestionsResponseInternal() null response/suggestions");
-            return;
+        Log.d(TAG,
+                "onInlineSuggestionsResponse() called: " + response.getInlineSuggestions().size());
+        final PendingInlineSuggestions pendingInlineSuggestions =
+                new PendingInlineSuggestions(response);
+        if (mPendingInlineSuggestions != null) {
+            mPendingInlineSuggestions.mValid.set(false);
         }
-        final List<InlineSuggestion> inlineSuggestions = response.getInlineSuggestions();
-        final int totalSuggestionsCount = inlineSuggestions.size();
-        Log.d(TAG, "onInlineSuggestionsResponseInternal() called. Suggestion="
-                + totalSuggestionsCount);
-
-        if (totalSuggestionsCount == 0) {
-            updateSuggestionViews(new View[]{} , new Size[]{});
+        mPendingInlineSuggestions = pendingInlineSuggestions;
+        if (pendingInlineSuggestions.mTotalCount == 0) {
+            updateInlineSuggestions(pendingInlineSuggestions);
+            return true;
         }
 
-        final AtomicInteger suggestionsCount = new AtomicInteger(totalSuggestionsCount);
-        final View[] suggestionViews = new View[totalSuggestionsCount];
-        final Size[] sizes = new Size[totalSuggestionsCount];
-
-        for (int i = 0; i < totalSuggestionsCount; i++) {
+        for (int i = 0; i < pendingInlineSuggestions.mTotalCount; i++) {
             final int index = i;
-            InlineSuggestion inlineSuggestion = inlineSuggestions.get(index);
+            InlineSuggestion inlineSuggestion =
+                    pendingInlineSuggestions.mResponse.getInlineSuggestions().get(index);
             Size size = inlineSuggestion.getInfo().getInlinePresentationSpec().getMaxSize();
-            Log.d(TAG, "Calling inflate on suggestion " + i);
-            inlineSuggestion.inflate(this, size,
+            inlineSuggestion.inflate(
+                    this,
+                    size,
                     AsyncTask.THREAD_POOL_EXECUTOR,
                     suggestionView -> {
                         Log.d(TAG, "new inline suggestion view ready");
-                        if(suggestionView != null) {
-                            suggestionViews[index] = suggestionView;
-                            sizes[index] = size;
+                        if (suggestionView != null) {
+                            pendingInlineSuggestions.mViews[index] = suggestionView;
+                            pendingInlineSuggestions.mViewSizes[index] = size;
                         }
-                        if (suggestionsCount.decrementAndGet() == 0) {
-                            updateSuggestionViews(suggestionViews, sizes);
+                        if (pendingInlineSuggestions.mInflatedViewCount.incrementAndGet()
+                                == pendingInlineSuggestions.mTotalCount
+                                && pendingInlineSuggestions.mValid.get()) {
+                            Log.d(TAG, "ready to display all suggestions");
+                            getMainExecutor().execute(
+                                    () -> updateInlineSuggestions(pendingInlineSuggestions));
                         }
                     });
         }
+        return true;
+    }
+
+    @MainThread
+    private void updateInlineSuggestions(PendingInlineSuggestions pendingInlineSuggestions) {
+        Log.d(TAG, "updateInlineSuggestions() called: " + pendingInlineSuggestions.mTotalCount);
+        if (sSuggestionView == null || !pendingInlineSuggestions.mValid.get()) {
+            return;
+        }
+        sSuggestionView.removeAllViews();
+        for (int i = 0; i < pendingInlineSuggestions.mTotalCount; i++) {
+            View view = pendingInlineSuggestions.mViews[i];
+            Size size = pendingInlineSuggestions.mViewSizes[i];
+            if (view == null || size == null) {
+                continue;
+            }
+            sSuggestionView.addView(view, size.getWidth(), size.getHeight());
+        }
     }
 
     /**
@@ -1000,9 +1003,5 @@
             imeLayoutInfo.writeToBundle(arguments);
             recordEventInternal("onInputViewLayoutChanged", runnable, arguments);
         }
-
-        public void onSuggestionViewUpdated() {
-            recordEventInternal("onSuggestionViewUpdated", () -> {}, new Bundle());
-        }
     }
 }
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
index 3d318b6..9090648 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -18,8 +18,6 @@
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
-import static org.junit.Assume.assumeTrue;
-
 import android.app.UiAutomation;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/Watermark.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/Watermark.java
new file mode 100644
index 0000000..5a60da2
--- /dev/null
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/Watermark.java
@@ -0,0 +1,241 @@
+/*
+ * 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.mockime;
+
+import android.graphics.Bitmap;
+
+import androidx.annotation.AnyThread;
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+
+/**
+ * A utility class to put a unique image on {@link MockIme} so that whether the software keyboard is
+ * visible to the user or not can be determined from the screenshot.
+ */
+public final class Watermark {
+    /**
+     * A utility class that represents A8R8G8B bitmap as an integer array.
+     */
+    private final static class BitmapImage {
+        @NonNull
+        private final int[] mPixels;
+        private final int mWidth;
+        private final int mHeight;
+
+        BitmapImage(@NonNull int[] pixels, int width, int height) {
+            mWidth = width;
+            mHeight = height;
+            mPixels = pixels;
+        }
+
+        /**
+         * Create {@link BitmapImage} from {@link Bitmap}.
+         *
+         * @param bitmap {@link Bitmap} from which {@link BitmapImage} will be created.
+         * @return A new instance of {@link BitmapImage}.
+         */
+        @AnyThread
+        static BitmapImage createFromBitmap(@NonNull Bitmap bitmap) {
+            final int width = bitmap.getWidth();
+            final int height = bitmap.getHeight();
+            final int[] pixels = new int[width * height];
+            bitmap.getPixels(pixels, 0, width, 0, 0, width, height);
+            return new BitmapImage(pixels, width, height);
+        }
+
+        /**
+         * @return Width of this image.
+         */
+        @AnyThread
+        int getWidth() {
+            return mWidth;
+        }
+
+        /**
+         * @return Height of this image.
+         */
+        @AnyThread
+        int getHeight() {
+            return mHeight;
+        }
+
+        /**
+         * @return {@link Bitmap} that has the same pixel patterns.
+         */
+        @AnyThread
+        @NonNull
+        Bitmap toBitmap() {
+            return Bitmap.createBitmap(mPixels, mWidth, mHeight, Bitmap.Config.ARGB_8888);
+        }
+
+        /**
+         * Returns pixel color in A8R8G8B8 format.
+         *
+         * @param x X coordinate of the pixel.
+         * @param y Y coordinate of the pixel.
+         * @return Pixel color in A8R8G8B8 format.
+         */
+        @ColorInt
+        @AnyThread
+        int getPixel(int x, int y) {
+            return mPixels[y * mWidth + x];
+        }
+
+        /**
+         * Checks if the same image can be found in the specified {@link BitmapImage}
+         *
+         * @param targetImage {@link BitmapImage} to be checked.
+         * @param offsetX X offset in the {@code targetImage} used when comparing.
+         * @param offsetY Y offset in the {@code targetImage} used when comparing.
+         * @return
+         */
+        @AnyThread
+        boolean match(@NonNull BitmapImage targetImage, int offsetX, int offsetY) {
+            final int targetWidth = targetImage.getWidth();
+            final int targetHeight = targetImage.getHeight();
+
+            for (int y = 0; y < mHeight; ++y) {
+                for (int x = 0; x < mWidth; ++x) {
+                    final int targetX = x + offsetX;
+                    if (targetX < 0 || targetWidth <= targetX) {
+                        return false;
+                    }
+                    final int targetY = y + offsetY;
+                    if (targetY < 0 || targetHeight <= targetY) {
+                        return false;
+                    }
+                    if (targetImage.getPixel(targetX, targetY) != getPixel(x, y)) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+    }
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private Watermark() {
+    }
+
+    private static final BitmapImage sImage;
+    static {
+        final long[] bitImage = new long[] {
+                0b0000000000000000000000000000000000000000000000000000000000000000L,
+                0b0000000000000000000000000000000000000000000000000000000000000000L,
+                0b0000000000000000000000000000000000000000000000000000000000000000L,
+                0b0001111111111111111111111111111111111111111111111111111111111000L,
+                0b0001111111111111111111111111111111111111111111111111111111111000L,
+                0b0001110010110100010000111100001000100001111000010001011010011000L,
+                0b0001100011111011001110101101100000001101101011100110111110011000L,
+                0b0001101101001011101111000011110111011110000111101110100101111000L,
+                0b0001111100000100110001010010011111110010010100011001000001111000L,
+                0b0001110010110100010000111100001000100001111000010001011010011000L,
+                0b0001110011111011001110101101100000001101101011100110111110011000L,
+                0b0001110101001011101111000011110111011110000111101110100101011000L,
+                0b0001100100000100110001010010011111110010010100011001000001011000L,
+                0b0001101110110100010000111100001000100001111000010001011011111000L,
+                0b0001111111111011001110101101100000001101101011100110111111111000L,
+                0b0001110011001011101111000011110111011110000111101110100110011000L,
+                0b0001100001000100110001010010011111110010010100011001000100011000L,
+                0b0001101101010100010000111100001000100001111000010001010101111000L,
+                0b0001111110011011001110101101100000001101101011100110110011111000L,
+                0b0001110010111011101111000011110111011110000111101110111010011000L,
+                0b0001100001111100110001010010011111110010010100011001111100011000L,
+                0b0001101101001000010000111100001000100001111000010000100101111000L,
+                0b0001111110000101001110101101100000001101101011100101000011111000L,
+                0b0001110010110100101111000011110111011110000111101001011010011000L,
+                0b0001100001111011110001010010011111110010010100011110111100011000L,
+                0b0001101101001011100000111100001000100001111000001110100101111000L,
+                0b0001111110000100010110101101100000001101101011010001000011111000L,
+                0b0001110010110100010011000011110111011110000110010001011010011000L,
+                0b0001100001111011101111010010011111110010010111101110111100011000L,
+                0b0001101101001011101111011100001000100001110111101110100101111000L,
+                0b0001111110000100010001011101100000001101110100010001000011111000L,
+                0b0001110010110100010000111101110111011101111000010001011010011000L,
+                0b0001100001111011101110100101100010001101001011101110111100011000L,
+                0b0001101101001011101111000011110111011110000111101110100101111000L,
+                0b0001111110000100010001011010011101110010110100010001000011111000L,
+                0b0001110010110100010000111100001000100001111000010001011010011000L,
+                0b0001100001111011101110100101100010001101001011101110111100011000L,
+                0b0001101101001011101111000011110111011110000111101110100101111000L,
+                0b0001111110000100010001011010011101110010110100010001000011111000L,
+                0b0001110010110100010000111100001000100001111000010001011010011000L,
+                0b0001100001111011101110100101100010001101001011101110111100011000L,
+                0b0001101101001011101111000011110111011110000111101110100101111000L,
+                0b0001111110000100010001011010011101110010110100010001000011111000L,
+                0b0001110010110100010000111100001000100001111000010001011010011000L,
+                0b0001100001111011101110100101100010001101001011101110111100011000L,
+                0b0001101101001011101111000011110111011110000111101110100101111000L,
+                0b0001111110000100010001011010011101110010110100010001000011111000L,
+                0b0001110010110100010000111100001000100001111000010001011010011000L,
+                0b0001100001111011101110100101100010001101001011101110111100011000L,
+                0b0001101101001011101111000011110111011110000111101110100101111000L,
+                0b0001111110000100010001011010011101110010110100010001000011111000L,
+                0b0001110010110100010000111100001000100001111000010001011010011000L,
+                0b0001100001111011101110100101100010001101001011101110111100011000L,
+                0b0001101101001011101111000011110111011110000111101110100101111000L,
+                0b0001111110000100010001011010011101110010110100010001000011111000L,
+                0b0001110010110100010000111100001000100001111000010001011010011000L,
+                0b0001100001111011101110100101100010001101001011101110111100011000L,
+                0b0001101101001011101111000011110111011110000111101110100101111000L,
+                0b0001111110000100010001011010011101110010110100010001000011111000L,
+                0b0001111111111111111111111111111111111111111111111111111111111000L,
+                0b0001111111111111111111111111111111111111111111111111111111111000L,
+                0b0000000000000000000000000000000000000000000000000000000000000000L,
+                0b0000000000000000000000000000000000000000000000000000000000000000L,
+                0b0000000000000000000000000000000000000000000000000000000000000000L,
+        };
+        final int size = 64;
+        final long mask = 0b1000000000000000000000000000000000000000000000000000000000000000L;
+        final int[] pixels = new int[size * size];
+        for (int y = 0; y < size; ++y) {
+            for (int x = 0; x < size; ++x) {
+                pixels[y * size + x] =
+                        ((bitImage[y] << x) & mask) == mask ? 0xffffffff : 0xff000000;
+            }
+        }
+        sImage = new BitmapImage(pixels, size, size);
+    }
+
+    /**
+     * @return {@link Bitmap} object of the unique image.
+     */
+    public static Bitmap create() {
+        return sImage.toBitmap();
+    }
+
+    /**
+     * Check the unique image can be found in the specified {@link Bitmap}.
+     *
+     * @param bitmap {@link Bitmap} to be checked.
+     * @return {@code true} if the corresponding unique image is found in the {@code bitmap}.
+     */
+    public static boolean detect(@NonNull Bitmap bitmap) {
+        final BitmapImage targetImage = BitmapImage.createFromBitmap(bitmap);
+        // Search from the bottom line with an assumption that the IME is shown at the bottom.
+        for (int offsetY = targetImage.getHeight() - 1; offsetY >= 0; --offsetY) {
+            for (int offsetX = 0; offsetX < targetImage.getWidth(); ++offsetX) {
+                if (sImage.match(targetImage, offsetX, offsetY)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
index d783104..0492189 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/EditorInfoTest.java
@@ -324,7 +324,6 @@
     }
 
     private static CharSequence nullToEmpty(CharSequence source) {
-        return (source == null) ?
-                new SpannableStringBuilder("") : source;
+        return (source == null) ? new SpannableStringBuilder("") : source;
     }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
index 653507d..9e3b908 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/FocusHandlingTest.java
@@ -18,7 +18,10 @@
 
 import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
+import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible;
 import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync;
+import static android.widget.PopupWindow.INPUT_METHOD_NEEDED;
 import static android.widget.PopupWindow.INPUT_METHOD_NOT_NEEDED;
 
 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
@@ -116,7 +119,7 @@
         editText.post(() -> {
             final ViewTreeObserver observerForEditText = editText.getViewTreeObserver();
             observerForEditText.addOnWindowFocusChangeListener((hasFocus) ->
-                outEditHasWindowFocusRef.set(editText.hasWindowFocus()));
+                    outEditHasWindowFocusRef.set(editText.hasWindowFocus()));
             outEditHasWindowFocusRef.set(editText.hasWindowFocus());
         });
         return editText;
@@ -364,6 +367,64 @@
     }
 
     /**
+     * Test case for Bug 152698568.
+     *
+     * <p>This test ensures that showing a non-focusable {@link PopupWindow} with
+     * {@link PopupWindow#INPUT_METHOD_NEEDED} does not affect IME visibility.</p>
+     */
+    @Test
+    public void testNonFocusablePopupWindowDoesNotAffectImeVisibility() throws Exception {
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+        try (MockImeSession imeSession = MockImeSession.create(
+                InstrumentationRegistry.getContext(),
+                instrumentation.getUiAutomation(),
+                new ImeSettings.Builder())) {
+            final ImeEventStream stream = imeSession.openEventStream();
+
+            final String marker = getTestMarker();
+            final EditText editText = launchTestActivity(marker);
+
+            // Wait until the MockIme is connected to the edit text.
+            runOnMainSync(editText::requestFocus);
+            expectBindInput(stream, Process.myPid(), TIMEOUT);
+            expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
+
+            expectImeInvisible(TIMEOUT);
+
+            // Show IME.
+            runOnMainSync(() -> editText.getContext().getSystemService(InputMethodManager.class)
+                    .showSoftInput(editText, 0));
+
+            expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectImeVisible(TIMEOUT);
+
+            // Create a non-focusable PopupWindow with INPUT_METHOD_NEEDED.
+            final PopupWindow popupWindow = TestUtils.getOnMainSync(() -> {
+                final Context context = instrumentation.getTargetContext();
+                final PopupWindow popup = new PopupWindow(context);
+                popup.setFocusable(false);
+                popup.setInputMethodMode(INPUT_METHOD_NEEDED);
+                final TextView textView = new TextView(context);
+                textView.setText("Popup");
+                popup.setContentView(textView);
+                return popup;
+            });
+
+            // Show the popup window.
+            runOnMainSync(() -> popupWindow.showAsDropDown(editText));
+            instrumentation.waitForIdleSync();
+
+            // Make sure that the IME remains to be visible.
+            expectImeVisible(TIMEOUT);
+
+            SystemClock.sleep(NOT_EXPECT_TIMEOUT);
+
+            // Make sure that the IME remains to be visible.
+            expectImeVisible(TIMEOUT);
+        }
+    }
+
+    /**
      * Test case for Bug 70629102.
      *
      * {@link InputMethodManager#restartInput(View)} can be called even when another process
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java
index 0c05bf3..b27b5a6 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/ImeInsetsControllerTest.java
@@ -18,8 +18,10 @@
 
 import static android.view.WindowInsets.CONSUMED;
 import static android.view.WindowInsets.Type.ime;
+
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
 
@@ -38,6 +40,11 @@
 import android.widget.EditText;
 import android.widget.LinearLayout;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.test.filters.MediumTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
 import com.android.cts.mockime.ImeEventStream;
 import com.android.cts.mockime.ImeSettings;
 import com.android.cts.mockime.MockImeSession;
@@ -49,11 +56,6 @@
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.MediumTest;
-import androidx.test.platform.app.InstrumentationRegistry;
-
 @MediumTest
 public class ImeInsetsControllerTest extends EndToEndImeTestBase {
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
@@ -121,13 +123,13 @@
                 editText.getWindowInsetsController().addOnControllableInsetsChangedListener(
                         new OnControllableInsetsChangedListener() {
 
-                            boolean controlling = false;
+                            boolean mControlling = false;
 
                             @Override
                             public void onControllableInsetsChanged(
                                     @NonNull WindowInsetsController controller, int typeMask) {
-                                if ((typeMask & ime()) != 0 && !controlling) {
-                                    controlling = true;
+                                if ((typeMask & ime()) != 0 && !mControlling) {
+                                    mControlling = true;
                                     controller.controlWindowInsetsAnimation(ime(), -1, null, null,
                                             createControlListener(animController, controlLatch));
                                 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionInfoTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionInfoTest.java
index 8cd7e2f..0ce65c6 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionInfoTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionInfoTest.java
@@ -23,8 +23,8 @@
 
 import android.os.Parcel;
 import android.util.Size;
-import android.widget.inline.InlinePresentationSpec;
 import android.view.inputmethod.InlineSuggestionInfo;
+import android.widget.inline.InlinePresentationSpec;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionTest.java
index 9310963..3a3d15e 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionTest.java
@@ -25,10 +25,10 @@
 import android.os.AsyncTask;
 import android.os.Parcel;
 import android.util.Size;
-import android.widget.inline.InlineContentView;
-import android.widget.inline.InlinePresentationSpec;
 import android.view.inputmethod.InlineSuggestion;
 import android.view.inputmethod.InlineSuggestionInfo;
+import android.widget.inline.InlineContentView;
+import android.widget.inline.InlinePresentationSpec;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionsRequestTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionsRequestTest.java
index 7b582d7..68e91c1 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionsRequestTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InlineSuggestionsRequestTest.java
@@ -108,14 +108,17 @@
     @Test
     public void testInlineSuggestionsRequestValues() {
         final int suggestionCount = 3;
-        ArrayList<InlinePresentationSpec> presentationSpecs = new ArrayList<>();
-        LocaleList localeList = LocaleList.forLanguageTags("fa-IR");
-        Bundle extra = new Bundle();
+        final LocaleList localeList = LocaleList.forLanguageTags("fa-IR");
+        final Bundle extra = new Bundle();
         extra.putString("key", "value");
-        Bundle style = new Bundle();
+        final Bundle style = new Bundle();
         style.putString("style", "value");
-        InlineSuggestionsRequest request =
-                new InlineSuggestionsRequest.Builder(presentationSpecs)
+        final ArrayList<InlinePresentationSpec> presentationSpecs = new ArrayList<>();
+        presentationSpecs.add(new InlinePresentationSpec.Builder(new Size(100, 100),
+                new Size(400, 100)).setStyle(style).build());
+        final InlineSuggestionsRequest request =
+                new InlineSuggestionsRequest.Builder(new ArrayList<InlinePresentationSpec>())
+                        .setInlinePresentationSpecs(presentationSpecs)
                         .addInlinePresentationSpecs(
                                 new InlinePresentationSpec.Builder(new Size(100, 100),
                                         new Size(400, 100)).setStyle(style).build())
@@ -125,7 +128,7 @@
 
         assertThat(request.getMaxSuggestionCount()).isEqualTo(suggestionCount);
         assertThat(request.getInlinePresentationSpecs()).isNotNull();
-        assertThat(request.getInlinePresentationSpecs().size()).isEqualTo(1);
+        assertThat(request.getInlinePresentationSpecs().size()).isEqualTo(2);
         assertThat(request.getInlinePresentationSpecs().get(0).getStyle()).isEqualTo(style);
         assertThat(request.getExtras()).isEqualTo(extra);
         assertThat(request.getSupportedLocales()).isEqualTo(localeList);
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
index 2db7ee1..e27549c 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
@@ -18,15 +18,17 @@
 
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
+import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible;
 import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
 import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync;
 import static android.view.inputmethod.cts.util.TestUtils.waitOnMainUntil;
 
 import static com.android.cts.mockime.ImeEventStreamTestUtils.EventFilterMode.CHECK_EXIT_EVENT_ONLY;
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
 
 import static org.junit.Assert.assertEquals;
@@ -203,11 +205,15 @@
             createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
             expectEvent(stream, event -> "onStartInputView".equals(event.getEventName()), TIMEOUT);
 
+            expectImeVisible(TIMEOUT);
+
             imeSession.callRequestHideSelf(0);
             expectEvent(stream, event -> "hideSoftInput".equals(event.getEventName()), TIMEOUT);
             expectEvent(stream, event -> "onFinishInputView".equals(event.getEventName()), TIMEOUT);
             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
                     View.GONE, TIMEOUT);
+
+            expectImeInvisible(TIMEOUT);
         }
     }
 
@@ -223,11 +229,15 @@
             notExpectEvent(
                     stream, event -> "onStartInputView".equals(event.getEventName()), TIMEOUT);
 
+            expectImeInvisible(TIMEOUT);
+
             imeSession.callRequestShowSelf(0);
             expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()), TIMEOUT);
             expectEvent(stream, event -> "onStartInputView".equals(event.getEventName()), TIMEOUT);
             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
                     View.VISIBLE, TIMEOUT);
+
+            expectImeVisible(TIMEOUT);
         }
     }
 
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
index 48e8ca3..e4d6319 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/KeyboardVisibilityControlTest.java
@@ -16,11 +16,13 @@
 
 package android.view.inputmethod.cts;
 
+import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
+import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible;
 import static android.view.inputmethod.cts.util.TestUtils.getOnMainSync;
 import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync;
 
-import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEventWithKeyValue;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.notExpectEvent;
 
 import static org.junit.Assert.assertFalse;
@@ -152,6 +154,7 @@
 
             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
             notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectImeInvisible(TIMEOUT);
 
             assertTrue("isActive() must return true if the View has IME focus",
                     getOnMainSync(() -> imm.isActive(editText)));
@@ -164,6 +167,7 @@
             expectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
                     View.VISIBLE, TIMEOUT);
+            expectImeVisible(TIMEOUT);
 
             // Test hideSoftInputFromWindow() flow
             assertTrue("hideSoftInputFromWindow must success if the View has IME focus",
@@ -173,6 +177,7 @@
             expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
             expectEventWithKeyValue(stream, "onWindowVisibilityChanged", "visible",
                     View.GONE, TIMEOUT);
+            expectImeInvisible(TIMEOUT);
         }
     }
 
@@ -195,6 +200,7 @@
 
             expectEvent(stream, editorMatcher("onStartInput", focusedMarker), TIMEOUT);
 
+            expectImeInvisible(TIMEOUT);
             assertFalse("isActive() must return false if the View does not have IME focus",
                     getOnMainSync(() -> imm.isActive(nonFocusedEditText)));
             assertFalse("showSoftInput must fail if the View does not have IME focus",
@@ -205,6 +211,7 @@
                     getOnMainSync(() -> imm.hideSoftInputFromWindow(
                             nonFocusedEditText.getWindowToken(), 0)));
             notExpectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
+            expectImeInvisible(TIMEOUT);
         }
     }
 
@@ -224,18 +231,21 @@
 
             expectEvent(stream, editorMatcher("onStartInput", marker), TIMEOUT);
             notExpectEvent(stream, editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectImeInvisible(TIMEOUT);
 
             // Test toggleSoftInputFromWindow() flow
             runOnMainSync(() -> imm.toggleSoftInputFromWindow(editText.getWindowToken(), 0, 0));
 
             expectEvent(stream.copy(), showSoftInputMatcher(InputMethod.SHOW_EXPLICIT), TIMEOUT);
             expectEvent(stream.copy(), editorMatcher("onStartInputView", marker), TIMEOUT);
+            expectImeVisible(TIMEOUT);
 
             // Calling toggleSoftInputFromWindow() must hide the IME.
             runOnMainSync(() -> imm.toggleSoftInputFromWindow(editText.getWindowToken(), 0, 0));
 
             expectEvent(stream, hideSoftInputMatcher(), TIMEOUT);
             expectEvent(stream, onFinishInputViewMatcher(false), TIMEOUT);
+            expectImeInvisible(TIMEOUT);
         }
     }
 }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java
index f9ea069..a549874 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/SearchViewTest.java
@@ -16,6 +16,10 @@
 
 package android.view.inputmethod.cts;
 
+import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeInvisible;
+import static android.view.inputmethod.cts.util.InputMethodVisibilityVerifier.expectImeVisible;
+import static android.view.inputmethod.cts.util.TestUtils.runOnMainSync;
+
 import static com.android.cts.mockime.ImeEventStreamTestUtils.EventFilterMode.CHECK_EXIT_EVENT_ONLY;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectBindInput;
 import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
@@ -137,10 +141,13 @@
                             && !event.getExitState().hasDummyInputConnection(),
                     CHECK_EXIT_EVENT_ONLY, TIMEOUT);
 
+            expectImeVisible(TIMEOUT);
+
             // Make sure that "setQuery" triggers "hideSoftInput" in the IME side.
-            InstrumentationRegistry.getInstrumentation().runOnMainSync(
-                    () -> searchView.setQuery("test", true /* submit */));
+            runOnMainSync(() -> searchView.setQuery("test", true /* submit */));
             expectEvent(stream, event -> "hideSoftInput".equals(event.getEventName()), TIMEOUT);
+
+            expectImeInvisible(TIMEOUT);
         }
     }
 
@@ -157,9 +164,8 @@
             // Expect input to bind since checkInputConnectionProxy() returns true.
             expectBindInput(stream, Process.myPid(), TIMEOUT);
 
-            InstrumentationRegistry.getInstrumentation()
-                    .getTargetContext().getSystemService(InputMethodManager.class)
-                    .showSoftInput(searchView, 0);
+            runOnMainSync(() -> searchView.getContext().getSystemService(InputMethodManager.class)
+                    .showSoftInput(searchView, 0));
 
             // Wait until "showSoftInput" gets called on searchView's inner editor
             // (SearchAutoComplete) with real InputConnection.
@@ -167,6 +173,8 @@
                     "showSoftInput".equals(event.getEventName())
                             && !event.getExitState().hasDummyInputConnection(),
                     CHECK_EXIT_EVENT_ONLY, TIMEOUT);
+
+            expectImeVisible(TIMEOUT);
         }
     }
 
@@ -193,6 +201,8 @@
                                     && !event.getExitState().hasDummyInputConnection(),
                     CHECK_EXIT_EVENT_ONLY, TIMEOUT);
 
+            expectImeVisible(TIMEOUT);
+
             notExpectEvent(stream, event -> "hideSoftInput".equals(event.getEventName()),
                     NOT_EXPECT_TIMEOUT);
         }
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/DisableScreenDozeRule.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/DisableScreenDozeRule.java
index e6e8e30..206fdc8 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/DisableScreenDozeRule.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/DisableScreenDozeRule.java
@@ -58,12 +58,14 @@
             @Override
             public void evaluate() throws Throwable {
                 final Map<String, String> initialValues = new HashMap<>();
-                Arrays.stream(DOZE_SETTINGS).forEach(k -> initialValues.put(k, getSecureSetting(k)));
+                Arrays.stream(DOZE_SETTINGS).forEach(
+                        k -> initialValues.put(k, getSecureSetting(k)));
                 try {
                     Arrays.stream(DOZE_SETTINGS).forEach(k -> putSecureSetting(k, "0"));
                     base.evaluate();
                 } finally {
-                    Arrays.stream(DOZE_SETTINGS).forEach(k -> putSecureSetting(k, initialValues.get(k)));
+                    Arrays.stream(DOZE_SETTINGS).forEach(
+                            k -> putSecureSetting(k, initialValues.get(k)));
                 }
             }
         };
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java
new file mode 100644
index 0000000..ebdf0a8
--- /dev/null
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/InputMethodVisibilityVerifier.java
@@ -0,0 +1,111 @@
+/*
+ * 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.view.inputmethod.cts.util;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Instrumentation;
+import android.app.UiAutomation;
+import android.os.SystemClock;
+
+import androidx.annotation.NonNull;
+import androidx.test.InstrumentationRegistry;
+
+import com.android.cts.mockime.ImeSettings;
+import com.android.cts.mockime.Watermark;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
+
+/**
+ * Provides utility methods to test whether test IMEs are visible to the user or not.
+ */
+public final class InputMethodVisibilityVerifier {
+
+    private static final long SCREENSHOT_DELAY = 100;  // msec
+    private static final long SCREENSHOT_TIME_SLICE = 500;  // msec
+
+    /**
+     * Not intended to be instantiated.
+     */
+    private InputMethodVisibilityVerifier() {
+    }
+
+    @NonNull
+    private static boolean containsWatermark(@NonNull UiAutomation uiAutomation) {
+        return Watermark.detect(uiAutomation.takeScreenshot());
+    }
+
+    @NonNull
+    private static boolean notContainsWatermark(@NonNull UiAutomation uiAutomation) {
+        return !Watermark.detect(uiAutomation.takeScreenshot());
+    }
+
+    private static boolean waitUntil(long timeout, @NonNull Predicate<UiAutomation> condition) {
+        final long startTime = SystemClock.elapsedRealtime();
+        SystemClock.sleep(SCREENSHOT_DELAY);
+
+        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+        // Wait until the main thread becomes idle.
+        final CountDownLatch latch = new CountDownLatch(1);
+        instrumentation.waitForIdle(latch::countDown);
+        try {
+            if (!latch.await(timeout, TimeUnit.MILLISECONDS)) {
+                return false;
+            }
+        } catch (InterruptedException e) {
+        }
+
+        final UiAutomation uiAutomation = instrumentation.getUiAutomation();
+        if (condition.test(uiAutomation)) {
+            return true;
+        }
+        while ((SystemClock.elapsedRealtime() - startTime) < timeout) {
+            SystemClock.sleep(SCREENSHOT_TIME_SLICE);
+            if (condition.test(uiAutomation)) {
+                return true;
+            }
+        }
+        return condition.test(uiAutomation);
+    }
+
+    /**
+     * Asserts that {@link com.android.cts.mockime.MockIme} is visible to the user.
+     *
+     * <p>This never succeeds when {@link ImeSettings.Builder#setWatermarkEnabled(boolean)} is
+     * explicitly called with {@code false}.</p>
+     *
+     * @param timeout timeout in milliseconds.
+     */
+    public static void expectImeVisible(long timeout) {
+        assertTrue(waitUntil(timeout, InputMethodVisibilityVerifier::containsWatermark));
+    }
+
+    /**
+     * Asserts that {@link com.android.cts.mockime.MockIme} is not visible to the user.
+     *
+     * <p>This always succeeds when {@link ImeSettings.Builder#setWatermarkEnabled(boolean)} is
+     * explicitly called with {@code false}.</p>
+     *
+     * @param timeout timeout in milliseconds.
+     */
+    public static void expectImeInvisible(long timeout) {
+        assertTrue(waitUntil(timeout, InputMethodVisibilityVerifier::notContainsWatermark));
+    }
+}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
index c3e889d..40eb468 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/TestUtils.java
@@ -19,7 +19,6 @@
 import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
 
 import android.app.Instrumentation;
-import android.app.KeyguardManager;
 import android.content.Context;
 import android.os.PowerManager;
 
@@ -121,7 +120,7 @@
         final PowerManager pm = context.getSystemService(PowerManager.class);
         runShellCommand("input keyevent KEYCODE_WAKEUP");
         CommonTestUtils.waitUntil("Device does not wake up after 5 seconds", 5,
-                () -> pm!= null && pm.isInteractive());
+                () -> pm != null && pm.isInteractive());
     }
 
     /**
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/UnlockScreenRule.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/UnlockScreenRule.java
index 2763095..fd2d98d 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/UnlockScreenRule.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/UnlockScreenRule.java
@@ -40,7 +40,7 @@
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                if (pm!= null && !pm.isInteractive()) {
+                if (pm != null && !pm.isInteractive()) {
                     TestUtils.turnScreenOn();
                 }
                 if (km != null && km.isKeyguardLocked()) {
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java
index 427cf11..b817a2c 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/util/WindowFocusHandleService.java
@@ -23,8 +23,6 @@
 import static android.view.WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
-import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
 import android.app.Service;
 import android.content.Context;
 import android.content.Intent;
@@ -96,7 +94,7 @@
             @Override
             public void onWindowFocusChanged(boolean hasWindowFocus) {
                 if (Log.isLoggable(TAG, Log.VERBOSE)) {
-                    Log.v(TAG,"onWindowFocusChanged for view=" + this
+                    Log.v(TAG, "onWindowFocusChanged for view=" + this
                             + ", hasWindowfocus: " + hasWindowFocus);
                 }
             }
diff --git a/tests/media/Android.bp b/tests/media/Android.bp
index 0e2b012..27232c8 100644
--- a/tests/media/Android.bp
+++ b/tests/media/Android.bp
@@ -28,6 +28,7 @@
     jni_libs: [
         "libctsmediav2muxer_jni",
         "libctsmediav2extractor_jni",
+        "libctsmediav2codec_jni",
     ],
     srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
diff --git a/tests/media/OWNERS b/tests/media/OWNERS
index fad6041..4f734c8 100644
--- a/tests/media/OWNERS
+++ b/tests/media/OWNERS
@@ -1,2 +1,11 @@
 # Bug component: 1344
-include ../tests/media/OWNERS
+# include media developers and framework video team
+include platform/frameworks/av:/media/OWNERS
+chz@google.com
+dichenzhang@google.com
+essick@google.com
+gokrishnan@google.com
+lajos@google.com
+marcone@google.com
+pawin@google.com
+wonsik@google.com
diff --git a/tests/media/jni/Android.bp b/tests/media/jni/Android.bp
index b8e4314..0db7dad 100644
--- a/tests/media/jni/Android.bp
+++ b/tests/media/jni/Android.bp
@@ -55,3 +55,27 @@
     ],
     gtest: false,
 }
+
+cc_test_library {
+    name: "libctsmediav2codec_jni",
+    srcs: [
+        "NativeMediaConstants.cpp",
+        "NativeCodecDecoderTest.cpp",
+        "NativeCodecEncoderTest.cpp",
+        "NativeCodecTestBase.cpp",
+        "NativeCodecUnitTest.cpp",
+    ],
+    shared_libs: [
+        "libmediandk",
+        "liblog",
+    ],
+    include_dirs: [
+        "frameworks/av/media/ndk/include/media",
+    ],
+    stl: "libc++_static",
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+    gtest: false,
+}
diff --git a/tests/media/jni/NativeCodecDecoderTest.cpp b/tests/media/jni/NativeCodecDecoderTest.cpp
new file mode 100644
index 0000000..fb9c66d
--- /dev/null
+++ b/tests/media/jni/NativeCodecDecoderTest.cpp
@@ -0,0 +1,715 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeCodecDecoderTest"
+#include <log/log.h>
+
+#include <NdkMediaExtractor.h>
+#include <jni.h>
+#include <sys/stat.h>
+
+#include <array>
+#include <fstream>
+
+#include "NativeCodecTestBase.h"
+#include "NativeMediaConstants.h"
+
+class CodecDecoderTest final : public CodecTestBase {
+  private:
+    uint8_t* mRefData;
+    size_t mRefLength;
+    AMediaExtractor* mExtractor;
+    AMediaFormat* mInpDecFormat;
+    AMediaFormat* mInpDecDupFormat;
+    std::vector<std::pair<void*, size_t>> mCsdBuffers;
+    int mCurrCsdIdx;
+
+    void setUpAudioReference(const char* refFile);
+    void deleteReference();
+    bool setUpExtractor(const char* srcFile);
+    void deleteExtractor();
+    bool configureCodec(AMediaFormat* format, bool isAsync, bool signalEOSWithLastFrame,
+                        bool isEncoder) override;
+    bool enqueueInput(size_t bufferIndex) override;
+    bool dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* bufferInfo) override;
+    bool queueCodecConfig();
+    bool enqueueCodecConfig(int32_t bufferIndex);
+    bool decodeToMemory(const char* decoder, AMediaFormat* format, int frameLimit,
+                        OutputManager* ref, int64_t pts, SeekMode mode);
+
+  public:
+    explicit CodecDecoderTest(const char* mime);
+    ~CodecDecoderTest();
+
+    bool testSimpleDecode(const char* decoder, const char* testFile, const char* refFile,
+                          float rmsError);
+    bool testFlush(const char* decoder, const char* testFile);
+    bool testOnlyEos(const char* decoder, const char* testFile);
+    bool testSimpleDecodeQueueCSD(const char* decoder, const char* testFile);
+};
+
+CodecDecoderTest::CodecDecoderTest(const char* mime)
+    : CodecTestBase(mime),
+      mRefData(nullptr),
+      mRefLength(0),
+      mExtractor(nullptr),
+      mInpDecFormat(nullptr),
+      mInpDecDupFormat(nullptr),
+      mCurrCsdIdx(0) {}
+
+CodecDecoderTest::~CodecDecoderTest() {
+    deleteReference();
+    deleteExtractor();
+}
+
+void CodecDecoderTest::setUpAudioReference(const char* refFile) {
+    FILE* fp = fopen(refFile, "rbe");
+    struct stat buf {};
+    if (fp && !fstat(fileno(fp), &buf)) {
+        deleteReference();
+        mRefLength = buf.st_size;
+        mRefData = new uint8_t[mRefLength];
+        fread(mRefData, sizeof(uint8_t), mRefLength, fp);
+    } else {
+        ALOGE("unable to open input file %s", refFile);
+    }
+    if (fp) fclose(fp);
+}
+
+void CodecDecoderTest::deleteReference() {
+    if (mRefData) {
+        delete[] mRefData;
+        mRefData = nullptr;
+    }
+    mRefLength = 0;
+}
+
+bool CodecDecoderTest::setUpExtractor(const char* srcFile) {
+    FILE* fp = fopen(srcFile, "rbe");
+    struct stat buf {};
+    if (fp && !fstat(fileno(fp), &buf)) {
+        deleteExtractor();
+        mExtractor = AMediaExtractor_new();
+        media_status_t res =
+                AMediaExtractor_setDataSourceFd(mExtractor, fileno(fp), 0, buf.st_size);
+        if (res != AMEDIA_OK) {
+            deleteExtractor();
+        } else {
+            for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(mExtractor);
+                 trackID++) {
+                AMediaFormat* currFormat = AMediaExtractor_getTrackFormat(mExtractor, trackID);
+                const char* mime = nullptr;
+                AMediaFormat_getString(currFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+                if (mime && strcmp(mMime, mime) == 0) {
+                    AMediaExtractor_selectTrack(mExtractor, trackID);
+                    if (!mIsAudio) {
+                        AMediaFormat_setInt32(currFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT,
+                                              COLOR_FormatYUV420Flexible);
+                    }
+                    mInpDecFormat = currFormat;
+                    break;
+                }
+                AMediaFormat_delete(currFormat);
+            }
+        }
+    }
+    if (fp) fclose(fp);
+    return mInpDecFormat != nullptr;
+}
+
+void CodecDecoderTest::deleteExtractor() {
+    if (mExtractor) {
+        AMediaExtractor_delete(mExtractor);
+        mExtractor = nullptr;
+    }
+    if (mInpDecFormat) {
+        AMediaFormat_delete(mInpDecFormat);
+        mInpDecFormat = nullptr;
+    }
+    if (mInpDecDupFormat) {
+        AMediaFormat_delete(mInpDecDupFormat);
+        mInpDecDupFormat = nullptr;
+    }
+}
+
+bool CodecDecoderTest::configureCodec(AMediaFormat* format, bool isAsync,
+                                      bool signalEOSWithLastFrame, bool isEncoder) {
+    resetContext(isAsync, signalEOSWithLastFrame);
+    CHECK_STATUS(mAsyncHandle.setCallBack(mCodec, isAsync),
+                 "AMediaCodec_setAsyncNotifyCallback failed");
+    CHECK_STATUS(AMediaCodec_configure(mCodec, format, nullptr, nullptr,
+                                       isEncoder ? AMEDIACODEC_CONFIGURE_FLAG_ENCODE : 0),
+                 "AMediaCodec_configure failed");
+    return true;
+}
+
+bool CodecDecoderTest::enqueueCodecConfig(int32_t bufferIndex) {
+    size_t bufSize;
+    uint8_t* buf = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &bufSize);
+    if (buf == nullptr) {
+        ALOGE("AMediaCodec_getInputBuffer failed");
+        return false;
+    }
+    void* csdBuffer = mCsdBuffers[mCurrCsdIdx].first;
+    size_t csdSize = mCsdBuffers[mCurrCsdIdx].second;
+    if (bufSize < csdSize) {
+        ALOGE("csd exceeds input buffer size, csdSize: %zu bufSize: %zu", csdSize, bufSize);
+        return false;
+    }
+    memcpy((void*)buf, csdBuffer, csdSize);
+    uint32_t flags = AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG;
+    CHECK_STATUS(AMediaCodec_queueInputBuffer(mCodec, bufferIndex, 0, csdSize, 0, flags),
+                 "AMediaCodec_queueInputBuffer failed");
+    return !hasSeenError();
+}
+
+bool CodecDecoderTest::enqueueInput(size_t bufferIndex) {
+    if (AMediaExtractor_getSampleSize(mExtractor) < 0) {
+        return enqueueEOS(bufferIndex);
+    } else {
+        uint32_t flags = 0;
+        size_t bufSize;
+        uint8_t* buf = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &bufSize);
+        if (buf == nullptr) {
+            ALOGE("AMediaCodec_getInputBuffer failed");
+            return false;
+        }
+        ssize_t size = AMediaExtractor_getSampleSize(mExtractor);
+        int64_t pts = AMediaExtractor_getSampleTime(mExtractor);
+        if (size > bufSize) {
+            ALOGE("extractor sample size exceeds codec input buffer size %zu %zu", size, bufSize);
+            return false;
+        }
+        if (size != AMediaExtractor_readSampleData(mExtractor, buf, bufSize)) {
+            ALOGE("AMediaExtractor_readSampleData failed");
+            return false;
+        }
+        if (!AMediaExtractor_advance(mExtractor) && mSignalEOSWithLastFrame) {
+            flags |= AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
+            mSawInputEOS = true;
+        }
+        CHECK_STATUS(AMediaCodec_queueInputBuffer(mCodec, bufferIndex, 0, size, pts, flags),
+                     "AMediaCodec_queueInputBuffer failed");
+        ALOGV("input: id: %zu  size: %zu  pts: %d  flags: %d", bufferIndex, size, (int)pts, flags);
+        if (size > 0) {
+            mOutputBuff->saveInPTS(pts);
+            mInputCount++;
+        }
+    }
+    return !hasSeenError();
+}
+
+bool CodecDecoderTest::dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* info) {
+    if ((info->flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) != 0) {
+        mSawOutputEOS = true;
+    }
+    if (info->size > 0 && (info->flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) == 0) {
+        if (mSaveToMem) {
+            size_t buffSize;
+            uint8_t* buf = AMediaCodec_getOutputBuffer(mCodec, bufferIndex, &buffSize);
+            if (mIsAudio) mOutputBuff->saveToMemory(buf, info);
+            else mOutputBuff->saveChecksum(buf, info);
+        }
+        mOutputBuff->saveOutPTS(info->presentationTimeUs);
+        mOutputCount++;
+    }
+    ALOGV("output: id: %zu  size: %d  pts: %d  flags: %d", bufferIndex, info->size,
+          (int)info->presentationTimeUs, info->flags);
+    CHECK_STATUS(AMediaCodec_releaseOutputBuffer(mCodec, bufferIndex, false),
+                 "AMediaCodec_releaseOutputBuffer failed");
+    return !hasSeenError();
+}
+
+bool CodecDecoderTest::queueCodecConfig() {
+    bool isOk = true;
+    if (mIsCodecInAsyncMode) {
+        for (mCurrCsdIdx = 0; !hasSeenError() && isOk && mCurrCsdIdx < mCsdBuffers.size();
+             mCurrCsdIdx++) {
+            callbackObject element = mAsyncHandle.getInput();
+            if (element.bufferIndex >= 0) {
+                isOk = enqueueCodecConfig(element.bufferIndex);
+            }
+        }
+    } else {
+        int bufferIndex;
+        for (mCurrCsdIdx = 0; isOk && mCurrCsdIdx < mCsdBuffers.size(); mCurrCsdIdx++) {
+            bufferIndex = AMediaCodec_dequeueInputBuffer(mCodec, -1);
+            if (bufferIndex >= 0) {
+                isOk = enqueueCodecConfig(bufferIndex);
+            } else {
+                ALOGE("unexpected return value from *_dequeueInputBuffer: %d", (int)bufferIndex);
+                return false;
+            }
+        }
+    }
+    return !hasSeenError() && isOk;
+}
+
+bool CodecDecoderTest::decodeToMemory(const char* decoder, AMediaFormat* format, int frameLimit,
+                                      OutputManager* ref, int64_t pts, SeekMode mode) {
+    mSaveToMem = true;
+    mOutputBuff = ref;
+    AMediaExtractor_seekTo(mExtractor, pts, mode);
+    mCodec = AMediaCodec_createCodecByName(decoder);
+    if (!mCodec) {
+        ALOGE("unable to create codec %s", decoder);
+        return false;
+    }
+    if (!configureCodec(format, false, true, false)) return false;
+    CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+    if (!doWork(frameLimit)) return false;
+    if (!queueEOS()) return false;
+    if (!waitForAllOutputs()) return false;
+    CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
+    mCodec = nullptr;
+    mSaveToMem = false;
+    return !hasSeenError();
+}
+
+bool CodecDecoderTest::testSimpleDecode(const char* decoder, const char* testFile,
+                                        const char* refFile, float rmsError) {
+    bool isPass = true;
+    if (!setUpExtractor(testFile)) return false;
+    mSaveToMem = true;
+    auto ref = &mRefBuff;
+    auto test = &mTestBuff;
+    const bool boolStates[]{true, false};
+    int loopCounter = 0;
+    for (auto eosType : boolStates) {
+        if (!isPass) break;
+        for (auto isAsync : boolStates) {
+            if (!isPass) break;
+            bool validateFormat = true;
+            char log[1000];
+            snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s, eos type: %s:: \n",
+                     decoder, testFile, (isAsync ? "async" : "sync"),
+                     (eosType ? "eos with last frame" : "eos separate"));
+            mOutputBuff = loopCounter == 0 ? ref : test;
+            mOutputBuff->reset();
+            AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
+            /* TODO(b/147348711) */
+            /* Instead of create and delete codec at every iteration, we would like to create
+             * once and use it for all iterations and delete before exiting */
+            mCodec = AMediaCodec_createCodecByName(decoder);
+            if (!mCodec) {
+                ALOGE("unable to create codec %s", decoder);
+                return false;
+            }
+            char* name = nullptr;
+            if (AMEDIA_OK == AMediaCodec_getName(mCodec, &name)) {
+                if (!name || strcmp(name, decoder) != 0) {
+                    ALOGE("%s error codec-name act/got: %s/%s", log, name, decoder);
+                    if (name) AMediaCodec_releaseName(mCodec, name);
+                    return false;
+                }
+            } else {
+                ALOGE("AMediaCodec_getName failed unexpectedly");
+                return false;
+            }
+            if (name) AMediaCodec_releaseName(mCodec, name);
+            if (!configureCodec(mInpDecFormat, isAsync, eosType, false)) return false;
+            AMediaFormat* decFormat = AMediaCodec_getOutputFormat(mCodec);
+            if (isFormatSimilar(mInpDecFormat, decFormat)) {
+                ALOGD("Input format is same as default for format for %s", decoder);
+                validateFormat = false;
+            }
+            AMediaFormat_delete(decFormat);
+            CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+            if (!doWork(INT32_MAX)) return false;
+            if (!queueEOS()) return false;
+            if (!waitForAllOutputs()) return false;
+            CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+            CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
+            mCodec = nullptr;
+            CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
+            CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
+            CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
+            CHECK_ERR((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
+                      isPass);
+            CHECK_ERR(loopCounter != 0 && (!ref->equals(test)), log, "output is flaky", isPass);
+            CHECK_ERR(
+                    loopCounter == 0 && mIsAudio && (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)),
+                    log, "pts is not strictly increasing", isPass);
+            CHECK_ERR(loopCounter == 0 && !mIsAudio &&
+                      (!ref->isOutPtsListIdenticalToInpPtsList(false)),
+                      log, "input pts list and output pts list are not identical", isPass);
+            if (validateFormat) {
+                if (mIsCodecInAsyncMode ? !mAsyncHandle.hasOutputFormatChanged()
+                                        : !mSignalledOutFormatChanged) {
+                    ALOGE(log, "not received format change");
+                    isPass = false;
+                } else if (!isFormatSimilar(mInpDecFormat, mIsCodecInAsyncMode
+                                                                   ? mAsyncHandle.getOutputFormat()
+                                                                   : mOutFormat)) {
+                    ALOGE(log, "configured format and output format are not similar");
+                    isPass = false;
+                }
+            }
+            loopCounter++;
+        }
+    }
+    if (mSaveToMem && refFile && rmsError >= 0) {
+        setUpAudioReference(refFile);
+        float error = ref->getRmsError(mRefData, mRefLength);
+        if (error > rmsError) {
+            isPass = false;
+            ALOGE("rms error too high for file %s, act/exp: %f/%f", testFile, error, rmsError);
+        }
+    }
+    return isPass;
+}
+
+bool CodecDecoderTest::testFlush(const char* decoder, const char* testFile) {
+    bool isPass = true;
+    if (!setUpExtractor(testFile)) return false;
+    mCsdBuffers.clear();
+    for (int i = 0;; i++) {
+        char csdName[16];
+        void* csdBuffer;
+        size_t csdSize;
+        snprintf(csdName, sizeof(csdName), "csd-%d", i);
+        if (AMediaFormat_getBuffer(mInpDecFormat, csdName, &csdBuffer, &csdSize)) {
+            mCsdBuffers.push_back(std::make_pair(csdBuffer, csdSize));
+        } else break;
+    }
+    const int64_t pts = 500000;
+    const SeekMode mode = AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC;
+    auto ref = &mRefBuff;
+    if (!decodeToMemory(decoder, mInpDecFormat, INT32_MAX, ref, pts, mode)) {
+        ALOGE("decodeToMemory failed for file: %s codec: %s", testFile, decoder);
+        return false;
+    }
+    CHECK_ERR(mIsAudio && (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)), "",
+              "pts is not strictly increasing", isPass);
+    CHECK_ERR(!mIsAudio && (!ref->isOutPtsListIdenticalToInpPtsList(false)), "",
+              "input pts list and output pts list are not identical", isPass);
+    if (!isPass) return false;
+
+    auto test = &mTestBuff;
+    mOutputBuff = test;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!isPass) break;
+        char log[1000];
+        snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s:: \n", decoder, testFile,
+                 (isAsync ? "async" : "sync"));
+        /* TODO(b/147348711) */
+        /* Instead of create and delete codec at every iteration, we would like to create
+         * once and use it for all iterations and delete before exiting */
+        mCodec = AMediaCodec_createCodecByName(decoder);
+        if (!mCodec) {
+            ALOGE("unable to create codec %s", decoder);
+            return false;
+        }
+        AMediaExtractor_seekTo(mExtractor, 0, mode);
+        if (!configureCodec(mInpDecFormat, isAsync, true, false)) return false;
+        AMediaFormat* defFormat = AMediaCodec_getOutputFormat(mCodec);
+        bool validateFormat = true;
+        if (isFormatSimilar(mInpDecFormat, defFormat)) {
+            ALOGD("Input format is same as default for format for %s", decoder);
+            validateFormat = false;
+        }
+        AMediaFormat_delete(defFormat);
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+
+        /* test flush in running state before queuing input */
+        if (!flushCodec()) return false;
+        if (mIsCodecInAsyncMode) {
+            CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        }
+        if (!queueCodecConfig()) return false; /* flushed codec too soon, resubmit csd */
+        if (!doWork(1)) return false;
+
+        if (!flushCodec()) return false;
+        if (mIsCodecInAsyncMode) {
+            CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        }
+        if (!queueCodecConfig()) return false; /* flushed codec too soon, resubmit csd */
+        AMediaExtractor_seekTo(mExtractor, 0, mode);
+        test->reset();
+        if (!doWork(23)) return false;
+        CHECK_ERR(!test->isPtsStrictlyIncreasing(mPrevOutputPts), "",
+                  "pts is not strictly increasing", isPass);
+
+        /* test flush in running state */
+        if (!flushCodec()) return false;
+        if (mIsCodecInAsyncMode) {
+            CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        }
+        mSaveToMem = true;
+        test->reset();
+        AMediaExtractor_seekTo(mExtractor, pts, mode);
+        if (!doWork(INT32_MAX)) return false;
+        if (!queueEOS()) return false;
+        if (!waitForAllOutputs()) return false;
+        CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
+        CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
+        CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
+        CHECK_ERR((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
+                  isPass);
+        CHECK_ERR((!ref->equals(test)), log, "output is flaky", isPass);
+        if (!isPass) continue;
+
+        /* test flush in eos state */
+        if (!flushCodec()) return false;
+        if (mIsCodecInAsyncMode) {
+            CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        }
+        test->reset();
+        AMediaExtractor_seekTo(mExtractor, pts, mode);
+        if (!doWork(INT32_MAX)) return false;
+        if (!queueEOS()) return false;
+        if (!waitForAllOutputs()) return false;
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
+        mCodec = nullptr;
+        CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
+        CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
+        CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
+        CHECK_ERR((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
+                  isPass);
+        CHECK_ERR((!ref->equals(test)), log, "output is flaky", isPass);
+        if (validateFormat) {
+            if (mIsCodecInAsyncMode ? !mAsyncHandle.hasOutputFormatChanged()
+                                    : !mSignalledOutFormatChanged) {
+                ALOGE(log, "not received format change");
+                isPass = false;
+            } else if (!isFormatSimilar(mInpDecFormat, mIsCodecInAsyncMode
+                                                               ? mAsyncHandle.getOutputFormat()
+                                                               : mOutFormat)) {
+                ALOGE(log, "configured format and output format are not similar");
+                isPass = false;
+            }
+        }
+        mSaveToMem = false;
+    }
+    return isPass;
+}
+
+bool CodecDecoderTest::testOnlyEos(const char* decoder, const char* testFile) {
+    bool isPass = true;
+    if (!setUpExtractor(testFile)) return false;
+    mSaveToMem = true;
+    auto ref = &mRefBuff;
+    auto test = &mTestBuff;
+    const bool boolStates[]{true, false};
+    int loopCounter = 0;
+    for (auto isAsync : boolStates) {
+        if (!isPass) break;
+        char log[1000];
+        snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s:: \n", decoder, testFile,
+                 (isAsync ? "async" : "sync"));
+        mOutputBuff = loopCounter == 0 ? ref : test;
+        mOutputBuff->reset();
+        /* TODO(b/147348711) */
+        /* Instead of create and delete codec at every iteration, we would like to create
+         * once and use it for all iterations and delete before exiting */
+        mCodec = AMediaCodec_createCodecByName(decoder);
+        if (!mCodec) {
+            ALOGE("unable to create codec %s", decoder);
+            return false;
+        }
+        if (!configureCodec(mInpDecFormat, isAsync, false, false)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        if (!queueEOS()) return false;
+        if (!waitForAllOutputs()) return false;
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
+        mCodec = nullptr;
+        CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
+        CHECK_ERR(loopCounter != 0 && (!ref->equals(test)), log, "output is flaky", isPass);
+        CHECK_ERR(loopCounter == 0 && mIsAudio && (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)),
+                  log, "pts is not strictly increasing", isPass);
+        CHECK_ERR(loopCounter == 0 && !mIsAudio && (!ref->isOutPtsListIdenticalToInpPtsList(false)),
+                  log, "input pts list and output pts list are not identical", isPass);
+        loopCounter++;
+    }
+    return isPass;
+}
+
+bool CodecDecoderTest::testSimpleDecodeQueueCSD(const char* decoder, const char* testFile) {
+    bool isPass = true;
+    if (!setUpExtractor(testFile)) return false;
+    std::vector<AMediaFormat*> formats;
+    formats.push_back(mInpDecFormat);
+    mInpDecDupFormat = AMediaFormat_new();
+    AMediaFormat_copy(mInpDecDupFormat, mInpDecFormat);
+    formats.push_back(mInpDecDupFormat);
+    mCsdBuffers.clear();
+    for (int i = 0;; i++) {
+        char csdName[16];
+        void* csdBuffer;
+        size_t csdSize;
+        snprintf(csdName, sizeof(csdName), "csd-%d", i);
+        if (AMediaFormat_getBuffer(mInpDecDupFormat, csdName, &csdBuffer, &csdSize)) {
+            mCsdBuffers.push_back(std::make_pair(csdBuffer, csdSize));
+            AMediaFormat_setBuffer(mInpDecFormat, csdName, nullptr, 0);
+        } else break;
+    }
+
+    const bool boolStates[]{true, false};
+    mSaveToMem = true;
+    auto ref = &mRefBuff;
+    auto test = &mTestBuff;
+    int loopCounter = 0;
+    for (int i = 0; i < formats.size() && isPass; i++) {
+        auto fmt = formats[i];
+        for (auto eosType : boolStates) {
+            if (!isPass) break;
+            for (auto isAsync : boolStates) {
+                if (!isPass) break;
+                bool validateFormat = true;
+                char log[1000];
+                snprintf(log, sizeof(log), "codec: %s, file: %s, async mode: %s, eos type: %s:: \n",
+                         decoder, testFile, (isAsync ? "async" : "sync"),
+                         (eosType ? "eos with last frame" : "eos separate"));
+                mOutputBuff = loopCounter == 0 ? ref : test;
+                mOutputBuff->reset();
+                /* TODO(b/147348711) */
+                /* Instead of create and delete codec at every iteration, we would like to create
+                 * once and use it for all iterations and delete before exiting */
+                mCodec = AMediaCodec_createCodecByName(decoder);
+                if (!mCodec) {
+                    ALOGE("unable to create codec");
+                    return false;
+                }
+                AMediaExtractor_seekTo(mExtractor, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
+                if (!configureCodec(fmt, isAsync, eosType, false)) return false;
+                AMediaFormat* defFormat = AMediaCodec_getOutputFormat(mCodec);
+                if (isFormatSimilar(defFormat, mInpDecFormat)) {
+                    ALOGD("Input format is same as default for format for %s", decoder);
+                    validateFormat = false;
+                }
+                AMediaFormat_delete(defFormat);
+                CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+                /* formats[0] doesn't contain csd-data, so queuing csd separately, formats[1]
+                 * contain csd-data */
+                if (i == 0 && !queueCodecConfig()) return false;
+                if (!doWork(INT32_MAX)) return false;
+                if (!queueEOS()) return false;
+                if (!waitForAllOutputs()) return false;
+                CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+                CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
+                mCodec = nullptr;
+                CHECK_ERR(hasSeenError(), log, "has seen error", isPass);
+                CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
+                CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
+                CHECK_ERR((!mIsAudio && mInputCount != mOutputCount), log,
+                          "input cnt != output cnt", isPass);
+                CHECK_ERR(loopCounter != 0 && (!ref->equals(test)), log, "output is flaky", isPass);
+                CHECK_ERR(loopCounter == 0 && mIsAudio &&
+                          (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)),
+                          log, "pts is not strictly increasing", isPass);
+                CHECK_ERR(loopCounter == 0 && !mIsAudio &&
+                                  (!ref->isOutPtsListIdenticalToInpPtsList(false)),
+                          log, "input pts list and output pts list are not identical", isPass);
+                if (validateFormat) {
+                    if (mIsCodecInAsyncMode ? !mAsyncHandle.hasOutputFormatChanged()
+                                            : !mSignalledOutFormatChanged) {
+                        ALOGE(log, "not received format change");
+                        isPass = false;
+                    } else if (!isFormatSimilar(mInpDecFormat,
+                                                mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat()
+                                                                    : mOutFormat)) {
+                        ALOGE(log, "configured format and output format are not similar");
+                        isPass = false;
+                    }
+                }
+                loopCounter++;
+            }
+        }
+    }
+    mSaveToMem = false;
+    return isPass;
+}
+
+static jboolean nativeTestSimpleDecode(JNIEnv* env, jobject, jstring jDecoder, jstring jMime,
+                                       jstring jtestFile, jstring jrefFile, jfloat jrmsError) {
+    const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
+    const char* cMime = env->GetStringUTFChars(jMime, nullptr);
+    const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
+    const char* cRefFile = env->GetStringUTFChars(jrefFile, nullptr);
+    float cRmsError = jrmsError;
+    auto codecDecoderTest = new CodecDecoderTest(cMime);
+    bool isPass = codecDecoderTest->testSimpleDecode(cDecoder, cTestFile, cRefFile, cRmsError);
+    delete codecDecoderTest;
+    env->ReleaseStringUTFChars(jDecoder, cDecoder);
+    env->ReleaseStringUTFChars(jMime, cMime);
+    env->ReleaseStringUTFChars(jtestFile, cTestFile);
+    env->ReleaseStringUTFChars(jrefFile, cRefFile);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestOnlyEos(JNIEnv* env, jobject, jstring jDecoder, jstring jMime,
+                                  jstring jtestFile) {
+    const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
+    const char* cMime = env->GetStringUTFChars(jMime, nullptr);
+    const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
+    auto codecDecoderTest = new CodecDecoderTest(cMime);
+    bool isPass = codecDecoderTest->testOnlyEos(cDecoder, cTestFile);
+    delete codecDecoderTest;
+    env->ReleaseStringUTFChars(jDecoder, cDecoder);
+    env->ReleaseStringUTFChars(jMime, cMime);
+    env->ReleaseStringUTFChars(jtestFile, cTestFile);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestFlush(JNIEnv* env, jobject, jstring jDecoder, jstring jMime,
+                                jstring jtestFile) {
+    const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
+    const char* cMime = env->GetStringUTFChars(jMime, nullptr);
+    const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
+    auto codecDecoderTest = new CodecDecoderTest(cMime);
+    bool isPass = codecDecoderTest->testFlush(cDecoder, cTestFile);
+    delete codecDecoderTest;
+    env->ReleaseStringUTFChars(jDecoder, cDecoder);
+    env->ReleaseStringUTFChars(jMime, cMime);
+    env->ReleaseStringUTFChars(jtestFile, cTestFile);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSimpleDecodeQueueCSD(JNIEnv* env, jobject, jstring jDecoder,
+                                               jstring jMime, jstring jtestFile) {
+    const char* cDecoder = env->GetStringUTFChars(jDecoder, nullptr);
+    const char* cMime = env->GetStringUTFChars(jMime, nullptr);
+    const char* cTestFile = env->GetStringUTFChars(jtestFile, nullptr);
+    auto codecDecoderTest = new CodecDecoderTest(cMime);
+    bool isPass = codecDecoderTest->testSimpleDecodeQueueCSD(cDecoder, cTestFile);
+    delete codecDecoderTest;
+    env->ReleaseStringUTFChars(jDecoder, cDecoder);
+    env->ReleaseStringUTFChars(jMime, cMime);
+    env->ReleaseStringUTFChars(jtestFile, cTestFile);
+    return static_cast<jboolean>(isPass);
+}
+
+int registerAndroidMediaV2CtsDecoderTest(JNIEnv* env) {
+    const JNINativeMethod methodTable[] = {
+            {"nativeTestSimpleDecode",
+             "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;F)Z",
+             (void*)nativeTestSimpleDecode},
+            {"nativeTestOnlyEos", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+             (void*)nativeTestOnlyEos},
+            {"nativeTestFlush", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+             (void*)nativeTestFlush},
+            {"nativeTestSimpleDecodeQueueCSD",
+             "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+             (void*)nativeTestSimpleDecodeQueueCSD},
+    };
+    jclass c = env->FindClass("android/mediav2/cts/CodecDecoderTest");
+    return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
+}
diff --git a/tests/media/jni/NativeCodecEncoderTest.cpp b/tests/media/jni/NativeCodecEncoderTest.cpp
new file mode 100644
index 0000000..626c4307
--- /dev/null
+++ b/tests/media/jni/NativeCodecEncoderTest.cpp
@@ -0,0 +1,1041 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeCodecEncoderTest"
+#include <log/log.h>
+
+#include <jni.h>
+#include <sys/stat.h>
+
+#include "NativeCodecTestBase.h"
+#include "NativeMediaConstants.h"
+
+class CodecEncoderTest final : CodecTestBase {
+  private:
+    uint8_t* mInputData;
+    size_t mInputLength;
+    int mNumBytesSubmitted;
+    int64_t mInputOffsetPts;
+    std::vector<AMediaFormat*> mFormats;
+    int mNumSyncFramesReceived;
+    std::vector<int> mSyncFramesPos;
+
+    int32_t* mBitRates;
+    int mLen0;
+    int32_t* mEncParamList1;
+    int mLen1;
+    int32_t* mEncParamList2;
+    int mLen2;
+
+    int mWidth, mHeight;
+    int mChannels;
+    int mSampleRate;
+    int mColorFormat;
+    int mMaxBFrames;
+    int mDefFrameRate;
+    const int kInpFrmWidth = 352;
+    const int kInpFrmHeight = 288;
+
+    void convertyuv420ptoyuv420sp();
+    void setUpSource(const char* srcPath);
+    void deleteSource();
+    void setUpParams(int limit);
+    void deleteParams();
+    bool flushCodec() override;
+    void resetContext(bool isAsync, bool signalEOSWithLastFrame) override;
+    bool enqueueInput(size_t bufferIndex) override;
+    bool dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* bufferInfo) override;
+    void initFormat(AMediaFormat* format);
+    bool encodeToMemory(const char* file, const char* encoder, int frameLimit, AMediaFormat* format,
+                        OutputManager* ref);
+    void fillByteBuffer(uint8_t* inputBuffer);
+    void forceSyncFrame(AMediaFormat* format);
+    void updateBitrate(AMediaFormat* format, int bitrate);
+
+  public:
+    CodecEncoderTest(const char* mime, int32_t* list0, int len0, int32_t* list1, int len1,
+                     int32_t* list2, int len2, int colorFormat);
+    ~CodecEncoderTest();
+
+    bool testSimpleEncode(const char* encoder, const char* srcPath);
+    bool testFlush(const char* encoder, const char* srcPath);
+    bool testReconfigure(const char* encoder, const char* srcPath);
+    bool testSetForceSyncFrame(const char* encoder, const char* srcPath);
+    bool testAdaptiveBitRate(const char* encoder, const char* srcPath);
+    bool testOnlyEos(const char* encoder);
+};
+
+CodecEncoderTest::CodecEncoderTest(const char* mime, int32_t* list0, int len0, int32_t* list1,
+                                   int len1, int32_t* list2, int len2, int colorFormat)
+    : CodecTestBase(mime),
+      mBitRates{list0},
+      mLen0{len0},
+      mEncParamList1{list1},
+      mLen1{len1},
+      mEncParamList2{list2},
+      mLen2{len2},
+      mColorFormat{colorFormat} {
+    mDefFrameRate = 30;
+    if (!strcmp(mime, AMEDIA_MIMETYPE_VIDEO_H263)) mDefFrameRate = 12;
+    else if (!strcmp(mime, AMEDIA_MIMETYPE_VIDEO_MPEG4)) mDefFrameRate = 12;
+    mMaxBFrames = 0;
+    mInputData = nullptr;
+    mInputLength = 0;
+    mNumBytesSubmitted = 0;
+    mInputOffsetPts = 0;
+}
+
+CodecEncoderTest::~CodecEncoderTest() {
+    deleteSource();
+    deleteParams();
+}
+
+void CodecEncoderTest::convertyuv420ptoyuv420sp() {
+    int ySize = kInpFrmWidth * kInpFrmHeight;
+    int uSize = kInpFrmWidth * kInpFrmHeight / 4;
+    int frameSize = ySize + uSize * 2;
+    int totalFrames = mInputLength / frameSize;
+    uint8_t* u = new uint8_t[uSize];
+    uint8_t* v = new uint8_t[uSize];
+    uint8_t* frameBase = mInputData;
+    for (int i = 0; i < totalFrames; i++) {
+        uint8_t* uvBase = frameBase + ySize;
+        memcpy(u, uvBase, uSize);
+        memcpy(v, uvBase + uSize, uSize);
+        for (int j = 0, idx = 0; j < uSize; j++, idx += 2) {
+            uvBase[idx] = u[j];
+            uvBase[idx + 1] = v[j];
+        }
+        frameBase += frameSize;
+    }
+    delete[] u;
+    delete[] v;
+}
+
+void CodecEncoderTest::setUpSource(const char* srcPath) {
+    FILE* fp = fopen(srcPath, "rbe");
+    struct stat buf {};
+    if (fp && !fstat(fileno(fp), &buf)) {
+        deleteSource();
+        mInputLength = buf.st_size;
+        mInputData = new uint8_t[mInputLength];
+        fread(mInputData, sizeof(uint8_t), mInputLength, fp);
+        if (mColorFormat == COLOR_FormatYUV420SemiPlanar) {
+            convertyuv420ptoyuv420sp();
+        }
+    } else {
+        ALOGE("unable to open input file %s", srcPath);
+    }
+    if (fp) fclose(fp);
+}
+
+void CodecEncoderTest::deleteSource() {
+    if (mInputData) {
+        delete[] mInputData;
+        mInputData = nullptr;
+    }
+    mInputLength = 0;
+}
+
+void CodecEncoderTest::setUpParams(int limit) {
+    int count = 0;
+    for (int k = 0; k < mLen0; k++) {
+        int bitrate = mBitRates[k];
+        if (mIsAudio) {
+            for (int j = 0; j < mLen1; j++) {
+                int rate = mEncParamList1[j];
+                for (int i = 0; i < mLen2; i++) {
+                    int channels = mEncParamList2[i];
+                    AMediaFormat* format = AMediaFormat_new();
+                    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, mMime);
+                    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, bitrate);
+                    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, rate);
+                    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, channels);
+                    mFormats.push_back(format);
+                    count++;
+                    if (count >= limit) break;
+                }
+            }
+        } else {
+            for (int j = 0; j < mLen1; j++) {
+                int width = mEncParamList1[j];
+                int height = mEncParamList2[j];
+                AMediaFormat* format = AMediaFormat_new();
+                AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, mMime);
+                AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, bitrate);
+                AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, width);
+                AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, height);
+                AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, mDefFrameRate);
+                AMediaFormat_setInt32(format, TBD_AMEDIACODEC_PARAMETER_KEY_MAX_B_FRAMES,
+                                      mMaxBFrames);
+                AMediaFormat_setFloat(format, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, 1.0F);
+                AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, mColorFormat);
+                mFormats.push_back(format);
+                count++;
+                if (count >= limit) break;
+            }
+        }
+    }
+}
+
+void CodecEncoderTest::deleteParams() {
+    for (auto format : mFormats) AMediaFormat_delete(format);
+    mFormats.clear();
+}
+
+void CodecEncoderTest::resetContext(bool isAsync, bool signalEOSWithLastFrame) {
+    CodecTestBase::resetContext(isAsync, signalEOSWithLastFrame);
+    mNumBytesSubmitted = 0;
+    mInputOffsetPts = 0;
+    mNumSyncFramesReceived = 0;
+    mSyncFramesPos.clear();
+}
+
+bool CodecEncoderTest::flushCodec() {
+    bool isOk = CodecTestBase::flushCodec();
+    if (mIsAudio) {
+        mInputOffsetPts = (mNumBytesSubmitted + 1024) * 1000000L / (2 * mChannels * mSampleRate);
+    } else {
+        mInputOffsetPts = (mInputCount + 5) * 1000000L / mDefFrameRate;
+    }
+    mPrevOutputPts = mInputOffsetPts - 1;
+    mNumBytesSubmitted = 0;
+    mNumSyncFramesReceived = 0;
+    mSyncFramesPos.clear();
+    return isOk;
+}
+
+void CodecEncoderTest::fillByteBuffer(uint8_t* inputBuffer) {
+    int width, height, tileWidth, tileHeight;
+    int offset = 0, frmOffset = mNumBytesSubmitted;
+    int numOfPlanes;
+    if (mColorFormat == COLOR_FormatYUV420SemiPlanar) {
+        numOfPlanes = 2;
+    } else {
+        numOfPlanes = 3;
+    }
+    for (int plane = 0; plane < numOfPlanes; plane++) {
+        if (plane == 0) {
+            width = mWidth;
+            height = mHeight;
+            tileWidth = kInpFrmWidth;
+            tileHeight = kInpFrmHeight;
+        } else {
+            if (mColorFormat == COLOR_FormatYUV420SemiPlanar) {
+                width = mWidth;
+                tileWidth = kInpFrmWidth;
+            } else {
+                width = mWidth / 2;
+                tileWidth = kInpFrmWidth / 2;
+            }
+            height = mHeight / 2;
+            tileHeight = kInpFrmHeight / 2;
+        }
+        for (int k = 0; k < height; k += tileHeight) {
+            int rowsToCopy = std::min(height - k, tileHeight);
+            for (int j = 0; j < rowsToCopy; j++) {
+                for (int i = 0; i < width; i += tileWidth) {
+                    int colsToCopy = std::min(width - i, tileWidth);
+                    memcpy(inputBuffer + (offset + (k + j) * width + i),
+                           mInputData + (frmOffset + j * tileWidth), colsToCopy);
+                }
+            }
+        }
+        offset += width * height;
+        frmOffset += tileWidth * tileHeight;
+    }
+}
+
+bool CodecEncoderTest::enqueueInput(size_t bufferIndex) {
+    if (mNumBytesSubmitted >= mInputLength) {
+        return enqueueEOS(bufferIndex);
+    } else {
+        int size = 0;
+        int flags = 0;
+        int64_t pts = mInputOffsetPts;
+        size_t buffSize;
+        uint8_t* inputBuffer = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &buffSize);
+        if (mIsAudio) {
+            pts += mNumBytesSubmitted * 1000000L / (2 * mChannels * mSampleRate);
+            size = std::min(buffSize, mInputLength - mNumBytesSubmitted);
+            memcpy(inputBuffer, mInputData + mNumBytesSubmitted, size);
+            if (mNumBytesSubmitted + size >= mInputLength && mSignalEOSWithLastFrame) {
+                flags |= AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
+                mSawInputEOS = true;
+            }
+            mNumBytesSubmitted += size;
+        } else {
+            pts += mInputCount * 1000000L / mDefFrameRate;
+            size = mWidth * mHeight * 3 / 2;
+            int frmSize = kInpFrmWidth * kInpFrmHeight * 3 / 2;
+            if (mNumBytesSubmitted + frmSize > mInputLength) {
+                ALOGE("received partial frame to encode");
+                return false;
+            } else if (size > buffSize) {
+                ALOGE("frame size exceeds buffer capacity of input buffer %d %zu", size, buffSize);
+                return false;
+            } else {
+                if (mWidth == kInpFrmWidth && mHeight == kInpFrmHeight) {
+                    memcpy(inputBuffer, mInputData + mNumBytesSubmitted, size);
+                } else {
+                    fillByteBuffer(inputBuffer);
+                }
+            }
+            if (mNumBytesSubmitted + frmSize >= mInputLength && mSignalEOSWithLastFrame) {
+                flags |= AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM;
+                mSawInputEOS = true;
+            }
+            mNumBytesSubmitted += frmSize;
+        }
+        CHECK_STATUS(AMediaCodec_queueInputBuffer(mCodec, bufferIndex, 0, size, pts, flags),
+                     "AMediaCodec_queueInputBuffer failed");
+        ALOGV("input: id: %zu  size: %d  pts: %d  flags: %d", bufferIndex, size, (int)pts, flags);
+        mOutputBuff->saveInPTS(pts);
+        mInputCount++;
+    }
+    return !hasSeenError();
+}
+
+bool CodecEncoderTest::dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* info) {
+    if ((info->flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) != 0) {
+        mSawOutputEOS = true;
+    }
+    if (info->size > 0) {
+        if (mSaveToMem) {
+            size_t buffSize;
+            uint8_t* buf = AMediaCodec_getOutputBuffer(mCodec, bufferIndex, &buffSize);
+            mOutputBuff->saveToMemory(buf, info);
+        }
+        if ((info->flags & TBD_AMEDIACODEC_BUFFER_FLAG_KEY_FRAME) != 0) {
+            mNumSyncFramesReceived += 1;
+            mSyncFramesPos.push_back(mOutputCount);
+        }
+        if ((info->flags & AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG) == 0) {
+            mOutputBuff->saveOutPTS(info->presentationTimeUs);
+            mOutputCount++;
+        }
+    }
+    ALOGV("output: id: %zu  size: %d  pts: %d  flags: %d", bufferIndex, info->size,
+          (int)info->presentationTimeUs, info->flags);
+    CHECK_STATUS(AMediaCodec_releaseOutputBuffer(mCodec, bufferIndex, false),
+                 "AMediaCodec_releaseOutputBuffer failed");
+    return !hasSeenError();
+}
+
+void CodecEncoderTest::initFormat(AMediaFormat* format) {
+    if (mIsAudio) {
+        AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, &mSampleRate);
+        AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &mChannels);
+    } else {
+        AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &mWidth);
+        AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &mHeight);
+    }
+}
+
+bool CodecEncoderTest::encodeToMemory(const char* file, const char* encoder, int32_t frameLimit,
+                                      AMediaFormat* format, OutputManager* ref) {
+    /* TODO(b/149027258) */
+    if (true) mSaveToMem = false;
+    else mSaveToMem = true;
+    mOutputBuff = ref;
+    mCodec = AMediaCodec_createCodecByName(encoder);
+    if (!mCodec) {
+        ALOGE("unable to create codec %s", encoder);
+        return false;
+    }
+    setUpSource(file);
+    if (!mInputData) return false;
+    if (!configureCodec(format, false, true, true)) return false;
+    initFormat(format);
+    CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+    if (!doWork(frameLimit)) return false;
+    if (!queueEOS()) return false;
+    if (!waitForAllOutputs()) return false;
+    CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
+    mCodec = nullptr;
+    mSaveToMem = false;
+    return !hasSeenError();
+}
+
+void CodecEncoderTest::forceSyncFrame(AMediaFormat* format) {
+    AMediaFormat_setInt32(format, TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
+    ALOGV("requesting key frame");
+    AMediaCodec_setParameters(mCodec, format);
+}
+
+void CodecEncoderTest::updateBitrate(AMediaFormat* format, int bitrate) {
+    AMediaFormat_setInt32(format, TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE, bitrate);
+    ALOGV("requesting bitrate to be changed to %d", bitrate);
+    AMediaCodec_setParameters(mCodec, format);
+}
+
+bool CodecEncoderTest::testSimpleEncode(const char* encoder, const char* srcPath) {
+    bool isPass = true;
+    setUpSource(srcPath);
+    if (!mInputData) return false;
+    setUpParams(INT32_MAX);
+    /* TODO(b/149027258) */
+    if (true) mSaveToMem = false;
+    else mSaveToMem = true;
+    auto ref = &mRefBuff;
+    auto test = &mTestBuff;
+    const bool boolStates[]{true, false};
+    for (int i = 0; i < mFormats.size() && isPass; i++) {
+        AMediaFormat* format = mFormats[i];
+        initFormat(format);
+        int loopCounter = 0;
+        for (auto eosType : boolStates) {
+            if (!isPass) break;
+            for (auto isAsync : boolStates) {
+                if (!isPass) break;
+                char log[1000];
+                snprintf(log, sizeof(log),
+                         "format: %s \n codec: %s, file: %s, mode: %s, eos type: %s:: ",
+                         AMediaFormat_toString(format), encoder, srcPath,
+                         (isAsync ? "async" : "sync"),
+                         (eosType ? "eos with last frame" : "eos separate"));
+                mOutputBuff = loopCounter == 0 ? ref : test;
+                mOutputBuff->reset();
+                /* TODO(b/147348711) */
+                /* Instead of create and delete codec at every iteration, we would like to create
+                 * once and use it for all iterations and delete before exiting */
+                mCodec = AMediaCodec_createCodecByName(encoder);
+                if (!mCodec) {
+                    ALOGE("%s unable to create media codec by name %s", log, encoder);
+                    isPass = false;
+                    continue;
+                }
+                char* name = nullptr;
+                if (AMEDIA_OK == AMediaCodec_getName(mCodec, &name)) {
+                    if (!name || strcmp(name, encoder) != 0) {
+                        ALOGE("%s error codec-name act/got: %s/%s", log, name, encoder);
+                        if (name) AMediaCodec_releaseName(mCodec, name);
+                        return false;
+                    }
+                } else {
+                    ALOGE("AMediaCodec_getName failed unexpectedly");
+                    return false;
+                }
+                if (name) AMediaCodec_releaseName(mCodec, name);
+                if (!configureCodec(format, isAsync, eosType, true)) return false;
+                CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+                if (!doWork(INT32_MAX)) return false;
+                if (!queueEOS()) return false;
+                if (!waitForAllOutputs()) return false;
+                CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+                CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
+                mCodec = nullptr;
+                CHECK_ERR((hasSeenError()), log, "has seen error", isPass);
+                CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
+                CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
+                CHECK_ERR((!mIsAudio && mInputCount != mOutputCount), log,
+                          "input cnt != output cnt", isPass);
+                CHECK_ERR((loopCounter != 0 && !ref->equals(test)), log, "output is flaky", isPass);
+                CHECK_ERR((loopCounter == 0 && mIsAudio &&
+                           !ref->isPtsStrictlyIncreasing(mPrevOutputPts)),
+                          log, "pts is not strictly increasing", isPass);
+                CHECK_ERR((loopCounter == 0 && !mIsAudio &&
+                           !ref->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)),
+                          log, "input pts list and output pts list are not identical", isPass);
+                loopCounter++;
+            }
+        }
+    }
+    return isPass;
+}
+
+bool CodecEncoderTest::testFlush(const char* encoder, const char* srcPath) {
+    bool isPass = true;
+    setUpSource(srcPath);
+    if (!mInputData) return false;
+    setUpParams(1);
+    mOutputBuff = &mTestBuff;
+    AMediaFormat* format = mFormats[0];
+    initFormat(format);
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!isPass) break;
+        char log[1000];
+        snprintf(log, sizeof(log),
+                 "format: %s \n codec: %s, file: %s, mode: %s:: ", AMediaFormat_toString(format),
+                 encoder, srcPath, (isAsync ? "async" : "sync"));
+        /* TODO(b/147348711) */
+        /* Instead of create and delete codec at every iteration, we would like to create
+         * once and use it for all iterations and delete before exiting */
+        mCodec = AMediaCodec_createCodecByName(encoder);
+        if (!mCodec) {
+            ALOGE("unable to create media codec by name %s", encoder);
+            isPass = false;
+            continue;
+        }
+        if (!configureCodec(format, isAsync, true, true)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+
+        /* test flush in running state before queuing input */
+        if (!flushCodec()) return false;
+        mOutputBuff->reset();
+        if (mIsCodecInAsyncMode) {
+            CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        }
+        if (!doWork(23)) return false;
+        CHECK_ERR((!mOutputBuff->isPtsStrictlyIncreasing(mPrevOutputPts)), log,
+                  "pts is not strictly increasing", isPass);
+        if (!isPass) continue;
+
+        /* test flush in running state */
+        if (!flushCodec()) return false;
+        mOutputBuff->reset();
+        if (mIsCodecInAsyncMode) {
+            CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        }
+        if (!doWork(INT32_MAX)) return false;
+        if (!queueEOS()) return false;
+        if (!waitForAllOutputs()) return false;
+        CHECK_ERR((hasSeenError()), log, "has seen error", isPass);
+        CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
+        CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
+        CHECK_ERR((mIsAudio && !mOutputBuff->isPtsStrictlyIncreasing(mPrevOutputPts)), log,
+                  "pts is not strictly increasing", isPass);
+        CHECK_ERR((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
+                  isPass);
+        CHECK_ERR((!mIsAudio && !mOutputBuff->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)),
+                  log, "input pts list and output pts list are not identical", isPass);
+        if (!isPass) continue;
+
+        /* test flush in eos state */
+        if (!flushCodec()) return false;
+        mOutputBuff->reset();
+        if (mIsCodecInAsyncMode) {
+            CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        }
+        if (!doWork(INT32_MAX)) return false;
+        if (!queueEOS()) return false;
+        if (!waitForAllOutputs()) return false;
+        CHECK_ERR((hasSeenError()), log, "has seen error", isPass);
+        CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
+        CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
+        CHECK_ERR((mIsAudio && !mOutputBuff->isPtsStrictlyIncreasing(mPrevOutputPts)), log,
+                  "pts is not strictly increasing", isPass);
+        CHECK_ERR(!mIsAudio && (mInputCount != mOutputCount), log, "input cnt != output cnt",
+                  isPass);
+        CHECK_ERR(!mIsAudio && (!mOutputBuff->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)),
+                  log, "input pts list and output pts list are not identical", isPass);
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
+        mCodec = nullptr;
+    }
+    return isPass;
+}
+
+bool CodecEncoderTest::testReconfigure(const char* encoder, const char* srcPath) {
+    bool isPass = true;
+    setUpSource(srcPath);
+    if (!mInputData) return false;
+    setUpParams(2);
+    auto configRef = &mReconfBuff;
+    if (mFormats.size() > 1) {
+        auto format = mFormats[1];
+        if (!encodeToMemory(srcPath, encoder, INT32_MAX, format, configRef)) {
+            ALOGE("encodeToMemory failed for file: %s codec: %s \n format: %s", srcPath, encoder,
+                  AMediaFormat_toString(format));
+            return false;
+        }
+        CHECK_ERR(mIsAudio && (!configRef->isPtsStrictlyIncreasing(mPrevOutputPts)), "",
+                  "pts is not strictly increasing", isPass);
+        CHECK_ERR(!mIsAudio && (!configRef->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)),
+                  "", "input pts list and output pts list are not identical", isPass);
+        if (!isPass) return false;
+    }
+    auto format = mFormats[0];
+    auto ref = &mRefBuff;
+    if (!encodeToMemory(srcPath, encoder, INT32_MAX, format, ref)) {
+        ALOGE("encodeToMemory failed for file: %s codec: %s \n format: %s", srcPath, encoder,
+              AMediaFormat_toString(format));
+        return false;
+    }
+    CHECK_ERR(mIsAudio && (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)), "",
+              "pts is not strictly increasing", isPass);
+    CHECK_ERR(!mIsAudio && (!ref->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)), "",
+              "input pts list and output pts list are not identical", isPass);
+    if (!isPass) return false;
+
+    auto test = &mTestBuff;
+    mOutputBuff = test;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!isPass) break;
+        char log[1000];
+        snprintf(log, sizeof(log),
+                 "format: %s \n codec: %s, file: %s, mode: %s:: ", AMediaFormat_toString(format),
+                 encoder, srcPath, (isAsync ? "async" : "sync"));
+        /* TODO(b/147348711) */
+        /* Instead of create and delete codec at every iteration, we would like to create
+         * once and use it for all iterations and delete before exiting */
+        mCodec = AMediaCodec_createCodecByName(encoder);
+        if (!mCodec) {
+            ALOGE("%s unable to create media codec by name %s", log, encoder);
+            isPass = false;
+            continue;
+        }
+        if (!configureCodec(format, isAsync, true, true)) return false;
+        /* test reconfigure in init state */
+        if (!reConfigureCodec(format, !isAsync, false, true)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+
+        /* test reconfigure in running state before queuing input */
+        if (!reConfigureCodec(format, !isAsync, false, true)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        if (!doWork(23)) return false;
+
+        /* test reconfigure codec in running state */
+        if (!reConfigureCodec(format, isAsync, true, true)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+
+        /* TODO(b/149027258) */
+        if (true) mSaveToMem = false;
+        else mSaveToMem = true;
+        test->reset();
+        if (!doWork(INT32_MAX)) return false;
+        if (!queueEOS()) return false;
+        if (!waitForAllOutputs()) return false;
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        CHECK_ERR((hasSeenError()), log, "has seen error", isPass);
+        CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
+        CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
+        CHECK_ERR((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
+                  isPass);
+        CHECK_ERR((!ref->equals(test)), log, "output is flaky", isPass);
+        if (!isPass) continue;
+
+        /* test reconfigure codec at eos state */
+        if (!reConfigureCodec(format, !isAsync, false, true)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        test->reset();
+        if (!doWork(INT32_MAX)) return false;
+        if (!queueEOS()) return false;
+        if (!waitForAllOutputs()) return false;
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        CHECK_ERR((hasSeenError()), log, "has seen error", isPass);
+        CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
+        CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
+        CHECK_ERR((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
+                  isPass);
+        CHECK_ERR((!ref->equals(test)), log, "output is flaky", isPass);
+
+        /* test reconfigure codec for new format */
+        if (mFormats.size() > 1) {
+            if (!reConfigureCodec(mFormats[1], isAsync, false, true)) return false;
+            CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+            test->reset();
+            if (!doWork(INT32_MAX)) return false;
+            if (!queueEOS()) return false;
+            if (!waitForAllOutputs()) return false;
+            CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+            CHECK_ERR((hasSeenError()), log, "has seen error", isPass);
+            CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
+            CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
+            CHECK_ERR((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
+                      isPass);
+            CHECK_ERR((!configRef->equals(test)), log, "output is flaky", isPass);
+        }
+        mSaveToMem = false;
+        CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
+        mCodec = nullptr;
+    }
+    return isPass;
+}
+
+bool CodecEncoderTest::testOnlyEos(const char* encoder) {
+    bool isPass = true;
+    setUpParams(1);
+    /* TODO(b/149027258) */
+    if (true) mSaveToMem = false;
+    else mSaveToMem = true;
+    auto ref = &mRefBuff;
+    auto test = &mTestBuff;
+    const bool boolStates[]{true, false};
+    AMediaFormat* format = mFormats[0];
+    int loopCounter = 0;
+    for (int k = 0; (k < (sizeof(boolStates) / sizeof(boolStates[0]))) && isPass; k++) {
+        bool isAsync = boolStates[k];
+        char log[1000];
+        snprintf(log, sizeof(log),
+                 "format: %s \n codec: %s, mode: %s:: ", AMediaFormat_toString(format), encoder,
+                 (isAsync ? "async" : "sync"));
+        mOutputBuff = loopCounter == 0 ? ref : test;
+        mOutputBuff->reset();
+        /* TODO(b/147348711) */
+        /* Instead of create and delete codec at every iteration, we would like to create
+         * once and use it for all iterations and delete before exiting */
+        mCodec = AMediaCodec_createCodecByName(encoder);
+        if (!mCodec) {
+            ALOGE("unable to create media codec by name %s", encoder);
+            isPass = false;
+            continue;
+        }
+        if (!configureCodec(format, isAsync, false, true)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        if (!queueEOS()) return false;
+        if (!waitForAllOutputs()) return false;
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
+        mCodec = nullptr;
+        CHECK_ERR((hasSeenError()), log, "has seen error", isPass);
+        CHECK_ERR(loopCounter != 0 && (!ref->equals(test)), log, "output is flaky", isPass);
+        CHECK_ERR(loopCounter == 0 && mIsAudio && (!ref->isPtsStrictlyIncreasing(mPrevOutputPts)),
+                  log, "pts is not strictly increasing", isPass);
+        CHECK_ERR(loopCounter == 0 && !mIsAudio &&
+                  (!ref->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)),
+                  log, "input pts list and output pts list are not identical", isPass);
+        loopCounter++;
+    }
+    return isPass;
+}
+
+bool CodecEncoderTest::testSetForceSyncFrame(const char* encoder, const char* srcPath) {
+    bool isPass = true;
+    setUpSource(srcPath);
+    if (!mInputData) return false;
+    setUpParams(1);
+    AMediaFormat* format = mFormats[0];
+    AMediaFormat_setFloat(format, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, 500.f);
+    AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &mWidth);
+    AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &mHeight);
+    // Maximum allowed key frame interval variation from the target value.
+    int kMaxKeyFrameIntervalVariation = 3;
+    int kKeyFrameInterval = 2;  // force key frame every 2 seconds.
+    int kKeyFramePos = mDefFrameRate * kKeyFrameInterval;
+    int kNumKeyFrameRequests = 7;
+    AMediaFormat* params = AMediaFormat_new();
+    mFormats.push_back(params);
+    mOutputBuff = &mTestBuff;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!isPass) break;
+        char log[1000];
+        snprintf(log, sizeof(log),
+                 "format: %s \n codec: %s, file: %s, mode: %s:: ", AMediaFormat_toString(format),
+                 encoder, srcPath, (isAsync ? "async" : "sync"));
+        mOutputBuff->reset();
+        /* TODO(b/147348711) */
+        /* Instead of create and delete codec at every iteration, we would like to create
+         * once and use it for all iterations and delete before exiting */
+        mCodec = AMediaCodec_createCodecByName(encoder);
+        if (!mCodec) {
+            ALOGE("%s unable to create media codec by name %s", log, encoder);
+            isPass = false;
+            continue;
+        }
+        if (!configureCodec(format, isAsync, false, true)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        for (int i = 0; i < kNumKeyFrameRequests; i++) {
+            if (!doWork(kKeyFramePos)) return false;
+            assert(!mSawInputEOS);
+            forceSyncFrame(params);
+            mNumBytesSubmitted = 0;
+        }
+        if (!queueEOS()) return false;
+        if (!waitForAllOutputs()) return false;
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
+        mCodec = nullptr;
+        CHECK_ERR((hasSeenError()), log, "has seen error", isPass);
+        CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
+        CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
+        CHECK_ERR((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
+                  isPass);
+        CHECK_ERR((!mOutputBuff->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)), log,
+                  "input pts list and output pts list are not identical", isPass);
+        CHECK_ERR((mNumSyncFramesReceived < kNumKeyFrameRequests), log,
+                  "Num Sync Frames Received != Num Key Frame Requested", isPass);
+        ALOGD("mNumSyncFramesReceived %d", mNumSyncFramesReceived);
+        for (int i = 0, expPos = 0, index = 0; i < kNumKeyFrameRequests; i++) {
+            int j = index;
+            for (; j < mSyncFramesPos.size(); j++) {
+                // Check key frame intervals:
+                // key frame position should not be greater than target value + 3
+                // key frame position should not be less than target value - 3
+                if (abs(expPos - mSyncFramesPos.at(j)) <= kMaxKeyFrameIntervalVariation) {
+                    index = j;
+                    break;
+                }
+            }
+            if (j == mSyncFramesPos.size()) {
+                ALOGW("requested key frame at frame index %d none found near by", expPos);
+            }
+            expPos += kKeyFramePos;
+        }
+    }
+    return isPass;
+}
+
+bool CodecEncoderTest::testAdaptiveBitRate(const char* encoder, const char* srcPath) {
+    bool isPass = true;
+    setUpSource(srcPath);
+    if (!mInputData) return false;
+    setUpParams(1);
+    AMediaFormat* format = mFormats[0];
+    AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &mWidth);
+    AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &mHeight);
+    int kAdaptiveBitrateInterval = 3;  // change bitrate every 3 seconds.
+    int kAdaptiveBitrateDurationFrame = mDefFrameRate * kAdaptiveBitrateInterval;
+    int kBitrateChangeRequests = 7;
+    AMediaFormat* params = AMediaFormat_new();
+    mFormats.push_back(params);
+    // Setting in CBR Mode
+    AMediaFormat_setInt32(format, TBD_AMEDIAFORMAT_KEY_BIT_RATE_MODE, kBitrateModeConstant);
+    mOutputBuff = &mTestBuff;
+    mSaveToMem = true;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!isPass) break;
+        char log[1000];
+        snprintf(log, sizeof(log),
+                 "format: %s \n codec: %s, file: %s, mode: %s:: ", AMediaFormat_toString(format),
+                 encoder, srcPath, (isAsync ? "async" : "sync"));
+        mOutputBuff->reset();
+        /* TODO(b/147348711) */
+        /* Instead of create and delete codec at every iteration, we would like to create
+         * once and use it for all iterations and delete before exiting */
+        mCodec = AMediaCodec_createCodecByName(encoder);
+        if (!mCodec) {
+            ALOGE("%s unable to create media codec by name %s", log, encoder);
+            isPass = false;
+            continue;
+        }
+        if (!configureCodec(format, isAsync, false, true)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        int expOutSize = 0;
+        int bitrate;
+        AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, &bitrate);
+        for (int i = 0; i < kBitrateChangeRequests; i++) {
+            if (!doWork(kAdaptiveBitrateDurationFrame)) return false;
+            assert(!mSawInputEOS);
+            expOutSize += kAdaptiveBitrateInterval * bitrate;
+            if ((i & 1) == 1) bitrate *= 2;
+            else bitrate /= 2;
+            updateBitrate(params, bitrate);
+            mNumBytesSubmitted = 0;
+        }
+        if (!queueEOS()) return false;
+        if (!waitForAllOutputs()) return false;
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        CHECK_STATUS(AMediaCodec_delete(mCodec), "AMediaCodec_delete failed");
+        mCodec = nullptr;
+        CHECK_ERR((hasSeenError()), log, "has seen error", isPass);
+        CHECK_ERR((0 == mInputCount), log, "queued 0 inputs", isPass);
+        CHECK_ERR((0 == mOutputCount), log, "received 0 outputs", isPass);
+        CHECK_ERR((!mIsAudio && mInputCount != mOutputCount), log, "input cnt != output cnt",
+                  isPass);
+        CHECK_ERR((!mOutputBuff->isOutPtsListIdenticalToInpPtsList(mMaxBFrames != 0)), log,
+                  "input pts list and output pts list are not identical", isPass);
+        /* TODO: validate output br with sliding window constraints Sec 5.2 cdd */
+        int outSize = mOutputBuff->getOutStreamSize() * 8;
+        float brDev = abs(expOutSize - outSize) * 100.0f / expOutSize;
+        ALOGD("%s relative bitrate error is %f %%", log, brDev);
+        if (brDev > 50) {
+            ALOGE("%s relative bitrate error is is too large %f %%", log, brDev);
+            return false;
+        }
+    }
+    return isPass;
+}
+
+static jboolean nativeTestSimpleEncode(JNIEnv* env, jobject, jstring jEncoder, jstring jsrcPath,
+                                       jstring jMime, jintArray jList0, jintArray jList1,
+                                       jintArray jList2, jint colorFormat) {
+    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+    const char* cmime = env->GetStringUTFChars(jMime, nullptr);
+    const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
+    jsize cLen0 = env->GetArrayLength(jList0);
+    jint* cList0 = env->GetIntArrayElements(jList0, nullptr);
+    jsize cLen1 = env->GetArrayLength(jList1);
+    jint* cList1 = env->GetIntArrayElements(jList1, nullptr);
+    jsize cLen2 = env->GetArrayLength(jList2);
+    jint* cList2 = env->GetIntArrayElements(jList2, nullptr);
+    auto codecEncoderTest = new CodecEncoderTest(cmime, cList0, cLen0, cList1, cLen1, cList2, cLen2,
+                                                 (int)colorFormat);
+    bool isPass = codecEncoderTest->testSimpleEncode(cEncoder, csrcPath);
+    delete codecEncoderTest;
+    env->ReleaseIntArrayElements(jList0, cList0, 0);
+    env->ReleaseIntArrayElements(jList1, cList1, 0);
+    env->ReleaseIntArrayElements(jList2, cList2, 0);
+    env->ReleaseStringUTFChars(jEncoder, cEncoder);
+    env->ReleaseStringUTFChars(jMime, cmime);
+    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestFlush(JNIEnv* env, jobject, jstring jEncoder, jstring jsrcPath,
+                                jstring jMime, jintArray jList0, jintArray jList1, jintArray jList2,
+                                jint colorFormat) {
+    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+    const char* cmime = env->GetStringUTFChars(jMime, nullptr);
+    const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
+    jsize cLen0 = env->GetArrayLength(jList0);
+    jint* cList0 = env->GetIntArrayElements(jList0, nullptr);
+    jsize cLen1 = env->GetArrayLength(jList1);
+    jint* cList1 = env->GetIntArrayElements(jList1, nullptr);
+    jsize cLen2 = env->GetArrayLength(jList2);
+    jint* cList2 = env->GetIntArrayElements(jList2, nullptr);
+    auto codecEncoderTest = new CodecEncoderTest(cmime, cList0, cLen0, cList1, cLen1, cList2, cLen2,
+                                                 (int)colorFormat);
+    bool isPass = codecEncoderTest->testFlush(cEncoder, csrcPath);
+    delete codecEncoderTest;
+    env->ReleaseIntArrayElements(jList0, cList0, 0);
+    env->ReleaseIntArrayElements(jList1, cList1, 0);
+    env->ReleaseIntArrayElements(jList2, cList2, 0);
+    env->ReleaseStringUTFChars(jEncoder, cEncoder);
+    env->ReleaseStringUTFChars(jMime, cmime);
+    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestReconfigure(JNIEnv* env, jobject, jstring jEncoder, jstring jsrcPath,
+                                      jstring jMime, jintArray jList0, jintArray jList1,
+                                      jintArray jList2, jint colorFormat) {
+    bool isPass;
+    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+    const char* cmime = env->GetStringUTFChars(jMime, nullptr);
+    const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
+    jsize cLen0 = env->GetArrayLength(jList0);
+    jint* cList0 = env->GetIntArrayElements(jList0, nullptr);
+    jsize cLen1 = env->GetArrayLength(jList1);
+    jint* cList1 = env->GetIntArrayElements(jList1, nullptr);
+    jsize cLen2 = env->GetArrayLength(jList2);
+    jint* cList2 = env->GetIntArrayElements(jList2, nullptr);
+    auto codecEncoderTest = new CodecEncoderTest(cmime, cList0, cLen0, cList1, cLen1, cList2, cLen2,
+                                                 (int)colorFormat);
+    isPass = codecEncoderTest->testReconfigure(cEncoder, csrcPath);
+    delete codecEncoderTest;
+    env->ReleaseIntArrayElements(jList0, cList0, 0);
+    env->ReleaseIntArrayElements(jList1, cList1, 0);
+    env->ReleaseIntArrayElements(jList2, cList2, 0);
+    env->ReleaseStringUTFChars(jEncoder, cEncoder);
+    env->ReleaseStringUTFChars(jMime, cmime);
+    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSetForceSyncFrame(JNIEnv* env, jobject, jstring jEncoder,
+                                            jstring jsrcPath, jstring jMime, jintArray jList0,
+                                            jintArray jList1, jintArray jList2, jint colorFormat) {
+    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+    const char* cmime = env->GetStringUTFChars(jMime, nullptr);
+    const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
+    jsize cLen0 = env->GetArrayLength(jList0);
+    jint* cList0 = env->GetIntArrayElements(jList0, nullptr);
+    jsize cLen1 = env->GetArrayLength(jList1);
+    jint* cList1 = env->GetIntArrayElements(jList1, nullptr);
+    jsize cLen2 = env->GetArrayLength(jList2);
+    jint* cList2 = env->GetIntArrayElements(jList2, nullptr);
+    auto codecEncoderTest = new CodecEncoderTest(cmime, cList0, cLen0, cList1, cLen1, cList2, cLen2,
+                                                 (int)colorFormat);
+    bool isPass = codecEncoderTest->testSetForceSyncFrame(cEncoder, csrcPath);
+    delete codecEncoderTest;
+    env->ReleaseIntArrayElements(jList0, cList0, 0);
+    env->ReleaseIntArrayElements(jList1, cList1, 0);
+    env->ReleaseIntArrayElements(jList2, cList2, 0);
+    env->ReleaseStringUTFChars(jEncoder, cEncoder);
+    env->ReleaseStringUTFChars(jMime, cmime);
+    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestAdaptiveBitRate(JNIEnv* env, jobject, jstring jEncoder, jstring jsrcPath,
+                                          jstring jMime, jintArray jList0, jintArray jList1,
+                                          jintArray jList2, jint colorFormat) {
+    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+    const char* cmime = env->GetStringUTFChars(jMime, nullptr);
+    const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
+    jsize cLen0 = env->GetArrayLength(jList0);
+    jint* cList0 = env->GetIntArrayElements(jList0, nullptr);
+    jsize cLen1 = env->GetArrayLength(jList1);
+    jint* cList1 = env->GetIntArrayElements(jList1, nullptr);
+    jsize cLen2 = env->GetArrayLength(jList2);
+    jint* cList2 = env->GetIntArrayElements(jList2, nullptr);
+    auto codecEncoderTest = new CodecEncoderTest(cmime, cList0, cLen0, cList1, cLen1, cList2, cLen2,
+                                                 (int)colorFormat);
+    bool isPass = codecEncoderTest->testAdaptiveBitRate(cEncoder, csrcPath);
+    delete codecEncoderTest;
+    env->ReleaseIntArrayElements(jList0, cList0, 0);
+    env->ReleaseIntArrayElements(jList1, cList1, 0);
+    env->ReleaseIntArrayElements(jList2, cList2, 0);
+    env->ReleaseStringUTFChars(jEncoder, cEncoder);
+    env->ReleaseStringUTFChars(jMime, cmime);
+    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestOnlyEos(JNIEnv* env, jobject, jstring jEncoder, jstring jMime,
+                                  jintArray jList0, jintArray jList1, jintArray jList2,
+                                  jint colorFormat) {
+    const char* cmime = env->GetStringUTFChars(jMime, nullptr);
+    const char* cEncoder = env->GetStringUTFChars(jEncoder, nullptr);
+    jsize cLen0 = env->GetArrayLength(jList0);
+    jint* cList0 = env->GetIntArrayElements(jList0, nullptr);
+    jsize cLen1 = env->GetArrayLength(jList1);
+    jint* cList1 = env->GetIntArrayElements(jList1, nullptr);
+    jsize cLen2 = env->GetArrayLength(jList2);
+    jint* cList2 = env->GetIntArrayElements(jList2, nullptr);
+    auto codecEncoderTest = new CodecEncoderTest(cmime, cList0, cLen0, cList1, cLen1, cList2, cLen2,
+                                                 (int)colorFormat);
+    bool isPass = codecEncoderTest->testOnlyEos(cEncoder);
+    delete codecEncoderTest;
+    env->ReleaseIntArrayElements(jList0, cList0, 0);
+    env->ReleaseIntArrayElements(jList1, cList1, 0);
+    env->ReleaseIntArrayElements(jList2, cList2, 0);
+    env->ReleaseStringUTFChars(jEncoder, cEncoder);
+    env->ReleaseStringUTFChars(jMime, cmime);
+    return static_cast<jboolean>(isPass);
+}
+
+int registerAndroidMediaV2CtsEncoderTest(JNIEnv* env) {
+    const JNINativeMethod methodTable[] = {
+            {"nativeTestSimpleEncode",
+             "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I[I[II)Z",
+             (void*)nativeTestSimpleEncode},
+            {"nativeTestFlush", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I[I[II)Z",
+             (void*)nativeTestFlush},
+            {"nativeTestReconfigure",
+             "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I[I[II)Z",
+             (void*)nativeTestReconfigure},
+            {"nativeTestSetForceSyncFrame",
+             "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I[I[II)Z",
+             (void*)nativeTestSetForceSyncFrame},
+            {"nativeTestAdaptiveBitRate",
+             "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;[I[I[II)Z",
+             (void*)nativeTestAdaptiveBitRate},
+            {"nativeTestOnlyEos", "(Ljava/lang/String;Ljava/lang/String;[I[I[II)Z",
+             (void*)nativeTestOnlyEos},
+    };
+    jclass c = env->FindClass("android/mediav2/cts/CodecEncoderTest");
+    return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
+}
+
+extern int registerAndroidMediaV2CtsCodecUnitTest(JNIEnv* env);
+extern int registerAndroidMediaV2CtsDecoderTest(JNIEnv* env);
+
+extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
+    JNIEnv* env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR;
+    if (registerAndroidMediaV2CtsCodecUnitTest(env) != JNI_OK) return JNI_ERR;
+    if (registerAndroidMediaV2CtsEncoderTest(env) != JNI_OK) return JNI_ERR;
+    if (registerAndroidMediaV2CtsDecoderTest(env) != JNI_OK) return JNI_ERR;
+    return JNI_VERSION_1_6;
+}
\ No newline at end of file
diff --git a/tests/media/jni/NativeCodecTestBase.cpp b/tests/media/jni/NativeCodecTestBase.cpp
new file mode 100644
index 0000000..750abe6
--- /dev/null
+++ b/tests/media/jni/NativeCodecTestBase.cpp
@@ -0,0 +1,577 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeCodecTestBase"
+#include <log/log.h>
+
+#include "NativeCodecTestBase.h"
+
+static void onAsyncInputAvailable(AMediaCodec* codec, void* userdata, int32_t index) {
+    (void)codec;
+    assert(index >= 0);
+    auto* aSyncHandle = static_cast<CodecAsyncHandler*>(userdata);
+    callbackObject element{index};
+    aSyncHandle->pushToInputList(element);
+}
+
+static void onAsyncOutputAvailable(AMediaCodec* codec, void* userdata, int32_t index,
+                                   AMediaCodecBufferInfo* bufferInfo) {
+    (void)codec;
+    assert(index >= 0);
+    auto* aSyncHandle = static_cast<CodecAsyncHandler*>(userdata);
+    callbackObject element{index, bufferInfo};
+    aSyncHandle->pushToOutputList(element);
+}
+
+static void onAsyncFormatChanged(AMediaCodec* codec, void* userdata, AMediaFormat* format) {
+    (void)codec;
+    auto* aSyncHandle = static_cast<CodecAsyncHandler*>(userdata);
+    aSyncHandle->setOutputFormat(format);
+    ALOGI("Output format changed: %s", AMediaFormat_toString(format));
+}
+
+static void onAsyncError(AMediaCodec* codec, void* userdata, media_status_t error,
+                         int32_t actionCode, const char* detail) {
+    (void)codec;
+    auto* aSyncHandle = static_cast<CodecAsyncHandler*>(userdata);
+    aSyncHandle->setError(true);
+    ALOGE("received media codec error: %s , code : %d , action code: %d ", detail, error,
+          actionCode);
+}
+
+CodecAsyncHandler::CodecAsyncHandler() {
+    mOutFormat = nullptr;
+    mSignalledOutFormatChanged = false;
+    mSignalledError = false;
+}
+
+CodecAsyncHandler::~CodecAsyncHandler() {
+    if (mOutFormat) {
+        AMediaFormat_delete(mOutFormat);
+        mOutFormat = nullptr;
+    }
+}
+
+void CodecAsyncHandler::pushToInputList(callbackObject element) {
+    std::unique_lock<std::mutex> lock{mMutex};
+    mCbInputQueue.push_back(element);
+    mCondition.notify_all();
+}
+
+void CodecAsyncHandler::pushToOutputList(callbackObject element) {
+    std::unique_lock<std::mutex> lock{mMutex};
+    mCbOutputQueue.push_back(element);
+    mCondition.notify_all();
+}
+
+callbackObject CodecAsyncHandler::getInput() {
+    callbackObject element{-1};
+    std::unique_lock<std::mutex> lock{mMutex};
+    while (!mSignalledError) {
+        if (mCbInputQueue.empty()) {
+            mCondition.wait(lock);
+        } else {
+            element = mCbInputQueue.front();
+            mCbInputQueue.pop_front();
+            break;
+        }
+    }
+    return element;
+}
+
+callbackObject CodecAsyncHandler::getOutput() {
+    callbackObject element;
+    std::unique_lock<std::mutex> lock{mMutex};
+    while (!mSignalledError) {
+        if (mCbOutputQueue.empty()) {
+            mCondition.wait(lock);
+        } else {
+            element = mCbOutputQueue.front();
+            mCbOutputQueue.pop_front();
+            break;
+        }
+    }
+    return element;
+}
+
+callbackObject CodecAsyncHandler::getWork() {
+    callbackObject element;
+    std::unique_lock<std::mutex> lock{mMutex};
+    while (!mSignalledError) {
+        if (mCbInputQueue.empty() && mCbOutputQueue.empty()) {
+            mCondition.wait(lock);
+        } else {
+            if (!mCbOutputQueue.empty()) {
+                element = mCbOutputQueue.front();
+                mCbOutputQueue.pop_front();
+                break;
+            } else {
+                element = mCbInputQueue.front();
+                mCbInputQueue.pop_front();
+                break;
+            }
+        }
+    }
+    return element;
+}
+
+bool CodecAsyncHandler::isInputQueueEmpty() {
+    std::unique_lock<std::mutex> lock{mMutex};
+    return mCbInputQueue.empty();
+}
+
+void CodecAsyncHandler::clearQueues() {
+    std::unique_lock<std::mutex> lock{mMutex};
+    mCbInputQueue.clear();
+    mCbOutputQueue.clear();
+}
+
+void CodecAsyncHandler::setOutputFormat(AMediaFormat* format) {
+    assert(format != nullptr);
+    if (mOutFormat) {
+        AMediaFormat_delete(mOutFormat);
+        mOutFormat = nullptr;
+    }
+    mOutFormat = format;
+    mSignalledOutFormatChanged = true;
+}
+
+AMediaFormat* CodecAsyncHandler::getOutputFormat() {
+    return mOutFormat;
+}
+
+bool CodecAsyncHandler::hasOutputFormatChanged() {
+    return mSignalledOutFormatChanged;
+}
+
+void CodecAsyncHandler::setError(bool status) {
+    std::unique_lock<std::mutex> lock{mMutex};
+    mSignalledError = status;
+    mCondition.notify_all();
+}
+
+bool CodecAsyncHandler::getError() {
+    return mSignalledError;
+}
+
+void CodecAsyncHandler::resetContext() {
+    clearQueues();
+    if (mOutFormat) {
+        AMediaFormat_delete(mOutFormat);
+        mOutFormat = nullptr;
+    }
+    mSignalledOutFormatChanged = false;
+    mSignalledError = false;
+}
+
+media_status_t CodecAsyncHandler::setCallBack(AMediaCodec* codec, bool isCodecInAsyncMode) {
+    media_status_t status = AMEDIA_OK;
+    if (isCodecInAsyncMode) {
+        AMediaCodecOnAsyncNotifyCallback callBack = {onAsyncInputAvailable, onAsyncOutputAvailable,
+                                                     onAsyncFormatChanged, onAsyncError};
+        status = AMediaCodec_setAsyncNotifyCallback(codec, callBack, this);
+    }
+    return status;
+}
+
+uint32_t OutputManager::adler32(const uint8_t* input, int offset, int len) {
+    constexpr uint32_t modAdler = 65521;
+    constexpr uint32_t overflowLimit = 5500;
+    uint32_t a = 1;
+    uint32_t b = 0;
+    for (int i = offset, count = 0; i < len; i++) {
+        a += input[i];
+        b += a;
+        count++;
+        if (count > overflowLimit) {
+            a %= modAdler;
+            b %= modAdler;
+            count = 0;
+        }
+    }
+    return b * 65536 + a;
+}
+
+bool OutputManager::isPtsStrictlyIncreasing(int64_t lastPts) {
+    bool result = true;
+    for (auto it1 = outPtsArray.cbegin(); it1 < outPtsArray.cend(); it1++) {
+        if (lastPts < *it1) {
+            lastPts = *it1;
+        } else {
+            ALOGE("Timestamp ordering check failed: last timestamp: %d / current timestamp: %d",
+                  (int)lastPts, (int)*it1);
+            result = false;
+            break;
+        }
+    }
+    return result;
+}
+
+bool OutputManager::isOutPtsListIdenticalToInpPtsList(bool requireSorting) {
+    bool isEqual = true;
+    std::sort(inpPtsArray.begin(), inpPtsArray.end());
+    if (requireSorting) {
+        std::sort(outPtsArray.begin(), outPtsArray.end());
+    }
+    if (outPtsArray != inpPtsArray) {
+        if (outPtsArray.size() != inpPtsArray.size()) {
+            ALOGE("input and output presentation timestamp list sizes are not identical sizes "
+                  "exp/rec %d/%d", (int)inpPtsArray.size(), (int)outPtsArray.size());
+            isEqual = false;
+        } else {
+            int count = 0;
+            for (auto it1 = outPtsArray.cbegin(), it2 = inpPtsArray.cbegin();
+                 it1 < outPtsArray.cend(); it1++, it2++) {
+                if (*it1 != *it2) {
+                    ALOGE("input output pts mismatch, exp/rec %d/%d", (int)*it2, (int)*it1);
+                    count++;
+                }
+                if (count == 20) {
+                    ALOGE("stopping after 20 mismatches ... ");
+                    break;
+                }
+            }
+            if (count != 0) isEqual = false;
+        }
+    }
+    return isEqual;
+}
+
+bool OutputManager::equals(const OutputManager* that) {
+    if (this == that) return true;
+    if (outPtsArray != that->outPtsArray) {
+        if (outPtsArray.size() != that->outPtsArray.size()) {
+            ALOGE("ref and test outputs presentation timestamp arrays are of unequal sizes %d, %d",
+                  (int)outPtsArray.size(), (int)that->outPtsArray.size());
+            return false;
+        } else {
+            int count = 0;
+            for (auto it1 = outPtsArray.cbegin(), it2 = that->outPtsArray.cbegin();
+                 it1 < outPtsArray.cend(); it1++, it2++) {
+                if (*it1 != *it2) {
+                    ALOGE("presentation timestamp exp/rec %d/%d", (int)*it1, (int)*it2);
+                    count++;
+                }
+                if (count == 20) {
+                    ALOGE("stopping after 20 mismatches ... ");
+                    break;
+                }
+            }
+            if (count != 0) return false;
+        }
+    }
+    if (memory != that->memory) {
+        if (memory.size() != that->memory.size()) {
+            ALOGE("ref and test outputs decoded buffer are of unequal sizes %d, %d",
+                  (int)memory.size(), (int)that->memory.size());
+            return false;
+        } else {
+            int count = 0;
+            for (auto it1 = memory.cbegin(), it2 = that->memory.cbegin(); it1 < memory.cend();
+                 it1++, it2++) {
+                if (*it1 != *it2) {
+                    ALOGE("decoded sample exp/rec %d/%d", (int)*it1, (int)*it2);
+                    count++;
+                }
+                if (count == 20) {
+                    ALOGE("stopping after 20 mismatches ... ");
+                    break;
+                }
+            }
+            if (count != 0) return false;
+        }
+    }
+    if (checksum != that->checksum) {
+        if (checksum.size() != that->checksum.size()) {
+            ALOGE("ref and test outputs checksum arrays are of unequal sizes %d, %d",
+                  (int)checksum.size(), (int)that->checksum.size());
+            return false;
+        } else {
+            int count = 0;
+            for (auto it1 = checksum.cbegin(), it2 = that->checksum.cbegin(); it1 < checksum.cend();
+                 it1++, it2++) {
+                if (*it1 != *it2) {
+                    ALOGE("adler32 checksum exp/rec %u/%u", (int)*it1, (int)*it2);
+                    count++;
+                }
+                if (count == 20) {
+                    ALOGE("stopping after 20 mismatches ... ");
+                    break;
+                }
+            }
+            if (count != 0) return false;
+        }
+    }
+    return true;
+}
+
+float OutputManager::getRmsError(uint8_t* refData, int length) {
+    long totalErrorSquared = 0;
+    if (length != memory.size()) return -1.0F;
+    if ((length % 2) != 0) return -1.0F;
+    auto* testData = new uint8_t[length];
+    std::copy(memory.begin(), memory.end(), testData);
+    auto* testDataReinterpret = reinterpret_cast<int16_t*>(testData);
+    auto* refDataReinterpret = reinterpret_cast<int16_t*>(refData);
+    for (int i = 0; i < length / 2; i++) {
+        int d = testDataReinterpret[i] - refDataReinterpret[i];
+        totalErrorSquared += d * d;
+    }
+    delete[] testData;
+    long avgErrorSquared = (totalErrorSquared / (length / 2));
+    return (float)sqrt(avgErrorSquared);
+}
+
+CodecTestBase::CodecTestBase(const char* mime) {
+    mMime = mime;
+    mIsAudio = strncmp(mime, "audio/", strlen("audio/")) == 0;
+    mIsCodecInAsyncMode = false;
+    mSawInputEOS = false;
+    mSawOutputEOS = false;
+    mSignalEOSWithLastFrame = false;
+    mInputCount = 0;
+    mOutputCount = 0;
+    mPrevOutputPts = INT32_MIN;
+    mSignalledOutFormatChanged = false;
+    mOutFormat = nullptr;
+    mSaveToMem = false;
+    mOutputBuff = nullptr;
+    mCodec = nullptr;
+}
+
+CodecTestBase::~CodecTestBase() {
+    if (mOutFormat) {
+        AMediaFormat_delete(mOutFormat);
+        mOutFormat = nullptr;
+    }
+    if (mCodec) {
+        AMediaCodec_delete(mCodec);
+        mCodec = nullptr;
+    }
+}
+
+bool CodecTestBase::configureCodec(AMediaFormat* format, bool isAsync, bool signalEOSWithLastFrame,
+                                   bool isEncoder) {
+    resetContext(isAsync, signalEOSWithLastFrame);
+    CHECK_STATUS(mAsyncHandle.setCallBack(mCodec, isAsync),
+                 "AMediaCodec_setAsyncNotifyCallback failed");
+    CHECK_STATUS(AMediaCodec_configure(mCodec, format, nullptr, nullptr,
+                                       isEncoder ? AMEDIACODEC_CONFIGURE_FLAG_ENCODE : 0),
+                 "AMediaCodec_configure failed");
+    return true;
+}
+
+bool CodecTestBase::flushCodec() {
+    CHECK_STATUS(AMediaCodec_flush(mCodec), "AMediaCodec_flush failed");
+    // TODO(b/147576107): is it ok to clearQueues right away or wait for some signal
+    mAsyncHandle.clearQueues();
+    mSawInputEOS = false;
+    mSawOutputEOS = false;
+    mInputCount = 0;
+    mOutputCount = 0;
+    mPrevOutputPts = INT32_MIN;
+    return true;
+}
+
+bool CodecTestBase::reConfigureCodec(AMediaFormat* format, bool isAsync,
+                                     bool signalEOSWithLastFrame, bool isEncoder) {
+    CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    return configureCodec(format, isAsync, signalEOSWithLastFrame, isEncoder);
+}
+
+void CodecTestBase::resetContext(bool isAsync, bool signalEOSWithLastFrame) {
+    mAsyncHandle.resetContext();
+    mIsCodecInAsyncMode = isAsync;
+    mSawInputEOS = false;
+    mSawOutputEOS = false;
+    mSignalEOSWithLastFrame = signalEOSWithLastFrame;
+    mInputCount = 0;
+    mOutputCount = 0;
+    mPrevOutputPts = INT32_MIN;
+    mSignalledOutFormatChanged = false;
+    if (mOutFormat) {
+        AMediaFormat_delete(mOutFormat);
+        mOutFormat = nullptr;
+    }
+}
+
+bool CodecTestBase::enqueueEOS(size_t bufferIndex) {
+    if (!hasSeenError() && !mSawInputEOS) {
+        CHECK_STATUS(AMediaCodec_queueInputBuffer(mCodec, bufferIndex, 0, 0, 0,
+                                                  AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM),
+                     "AMediaCodec_queueInputBuffer failed");
+        mSawInputEOS = true;
+        ALOGV("Queued End of Stream");
+    }
+    return !hasSeenError();
+}
+
+bool CodecTestBase::doWork(int frameLimit) {
+    bool isOk = true;
+    int frameCnt = 0;
+    if (mIsCodecInAsyncMode) {
+        // output processing after queuing EOS is done in waitForAllOutputs()
+        while (!hasSeenError() && isOk && !mSawInputEOS && frameCnt < frameLimit) {
+            callbackObject element = mAsyncHandle.getWork();
+            if (element.bufferIndex >= 0) {
+                if (element.isInput) {
+                    isOk = enqueueInput(element.bufferIndex);
+                    frameCnt++;
+                } else {
+                    isOk = dequeueOutput(element.bufferIndex, &element.bufferInfo);
+                }
+            }
+        }
+    } else {
+        AMediaCodecBufferInfo outInfo;
+        // output processing after queuing EOS is done in waitForAllOutputs()
+        while (isOk && !mSawInputEOS && frameCnt < frameLimit) {
+            ssize_t oBufferID = AMediaCodec_dequeueOutputBuffer(mCodec, &outInfo, kQDeQTimeOutUs);
+            if (oBufferID >= 0) {
+                isOk = dequeueOutput(oBufferID, &outInfo);
+            } else if (oBufferID == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+                if (mOutFormat) {
+                    AMediaFormat_delete(mOutFormat);
+                    mOutFormat = nullptr;
+                }
+                mOutFormat = AMediaCodec_getOutputFormat(mCodec);
+                mSignalledOutFormatChanged = true;
+            } else if (oBufferID == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+            } else if (oBufferID == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+            } else {
+                ALOGE("unexpected return value from *_dequeueOutputBuffer: %d", (int)oBufferID);
+                return false;
+            }
+            ssize_t iBufferId = AMediaCodec_dequeueInputBuffer(mCodec, kQDeQTimeOutUs);
+            if (iBufferId >= 0) {
+                isOk = enqueueInput(iBufferId);
+                frameCnt++;
+            } else if (iBufferId == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+            } else {
+                ALOGE("unexpected return value from *_dequeueInputBuffer: %d", (int)iBufferId);
+                return false;
+            }
+        }
+    }
+    return !hasSeenError() && isOk;
+}
+
+bool CodecTestBase::queueEOS() {
+    bool isOk = true;
+    if (mIsCodecInAsyncMode) {
+        if (!hasSeenError() && isOk && !mSawInputEOS) {
+            callbackObject element = mAsyncHandle.getInput();
+            if (element.bufferIndex >= 0) {
+                isOk = enqueueEOS(element.bufferIndex);
+            }
+        }
+    } else {
+        if (!mSawInputEOS) {
+            int bufferIndex = AMediaCodec_dequeueInputBuffer(mCodec, -1);
+            if (bufferIndex >= 0) {
+                isOk = enqueueEOS(bufferIndex);
+            } else {
+                ALOGE("unexpected return value from *_dequeueInputBuffer: %d", (int)bufferIndex);
+                return false;
+            }
+        }
+    }
+    return !hasSeenError() && isOk;
+}
+
+bool CodecTestBase::waitForAllOutputs() {
+    bool isOk = true;
+    if (mIsCodecInAsyncMode) {
+        while (!hasSeenError() && isOk && !mSawOutputEOS) {
+            callbackObject element = mAsyncHandle.getOutput();
+            if (element.bufferIndex >= 0) {
+                isOk = dequeueOutput(element.bufferIndex, &element.bufferInfo);
+            }
+        }
+    } else {
+        AMediaCodecBufferInfo outInfo;
+        while (!mSawOutputEOS) {
+            int bufferID = AMediaCodec_dequeueOutputBuffer(mCodec, &outInfo, kQDeQTimeOutUs);
+            if (bufferID >= 0) {
+                isOk = dequeueOutput(bufferID, &outInfo);
+            } else if (bufferID == AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED) {
+                if (mOutFormat) {
+                    AMediaFormat_delete(mOutFormat);
+                    mOutFormat = nullptr;
+                }
+                mOutFormat = AMediaCodec_getOutputFormat(mCodec);
+                mSignalledOutFormatChanged = true;
+            } else if (bufferID == AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+            } else if (bufferID == AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+            } else {
+                ALOGE("unexpected return value from *_dequeueOutputBuffer: %d", (int)bufferID);
+                return false;
+            }
+        }
+    }
+    return !hasSeenError() && isOk;
+}
+
+int CodecTestBase::getWidth(AMediaFormat* format) {
+    int width = -1;
+    int cropLeft, cropRight;
+    AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_WIDTH, &width);
+    if (AMediaFormat_getInt32(format, "crop-left", &cropLeft) &&
+        AMediaFormat_getInt32(format, "crop-right", &cropRight)) {
+        width = cropRight + 1 - cropLeft;
+    }
+    return width;
+}
+
+int CodecTestBase::getHeight(AMediaFormat* format) {
+    int height = -1;
+    int cropTop, cropBottom;
+    AMediaFormat_getInt32(format, AMEDIAFORMAT_KEY_HEIGHT, &height);
+    if (AMediaFormat_getInt32(format, "crop-top", &cropTop) &&
+        AMediaFormat_getInt32(format, "crop-bottom", &cropBottom)) {
+        height = cropBottom + 1 - cropTop;
+    }
+    return height;
+}
+
+bool CodecTestBase::isFormatSimilar(AMediaFormat* inpFormat, AMediaFormat* outFormat) {
+    const char *refMime = nullptr, *testMime = nullptr;
+    bool hasRefMime = AMediaFormat_getString(inpFormat, AMEDIAFORMAT_KEY_MIME, &refMime);
+    bool hasTestMime = AMediaFormat_getString(outFormat, AMEDIAFORMAT_KEY_MIME, &testMime);
+
+    if (!hasRefMime || !hasTestMime) return false;
+    if (!strncmp(refMime, "audio/", strlen("audio/"))) {
+        int32_t refSampleRate = -1;
+        int32_t testSampleRate = -2;
+        int32_t refNumChannels = -1;
+        int32_t testNumChannels = -2;
+        AMediaFormat_getInt32(inpFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE, &refSampleRate);
+        AMediaFormat_getInt32(outFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE, &testSampleRate);
+        AMediaFormat_getInt32(inpFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &refNumChannels);
+        AMediaFormat_getInt32(outFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &testNumChannels);
+        return refNumChannels == testNumChannels && refSampleRate == testSampleRate &&
+               (strncmp(testMime, "audio/", strlen("audio/")) == 0);
+    } else if (!strncmp(refMime, "video/", strlen("video/"))) {
+        int32_t refWidth = getWidth(inpFormat);
+        int32_t testWidth = getWidth(outFormat);
+        int32_t refHeight = getHeight(inpFormat);
+        int32_t testHeight = getHeight(outFormat);
+        return refWidth != -1 && refHeight != -1 && refWidth == testWidth &&
+               refHeight == testHeight && (strncmp(testMime, "video/", strlen("video/")) == 0);
+    }
+    return true;
+}
diff --git a/tests/media/jni/NativeCodecTestBase.h b/tests/media/jni/NativeCodecTestBase.h
new file mode 100644
index 0000000..65f4111e
--- /dev/null
+++ b/tests/media/jni/NativeCodecTestBase.h
@@ -0,0 +1,160 @@
+/*
+ * 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.
+ */
+
+#ifndef MEDIACTSNATIVE_NATIVE_CODEC_TEST_BASE_H
+#define MEDIACTSNATIVE_NATIVE_CODEC_TEST_BASE_H
+
+#include <NdkMediaCodec.h>
+
+#include <cmath>
+#include <cstdint>
+#include <list>
+#include <mutex>
+#include <vector>
+
+#define CHECK_STATUS(status, str)                  \
+    {                                              \
+        media_status_t val = (status);             \
+        if (AMEDIA_OK != val) {                    \
+            ALOGE("%s with error %d", (str), val); \
+            return false;                          \
+        }                                          \
+    }
+
+#define CHECK_ERR(val, strA, strB, result) \
+    if ((val)) {                           \
+        (result) = false;                  \
+        ALOGE("%s %s", (strA), (strB));    \
+    }
+
+struct callbackObject {
+    AMediaCodecBufferInfo bufferInfo;
+    int32_t bufferIndex;
+    bool isInput;
+
+    callbackObject(int32_t index, AMediaCodecBufferInfo* info)
+        : bufferInfo{*info}, bufferIndex{index}, isInput{false} {}
+
+    callbackObject(int32_t index) : bufferIndex{index}, isInput{true} {}
+
+    callbackObject() : bufferIndex{-1}, isInput{false} {}
+};
+
+class CodecAsyncHandler {
+  private:
+    std::mutex mMutex;
+    std::condition_variable mCondition;
+    std::list<callbackObject> mCbInputQueue;
+    std::list<callbackObject> mCbOutputQueue;
+    AMediaFormat* mOutFormat;
+    bool mSignalledOutFormatChanged;
+    volatile bool mSignalledError;
+
+  public:
+    CodecAsyncHandler();
+    ~CodecAsyncHandler();
+    void pushToInputList(callbackObject element);
+    void pushToOutputList(callbackObject element);
+    callbackObject getInput();
+    callbackObject getOutput();
+    callbackObject getWork();
+    bool isInputQueueEmpty();
+    void clearQueues();
+    void setOutputFormat(AMediaFormat* format);
+    AMediaFormat* getOutputFormat();
+    bool hasOutputFormatChanged();
+    void setError(bool status);
+    bool getError();
+    void resetContext();
+    media_status_t setCallBack(AMediaCodec* codec, bool isCodecInAsyncMode);
+};
+
+class OutputManager {
+  private:
+    std::vector<int64_t> inpPtsArray;
+    std::vector<int64_t> outPtsArray;
+    std::vector<uint8_t> memory;
+    std::vector<uint32_t> checksum;
+
+    uint32_t adler32(const uint8_t* input, int offset, int len);
+
+  public:
+    void saveInPTS(int64_t pts) { inpPtsArray.push_back(pts); }
+    void saveOutPTS(int64_t pts) { outPtsArray.push_back(pts); }
+    bool isPtsStrictlyIncreasing(int64_t lastPts);
+    bool isOutPtsListIdenticalToInpPtsList(bool requireSorting);
+    void saveToMemory(uint8_t* buf, AMediaCodecBufferInfo* info) {
+        memory.insert(memory.end(), buf + info->offset, buf + info->size);
+    }
+    void saveChecksum(uint8_t* buf, AMediaCodecBufferInfo* info) {
+        checksum.push_back(adler32(buf, info->offset, info->size));
+    }
+    void reset() {
+        inpPtsArray.clear();
+        outPtsArray.clear();
+        memory.clear();
+        checksum.clear();
+    }
+    bool equals(const OutputManager* that);
+    float getRmsError(uint8_t* refData, int length);
+    int getOutStreamSize() { return memory.size(); }
+};
+
+class CodecTestBase {
+  protected:
+    const long kQDeQTimeOutUs = 5000;
+    const char* mMime;
+    bool mIsAudio;
+    CodecAsyncHandler mAsyncHandle;
+    bool mIsCodecInAsyncMode;
+    bool mSawInputEOS;
+    bool mSawOutputEOS;
+    bool mSignalEOSWithLastFrame;
+    int mInputCount;
+    int mOutputCount;
+    int64_t mPrevOutputPts;
+    bool mSignalledOutFormatChanged;
+    AMediaFormat* mOutFormat;
+
+    bool mSaveToMem;
+    OutputManager* mOutputBuff;
+    OutputManager mRefBuff;
+    OutputManager mTestBuff;
+    OutputManager mReconfBuff;
+
+    AMediaCodec* mCodec;
+
+    CodecTestBase(const char* mime);
+    ~CodecTestBase();
+    virtual bool configureCodec(AMediaFormat* format, bool isAsync, bool signalEOSWithLastFrame,
+                                bool isEncoder);
+    virtual bool flushCodec();
+    bool reConfigureCodec(AMediaFormat* format, bool isAsync, bool signalEOSWithLastFrame,
+                          bool isEncoder);
+    virtual void resetContext(bool isAsync, bool signalEOSWithLastFrame);
+    virtual bool enqueueInput(size_t bufferIndex) = 0;
+    virtual bool dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* bufferInfo) = 0;
+    bool enqueueEOS(size_t bufferIndex);
+    bool doWork(int frameLimit);
+    bool queueEOS();
+    bool waitForAllOutputs();
+    int getWidth(AMediaFormat* format);
+    int getHeight(AMediaFormat* format);
+    bool isFormatSimilar(AMediaFormat* inpFormat, AMediaFormat* outFormat);
+    bool hasSeenError() { return mAsyncHandle.getError(); }
+};
+
+#endif  // MEDIACTSNATIVE_NATIVE_CODEC_TEST_BASE_H
diff --git a/tests/media/jni/NativeCodecUnitTest.cpp b/tests/media/jni/NativeCodecUnitTest.cpp
new file mode 100644
index 0000000..dc06169
--- /dev/null
+++ b/tests/media/jni/NativeCodecUnitTest.cpp
@@ -0,0 +1,2073 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeCodecUnitTest"
+#include <NdkMediaExtractor.h>
+#include <jni.h>
+#include <log/log.h>
+#include <sys/stat.h>
+
+#include <thread>
+
+#include "NativeCodecTestBase.h"
+#include "NativeMediaConstants.h"
+
+class NativeCodecUnitTest final : CodecTestBase {
+  private:
+    AMediaFormat* mFormat;
+    bool enqueueInput(size_t bufferIndex) override;
+    bool dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* bufferInfo) override;
+
+    const long kStallTimeMs = 1000;
+
+  public:
+    NativeCodecUnitTest(const char* mime);
+    ~NativeCodecUnitTest();
+
+    bool setupCodec(bool isAudio, bool isEncoder);
+
+    bool testConfigureCodecForIncompleteFormat(bool isAudio, bool isEncoder);
+    bool testConfigureCodecForBadFlags(bool isEncoder);
+    bool testConfigureInInitState();
+    bool testConfigureInRunningState();
+    bool testConfigureInUnInitState();
+    bool testDequeueInputBufferInInitState();
+    bool testDequeueInputBufferInRunningState();
+    bool testDequeueInputBufferInUnInitState();
+    bool testDequeueOutputBufferInInitState();
+    bool testDequeueOutputBufferInRunningState();
+    bool testDequeueOutputBufferInUnInitState();
+    bool testFlushInInitState();
+    bool testFlushInRunningState();
+    bool testFlushInUnInitState();
+    bool testGetNameInInitState();
+    bool testGetNameInRunningState();
+    bool testGetNameInUnInitState();
+    bool testSetAsyncNotifyCallbackInInitState();
+    bool testSetAsyncNotifyCallbackInRunningState();
+    bool testSetAsyncNotifyCallbackInUnInitState();
+    bool testGetInputBufferInInitState();
+    bool testGetInputBufferInRunningState();
+    bool testGetInputBufferInUnInitState();
+    bool testGetInputFormatInInitState();
+    bool testGetInputFormatInRunningState();
+    bool testGetInputFormatInUnInitState();
+    bool testGetOutputBufferInInitState();
+    bool testGetOutputBufferInRunningState();
+    bool testGetOutputBufferInUnInitState();
+    bool testGetOutputFormatInInitState();
+    bool testGetOutputFormatInRunningState();
+    bool testGetOutputFormatInUnInitState();
+    bool testSetParametersInInitState();
+    bool testSetParametersInRunningState();
+    bool testSetParametersInUnInitState();
+    bool testStartInRunningState();
+    bool testStartInUnInitState();
+    bool testStopInInitState();
+    bool testStopInRunningState();
+    bool testStopInUnInitState();
+    bool testQueueInputBufferInInitState();
+    bool testQueueInputBufferWithBadIndex();
+    bool testQueueInputBufferWithBadSize();
+    bool testQueueInputBufferWithBadBuffInfo();
+    bool testQueueInputBufferWithBadOffset();
+    bool testQueueInputBufferInUnInitState();
+    bool testReleaseOutputBufferInInitState();
+    bool testReleaseOutputBufferInRunningState();
+    bool testReleaseOutputBufferInUnInitState();
+    bool testGetBufferFormatInInitState();
+    bool testGetBufferFormatInRunningState();
+    bool testGetBufferFormatInUnInitState();
+};
+
+NativeCodecUnitTest::NativeCodecUnitTest(const char* mime) : CodecTestBase(mime) {
+    mFormat = nullptr;
+}
+
+NativeCodecUnitTest::~NativeCodecUnitTest() {
+    if (mFormat) AMediaFormat_delete(mFormat);
+    mFormat = nullptr;
+}
+
+bool NativeCodecUnitTest::enqueueInput(size_t bufferIndex) {
+    (void)bufferIndex;
+    return false;
+}
+
+bool NativeCodecUnitTest::dequeueOutput(size_t bufferIndex, AMediaCodecBufferInfo* info) {
+    if ((info->flags & AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM) != 0) {
+        mSawOutputEOS = true;
+    }
+    CHECK_STATUS(AMediaCodec_releaseOutputBuffer(mCodec, bufferIndex, false),
+                 "AMediaCodec_releaseOutputBuffer failed");
+    return !hasSeenError();
+}
+
+AMediaFormat* getSampleAudioFormat() {
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_AUDIO_AAC);
+    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, 64000);
+    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, 16000);
+    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, 1);
+    return format;
+}
+
+AMediaFormat* getSampleVideoFormat() {
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_AVC);
+    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_BIT_RATE, 512000);
+    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_WIDTH, 352);
+    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_HEIGHT, 288);
+    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_FRAME_RATE, 30);
+    AMediaFormat_setFloat(format, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, 1.0F);
+    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+    return format;
+}
+
+bool NativeCodecUnitTest::setupCodec(bool isAudio, bool isEncoder) {
+    bool isPass = true;
+    mFormat = isAudio ? getSampleAudioFormat() : getSampleVideoFormat();
+    const char* mime = nullptr;
+    AMediaFormat_getString(mFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+    mCodec = isEncoder ? AMediaCodec_createEncoderByType(mime)
+                       : AMediaCodec_createDecoderByType(mime);
+    if (!mCodec) {
+        ALOGE("unable to create codec %s", mime);
+        isPass = false;
+    }
+    return isPass;
+}
+
+/* Structure to keep format key and their value to initialize format. Value can be of type
+ * string(stringVal) or int(intVal). At once, only one of stringVal or intVal is initialise with
+ * valid value. */
+struct formatKey {
+    const char* key = nullptr;
+    const char* stringVal = nullptr;
+    int32_t intVal = 0;
+};
+
+void setUpDefaultFormatElementsList(std::vector<formatKey*>& vec, bool isAudio, bool isEncoder) {
+    if (isAudio) {
+        vec.push_back(new formatKey{AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_AUDIO_AAC, -1});
+        vec.push_back(new formatKey{AMEDIAFORMAT_KEY_SAMPLE_RATE, nullptr, 16000});
+        vec.push_back(new formatKey{AMEDIAFORMAT_KEY_CHANNEL_COUNT, nullptr, 1});
+        if (isEncoder) {
+            vec.push_back(new formatKey{AMEDIAFORMAT_KEY_BIT_RATE, nullptr, 64000});
+        }
+    } else {
+        vec.push_back(new formatKey{AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_VIDEO_AVC, -1});
+        vec.push_back(new formatKey{AMEDIAFORMAT_KEY_WIDTH, nullptr, 176});
+        vec.push_back(new formatKey{AMEDIAFORMAT_KEY_HEIGHT, nullptr, 144});
+        if (isEncoder) {
+            vec.push_back(new formatKey{AMEDIAFORMAT_KEY_FRAME_RATE, nullptr, 24});
+            vec.push_back(new formatKey{AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, nullptr, 1});
+            vec.push_back(new formatKey{AMEDIAFORMAT_KEY_BIT_RATE, nullptr, 256000});
+            vec.push_back(new formatKey{AMEDIAFORMAT_KEY_COLOR_FORMAT, nullptr,
+                                        COLOR_FormatYUV420Flexible});
+        }
+    }
+}
+
+void deleteDefaultFormatElementsList(std::vector<formatKey*>& vector) {
+    for (int i = 0; i < vector.size(); i++) delete vector.at(i);
+}
+
+AMediaFormat* getSampleFormat(std::vector<formatKey*> vector, int skipIndex) {
+    AMediaFormat* format = AMediaFormat_new();
+    for (int i = 0; i < vector.size(); i++) {
+        if (skipIndex == i) continue;
+        formatKey* element = vector.at(i);
+        if (element->stringVal) {
+            AMediaFormat_setString(format, element->key, element->stringVal);
+        } else {
+            AMediaFormat_setInt32(format, element->key, element->intVal);
+        }
+    }
+    return format;
+}
+
+bool NativeCodecUnitTest::testConfigureCodecForIncompleteFormat(bool isAudio, bool isEncoder) {
+    const char* mime = isAudio ? AMEDIA_MIMETYPE_AUDIO_AAC : AMEDIA_MIMETYPE_VIDEO_AVC;
+    mCodec = isEncoder ? AMediaCodec_createEncoderByType(mime)
+                       : AMediaCodec_createDecoderByType(mime);
+    if (!mCodec) {
+        ALOGE("unable to create codec %s", mime);
+        return false;
+    }
+    std::vector<formatKey*> vector;
+    bool isPass = true;
+    setUpDefaultFormatElementsList(vector, isAudio, isEncoder);
+    AMediaFormat* format = nullptr;
+    int i;
+    for (i = 0; i < vector.size(); i++) {
+        if (!isPass) break;
+        format = getSampleFormat(vector, i);
+        if (AMEDIA_OK == AMediaCodec_configure(mCodec, format, nullptr, nullptr,
+                                               isEncoder ? AMEDIACODEC_CONFIGURE_FLAG_ENCODE : 0)) {
+            ALOGE("codec configure succeeds for format with missing key %s", vector.at(i)->key);
+            isPass = false;
+        }
+        AMediaFormat_delete(format);
+    }
+    format = getSampleFormat(vector, i);
+    if (AMEDIA_OK != AMediaCodec_configure(mCodec, format, nullptr, nullptr,
+                                           isEncoder ? AMEDIACODEC_CONFIGURE_FLAG_ENCODE : 0)) {
+        ALOGE("codec configure fails for valid format %s", AMediaFormat_toString(format));
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    deleteDefaultFormatElementsList(vector);
+    return isPass;
+}
+
+bool NativeCodecUnitTest::testConfigureCodecForBadFlags(bool isEncoder) {
+    bool isAudio = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    bool isPass = true;
+    if (AMEDIA_OK == AMediaCodec_configure(mCodec, mFormat, nullptr, nullptr,
+                                           isEncoder ? 0 : AMEDIACODEC_CONFIGURE_FLAG_ENCODE)) {
+        isPass = false;
+        ALOGE("codec configure succeeds with bad configure flag");
+    }
+    AMediaCodec_stop(mCodec);
+    return isPass;
+}
+
+bool NativeCodecUnitTest::testConfigureInInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, true, isEncoder)) return false;
+        if (AMEDIA_OK == AMediaCodec_configure(mCodec, mFormat, nullptr, nullptr,
+                                               isEncoder ? AMEDIACODEC_CONFIGURE_FLAG_ENCODE : 0)) {
+            ALOGE("codec configure succeeds in initialized state");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testConfigureInRunningState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        if (AMEDIA_OK == AMediaCodec_configure(mCodec, mFormat, nullptr, nullptr,
+                                               isEncoder ? AMEDIACODEC_CONFIGURE_FLAG_ENCODE : 0)) {
+            ALOGE("codec configure succeeds in initialized state");
+            return false;
+        }
+        if (!flushCodec()) return false;
+        if (AMEDIA_OK == AMediaCodec_configure(mCodec, mFormat, nullptr, nullptr,
+                                               isEncoder ? AMEDIACODEC_CONFIGURE_FLAG_ENCODE : 0)) {
+            ALOGE("codec configure succeeds in flush state");
+            return false;
+        }
+        if (mIsCodecInAsyncMode) {
+            CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        }
+        if (!queueEOS()) return false;
+        if (AMEDIA_OK == AMediaCodec_configure(mCodec, mFormat, nullptr, nullptr,
+                                               isEncoder ? AMEDIACODEC_CONFIGURE_FLAG_ENCODE : 0)) {
+            ALOGE("codec configure succeeds in running state");
+            return false;
+        }
+        if (!waitForAllOutputs()) return false;
+        if (AMEDIA_OK == AMediaCodec_configure(mCodec, mFormat, nullptr, nullptr,
+                                               isEncoder ? AMEDIACODEC_CONFIGURE_FLAG_ENCODE : 0)) {
+            ALOGE("codec configure succeeds in eos state");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testConfigureInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        CHECK_STATUS(AMediaCodec_configure(mCodec, mFormat, nullptr, nullptr,
+                                           isEncoder ? AMEDIACODEC_CONFIGURE_FLAG_ENCODE : 0),
+                     "codec configure fails in uninitialized state");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testDequeueInputBufferInInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        if (AMediaCodec_dequeueInputBuffer(mCodec, kQDeQTimeOutUs) >=
+            AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+            ALOGE("dequeue input buffer succeeds in uninitialized state");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testDequeueInputBufferInRunningState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        if (mIsCodecInAsyncMode) {
+            if (AMediaCodec_dequeueInputBuffer(mCodec, kQDeQTimeOutUs) >=
+                AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+                ALOGE("dequeue input buffer succeeds in running state in async mode");
+                return false;
+            }
+        }
+        if (!queueEOS()) return false;
+        if (!waitForAllOutputs()) return false;
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testDequeueInputBufferInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (AMediaCodec_dequeueInputBuffer(mCodec, kQDeQTimeOutUs) >=
+            AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+            ALOGE("dequeue input buffer succeeds in uninitialized state");
+            return false;
+        }
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        if (AMediaCodec_dequeueInputBuffer(mCodec, kQDeQTimeOutUs) >= -1) {
+            ALOGE("dequeue input buffer succeeds in stopped state");
+            return false;
+        }
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testDequeueOutputBufferInInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        AMediaCodecBufferInfo outInfo;
+        if (AMediaCodec_dequeueOutputBuffer(mCodec, &outInfo, kQDeQTimeOutUs) >=
+            AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+            ALOGE("dequeue output buffer succeeds in uninitialized state");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testDequeueOutputBufferInRunningState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        if (mIsCodecInAsyncMode) {
+            AMediaCodecBufferInfo outInfo;
+            if (AMediaCodec_dequeueOutputBuffer(mCodec, &outInfo, kQDeQTimeOutUs) >=
+                AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+                ALOGE("dequeue output buffer succeeds in running state in async mode");
+                return false;
+            }
+        }
+        if (!queueEOS()) return false;
+        if (!waitForAllOutputs()) return false;
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testDequeueOutputBufferInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        AMediaCodecBufferInfo outInfo;
+        if (AMediaCodec_dequeueOutputBuffer(mCodec, &outInfo, kQDeQTimeOutUs) >=
+            AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+            ALOGE("dequeue output buffer succeeds in uninitialized state");
+            return false;
+        }
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        if (AMediaCodec_dequeueOutputBuffer(mCodec, &outInfo, kQDeQTimeOutUs) >=
+            AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED) {
+            ALOGE("dequeue output buffer succeeds in stopped state");
+            return false;
+        }
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testFlushInInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        if (flushCodec()) {
+            ALOGE("codec flush succeeds in uninitialized state");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testFlushInRunningState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    bool isAsync = true;
+    if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+    CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+    if (!flushCodec()) return false;
+    std::this_thread::sleep_for(std::chrono::milliseconds(kStallTimeMs));
+    if (!mAsyncHandle.isInputQueueEmpty()) {
+        ALOGE("received input buffer callback before start");
+        return false;
+    }
+    CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+    std::this_thread::sleep_for(std::chrono::milliseconds(kStallTimeMs));
+    if (mAsyncHandle.isInputQueueEmpty()) {
+        ALOGE("did not receive input buffer callback after start");
+        return false;
+    }
+    CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testFlushInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (flushCodec()) {
+            ALOGE("codec flush succeeds in uninitialized state");
+            return false;
+        }
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        if (flushCodec()) {
+            ALOGE("codec flush succeeds in uninitialized state");
+            return false;
+        }
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetNameInInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        char* name = nullptr;
+        if (AMEDIA_OK != AMediaCodec_getName(mCodec, &name) || !name) {
+            ALOGE("codec get metadata call fails in initialized state");
+            if (name) AMediaCodec_releaseName(mCodec, name);
+            return false;
+        }
+        if (name) AMediaCodec_releaseName(mCodec, name);
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetNameInRunningState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        char* name = nullptr;
+        if (AMEDIA_OK != AMediaCodec_getName(mCodec, &name) || !name) {
+            ALOGE("codec get metadata call fails in running state");
+            if (name) AMediaCodec_releaseName(mCodec, name);
+            return false;
+        }
+        if (name) AMediaCodec_releaseName(mCodec, name);
+        name = nullptr;
+        if (!queueEOS()) return false;
+        if (!waitForAllOutputs()) return false;
+        if (AMEDIA_OK != AMediaCodec_getName(mCodec, &name) || !name) {
+            ALOGE("codec get metadata call fails in running state");
+            if (name) AMediaCodec_releaseName(mCodec, name);
+            return false;
+        }
+        if (name) AMediaCodec_releaseName(mCodec, name);
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetNameInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    char* name = nullptr;
+    if (AMEDIA_OK != AMediaCodec_getName(mCodec, &name) || !name) {
+        ALOGE("codec get metadata call fails in uninitialized state");
+        if (name) AMediaCodec_releaseName(mCodec, name);
+        return false;
+    }
+    if (name) AMediaCodec_releaseName(mCodec, name);
+    name = nullptr;
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        if (AMEDIA_OK != AMediaCodec_getName(mCodec, &name) || !name) {
+            ALOGE("codec get metadata call fails in uninitialized state");
+            if (name) AMediaCodec_releaseName(mCodec, name);
+            return false;
+        }
+        if (name) AMediaCodec_releaseName(mCodec, name);
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testSetAsyncNotifyCallbackInInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    bool isAsync = true;
+
+    // configure component in sync mode
+    if (!configureCodec(mFormat, !isAsync, false, isEncoder)) return false;
+    // setCallBack in async mode
+    CHECK_STATUS(mAsyncHandle.setCallBack(mCodec, isAsync),
+                 "AMediaCodec_setAsyncNotifyCallback failed");
+    mIsCodecInAsyncMode = true;
+    CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+    if (!queueEOS()) return false;
+    if (!waitForAllOutputs()) return false;
+    CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+
+    // configure component in async mode
+    if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+    CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+    if (!queueEOS()) return false;
+    if (!waitForAllOutputs()) return false;
+    CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+
+    // configure component in async mode
+    if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+    CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    // configure component in sync mode
+    if (!reConfigureCodec(mFormat, !isAsync, false, isEncoder)) return false;
+    CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+    if (!queueEOS()) return false;
+    if (!waitForAllOutputs()) return false;
+    CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testSetAsyncNotifyCallbackInRunningState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        // setCallBack in async mode
+        if (AMEDIA_OK == mAsyncHandle.setCallBack(mCodec, true)) {
+            ALOGE("setAsyncNotifyCallback call succeeds in running state");
+            return false;
+        }
+        if (!queueEOS()) return false;
+        if (!waitForAllOutputs()) return false;
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testSetAsyncNotifyCallbackInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    bool isAsync = true;
+    // setCallBack in async mode
+    CHECK_STATUS(mAsyncHandle.setCallBack(mCodec, isAsync),
+                 "AMediaCodec_setAsyncNotifyCallback fails in uninitalized state");
+    CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    // configure component in sync mode
+    if (!reConfigureCodec(mFormat, !isAsync, false, isEncoder)) return false;
+    CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+    if (!queueEOS()) return false;
+    if (!waitForAllOutputs()) return false;
+    CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+
+    // setCallBack in async mode
+    CHECK_STATUS(mAsyncHandle.setCallBack(mCodec, isAsync),
+                 "AMediaCodec_setAsyncNotifyCallback fails in stopped state");
+    CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    // configure component in sync mode
+    if (!reConfigureCodec(mFormat, !isAsync, false, isEncoder)) return false;
+    CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+    if (!queueEOS()) return false;
+    if (!waitForAllOutputs()) return false;
+    CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetInputBufferInInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        size_t bufSize;
+        uint8_t* buf = AMediaCodec_getInputBuffer(mCodec, 0, &bufSize);
+        if (buf != nullptr) {
+            ALOGE("getInputBuffer succeeds in initialized state");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetInputBufferInRunningState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        size_t bufSize;
+        if (AMediaCodec_getInputBuffer(mCodec, -1, &bufSize) != nullptr) {
+            ALOGE("getInputBuffer succeeds for bad buffer index -1");
+            return false;
+        }
+        int bufferIndex = mIsCodecInAsyncMode ? mAsyncHandle.getInput().bufferIndex
+                                              : AMediaCodec_dequeueInputBuffer(mCodec, -1);
+        uint8_t* buf = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &bufSize);
+        if (buf == nullptr) {
+            ALOGE("getInputBuffer fails for valid index");
+            return false;
+        }
+        if (!enqueueEOS(bufferIndex)) return false;
+        if (!waitForAllOutputs()) return false;
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetInputBufferInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        size_t bufSize;
+        if (AMediaCodec_getInputBuffer(mCodec, 0, &bufSize) != nullptr) {
+            ALOGE("getInputBuffer succeeds in uninitialized state");
+            return false;
+        }
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        if (AMediaCodec_getInputBuffer(mCodec, 0, &bufSize) != nullptr) {
+            ALOGE("getInputBuffer succeeds in stopped state");
+            return false;
+        }
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetInputFormatInInitState() {
+    bool isAudio = true;
+    bool isEncoder = false;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    const char* mime = nullptr;
+    AMediaFormat_getString(mFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        AMediaFormat* dupFormat = AMediaCodec_getInputFormat(mCodec);
+        const char* dupMime = nullptr;
+        AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+        AMediaFormat_delete(dupFormat);
+        if (!dupMime || strcmp(dupMime, mime) != 0) {
+            ALOGE("getInputFormat fails in initialized state");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetInputFormatInRunningState() {
+    bool isAudio = true;
+    bool isEncoder = false;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    const char* mime = nullptr;
+    AMediaFormat_getString(mFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        AMediaFormat* dupFormat = AMediaCodec_getInputFormat(mCodec);
+        const char* dupMime = nullptr;
+        AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+        AMediaFormat_delete(dupFormat);
+        if (!dupMime || strcmp(dupMime, mime) != 0) {
+            ALOGE("getInputFormat fails in running state");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetInputFormatInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    const char* mime = nullptr;
+    AMediaFormat_getString(mFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+    for (auto isAsync : boolStates) {
+        AMediaFormat* dupFormat = AMediaCodec_getInputFormat(mCodec);
+        const char* dupMime = nullptr;
+        AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+        AMediaFormat_delete(dupFormat);
+        if (dupMime) {
+            ALOGE("getInputFormat succeeds in uninitialized state");
+            return false;
+        }
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        dupFormat = AMediaCodec_getInputFormat(mCodec);
+        dupMime = nullptr;
+        AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+        AMediaFormat_delete(dupFormat);
+        if (dupMime) {
+            ALOGE("getInputFormat succeeds in stopped state");
+            return false;
+        }
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetOutputBufferInInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        size_t bufSize;
+        if (AMediaCodec_getOutputBuffer(mCodec, 0, &bufSize) != nullptr) {
+            ALOGE("GetOutputBuffer succeeds in initialized state");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return true;
+}
+
+bool NativeCodecUnitTest::testGetOutputBufferInRunningState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    AMediaCodecBufferInfo outInfo;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        size_t bufSize;
+        if (AMediaCodec_getOutputBuffer(mCodec, -1, &bufSize)) {
+            ALOGE("GetOutputBuffer succeeds for bad buffer index -1");
+            return false;
+        }
+        if (!queueEOS()) return false;
+        bool isOk = true;
+        if (!hasSeenError()) {
+            int bufferIndex = 0;
+            size_t buffSize;
+            while (!mSawOutputEOS && isOk) {
+                if (mIsCodecInAsyncMode) {
+                    callbackObject element = mAsyncHandle.getOutput();
+                    bufferIndex = element.bufferIndex;
+                    if (element.bufferIndex >= 0) {
+                        if (!AMediaCodec_getOutputBuffer(mCodec, bufferIndex, &buffSize)) {
+                            ALOGE("GetOutputBuffer fails for valid bufffer index");
+                            return false;
+                        }
+                        isOk = dequeueOutput(element.bufferIndex, &element.bufferInfo);
+                    }
+                } else {
+                    bufferIndex = AMediaCodec_dequeueOutputBuffer(mCodec, &outInfo, kQDeQTimeOutUs);
+                    if (bufferIndex >= 0) {
+                        if (!AMediaCodec_getOutputBuffer(mCodec, bufferIndex, &buffSize)) {
+                            ALOGE("GetOutputBuffer fails for valid bufffer index");
+                            return false;
+                        }
+                        isOk = dequeueOutput(bufferIndex, &outInfo);
+                    }
+                }
+                if (hasSeenError() || !isOk) {
+                    ALOGE("Got unexpected error");
+                    return false;
+                }
+            }
+            if (AMediaCodec_getOutputBuffer(mCodec, bufferIndex, &bufSize) != nullptr) {
+                ALOGE("getOutputBuffer succeeds for buffer index not owned by client");
+                return false;
+            }
+        } else {
+            ALOGE("Got unexpected error");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetOutputBufferInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        size_t bufSize;
+        if (AMediaCodec_getOutputBuffer(mCodec, 0, &bufSize) != nullptr) {
+            ALOGE("GetOutputBuffer succeeds in uninitialized state");
+            return false;
+        }
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        if (AMediaCodec_getOutputBuffer(mCodec, 0, &bufSize) != nullptr) {
+            ALOGE("GetOutputBuffer succeeds in stopped state");
+            return false;
+        }
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetOutputFormatInInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const char* mime = nullptr;
+    AMediaFormat_getString(mFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        AMediaFormat* dupFormat = AMediaCodec_getOutputFormat(mCodec);
+        const char* dupMime = nullptr;
+        AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+        AMediaFormat_delete(dupFormat);
+        if (!dupMime || strcmp(dupMime, mime) != 0) {
+            ALOGE("getOutputFormat fails in initialized state");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetOutputFormatInRunningState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const char* mime = nullptr;
+    AMediaFormat_getString(mFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        AMediaFormat* dupFormat = AMediaCodec_getOutputFormat(mCodec);
+        const char* dupMime = nullptr;
+        AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+        AMediaFormat_delete(dupFormat);
+        if (!dupMime || strcmp(dupMime, mime) != 0) {
+            ALOGE("getOutputFormat fails in running state");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetOutputFormatInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        AMediaFormat* dupFormat = AMediaCodec_getOutputFormat(mCodec);
+        const char* dupMime = nullptr;
+        AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+        AMediaFormat_delete(dupFormat);
+        if (dupMime) {
+            ALOGE("getOutputFormat succeeds in uninitialized state");
+            return false;
+        }
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        dupFormat = AMediaCodec_getOutputFormat(mCodec);
+        dupMime = nullptr;
+        AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+        AMediaFormat_delete(dupFormat);
+        if (dupMime) {
+            ALOGE("getOutputFormat succeeds in stopped state");
+            return false;
+        }
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testSetParametersInInitState() {
+    bool isAudio = false;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        int bitrate;
+        AMediaFormat_getInt32(mFormat, AMEDIAFORMAT_KEY_BIT_RATE, &bitrate);
+        AMediaFormat* params = AMediaFormat_new();
+        AMediaFormat_setInt32(params, TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE, bitrate >> 1);
+        if (AMEDIA_OK == AMediaCodec_setParameters(mCodec, params)) {
+            ALOGE("SetParameters succeeds in initialized state");
+            AMediaFormat_delete(params);
+            return false;
+        }
+        AMediaFormat_delete(params);
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testSetParametersInRunningState() {
+    bool isAudio = false;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    int bitrate;
+    AMediaFormat_getInt32(mFormat, AMEDIAFORMAT_KEY_BIT_RATE, &bitrate);
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        // behaviour of setParams with null argument is acceptable according to SDK
+        AMediaCodec_setParameters(mCodec, nullptr);
+        AMediaFormat* params = AMediaFormat_new();
+        AMediaFormat_setInt32(params, TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE, bitrate >> 1);
+        if (AMEDIA_OK != AMediaCodec_setParameters(mCodec, params)) {
+            ALOGE("SetParameters fails in running state");
+            AMediaFormat_delete(params);
+            return false;
+        }
+        if (!queueEOS()) return false;
+        AMediaCodec_setParameters(mCodec, nullptr);
+        AMediaFormat_setInt32(mFormat, TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE, bitrate << 1);
+        if (AMEDIA_OK != AMediaCodec_setParameters(mCodec, mFormat)) {
+            ALOGE("SetParameters fails in running state");
+            AMediaFormat_delete(params);
+            return false;
+        }
+        if (!waitForAllOutputs()) return false;
+        AMediaCodec_setParameters(mCodec, nullptr);
+        AMediaFormat_setInt32(mFormat, TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE, bitrate);
+        if (AMEDIA_OK != AMediaCodec_setParameters(mCodec, mFormat)) {
+            ALOGE("SetParameters fails in running state");
+            AMediaFormat_delete(params);
+            return false;
+        }
+        AMediaFormat_delete(params);
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testSetParametersInUnInitState() {
+    bool isAudio = false;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        int bitrate;
+        AMediaFormat_getInt32(mFormat, AMEDIAFORMAT_KEY_BIT_RATE, &bitrate);
+        AMediaFormat* params = AMediaFormat_new();
+        AMediaFormat_setInt32(params, TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE, bitrate >> 1);
+        if (AMEDIA_OK == AMediaCodec_setParameters(mCodec, params)) {
+            ALOGE("SetParameters succeeds in stopped state");
+            AMediaFormat_delete(params);
+            return false;
+        }
+        AMediaFormat_delete(params);
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testStartInRunningState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    if (!configureCodec(mFormat, false, false, isEncoder)) return false;
+    CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+    if (AMEDIA_OK == AMediaCodec_start(mCodec)) {
+        ALOGE("Start succeeds in running state");
+        return false;
+    }
+    CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testStartInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    if (AMEDIA_OK == AMediaCodec_start(mCodec)) {
+        ALOGE("codec start succeeds before initialization");
+        return false;
+    }
+    if (!configureCodec(mFormat, false, false, isEncoder)) return false;
+    CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+    CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    if (AMEDIA_OK == AMediaCodec_start(mCodec)) {
+        ALOGE("codec start succeeds in stopped state");
+        return false;
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testStopInInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "Stop fails in initialized state");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testStopInRunningState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        if (!queueEOS()) return false;
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "Stop fails in running state");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testStopInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "Stop fails in stopped state");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testQueueInputBufferInInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        if (AMEDIA_OK == AMediaCodec_queueInputBuffer(mCodec, 0, 0, 0, 0,
+                                                      AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM)) {
+            ALOGE("queueInputBuffer succeeds in initialized state");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testQueueInputBufferWithBadIndex() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        if (AMEDIA_OK == AMediaCodec_queueInputBuffer(mCodec, -1, 0, 0, 0,
+                                                      AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM)) {
+            ALOGE("queueInputBuffer succeeds with bad buffer index :: -1");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testQueueInputBufferWithBadSize() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        int bufferIndex = mIsCodecInAsyncMode ? mAsyncHandle.getInput().bufferIndex
+                                              : AMediaCodec_dequeueInputBuffer(mCodec, -1);
+        size_t bufSize;
+        uint8_t* buf = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &bufSize);
+        if (buf == nullptr) {
+            ALOGE("getInputBuffer fails for valid index");
+            return false;
+        } else {
+            if (AMEDIA_OK == AMediaCodec_queueInputBuffer(mCodec, 0, 0, bufSize + 100, 0,
+                                                          AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM)) {
+                ALOGE("queueInputBuffer succeeds for bad size %d, buffer capacity %d, ",
+                      (int)bufSize + 100, (int)bufSize);
+                return false;
+            }
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testQueueInputBufferWithBadBuffInfo() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        int bufferIndex = mIsCodecInAsyncMode ? mAsyncHandle.getInput().bufferIndex
+                                              : AMediaCodec_dequeueInputBuffer(mCodec, -1);
+        size_t bufSize;
+        uint8_t* buf = AMediaCodec_getInputBuffer(mCodec, bufferIndex, &bufSize);
+        if (buf == nullptr) {
+            ALOGE("getInputBuffer fails for valid index");
+            return false;
+        } else {
+            if (AMEDIA_OK == AMediaCodec_queueInputBuffer(mCodec, 0, 16, bufSize, 0,
+                                                          AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM)) {
+                ALOGE("queueInputBuffer succeeds with bad offset and size param");
+                return false;
+            }
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testQueueInputBufferWithBadOffset() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        if (AMEDIA_OK == AMediaCodec_queueInputBuffer(mCodec, 0, -1, 0, 0,
+                                                      AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM)) {
+            ALOGE("queueInputBuffer succeeds with bad buffer offset :: -1");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testQueueInputBufferInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (AMEDIA_OK == AMediaCodec_queueInputBuffer(mCodec, 0, 0, 0, 0,
+                                                      AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM)) {
+            ALOGE("queueInputBuffer succeeds in uninitialized state");
+            return false;
+        }
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        if (AMEDIA_OK == AMediaCodec_queueInputBuffer(mCodec, 0, 0, 0, 0,
+                                                      AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM)) {
+            ALOGE("queueInputBuffer succeeds in stopped state");
+            return false;
+        }
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testReleaseOutputBufferInInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        if (AMEDIA_OK == AMediaCodec_releaseOutputBuffer(mCodec, 0, false)) {
+            ALOGE("ReleaseOutputBuffer succeeds in initialized state");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testReleaseOutputBufferInRunningState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    AMediaCodecBufferInfo outInfo;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        if (AMEDIA_OK == AMediaCodec_releaseOutputBuffer(mCodec, -1, false)) {
+            ALOGE("ReleaseOutputBuffer succeeds for bad buffer index -1");
+            return false;
+        }
+        if (!queueEOS()) return false;
+        if (!hasSeenError()) {
+            int bufferIndex = 0;
+            size_t buffSize;
+            bool isOk = true;
+            while (!mSawOutputEOS && isOk) {
+                if (mIsCodecInAsyncMode) {
+                    callbackObject element = mAsyncHandle.getOutput();
+                    bufferIndex = element.bufferIndex;
+                    if (element.bufferIndex >= 0) {
+                        if (!AMediaCodec_getOutputBuffer(mCodec, bufferIndex, &buffSize)) {
+                            ALOGE("GetOutputBuffer fails for valid buffer index");
+                            return false;
+                        }
+                        isOk = dequeueOutput(element.bufferIndex, &element.bufferInfo);
+                    }
+                } else {
+                    bufferIndex = AMediaCodec_dequeueOutputBuffer(mCodec, &outInfo, kQDeQTimeOutUs);
+                    if (bufferIndex >= 0) {
+                        if (!AMediaCodec_getOutputBuffer(mCodec, bufferIndex, &buffSize)) {
+                            ALOGE("GetOutputBuffer fails for valid bufffer index");
+                            return false;
+                        }
+                        isOk = dequeueOutput(bufferIndex, &outInfo);
+                    }
+                }
+                if (hasSeenError() || !isOk) {
+                    ALOGE("Got unexpected error");
+                    return false;
+                }
+            }
+            if (AMEDIA_OK == AMediaCodec_releaseOutputBuffer(mCodec, bufferIndex, false)) {
+                ALOGE("ReleaseOutputBuffer succeeds for buffer index not owned by client");
+                return false;
+            }
+        } else {
+            ALOGE("Got unexpected error");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testReleaseOutputBufferInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (AMEDIA_OK == AMediaCodec_releaseOutputBuffer(mCodec, 0, false)) {
+            ALOGE("ReleaseOutputBuffer succeeds in uninitialized state");
+            return false;
+        }
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        if (AMEDIA_OK == AMediaCodec_releaseOutputBuffer(mCodec, 0, false)) {
+            ALOGE("ReleaseOutputBuffer succeeds in stopped state");
+            return false;
+        }
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetBufferFormatInInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        AMediaFormat* dupFormat = AMediaCodec_getBufferFormat(mCodec, 0);
+        const char* dupMime = nullptr;
+        AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+        AMediaFormat_delete(dupFormat);
+        if (dupMime) {
+            ALOGE("GetBufferFormat succeeds in initialized state");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetBufferFormatInRunningState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const char* mime = nullptr;
+    AMediaFormat_getString(mFormat, AMEDIAFORMAT_KEY_MIME, &mime);
+    AMediaCodecBufferInfo outInfo;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        AMediaFormat* dupFormat = AMediaCodec_getBufferFormat(mCodec, -1);
+        const char* dupMime = nullptr;
+        AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+        AMediaFormat_delete(dupFormat);
+        if (dupMime) {
+            ALOGE("GetBufferFormat succeeds for bad buffer index -1");
+            return false;
+        }
+        if (!queueEOS()) return false;
+        if (!hasSeenError()) {
+            int bufferIndex = 0;
+            bool isOk = true;
+            while (!mSawOutputEOS && isOk) {
+                if (mIsCodecInAsyncMode) {
+                    callbackObject element = mAsyncHandle.getOutput();
+                    bufferIndex = element.bufferIndex;
+                    if (element.bufferIndex >= 0) {
+                        dupFormat = AMediaCodec_getBufferFormat(mCodec, bufferIndex);
+                        dupMime = nullptr;
+                        AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+                        AMediaFormat_delete(dupFormat);
+                        if (!dupMime || strcmp(dupMime, mime) != 0) {
+                            ALOGE("GetBufferFormat fails in running state");
+                            return false;
+                        }
+                        isOk = dequeueOutput(element.bufferIndex, &element.bufferInfo);
+                    }
+                } else {
+                    bufferIndex = AMediaCodec_dequeueOutputBuffer(mCodec, &outInfo, kQDeQTimeOutUs);
+                    if (bufferIndex >= 0) {
+                        dupFormat = AMediaCodec_getBufferFormat(mCodec, bufferIndex);
+                        dupMime = nullptr;
+                        AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+                        AMediaFormat_delete(dupFormat);
+                        if (!dupMime || strcmp(dupMime, mime) != 0) {
+                            ALOGE("GetBufferFormat fails in running state");
+                            return false;
+                        }
+                        isOk = dequeueOutput(bufferIndex, &outInfo);
+                    }
+                }
+                if (hasSeenError() || !isOk) {
+                    ALOGE("Got unexpected error");
+                    return false;
+                }
+            }
+            dupFormat = AMediaCodec_getBufferFormat(mCodec, bufferIndex);
+            dupMime = nullptr;
+            AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+            AMediaFormat_delete(dupFormat);
+            if (dupMime) {
+                ALOGE("GetBufferFormat succeeds for buffer index not owned by client");
+                return false;
+            }
+        } else {
+            ALOGE("Got unexpected error");
+            return false;
+        }
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+    }
+    return !hasSeenError();
+}
+
+bool NativeCodecUnitTest::testGetBufferFormatInUnInitState() {
+    bool isAudio = true;
+    bool isEncoder = true;
+    if (!setupCodec(isAudio, isEncoder)) return false;
+    const bool boolStates[]{true, false};
+    for (auto isAsync : boolStates) {
+        AMediaFormat* dupFormat = AMediaCodec_getBufferFormat(mCodec, 0);
+        const char* dupMime = nullptr;
+        AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+        AMediaFormat_delete(dupFormat);
+        if (dupMime) {
+            ALOGE("GetBufferFormat succeeds in uninitialized state");
+            return false;
+        }
+        if (!configureCodec(mFormat, isAsync, false, isEncoder)) return false;
+        CHECK_STATUS(AMediaCodec_start(mCodec), "AMediaCodec_start failed");
+        CHECK_STATUS(AMediaCodec_stop(mCodec), "AMediaCodec_stop failed");
+        dupFormat = AMediaCodec_getBufferFormat(mCodec, 0);
+        dupMime = nullptr;
+        AMediaFormat_getString(dupFormat, AMEDIAFORMAT_KEY_MIME, &dupMime);
+        AMediaFormat_delete(dupFormat);
+        if (dupMime) {
+            ALOGE("GetBufferFormat succeeds in stopped state");
+            return false;
+        }
+    }
+    return !hasSeenError();
+}
+
+static jboolean nativeTestCreateByCodecNameForNull(JNIEnv*, jobject) {
+    bool isPass = true;
+    AMediaCodec* codec = AMediaCodec_createCodecByName(nullptr);
+    if (codec) {
+        AMediaCodec_delete(codec);
+        ALOGE("AMediaCodec_createCodecByName succeeds with null argument");
+        isPass = false;
+    }
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestCreateByCodecNameForInvalidName(JNIEnv*, jobject) {
+    bool isPass = true;
+    AMediaCodec* codec = AMediaCodec_createCodecByName("invalid name");
+    if (codec) {
+        AMediaCodec_delete(codec);
+        ALOGE("AMediaCodec_createCodecByName succeeds with invalid name");
+        isPass = false;
+    }
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestCreateDecoderByTypeForNull(JNIEnv*, jobject) {
+    bool isPass = true;
+    AMediaCodec* codec = AMediaCodec_createDecoderByType(nullptr);
+    if (codec) {
+        AMediaCodec_delete(codec);
+        ALOGE("AMediaCodec_createDecoderByType succeeds with null argument");
+        isPass = false;
+    }
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestCreateDecoderByTypeForInvalidMime(JNIEnv*, jobject) {
+    bool isPass = true;
+    AMediaCodec* codec = AMediaCodec_createDecoderByType("invalid name");
+    if (codec) {
+        AMediaCodec_delete(codec);
+        ALOGE("AMediaCodec_createDecoderByType succeeds with invalid name");
+        isPass = false;
+    }
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestCreateEncoderByTypeForNull(JNIEnv*, jobject) {
+    bool isPass = true;
+    AMediaCodec* codec = AMediaCodec_createEncoderByType(nullptr);
+    if (codec) {
+        AMediaCodec_delete(codec);
+        ALOGE("AMediaCodec_createEncoderByType succeeds with null argument");
+        isPass = false;
+    }
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestCreateEncoderByTypeForInvalidMime(JNIEnv*, jobject) {
+    bool isPass = true;
+    AMediaCodec* codec = AMediaCodec_createEncoderByType("invalid name");
+    if (codec) {
+        AMediaCodec_delete(codec);
+        ALOGE("AMediaCodec_createEncoderByType succeeds with invalid name");
+        isPass = false;
+    }
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestConfigureForNullFormat(JNIEnv*, jobject) {
+    AMediaCodec* codec = AMediaCodec_createEncoderByType(AMEDIA_MIMETYPE_AUDIO_AAC);
+    if (!codec) {
+        ALOGE("unable to create codec %s", AMEDIA_MIMETYPE_AUDIO_AAC);
+        return static_cast<jboolean>(false);
+    }
+    bool isPass = (AMEDIA_OK != AMediaCodec_configure(codec, nullptr, nullptr, nullptr,
+                                                      AMEDIACODEC_CONFIGURE_FLAG_ENCODE));
+    if (!isPass) {
+        ALOGE("codec configure succeeds with null format");
+    }
+    AMediaCodec_delete(codec);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestConfigureForEmptyFormat(JNIEnv*, jobject) {
+    AMediaCodec* codec = AMediaCodec_createEncoderByType(AMEDIA_MIMETYPE_AUDIO_AAC);
+    if (!codec) {
+        ALOGE("unable to create codec %s", AMEDIA_MIMETYPE_AUDIO_AAC);
+        return static_cast<jboolean>(false);
+    }
+    AMediaFormat* format = AMediaFormat_new();
+    bool isPass = (AMEDIA_OK != AMediaCodec_configure(codec, format, nullptr, nullptr,
+                                                      AMEDIACODEC_CONFIGURE_FLAG_ENCODE));
+    if (!isPass) {
+        ALOGE("codec configure succeeds with empty format");
+    }
+    AMediaFormat_delete(format);
+    AMediaCodec_delete(codec);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestConfigureCodecForIncompleteFormat(JNIEnv*, jobject, bool isAudio,
+                                                            bool isEncoder) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testConfigureCodecForIncompleteFormat(isAudio, isEncoder);
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestConfigureEncoderForBadFlags(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isEncoder = true;
+    bool isPass = nativeCodecUnitTest->testConfigureCodecForBadFlags(isEncoder);
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestConfigureDecoderForBadFlags(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isEncoder = false;
+    bool isPass = nativeCodecUnitTest->testConfigureCodecForBadFlags(isEncoder);
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestConfigureInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testConfigureInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestConfigureInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testConfigureInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestConfigureInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testConfigureInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestDequeueInputBufferInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testDequeueInputBufferInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestDequeueInputBufferInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testDequeueInputBufferInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestDequeueInputBufferInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testDequeueInputBufferInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestDequeueOutputBufferInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testDequeueOutputBufferInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestDequeueOutputBufferInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testDequeueOutputBufferInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestDequeueOutputBufferInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testDequeueOutputBufferInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestFlushInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testFlushInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestFlushInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testFlushInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestFlushInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testFlushInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetNameInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetNameInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetNameInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetNameInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetNameInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetNameInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSetAsyncNotifyCallbackInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testSetAsyncNotifyCallbackInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSetAsyncNotifyCallbackInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testSetAsyncNotifyCallbackInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSetAsyncNotifyCallbackInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testSetAsyncNotifyCallbackInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetInputBufferInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetInputBufferInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetInputBufferInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetInputBufferInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetInputBufferInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetInputBufferInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetInputFormatInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetInputFormatInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetInputFormatInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetInputFormatInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetInputFormatInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetInputFormatInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetOutputBufferInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetOutputBufferInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetOutputBufferInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetOutputBufferInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetOutputBufferInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetOutputBufferInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetOutputFormatInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetOutputFormatInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetOutputFormatInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetOutputFormatInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetOutputFormatInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetOutputFormatInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSetParametersInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testSetParametersInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSetParametersInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testSetParametersInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSetParametersInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testSetParametersInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestStartInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testStartInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestStartInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testStartInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestStopInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testStopInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestStopInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testStopInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestStopInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testStopInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestQueueInputBufferInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testQueueInputBufferInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestQueueInputBufferWithBadIndex(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testQueueInputBufferWithBadIndex();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestQueueInputBufferWithBadSize(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testQueueInputBufferWithBadSize();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestQueueInputBufferWithBadBuffInfo(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testQueueInputBufferWithBadBuffInfo();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestQueueInputBufferWithBadOffset(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testQueueInputBufferWithBadOffset();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestQueueInputBufferInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testQueueInputBufferInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestReleaseOutputBufferInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testReleaseOutputBufferInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestReleaseOutputBufferInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testReleaseOutputBufferInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestReleaseOutputBufferInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testReleaseOutputBufferInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetBufferFormatInInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetBufferFormatInInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetBufferFormatInRunningState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetBufferFormatInRunningState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestGetBufferFormatInUnInitState(JNIEnv*, jobject) {
+    auto* nativeCodecUnitTest = new NativeCodecUnitTest(AMEDIA_MIMETYPE_AUDIO_AAC);
+    bool isPass = nativeCodecUnitTest->testGetBufferFormatInUnInitState();
+    delete nativeCodecUnitTest;
+    return static_cast<jboolean>(isPass);
+}
+
+int registerAndroidMediaV2CtsCodecUnitTest(JNIEnv* env) {
+    const JNINativeMethod methodTable[] = {
+            {"nativeTestCreateByCodecNameForNull", "()Z",
+             (void*)nativeTestCreateByCodecNameForNull},
+            {"nativeTestCreateByCodecNameForInvalidName", "()Z",
+             (void*)nativeTestCreateByCodecNameForInvalidName},
+            {"nativeTestCreateDecoderByTypeForNull", "()Z",
+             (void*)nativeTestCreateDecoderByTypeForNull},
+            {"nativeTestCreateDecoderByTypeForInvalidMime", "()Z",
+             (void*)nativeTestCreateDecoderByTypeForInvalidMime},
+            {"nativeTestCreateEncoderByTypeForNull", "()Z",
+             (void*)nativeTestCreateEncoderByTypeForNull},
+            {"nativeTestCreateEncoderByTypeForInvalidMime", "()Z",
+             (void*)nativeTestCreateEncoderByTypeForInvalidMime},
+            {"nativeTestConfigureForNullFormat", "()Z", (void*)nativeTestConfigureForNullFormat},
+            {"nativeTestConfigureForEmptyFormat", "()Z", (void*)nativeTestConfigureForEmptyFormat},
+            {"nativeTestConfigureCodecForIncompleteFormat", "(ZZ)Z",
+             (void*)nativeTestConfigureCodecForIncompleteFormat},
+            {"nativeTestConfigureEncoderForBadFlags", "()Z",
+             (void*)nativeTestConfigureEncoderForBadFlags},
+            {"nativeTestConfigureDecoderForBadFlags", "()Z",
+             (void*)nativeTestConfigureDecoderForBadFlags},
+            {"nativeTestConfigureInInitState", "()Z", (void*)nativeTestConfigureInInitState},
+            {"nativeTestConfigureInRunningState", "()Z", (void*)nativeTestConfigureInRunningState},
+            {"nativeTestConfigureInUnInitState", "()Z", (void*)nativeTestConfigureInUnInitState},
+            {"nativeTestDequeueInputBufferInInitState", "()Z",
+             (void*)nativeTestDequeueInputBufferInInitState},
+            {"nativeTestDequeueInputBufferInRunningState", "()Z",
+             (void*)nativeTestDequeueInputBufferInRunningState},
+            {"nativeTestDequeueInputBufferInUnInitState", "()Z",
+             (void*)nativeTestDequeueInputBufferInUnInitState},
+            {"nativeTestDequeueOutputBufferInInitState", "()Z",
+             (void*)nativeTestDequeueOutputBufferInInitState},
+            {"nativeTestDequeueOutputBufferInRunningState", "()Z",
+             (void*)nativeTestDequeueOutputBufferInRunningState},
+            {"nativeTestDequeueOutputBufferInUnInitState", "()Z",
+             (void*)nativeTestDequeueOutputBufferInUnInitState},
+            {"nativeTestFlushInInitState", "()Z", (void*)nativeTestFlushInInitState},
+            {"nativeTestFlushInRunningState", "()Z", (void*)nativeTestFlushInRunningState},
+            {"nativeTestFlushInUnInitState", "()Z", (void*)nativeTestFlushInUnInitState},
+            {"nativeTestGetNameInInitState", "()Z", (void*)nativeTestGetNameInInitState},
+            {"nativeTestGetNameInRunningState", "()Z", (void*)nativeTestGetNameInRunningState},
+            {"nativeTestGetNameInUnInitState", "()Z", (void*)nativeTestGetNameInUnInitState},
+            {"nativeTestSetAsyncNotifyCallbackInInitState", "()Z",
+             (void*)nativeTestSetAsyncNotifyCallbackInInitState},
+            {"nativeTestSetAsyncNotifyCallbackInRunningState", "()Z",
+             (void*)nativeTestSetAsyncNotifyCallbackInRunningState},
+            {"nativeTestSetAsyncNotifyCallbackInUnInitState", "()Z",
+             (void*)nativeTestSetAsyncNotifyCallbackInUnInitState},
+            {"nativeTestGetInputBufferInInitState", "()Z",
+             (void*)nativeTestGetInputBufferInInitState},
+            {"nativeTestGetInputBufferInRunningState", "()Z",
+             (void*)nativeTestGetInputBufferInRunningState},
+            {"nativeTestGetInputBufferInUnInitState", "()Z",
+             (void*)nativeTestGetInputBufferInUnInitState},
+            {"nativeTestGetInputFormatInInitState", "()Z",
+             (void*)nativeTestGetInputFormatInInitState},
+            {"nativeTestGetInputFormatInRunningState", "()Z",
+             (void*)nativeTestGetInputFormatInRunningState},
+            {"nativeTestGetInputFormatInUnInitState", "()Z",
+             (void*)nativeTestGetInputFormatInUnInitState},
+            {"nativeTestGetOutputBufferInInitState", "()Z",
+             (void*)nativeTestGetOutputBufferInInitState},
+            {"nativeTestGetOutputBufferInRunningState", "()Z",
+             (void*)nativeTestGetOutputBufferInRunningState},
+            {"nativeTestGetOutputBufferInUnInitState", "()Z",
+             (void*)nativeTestGetOutputBufferInUnInitState},
+            {"nativeTestGetOutputFormatInInitState", "()Z",
+             (void*)nativeTestGetOutputFormatInInitState},
+            {"nativeTestGetOutputFormatInRunningState", "()Z",
+             (void*)nativeTestGetOutputFormatInRunningState},
+            {"nativeTestGetOutputFormatInUnInitState", "()Z",
+             (void*)nativeTestGetOutputFormatInUnInitState},
+            {"nativeTestSetParametersInInitState", "()Z",
+             (void*)nativeTestSetParametersInInitState},
+            {"nativeTestSetParametersInRunningState", "()Z",
+             (void*)nativeTestSetParametersInRunningState},
+            {"nativeTestSetParametersInUnInitState", "()Z",
+             (void*)nativeTestSetParametersInUnInitState},
+            {"nativeTestStartInRunningState", "()Z", (void*)nativeTestStartInRunningState},
+            {"nativeTestStartInUnInitState", "()Z", (void*)nativeTestStartInUnInitState},
+            {"nativeTestStopInInitState", "()Z", (void*)nativeTestStopInInitState},
+            {"nativeTestStopInRunningState", "()Z", (void*)nativeTestStopInRunningState},
+            {"nativeTestStopInUnInitState", "()Z", (void*)nativeTestStopInUnInitState},
+            {"nativeTestQueueInputBufferInInitState", "()Z",
+             (void*)nativeTestQueueInputBufferInInitState},
+            {"nativeTestQueueInputBufferWithBadIndex", "()Z",
+             (void*)nativeTestQueueInputBufferWithBadIndex},
+            {"nativeTestQueueInputBufferWithBadSize", "()Z",
+             (void*)nativeTestQueueInputBufferWithBadSize},
+            {"nativeTestQueueInputBufferWithBadBuffInfo", "()Z",
+             (void*)nativeTestQueueInputBufferWithBadBuffInfo},
+            {"nativeTestQueueInputBufferWithBadOffset", "()Z",
+             (void*)nativeTestQueueInputBufferWithBadOffset},
+            {"nativeTestQueueInputBufferInUnInitState", "()Z",
+             (void*)nativeTestQueueInputBufferInUnInitState},
+            {"nativeTestReleaseOutputBufferInInitState", "()Z",
+             (void*)nativeTestReleaseOutputBufferInInitState},
+            {"nativeTestReleaseOutputBufferInRunningState", "()Z",
+             (void*)nativeTestReleaseOutputBufferInRunningState},
+            {"nativeTestReleaseOutputBufferInUnInitState", "()Z",
+             (void*)nativeTestReleaseOutputBufferInUnInitState},
+            {"nativeTestGetBufferFormatInInitState", "()Z",
+             (void*)nativeTestGetBufferFormatInInitState},
+            {"nativeTestGetBufferFormatInRunningState", "()Z",
+             (void*)nativeTestGetBufferFormatInRunningState},
+            {"nativeTestGetBufferFormatInUnInitState", "()Z",
+             (void*)nativeTestGetBufferFormatInUnInitState},
+
+    };
+    jclass c = env->FindClass("android/mediav2/cts/CodecUnitTest$TestApiNative");
+    return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
+}
diff --git a/tests/media/jni/NativeMediaConstants.cpp b/tests/media/jni/NativeMediaConstants.cpp
index a3867f9..e1ab6da 100644
--- a/tests/media/jni/NativeMediaConstants.cpp
+++ b/tests/media/jni/NativeMediaConstants.cpp
@@ -16,7 +16,9 @@
 
 #include "NativeMediaConstants.h"
 
-/* Note: constants used by the native media tests but not available in media ndk api */
+/* TODO(b/153592281)
+ * Note: constants used by the native media tests but not available in media ndk api
+ */
 const char* AMEDIA_MIMETYPE_VIDEO_VP8 = "video/x-vnd.on2.vp8";
 const char* AMEDIA_MIMETYPE_VIDEO_VP9 = "video/x-vnd.on2.vp9";
 const char* AMEDIA_MIMETYPE_VIDEO_AVC = "video/avc";
@@ -30,3 +32,8 @@
 const char* AMEDIA_MIMETYPE_AUDIO_VORBIS = "audio/vorbis";
 const char* AMEDIA_MIMETYPE_AUDIO_OPUS = "audio/opus";
 
+/* TODO(b/153592281) */
+const char* TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME = "request-sync";
+const char* TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE = "video-bitrate";
+const char* TBD_AMEDIACODEC_PARAMETER_KEY_MAX_B_FRAMES = "max-bframes";
+const char* TBD_AMEDIAFORMAT_KEY_BIT_RATE_MODE = "bitrate-mode";
diff --git a/tests/media/jni/NativeMediaConstants.h b/tests/media/jni/NativeMediaConstants.h
index 5a94368..4b60361 100644
--- a/tests/media/jni/NativeMediaConstants.h
+++ b/tests/media/jni/NativeMediaConstants.h
@@ -41,4 +41,18 @@
     OUTPUT_FORMAT_LIST_END = OUTPUT_FORMAT_START + 4,
 } MuxerFormat;
 
+// Color formats supported by encoder - should mirror supportedColorList
+// from MediaCodecConstants.h (are these going to be deprecated)
+constexpr int COLOR_FormatYUV420SemiPlanar = 21;
+constexpr int COLOR_FormatYUV420Flexible = 0x7F420888;
+
+// constants not defined in NDK
+extern const char* TBD_AMEDIACODEC_PARAMETER_KEY_REQUEST_SYNC_FRAME;
+extern const char* TBD_AMEDIACODEC_PARAMETER_KEY_VIDEO_BITRATE;
+extern const char* TBD_AMEDIACODEC_PARAMETER_KEY_MAX_B_FRAMES;
+extern const char* TBD_AMEDIAFORMAT_KEY_BIT_RATE_MODE;
+static const int TBD_AMEDIACODEC_BUFFER_FLAG_KEY_FRAME = 0x1;
+
+static const int kBitrateModeConstant = 2;
+
 #endif  // MEDIACTSNATIVE_NATIVE_MEDIA_CONSTANTS_H
diff --git a/tests/media/src/android/mediav2/cts/CodecDecoderTest.java b/tests/media/src/android/mediav2/cts/CodecDecoderTest.java
index 1fc3126..b4e3b2d 100644
--- a/tests/media/src/android/mediav2/cts/CodecDecoderTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecDecoderTest.java
@@ -19,10 +19,8 @@
 import android.media.Image;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
-import android.os.Build;
 import android.os.PersistableBundle;
 import android.util.Log;
 import android.util.Pair;
@@ -46,7 +44,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 
 import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
 import static org.junit.Assert.assertTrue;
@@ -334,8 +331,9 @@
 
     @Parameterized.Parameters(name = "{index}({0})")
     public static Collection<Object[]> input() {
-        final List<String> cddRequiredMimeList =
-                Arrays.asList(MediaFormat.MIMETYPE_AUDIO_MPEG,
+        final ArrayList<String> cddRequiredMimeList =
+                new ArrayList<>(Arrays.asList(
+                        MediaFormat.MIMETYPE_AUDIO_MPEG,
                         MediaFormat.MIMETYPE_AUDIO_AAC,
                         MediaFormat.MIMETYPE_AUDIO_FLAC,
                         MediaFormat.MIMETYPE_AUDIO_VORBIS,
@@ -348,7 +346,7 @@
                         MediaFormat.MIMETYPE_VIDEO_AVC,
                         MediaFormat.MIMETYPE_VIDEO_HEVC,
                         MediaFormat.MIMETYPE_VIDEO_VP8,
-                        MediaFormat.MIMETYPE_VIDEO_VP9);
+                        MediaFormat.MIMETYPE_VIDEO_VP9));
         if (isTv()) cddRequiredMimeList.add(MediaFormat.MIMETYPE_VIDEO_MPEG2);
         final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
                 {MediaFormat.MIMETYPE_AUDIO_MPEG, "bbb_1ch_8kHz_lame_cbr.mp3",
@@ -405,49 +403,7 @@
                 {MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_340x280_768kbps_30fps_av1.mp4", null,
                         "bbb_520x390_1mbps_30fps_av1.mp4", -1.0f},
         });
-        ArrayList<String> mimes = new ArrayList<>();
-        if (codecSelKeys.contains(CODEC_SEL_VALUE)) {
-            MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-            MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
-            for (MediaCodecInfo codecInfo : codecInfos) {
-                if (codecInfo.isEncoder()) continue;
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue;
-                String[] types = codecInfo.getSupportedTypes();
-                for (String type : types) {
-                    if (!mimes.contains(type)) {
-                        mimes.add(type);
-                    }
-                }
-            }
-            for (String mime : cddRequiredMimeList) {
-                if (!mimes.contains(mime)) {
-                    fail("no codec found to decode mime " + mime + " as required by cdd");
-                }
-            }
-        } else {
-            for (Map.Entry<String, String> entry : codecSelKeyMimeMap.entrySet()) {
-                String key = entry.getKey();
-                String value = entry.getValue();
-                if (codecSelKeys.contains(key) && !mimes.contains(value)) mimes.add(value);
-            }
-        }
-        final List<Object[]> argsList = new ArrayList<>();
-        for (String mime : mimes) {
-            boolean miss = true;
-            for (int i = 0; i < exhaustiveArgsList.size(); i++) {
-                if (mime.equals(exhaustiveArgsList.get(i)[0])) {
-                    argsList.add(exhaustiveArgsList.get(i));
-                    miss = false;
-                }
-            }
-            if (miss) {
-                if (cddRequiredMimeList.contains(mime)) {
-                    fail("no testvectors for required mimetype " + mime);
-                }
-                Log.w(LOG_TAG, "no test vectors available for optional mime type " + mime);
-            }
-        }
-        return argsList;
+        return prepareParamList(cddRequiredMimeList, exhaustiveArgsList, false);
     }
 
     /**
@@ -546,6 +502,23 @@
         mExtractor.release();
     }
 
+    private native boolean nativeTestSimpleDecode(String decoder, String mime, String testFile,
+            String refFile, float rmsError);
+
+
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testSimpleDecodeNative() {
+        ArrayList<String> listOfDecoders = selectCodecs(mMime, null, null, false);
+        if (listOfDecoders.isEmpty()) {
+            fail("no suitable codecs found for mime: " + mMime);
+        }
+        for (String decoder : listOfDecoders) {
+            assertTrue(nativeTestSimpleDecode(decoder, mMime, mInpPrefix + mTestFile,
+                    mInpPrefix + mRefFile, mRmsError));
+        }
+    }
+
     /**
      * Tests flush when codec is in sync and async mode. In these scenarios, Timestamp
      * ordering is verified. The output has to be consistent (not flaky) in all runs
@@ -671,6 +644,21 @@
         }
     }
 
+    private native boolean nativeTestFlush(String decoder, String mime, String testFile);
+
+    @Ignore("TODO(b/147576107)")
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testFlushNative() {
+        ArrayList<String> listOfDecoders = selectCodecs(mMime, null, null, false);
+        if (listOfDecoders.isEmpty()) {
+            fail("no suitable codecs found for mime: " + mMime);
+        }
+        for (String decoder : listOfDecoders) {
+            assertTrue(nativeTestFlush(decoder, mMime, mInpPrefix + mTestFile));
+        }
+    }
+
     /**
      * Tests reconfigure when codec is in sync and async mode. In these scenarios, Timestamp
      * ordering is verified. The output has to be consistent (not flaky) in all runs
@@ -900,6 +888,20 @@
         mExtractor.release();
     }
 
+    private native boolean nativeTestOnlyEos(String decoder, String mime, String testFile);
+
+    @SmallTest
+    @Test
+    public void testOnlyEosNative() {
+        ArrayList<String> listOfDecoders = selectCodecs(mMime, null, null, false);
+        if (listOfDecoders.isEmpty()) {
+            fail("no suitable codecs found for mime: " + mMime);
+        }
+        for (String decoder : listOfDecoders) {
+            assertTrue(nativeTestOnlyEos(decoder, mMime, mInpPrefix + mTestFile));
+        }
+    }
+
     /**
      * Test Decoder by Queuing CSD separately
      */
@@ -995,6 +997,24 @@
         mExtractor.release();
     }
 
+    private native boolean nativeTestSimpleDecodeQueueCSD(String decoder, String mime,
+            String testFile);
+
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testSimpleDecodeQueueCSDNative() throws IOException {
+        MediaFormat format = setUpSource(mTestFile);
+        Assume.assumeTrue("Format has no CSD, ignoring test for mime:" + mMime, hasCSD(format));
+        ArrayList<String> listOfDecoders = selectCodecs(mMime, null, null, false);
+        if (listOfDecoders.isEmpty()) {
+            fail("no suitable codecs found for mime: " + mMime);
+        }
+        for (String decoder : listOfDecoders) {
+            assertTrue(nativeTestSimpleDecodeQueueCSD(decoder, mMime, mInpPrefix + mTestFile));
+        }
+        mExtractor.release();
+    }
+
     /**
      * Test decoder for partial frame
      */
diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
new file mode 100644
index 0000000..f528065
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/CodecEncoderSurfaceTest.java
@@ -0,0 +1,550 @@
+/*
+ * 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.mediav2.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+import android.os.Build;
+import android.util.Log;
+import android.util.Pair;
+import android.view.Surface;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(Parameterized.class)
+public class CodecEncoderSurfaceTest {
+    private static final String LOG_TAG = CodecEncoderSurfaceTest.class.getSimpleName();
+    private static final String mInpPrefix = WorkDir.getMediaDirString();
+    private static final boolean ENABLE_LOGS = false;
+
+    private final String mMime;
+    private final String mTestFile;
+    private final int mBitrate;
+    private final int mFrameRate;
+    private final int mMaxBFrames;
+
+    private MediaExtractor mExtractor;
+    private MediaCodec mEncoder;
+    private CodecAsyncHandler mAsyncHandleEncoder;
+    private MediaCodec mDecoder;
+    private CodecAsyncHandler mAsyncHandleDecoder;
+    private boolean mIsCodecInAsyncMode;
+    private boolean mSignalEOSWithLastFrame;
+    private boolean mSawDecInputEOS;
+    private boolean mSawDecOutputEOS;
+    private boolean mSawEncOutputEOS;
+    private int mDecInputCount;
+    private int mDecOutputCount;
+    private int mEncOutputCount;
+
+    private boolean mSaveToMem;
+    private OutputManager mOutputBuff;
+
+    private Surface mSurface;
+
+    private MediaMuxer mMuxer;
+    private int mTrackID = -1;
+
+    static {
+        android.os.Bundle args = InstrumentationRegistry.getArguments();
+        CodecTestBase.codecSelKeys = args.getString(CodecTestBase.CODEC_SEL_KEY);
+        if (CodecTestBase.codecSelKeys == null)
+            CodecTestBase.codecSelKeys = CodecTestBase.CODEC_SEL_VALUE;
+    }
+
+    public CodecEncoderSurfaceTest(String mime, String testFile, int bitrate, int frameRate) {
+        mMime = mime;
+        mTestFile = testFile;
+        mBitrate = bitrate;
+        mFrameRate = frameRate;
+        mMaxBFrames = 0;
+        mAsyncHandleDecoder = new CodecAsyncHandler();
+        mAsyncHandleEncoder = new CodecAsyncHandler();
+    }
+
+    @Parameterized.Parameters(name = "{index}({0})")
+    public static Collection<Object[]> input() {
+        final ArrayList<String> cddRequiredMimeList =
+                new ArrayList<>(Arrays.asList(
+                        MediaFormat.MIMETYPE_VIDEO_MPEG4,
+                        MediaFormat.MIMETYPE_VIDEO_H263,
+                        MediaFormat.MIMETYPE_VIDEO_AVC,
+                        MediaFormat.MIMETYPE_VIDEO_HEVC,
+                        MediaFormat.MIMETYPE_VIDEO_VP8,
+                        MediaFormat.MIMETYPE_VIDEO_VP9));
+        final Object[][] exhaustiveArgsList = new Object[][]{
+                // Video - CodecMime, test file, bit rate, frame rate
+                {MediaFormat.MIMETYPE_VIDEO_H263, "bbb_176x144_128kbps_15fps_h263.3gp", 128000, 15},
+                {MediaFormat.MIMETYPE_VIDEO_MPEG4, "bbb_128x96_64kbps_12fps_mpeg4.mp4", 64000, 12},
+                {MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_cif_768kbps_30fps_avc.mp4", 512000, 30},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, "bbb_cif_768kbps_30fps_avc.mp4", 512000, 30},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, "bbb_cif_768kbps_30fps_avc.mp4", 512000, 30},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_cif_768kbps_30fps_avc.mp4", 512000, 30},
+                {MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_cif_768kbps_30fps_avc.mp4", 512000, 30},
+        };
+        ArrayList<String> mimes = new ArrayList<>();
+        if (CodecTestBase.codecSelKeys.contains(CodecTestBase.CODEC_SEL_VALUE)) {
+            MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
+            for (MediaCodecInfo codecInfo : codecInfos) {
+                if (!codecInfo.isEncoder()) continue;
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue;
+                String[] types = codecInfo.getSupportedTypes();
+                for (String type : types) {
+                    if (!mimes.contains(type)) {
+                        mimes.add(type);
+                    }
+                }
+            }
+            for (String mime : cddRequiredMimeList) {
+                if (!mimes.contains(mime)) {
+                    fail("no codec found for mime " + mime + " as required by cdd");
+                }
+            }
+        } else {
+            for (Map.Entry<String, String> entry : CodecTestBase.codecSelKeyMimeMap.entrySet()) {
+                String key = entry.getKey();
+                String value = entry.getValue();
+                if (CodecTestBase.codecSelKeys.contains(key) && !mimes.contains(value)) {
+                    mimes.add(value);
+                }
+            }
+        }
+        final List<Object[]> argsList = new ArrayList<>();
+        for (String mime : mimes) {
+            boolean miss = true;
+            for (Object[] arg : exhaustiveArgsList) {
+                if (mime.equals(arg[0])) {
+                    argsList.add(arg);
+                    miss = false;
+                }
+            }
+            if (miss) {
+                if (cddRequiredMimeList.contains(mime)) {
+                    fail("no test vectors for required mimetype " + mime);
+                }
+                Log.w(LOG_TAG, "no test vectors available for optional mime type " + mime);
+            }
+        }
+        return argsList;
+    }
+
+    private boolean hasSeenError() {
+        return mAsyncHandleDecoder.hasSeenError() || mAsyncHandleEncoder.hasSeenError();
+    }
+
+    private MediaFormat setUpSource(String srcFile) throws IOException {
+        mExtractor = new MediaExtractor();
+        mExtractor.setDataSource(mInpPrefix + srcFile);
+        for (int trackID = 0; trackID < mExtractor.getTrackCount(); trackID++) {
+            MediaFormat format = mExtractor.getTrackFormat(trackID);
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            if (mime.startsWith("video/")) {
+                mExtractor.selectTrack(trackID);
+                // COLOR_FormatYUV420Flexible by default should be supported by all components
+                // This call shouldn't effect configure() call for any codec
+                format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                        MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
+                return format;
+            }
+        }
+        mExtractor.release();
+        fail("No video track found in file: " + srcFile);
+        return null;
+    }
+
+    private void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) {
+        mAsyncHandleDecoder.resetContext();
+        mAsyncHandleEncoder.resetContext();
+        mIsCodecInAsyncMode = isAsync;
+        mSignalEOSWithLastFrame = signalEOSWithLastFrame;
+        mSawDecInputEOS = false;
+        mSawDecOutputEOS = false;
+        mSawEncOutputEOS = false;
+        mDecInputCount = 0;
+        mDecOutputCount = 0;
+        mEncOutputCount = 0;
+    }
+
+    private void configureCodec(MediaFormat decFormat, MediaFormat encFormat, boolean isAsync,
+            boolean signalEOSWithLastFrame) {
+        resetContext(isAsync, signalEOSWithLastFrame);
+        mAsyncHandleEncoder.setCallBack(mEncoder, isAsync);
+        mEncoder.configure(encFormat, null, MediaCodec.CONFIGURE_FLAG_ENCODE, null);
+        mSurface = mEncoder.createInputSurface();
+        assertTrue("Surface is not valid", mSurface.isValid());
+        mAsyncHandleDecoder.setCallBack(mDecoder, isAsync);
+        mDecoder.configure(decFormat, mSurface, null, 0);
+        if (ENABLE_LOGS) {
+            Log.v(LOG_TAG, "codec configured");
+        }
+    }
+
+    private void enqueueDecoderEOS(int bufferIndex) {
+        if (!mSawDecInputEOS) {
+            mDecoder.queueInputBuffer(bufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+            mSawDecInputEOS = true;
+            if (ENABLE_LOGS) {
+                Log.v(LOG_TAG, "Queued End of Stream");
+            }
+        }
+    }
+
+    private void enqueueDecoderInput(int bufferIndex) {
+        if (mExtractor.getSampleSize() < 0) {
+            enqueueDecoderEOS(bufferIndex);
+        } else {
+            ByteBuffer inputBuffer = mDecoder.getInputBuffer(bufferIndex);
+            mExtractor.readSampleData(inputBuffer, 0);
+            int size = (int) mExtractor.getSampleSize();
+            long pts = mExtractor.getSampleTime();
+            int extractorFlags = mExtractor.getSampleFlags();
+            int codecFlags = 0;
+            if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) {
+                codecFlags |= MediaCodec.BUFFER_FLAG_KEY_FRAME;
+            }
+            if ((extractorFlags & MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME) != 0) {
+                codecFlags |= MediaCodec.BUFFER_FLAG_PARTIAL_FRAME;
+            }
+            if (!mExtractor.advance() && mSignalEOSWithLastFrame) {
+                codecFlags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                mSawDecInputEOS = true;
+            }
+            if (ENABLE_LOGS) {
+                Log.v(LOG_TAG, "input: id: " + bufferIndex + " size: " + size + " pts: " + pts +
+                        " flags: " + codecFlags);
+            }
+            mDecoder.queueInputBuffer(bufferIndex, 0, size, pts, codecFlags);
+            if (size > 0 && (codecFlags & (MediaCodec.BUFFER_FLAG_CODEC_CONFIG |
+                    MediaCodec.BUFFER_FLAG_PARTIAL_FRAME)) == 0) {
+                mOutputBuff.saveInPTS(pts);
+                mDecInputCount++;
+            }
+        }
+    }
+
+    private void dequeueDecoderOutput(int bufferIndex, MediaCodec.BufferInfo info) {
+        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+            mSawDecOutputEOS = true;
+        }
+        if (ENABLE_LOGS) {
+            Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + info.flags + " size: " +
+                    info.size + " timestamp: " + info.presentationTimeUs);
+        }
+        if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+            mDecOutputCount++;
+        }
+        mDecoder.releaseOutputBuffer(bufferIndex, mSurface != null);
+    }
+
+    private void dequeueEncoderOutput(int bufferIndex, MediaCodec.BufferInfo info) {
+        if (ENABLE_LOGS) {
+            Log.v(LOG_TAG, "encoder output: id: " + bufferIndex + " flags: " + info.flags +
+                    " size: " + info.size + " timestamp: " + info.presentationTimeUs);
+        }
+        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+            mSawEncOutputEOS = true;
+        }
+        if (info.size > 0) {
+            ByteBuffer buf = mEncoder.getOutputBuffer(bufferIndex);
+            if (mSaveToMem) {
+                mOutputBuff.saveToMemory(buf, info);
+            }
+            if (mMuxer != null) {
+                if (mTrackID == -1) {
+                    mTrackID = mMuxer.addTrack(mEncoder.getOutputFormat());
+                    mMuxer.start();
+                }
+                mMuxer.writeSampleData(mTrackID, buf, info);
+            }
+            if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+                mOutputBuff.saveOutPTS(info.presentationTimeUs);
+                mEncOutputCount++;
+            }
+        }
+        mEncoder.releaseOutputBuffer(bufferIndex, false);
+    }
+
+    private void tryEncoderOutput(long timeOutUs) throws InterruptedException {
+        if (mIsCodecInAsyncMode) {
+            if (!hasSeenError() && !mSawEncOutputEOS) {
+                Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandleEncoder.getOutput();
+                if (element != null) {
+                    dequeueEncoderOutput(element.first, element.second);
+                }
+            }
+        } else {
+            MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+            if (!mSawEncOutputEOS) {
+                int outputBufferId = mEncoder.dequeueOutputBuffer(outInfo, timeOutUs);
+                if (outputBufferId >= 0) {
+                    dequeueEncoderOutput(outputBufferId, outInfo);
+                }
+            }
+        }
+    }
+
+    private void waitForAllEncoderOutputs() throws InterruptedException {
+        if (mIsCodecInAsyncMode) {
+            while (!hasSeenError() && !mSawEncOutputEOS) {
+                tryEncoderOutput(CodecTestBase.Q_DEQ_TIMEOUT_US);
+            }
+        } else {
+            while (!mSawEncOutputEOS) {
+                tryEncoderOutput(CodecTestBase.Q_DEQ_TIMEOUT_US);
+            }
+        }
+    }
+
+    private void queueEOS() throws InterruptedException {
+        if (!mSawDecInputEOS) {
+            if (mIsCodecInAsyncMode) {
+                Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandleDecoder.getInput();
+                if (element != null) {
+                    enqueueDecoderEOS(element.first);
+                }
+            } else {
+                enqueueDecoderEOS(mDecoder.dequeueInputBuffer(-1));
+            }
+        }
+        if (mIsCodecInAsyncMode) {
+            while (!hasSeenError() && !mSawDecOutputEOS) {
+                Pair<Integer, MediaCodec.BufferInfo> decOp = mAsyncHandleDecoder.getOutput();
+                if (decOp != null) dequeueDecoderOutput(decOp.first, decOp.second);
+                if (mSawDecOutputEOS) mEncoder.signalEndOfInputStream();
+                // TODO: remove fixed constant and change it according to encoder latency
+                if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+                    tryEncoderOutput(-1);
+                }
+            }
+        } else {
+            MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+            while (!mSawDecOutputEOS) {
+                int outputBufferId =
+                        mDecoder.dequeueOutputBuffer(outInfo, CodecTestBase.Q_DEQ_TIMEOUT_US);
+                if (outputBufferId >= 0) {
+                    dequeueDecoderOutput(outputBufferId, outInfo);
+                }
+                if (mSawDecOutputEOS) mEncoder.signalEndOfInputStream();
+                // TODO: remove fixed constant and change it according to encoder latency
+                if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+                    tryEncoderOutput(-1);
+                }
+            }
+        }
+    }
+
+    private void doWork(int frameLimit) throws InterruptedException {
+        int frameCnt = 0;
+        if (mIsCodecInAsyncMode) {
+            // dequeue output after inputEOS is expected to be done in waitForAllOutputs()
+            while (!hasSeenError() && !mSawDecInputEOS && frameCnt < frameLimit) {
+                Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandleDecoder.getWork();
+                if (element != null) {
+                    int bufferID = element.first;
+                    MediaCodec.BufferInfo info = element.second;
+                    if (info != null) {
+                        // <id, info> corresponds to output callback. Handle it accordingly
+                        dequeueDecoderOutput(bufferID, info);
+                    } else {
+                        // <id, null> corresponds to input callback. Handle it accordingly
+                        enqueueDecoderInput(bufferID);
+                        frameCnt++;
+                    }
+                }
+                // check decoder EOS
+                if (mSawDecOutputEOS) mEncoder.signalEndOfInputStream();
+                // encoder output
+                // TODO: remove fixed constant and change it according to encoder latency
+                if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+                    tryEncoderOutput(-1);
+                }
+            }
+        } else {
+            MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+            while (!mSawDecInputEOS && frameCnt < frameLimit) {
+                // decoder input
+                int inputBufferId = mDecoder.dequeueInputBuffer(CodecTestBase.Q_DEQ_TIMEOUT_US);
+                if (inputBufferId != -1) {
+                    enqueueDecoderInput(inputBufferId);
+                    frameCnt++;
+                }
+                // decoder output
+                int outputBufferId =
+                        mDecoder.dequeueOutputBuffer(outInfo, CodecTestBase.Q_DEQ_TIMEOUT_US);
+                if (outputBufferId >= 0) {
+                    dequeueDecoderOutput(outputBufferId, outInfo);
+                }
+                // check decoder EOS
+                if (mSawDecOutputEOS) mEncoder.signalEndOfInputStream();
+                // encoder output
+                // TODO: remove fixed constant and change it according to encoder latency
+                if (mDecOutputCount - mEncOutputCount > mMaxBFrames) {
+                    tryEncoderOutput(-1);
+                }
+            }
+        }
+    }
+
+    private MediaFormat setUpEncoderFormat(MediaFormat decoderFormat) {
+        MediaFormat encoderFormat = new MediaFormat();
+        encoderFormat.setString(MediaFormat.KEY_MIME, mMime);
+        encoderFormat.setInteger(MediaFormat.KEY_WIDTH,
+                decoderFormat.getInteger(MediaFormat.KEY_WIDTH));
+        encoderFormat.setInteger(MediaFormat.KEY_HEIGHT,
+                decoderFormat.getInteger(MediaFormat.KEY_HEIGHT));
+        encoderFormat.setInteger(MediaFormat.KEY_FRAME_RATE, mFrameRate);
+        encoderFormat.setInteger(MediaFormat.KEY_BIT_RATE, mBitrate);
+        encoderFormat.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f);
+        encoderFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
+        encoderFormat.setInteger(MediaFormat.KEY_MAX_B_FRAMES, mMaxBFrames);
+        return encoderFormat;
+    }
+
+    /**
+     * Tests listed encoder components for sync and async mode in surface mode.The output has to
+     * be consistent (not flaky) in all runs.
+     */
+    @LargeTest
+    @Test(timeout = CodecTestBase.PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testSimpleEncodeFromSurface() throws IOException, InterruptedException {
+        MediaFormat decoderFormat = setUpSource(mTestFile);
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        String decoder = codecList.findDecoderForFormat(decoderFormat);
+        if (decoder == null) {
+            mExtractor.release();
+            fail("no suitable decoder found for format: " + decoderFormat.toString());
+        }
+        mDecoder = MediaCodec.createByCodecName(decoder);
+        MediaFormat encoderFormat = setUpEncoderFormat(decoderFormat);
+        ArrayList<String> listOfEncoders = CodecTestBase.selectCodecs(mMime, null, null, true);
+        assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+        boolean muxOutput = true;
+        for (String encoder : listOfEncoders) {
+            mEncoder = MediaCodec.createByCodecName(encoder);
+            /* TODO(b/149027258) */
+            mSaveToMem = false;
+            OutputManager ref = new OutputManager();
+            OutputManager test = new OutputManager();
+            int loopCounter = 0;
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+                mOutputBuff = loopCounter == 0 ? ref : test;
+                mOutputBuff.reset();
+                if (muxOutput && loopCounter == 0) {
+                    String tmpPath;
+                    int muxerFormat;
+                    if (mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP8) ||
+                            mMime.equals(MediaFormat.MIMETYPE_VIDEO_VP9)) {
+                        muxerFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM;
+                        tmpPath = File.createTempFile("tmp", ".webm").getAbsolutePath();
+                    } else {
+                        muxerFormat = MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4;
+                        tmpPath = File.createTempFile("tmp", ".mp4").getAbsolutePath();
+                    }
+                    mMuxer = new MediaMuxer(tmpPath, muxerFormat);
+                }
+                configureCodec(decoderFormat, encoderFormat, isAsync, false);
+                mEncoder.start();
+                mDecoder.start();
+                MediaFormat inputFormat = mDecoder.getInputFormat();
+                doWork(Integer.MAX_VALUE);
+                queueEOS();
+                waitForAllEncoderOutputs();
+                if (muxOutput) {
+                    if (mTrackID != -1) {
+                        mMuxer.stop();
+                        mTrackID = -1;
+                    }
+                    if (mMuxer != null) {
+                        mMuxer.release();
+                        mMuxer = null;
+                    }
+                }
+                /* TODO(b/147348711) */
+                if (false) mDecoder.stop();
+                else mDecoder.reset();
+                /* TODO(b/147348711) */
+                if (false) mEncoder.stop();
+                else mEncoder.reset();
+                String log = String.format(
+                        "format: %s \n codec: %s, file: %s, mode: %s:: ",
+                        encoderFormat, encoder, mTestFile, (isAsync ? "async" : "sync"));
+                assertTrue(log + " unexpected error", !hasSeenError());
+                assertTrue(log + "no input sent", 0 != mDecInputCount);
+                assertTrue(log + "no decoder output received", 0 != mDecOutputCount);
+                assertTrue(log + "no encoder output received", 0 != mEncOutputCount);
+                assertTrue(log + "decoder input count != output count, act/exp: " +
+                        mDecOutputCount +
+                        " / " + mDecInputCount, mDecInputCount == mDecOutputCount);
+                /* TODO(b/153127506)
+                 *  Currently disabling all encoder output checks. Added checks only for encoder
+                 *  timeStamp is in increasing order or not.
+                 *  Once issue is fixed remove increasing timestamp check and enable encoder checks.
+                 */
+                /*assertTrue(log + "encoder output count != decoder output count, act/exp: " +
+                                mEncOutputCount + " / " + mDecOutputCount,
+                        mEncOutputCount == mDecOutputCount);
+                if (loopCounter != 0) {
+                    assertTrue(log + "encoder output is flaky", ref.equals(test));
+                } else {
+                    assertTrue(log + " input pts list and output pts list are not identical",
+                            ref.isOutPtsListIdenticalToInpPtsList((false)));
+                }*/
+                if (loopCounter != 0) {
+                    assertTrue("test output pts is not strictly increasing",
+                            test.isPtsStrictlyIncreasing(Long.MIN_VALUE));
+                } else {
+                    assertTrue("ref output pts is not strictly increasing",
+                            ref.isPtsStrictlyIncreasing(Long.MIN_VALUE));
+                }
+                loopCounter++;
+                mSurface.release();
+                mSurface = null;
+            }
+            mEncoder.release();
+        }
+        mDecoder.release();
+        mExtractor.release();
+    }
+}
+
diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
index 3bce40c..48470b8 100644
--- a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
+++ b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
@@ -20,9 +20,8 @@
 import android.media.Image;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
-import android.media.MediaCodecList;
 import android.media.MediaFormat;
-import android.os.Build;
+import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.util.Log;
 
@@ -30,6 +29,7 @@
 import androidx.test.filters.SmallTest;
 
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -43,7 +43,6 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.List;
-import java.util.Map;
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -76,6 +75,8 @@
     private byte[] mInputData;
     private int mNumBytesSubmitted;
     private long mInputOffsetPts;
+    private int mNumSyncFramesReceived;
+    private ArrayList<Integer> mSyncFramesPos;
 
     private int mWidth, mHeight;
     private int mChannels;
@@ -92,8 +93,9 @@
         mBitrates = bitrates;
         mEncParamList1 = encoderInfo1;
         mEncParamList2 = encoderInfo2;
-        mAsyncHandle = new CodecAsyncHandler();
         mFormats = new ArrayList<>();
+        mSyncFramesPos = new ArrayList<>();
+        mAsyncHandle = new CodecAsyncHandler();
         mIsAudio = mMime.startsWith("audio/");
         mInputFile = mIsAudio ? mInputAudioFile : mInputVideoFile;
     }
@@ -103,6 +105,8 @@
         super.resetContext(isAsync, signalEOSWithLastFrame);
         mNumBytesSubmitted = 0;
         mInputOffsetPts = 0;
+        mNumSyncFramesReceived = 0;
+        mSyncFramesPos.clear();
     }
 
     @Override
@@ -116,6 +120,8 @@
         }
         mPrevOutputPts = mInputOffsetPts - 1;
         mNumBytesSubmitted = 0;
+        mNumSyncFramesReceived = 0;
+        mSyncFramesPos.clear();
     }
 
     private void setUpSource(String srcFile) throws IOException {
@@ -262,20 +268,26 @@
     }
 
     void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
-        if (info.size > 0 && mSaveToMem) {
-            ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex);
-            mOutputBuff.saveToMemory(buf, info);
-        }
-        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
-            mSawOutputEOS = true;
-        }
         if (ENABLE_LOGS) {
             Log.v(LOG_TAG, "output: id: " + bufferIndex + " flags: " + info.flags + " size: " +
                     info.size + " timestamp: " + info.presentationTimeUs);
         }
-        if (info.size > 0 && (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
-            mOutputBuff.saveOutPTS(info.presentationTimeUs);
-            mOutputCount++;
+        if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+            mSawOutputEOS = true;
+        }
+        if (info.size > 0) {
+            if (mSaveToMem) {
+                ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex);
+                mOutputBuff.saveToMemory(buf, info);
+            }
+            if ((info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+                mOutputBuff.saveOutPTS(info.presentationTimeUs);
+                mOutputCount++;
+            }
+            if ((info.flags & MediaCodec.BUFFER_FLAG_KEY_FRAME) != 0) {
+                mNumSyncFramesReceived += 1;
+                mSyncFramesPos.add(mOutputCount);
+            }
         }
         mCodec.releaseOutputBuffer(bufferIndex, false);
     }
@@ -305,6 +317,27 @@
         mSaveToMem = false;
     }
 
+    /**
+     * Selects encoder input color format in byte buffer mode. As of now ndk tests support only
+     * 420p, 420sp. COLOR_FormatYUV420Flexible although can represent any form of yuv, it doesn't
+     * work in ndk due to lack of AMediaCodec_GetInputImage()
+     */
+    private static int findByteBufferColorFormat(String encoder, String mime) throws IOException {
+        MediaCodec codec = MediaCodec.createByCodecName(encoder);
+        MediaCodecInfo.CodecCapabilities cap = codec.getCodecInfo().getCapabilitiesForType(mime);
+        int colorFormat = -1;
+        for (int c : cap.colorFormats) {
+            if (c == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420SemiPlanar ||
+                    c == MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Planar) {
+                Log.v(LOG_TAG, "selecting color format: " + c);
+                colorFormat = c;
+                break;
+            }
+        }
+        codec.release();
+        return colorFormat;
+    }
+
     @Override
     PersistableBundle validateMetrics(String codec, MediaFormat format) {
         PersistableBundle metrics = super.validateMetrics(codec, format);
@@ -313,10 +346,29 @@
         return metrics;
     }
 
+    private void forceSyncFrame() {
+        final Bundle syncFrame = new Bundle();
+        syncFrame.putInt(MediaCodec.PARAMETER_KEY_REQUEST_SYNC_FRAME, 0);
+        if (ENABLE_LOGS) {
+            Log.v(LOG_TAG, "requesting key frame");
+        }
+        mCodec.setParameters(syncFrame);
+    }
+
+    private void updateBitrate(int bitrate) {
+        final Bundle bitrateUpdate = new Bundle();
+        bitrateUpdate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate);
+        if (ENABLE_LOGS) {
+            Log.v(LOG_TAG, "requesting bitrate to be changed to " + bitrate);
+        }
+        mCodec.setParameters(bitrateUpdate);
+    }
+
     @Parameterized.Parameters(name = "{index}({0})")
     public static Collection<Object[]> input() {
-        final List<String> cddRequiredMimeList =
-                Arrays.asList(MediaFormat.MIMETYPE_AUDIO_FLAC,
+        final ArrayList<String> cddRequiredMimeList =
+                new ArrayList<>(Arrays.asList(
+                        MediaFormat.MIMETYPE_AUDIO_FLAC,
                         MediaFormat.MIMETYPE_AUDIO_OPUS,
                         MediaFormat.MIMETYPE_AUDIO_AAC,
                         MediaFormat.MIMETYPE_AUDIO_AMR_NB,
@@ -326,7 +378,7 @@
                         MediaFormat.MIMETYPE_VIDEO_AVC,
                         MediaFormat.MIMETYPE_VIDEO_HEVC,
                         MediaFormat.MIMETYPE_VIDEO_VP8,
-                        MediaFormat.MIMETYPE_VIDEO_VP9);
+                        MediaFormat.MIMETYPE_VIDEO_VP9));
         final List<Object[]> exhaustiveArgsList = Arrays.asList(new Object[][]{
                 // Audio - CodecMime, arrays of bit-rates, sample rates, channel counts
                 {MediaFormat.MIMETYPE_AUDIO_AAC, new int[]{64000, 128000}, new int[]{8000, 11025,
@@ -341,64 +393,22 @@
                         , 96000, 192000}, new int[]{1, 2}},
 
                 // Video - CodecMime, arrays of bit-rates, height, width
-                {MediaFormat.MIMETYPE_VIDEO_H263, new int[]{64000}, new int[]{176}, new int[]{144}},
-                {MediaFormat.MIMETYPE_VIDEO_MPEG4, new int[]{64000}, new int[]{176},
+                {MediaFormat.MIMETYPE_VIDEO_H263, new int[]{32000, 64000}, new int[]{176},
                         new int[]{144}},
-                {MediaFormat.MIMETYPE_VIDEO_AVC, new int[]{512000}, new int[]{176, 352, 352, 480}
-                        , new int[]{144, 240, 288, 360}},
-                {MediaFormat.MIMETYPE_VIDEO_HEVC, new int[]{512000}, new int[]{176, 352, 352,
-                        480}, new int[]{144, 240, 288, 360}},
-                {MediaFormat.MIMETYPE_VIDEO_VP8, new int[]{512000}, new int[]{176, 352, 352, 480}
-                        , new int[]{144, 240, 288, 360}},
-                {MediaFormat.MIMETYPE_VIDEO_VP9, new int[]{512000}, new int[]{176, 352, 352, 480}
-                        , new int[]{144, 240, 288, 360}},
-                {MediaFormat.MIMETYPE_VIDEO_AV1, new int[]{512000}, new int[]{176, 352, 352, 480}
-                        , new int[]{144, 240, 288, 360}},
+                {MediaFormat.MIMETYPE_VIDEO_MPEG4, new int[]{32000, 64000}, new int[]{176},
+                        new int[]{144}},
+                {MediaFormat.MIMETYPE_VIDEO_AVC, new int[]{256000, 512000}, new int[]{176, 352,
+                        352, 480}, new int[]{144, 240, 288, 360}},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, new int[]{256000, 512000}, new int[]{176, 352,
+                        352, 480}, new int[]{144, 240, 288, 360}},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, new int[]{256000, 512000}, new int[]{176, 352,
+                        352, 480}, new int[]{144, 240, 288, 360}},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, new int[]{256000, 512000}, new int[]{176, 352,
+                        352, 480}, new int[]{144, 240, 288, 360}},
+                {MediaFormat.MIMETYPE_VIDEO_AV1, new int[]{256000, 512000}, new int[]{176, 352,
+                        352, 480}, new int[]{144, 240, 288, 360}},
         });
-
-        ArrayList<String> mimes = new ArrayList<>();
-        if (codecSelKeys.contains(CODEC_SEL_VALUE)) {
-            MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
-            MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
-            for (MediaCodecInfo codecInfo : codecInfos) {
-                if (!codecInfo.isEncoder()) continue;
-                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue;
-                String[] types = codecInfo.getSupportedTypes();
-                for (String type : types) {
-                    if (!mimes.contains(type)) {
-                        mimes.add(type);
-                    }
-                }
-            }
-            for (String mime : cddRequiredMimeList) {
-                if (!mimes.contains(mime)) {
-                    fail("no codec found to encoder mime " + mime + " as required by cdd");
-                }
-            }
-        } else {
-            for (Map.Entry<String, String> entry : codecSelKeyMimeMap.entrySet()) {
-                String key = entry.getKey();
-                String value = entry.getValue();
-                if (codecSelKeys.contains(key) && !mimes.contains(value)) mimes.add(value);
-            }
-        }
-        final List<Object[]> argsList = new ArrayList<>();
-        for (String mime : mimes) {
-            boolean miss = true;
-            for (int i = 0; i < exhaustiveArgsList.size(); i++) {
-                if (mime.equals(exhaustiveArgsList.get(i)[0])) {
-                    argsList.add(exhaustiveArgsList.get(i));
-                    miss = false;
-                }
-            }
-            if (miss) {
-                if (cddRequiredMimeList.contains(mime)) {
-                    fail("no testvectors for required mimetype " + mime);
-                }
-                Log.w(LOG_TAG, "no test vectors available for optional mime type " + mime);
-            }
-        }
-        return argsList;
+        return prepareParamList(cddRequiredMimeList, exhaustiveArgsList, true);
     }
 
     private void setUpParams(int limit) {
@@ -522,6 +532,25 @@
         }
     }
 
+    private native boolean nativeTestSimpleEncode(String encoder, String file, String mime,
+            int[] list0, int[] list1, int[] list2, int colorFormat);
+
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testSimpleEncodeNative() throws IOException {
+        ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+        assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+        int colorFormat = -1;
+        for (String encoder : listOfEncoders) {
+            if (!mIsAudio) {
+                colorFormat = findByteBufferColorFormat(encoder, mMime);
+                assertTrue("no valid color formats received", colorFormat != -1);
+            }
+            assertTrue(nativeTestSimpleEncode(encoder, mInpPrefix + mInputFile, mMime, mBitrates,
+                    mEncParamList1, mEncParamList2, colorFormat));
+        }
+    }
+
     /**
      * Tests flush when codec is in sync and async mode. In these scenarios, Timestamp
      * ordering is verified. The output has to be consistent (not flaky) in all runs
@@ -559,7 +588,6 @@
                 doWork(23);
                 assertTrue(log + " pts is not strictly increasing",
                         mOutputBuff.isPtsStrictlyIncreasing(mPrevOutputPts));
-
                 boolean checkMetrics = (mOutputCount != 0);
 
                 /* test flush in running state */
@@ -612,6 +640,26 @@
         }
     }
 
+    private native boolean nativeTestFlush(String encoder, String file, String mime,
+            int[] list0, int[] list1, int[] list2, int colorFormat);
+
+    @Ignore("TODO(b/147576107, b/148652492, b/148651699)")
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testFlushNative() throws IOException {
+        ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+        assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+        int colorFormat = -1;
+        for (String encoder : listOfEncoders) {
+            if (!mIsAudio) {
+                colorFormat = findByteBufferColorFormat(encoder, mMime);
+                assertTrue("no valid color formats received", colorFormat != -1);
+            }
+            assertTrue(nativeTestFlush(encoder, mInpPrefix + mInputFile, mMime, mBitrates,
+                    mEncParamList1, mEncParamList2, colorFormat));
+        }
+    }
+
     /**
      * Tests reconfigure when codec is in sync and async mode. In these
      * scenarios, Timestamp ordering is verified. The output has to be consistent (not flaky)
@@ -633,6 +681,13 @@
                 MediaFormat format = mFormats.get(1);
                 encodeToMemory(mInputFile, encoder, Integer.MAX_VALUE, format);
                 configRef = mOutputBuff;
+                if (mIsAudio) {
+                    assertTrue("config reference output pts is not strictly increasing",
+                            configRef.isPtsStrictlyIncreasing(mPrevOutputPts));
+                } else {
+                    assertTrue("input pts list and reconfig ref output pts list are not identical",
+                            configRef.isOutPtsListIdenticalToInpPtsList((mMaxBFrames != 0)));
+                }
             }
             MediaFormat format = mFormats.get(0);
             encodeToMemory(mInputFile, encoder, Integer.MAX_VALUE, format);
@@ -729,6 +784,26 @@
         }
     }
 
+    private native boolean nativeTestReconfigure(String encoder, String file, String mime,
+            int[] list0, int[] list1, int[] list2, int colorFormat);
+
+    @Ignore("TODO(b/147348711, b/149981033)")
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testReconfigureNative() throws IOException {
+        ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+        assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+        int colorFormat = -1;
+        for (String encoder : listOfEncoders) {
+            if (!mIsAudio) {
+                colorFormat = findByteBufferColorFormat(encoder, mMime);
+                assertTrue("no valid color formats received", colorFormat != -1);
+            }
+            assertTrue(nativeTestReconfigure(encoder, mInpPrefix + mInputFile, mMime, mBitrates,
+                    mEncParamList1, mEncParamList2, colorFormat));
+        }
+    }
+
     /**
      * Tests encoder for only EOS frame
      */
@@ -777,4 +852,222 @@
             mCodec.release();
         }
     }
+
+    private native boolean nativeTestOnlyEos(String encoder, String mime, int[] list0, int[] list1,
+            int[] list2, int colorFormat);
+
+    @SmallTest
+    @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
+    public void testOnlyEosNative() throws IOException {
+        ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+        assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+        int colorFormat = -1;
+        for (String encoder : listOfEncoders) {
+            if (!mIsAudio) {
+                colorFormat = findByteBufferColorFormat(encoder, mMime);
+                assertTrue("no valid color formats received", colorFormat != -1);
+            }
+            assertTrue(nativeTestOnlyEos(encoder, mMime, mBitrates, mEncParamList1, mEncParamList2,
+                    colorFormat));
+        }
+    }
+
+    /**
+     * Test set parameters : force key frame
+     */
+    @Ignore("TODO(b/151302863)")
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testSetForceSyncFrame() throws IOException, InterruptedException {
+        Assume.assumeTrue(!mIsAudio);
+        // Maximum allowed key frame interval variation from the target value.
+        final int MAX_KEYFRAME_INTERVAL_VARIATION = 3;
+        setUpParams(1);
+        ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+        assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+        boolean[] boolStates = {true, false};
+        setUpSource(mInputFile);
+        MediaFormat format = mFormats.get(0);
+        format.removeKey(MediaFormat.KEY_I_FRAME_INTERVAL);
+        format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 500.f);
+        mWidth = format.getInteger(MediaFormat.KEY_WIDTH);
+        mHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
+        final int KEY_FRAME_INTERVAL = 2; // force key frame every 2 seconds.
+        final int KEY_FRAME_POS = mFrameRate * KEY_FRAME_INTERVAL;
+        final int NUM_KEY_FRAME_REQUESTS = 7;
+        mOutputBuff = new OutputManager();
+        for (String encoder : listOfEncoders) {
+            mCodec = MediaCodec.createByCodecName(encoder);
+            for (boolean isAsync : boolStates) {
+                String log = String.format(
+                        "format: %s \n codec: %s, file: %s, mode: %s:: ", format, encoder,
+                        mInputFile, (isAsync ? "async" : "sync"));
+                mOutputBuff.reset();
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                for (int i = 0; i < NUM_KEY_FRAME_REQUESTS; i++) {
+                    doWork(KEY_FRAME_POS);
+                    assertTrue(!mSawInputEOS);
+                    forceSyncFrame();
+                    mNumBytesSubmitted = 0;
+                }
+                queueEOS();
+                waitForAllOutputs();
+                /* TODO(b/147348711) */
+                if (false) mCodec.stop();
+                else mCodec.reset();
+                assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+                assertTrue(log + "no input sent", 0 != mInputCount);
+                assertTrue(log + "output received", 0 != mOutputCount);
+                assertTrue(log + "input count != output count, act/exp: " + mOutputCount + " / " +
+                        mInputCount, mInputCount == mOutputCount);
+                assertTrue(log + " input pts list and output pts list are not identical",
+                        mOutputBuff.isOutPtsListIdenticalToInpPtsList((mMaxBFrames != 0)));
+                assertTrue(log + "sync frames exp/act: " + NUM_KEY_FRAME_REQUESTS + " / " +
+                        mNumSyncFramesReceived, mNumSyncFramesReceived >= NUM_KEY_FRAME_REQUESTS);
+                for (int i = 0, expPos = 0, index = 0; i < NUM_KEY_FRAME_REQUESTS; i++) {
+                    int j = index;
+                    for (; j < mSyncFramesPos.size(); j++) {
+                        // Check key frame intervals:
+                        // key frame position should not be greater than target value + 3
+                        // key frame position should not be less than target value - 3
+                        if (Math.abs(expPos - mSyncFramesPos.get(j)) <=
+                                MAX_KEYFRAME_INTERVAL_VARIATION) {
+                            index = j;
+                            break;
+                        }
+                    }
+                    if (j == mSyncFramesPos.size()) {
+                        Log.w(LOG_TAG, "requested key frame at frame index " + expPos +
+                                " none found near by");
+                    }
+                    expPos += KEY_FRAME_POS;
+                }
+            }
+            mCodec.release();
+        }
+    }
+
+    private native boolean nativeTestSetForceSyncFrame(String encoder, String file, String mime,
+            int[] list0, int[] list1, int[] list2, int colorFormat);
+
+    @Ignore("TODO(b/) = test sometimes timesout")
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testSetForceSyncFrameNative() throws IOException {
+        Assume.assumeTrue(!mIsAudio);
+        ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+        assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+        int colorFormat = -1;
+        for (String encoder : listOfEncoders) {
+            if (!mIsAudio) {
+                colorFormat = findByteBufferColorFormat(encoder, mMime);
+                assertTrue("no valid color formats received", colorFormat != -1);
+            }
+            assertTrue(nativeTestSetForceSyncFrame(encoder, mInpPrefix + mInputFile, mMime,
+                    mBitrates, mEncParamList1, mEncParamList2, colorFormat));
+        }
+    }
+
+    /**
+     * Test set parameters : change bitrate dynamically
+     */
+    @Ignore("TODO(b/151302863)")
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testAdaptiveBitRate() throws IOException, InterruptedException {
+        Assume.assumeTrue(!mIsAudio);
+        setUpParams(1);
+        ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+        assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+        boolean[] boolStates = {true, false};
+        setUpSource(mInputFile);
+        MediaFormat format = mFormats.get(0);
+        mWidth = format.getInteger(MediaFormat.KEY_WIDTH);
+        mHeight = format.getInteger(MediaFormat.KEY_HEIGHT);
+        final int ADAPTIVE_BR_INTERVAL = 3; // change br every 3 seconds.
+        final int ADAPTIVE_BR_DUR_FRM = mFrameRate * ADAPTIVE_BR_INTERVAL;
+        final int BR_CHANGE_REQUESTS = 7;
+        mOutputBuff = new OutputManager();
+        mSaveToMem = true;
+        for (String encoder : listOfEncoders) {
+            /* TODO(b/147574800) */
+            if (encoder.equals("c2.android.hevc.encoder")) continue;
+            mCodec = MediaCodec.createByCodecName(encoder);
+            format.removeKey(MediaFormat.KEY_BITRATE_MODE);
+            MediaCodecInfo.EncoderCapabilities cap =
+                    mCodec.getCodecInfo().getCapabilitiesForType(mMime).getEncoderCapabilities();
+            if (cap.isBitrateModeSupported(MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR)) {
+                format.setInteger(MediaFormat.KEY_BITRATE_MODE,
+                        MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_CBR);
+            } else {
+                format.setInteger(MediaFormat.KEY_BITRATE_MODE,
+                        MediaCodecInfo.EncoderCapabilities.BITRATE_MODE_VBR);
+            }
+            for (boolean isAsync : boolStates) {
+                String log = String.format(
+                        "format: %s \n codec: %s, file: %s, mode: %s:: ", format, encoder,
+                        mInputFile, (isAsync ? "async" : "sync"));
+                mOutputBuff.reset();
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                int expOutSize = 0;
+                int bitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
+                for (int i = 0; i < BR_CHANGE_REQUESTS; i++) {
+                    doWork(ADAPTIVE_BR_DUR_FRM);
+                    assertTrue(!mSawInputEOS);
+                    expOutSize += ADAPTIVE_BR_INTERVAL * bitrate;
+                    if ((i & 1) == 1) bitrate *= 2;
+                    else bitrate /= 2;
+                    updateBitrate(bitrate);
+                    mNumBytesSubmitted = 0;
+                }
+                queueEOS();
+                waitForAllOutputs();
+                /* TODO(b/147348711) */
+                if (false) mCodec.stop();
+                else mCodec.reset();
+                assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+                assertTrue(log + "no input sent", 0 != mInputCount);
+                assertTrue(log + "output received", 0 != mOutputCount);
+                assertTrue(log + "input count != output count, act/exp: " + mOutputCount + " / " +
+                        mInputCount, mInputCount == mOutputCount);
+                assertTrue(log + " input pts list and output pts list are not identical",
+                        mOutputBuff.isOutPtsListIdenticalToInpPtsList((mMaxBFrames != 0)));
+                /* TODO: validate output br with sliding window constraints Sec 5.2 cdd */
+                int outSize = mOutputBuff.getOutStreamSize() * 8;
+                float brDev = Math.abs(expOutSize - outSize) * 100.0f / expOutSize;
+                if (ENABLE_LOGS) {
+                    Log.d(LOG_TAG, log + "relative br error is " + brDev + '%');
+                }
+                if (brDev > 50) {
+                    fail(log + "relative br error is too large " + brDev + '%');
+                }
+            }
+            mCodec.release();
+        }
+    }
+
+    private native boolean nativeTestAdaptiveBitRate(String encoder, String file, String mime,
+            int[] list0, int[] list1, int[] list2, int colorFormat);
+
+    @Ignore("TODO(b/) = test sometimes timesout")
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testAdaptiveBitRateNative() throws IOException {
+        Assume.assumeTrue(!mIsAudio);
+        ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+        assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+        int colorFormat = -1;
+        for (String encoder : listOfEncoders) {
+            /* TODO(b/147574800) */
+            if (encoder.equals("c2.android.hevc.encoder")) continue;
+            if (!mIsAudio) {
+                colorFormat = findByteBufferColorFormat(encoder, mMime);
+                assertTrue("no valid color formats received", colorFormat != -1);
+            }
+            assertTrue(nativeTestAdaptiveBitRate(encoder, mInpPrefix + mInputFile, mMime, mBitrates,
+                    mEncParamList1, mEncParamList2, colorFormat));
+        }
+    }
 }
diff --git a/tests/media/src/android/mediav2/cts/CodecTestBase.java b/tests/media/src/android/mediav2/cts/CodecTestBase.java
index a30a239..b9db836 100644
--- a/tests/media/src/android/mediav2/cts/CodecTestBase.java
+++ b/tests/media/src/android/mediav2/cts/CodecTestBase.java
@@ -27,6 +27,7 @@
 import android.os.PersistableBundle;
 import android.util.Log;
 import android.util.Pair;
+import android.view.Surface;
 
 import androidx.annotation.NonNull;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -39,6 +40,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.List;
 import java.util.Map;
 import java.util.concurrent.locks.Condition;
 import java.util.concurrent.locks.Lock;
@@ -46,6 +48,7 @@
 import java.util.zip.CRC32;
 
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 class CodecAsyncHandler extends MediaCodec.Callback {
     private static final String LOG_TAG = CodecAsyncHandler.class.getSimpleName();
@@ -172,6 +175,13 @@
         return element;
     }
 
+    boolean isInputQueueEmpty() {
+        mLock.lock();
+        boolean isEmpty = mCbInputQueue.isEmpty();
+        mLock.unlock();
+        return isEmpty;
+    }
+
     boolean hasSeenError() {
         return mSignalledError;
     }
@@ -419,13 +429,13 @@
 
 abstract class CodecTestBase {
     private static final String LOG_TAG = CodecTestBase.class.getSimpleName();
-    private static final String CODEC_SEL_KEY = "codec-sel";
+    static final String CODEC_SEL_KEY = "codec-sel";
     static final String CODEC_SEL_VALUE = "default";
     static final Map<String, String> codecSelKeyMimeMap = new HashMap<>();
     static final boolean ENABLE_LOGS = false;
     static final int PER_TEST_TIMEOUT_LARGE_TEST_MS = 300000;
     static final int PER_TEST_TIMEOUT_SMALL_TEST_MS = 60000;
-    static final long Q_DEQ_TIMEOUT_US = 500;
+    static final long Q_DEQ_TIMEOUT_US = 5000;
     static final String mInpPrefix = WorkDir.getMediaDirString();
     static String codecSelKeys;
 
@@ -445,8 +455,11 @@
     OutputManager mOutputBuff;
 
     MediaCodec mCodec;
+    Surface mSurface;
 
     static {
+        System.loadLibrary("ctsmediav2codec_jni");
+
         codecSelKeyMimeMap.put("vp8", MediaFormat.MIMETYPE_VIDEO_VP8);
         codecSelKeyMimeMap.put("vp9", MediaFormat.MIMETYPE_VIDEO_VP9);
         codecSelKeyMimeMap.put("av1", MediaFormat.MIMETYPE_VIDEO_AV1);
@@ -478,6 +491,53 @@
                 .hasSystemFeature(PackageManager.FEATURE_LEANBACK);
     }
 
+    static List<Object[]> prepareParamList(ArrayList<String> cddRequiredMimeList,
+            List<Object[]> exhaustiveArgsList, boolean isEncoder) {
+        ArrayList<String> mimes = new ArrayList<>();
+        if (codecSelKeys.contains(CODEC_SEL_VALUE)) {
+            MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+            MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
+            for (MediaCodecInfo codecInfo : codecInfos) {
+                if (codecInfo.isEncoder() != isEncoder) continue;
+                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && codecInfo.isAlias()) continue;
+                String[] types = codecInfo.getSupportedTypes();
+                for (String type : types) {
+                    if (!mimes.contains(type)) {
+                        mimes.add(type);
+                    }
+                }
+            }
+            for (String mime : cddRequiredMimeList) {
+                if (!mimes.contains(mime)) {
+                    fail("no codec found for mime " + mime + " as required by cdd");
+                }
+            }
+        } else {
+            for (Map.Entry<String, String> entry : codecSelKeyMimeMap.entrySet()) {
+                String key = entry.getKey();
+                String value = entry.getValue();
+                if (codecSelKeys.contains(key) && !mimes.contains(value)) mimes.add(value);
+            }
+        }
+        final List<Object[]> argsList = new ArrayList<>();
+        for (String mime : mimes) {
+            boolean miss = true;
+            for (Object[] arg : exhaustiveArgsList) {
+                if (mime.equals(arg[0])) {
+                    argsList.add(arg);
+                    miss = false;
+                }
+            }
+            if (miss) {
+                if (cddRequiredMimeList.contains(mime)) {
+                    fail("no test vectors for required mimetype " + mime);
+                }
+                Log.w(LOG_TAG, "no test vectors available for optional mime type " + mime);
+            }
+        }
+        return argsList;
+    }
+
     abstract void enqueueInput(int bufferIndex) throws IOException;
 
     abstract void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info);
@@ -489,9 +549,11 @@
         // signalEOS flag has nothing to do with configure. We are using this flag to try all
         // available configure apis
         if (signalEOSWithLastFrame) {
-            mCodec.configure(format, null, null, isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
+            mCodec.configure(format, mSurface, null,
+                    isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
         } else {
-            mCodec.configure(format, null, isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0, null);
+            mCodec.configure(format, mSurface, isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0,
+                    null);
         }
         if (ENABLE_LOGS) {
             Log.v(LOG_TAG, "codec configured");
diff --git a/tests/media/src/android/mediav2/cts/CodecUnitTest.java b/tests/media/src/android/mediav2/cts/CodecUnitTest.java
new file mode 100644
index 0000000..3901b88
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/CodecUnitTest.java
@@ -0,0 +1,2417 @@
+/*
+ * 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.mediav2.cts;
+
+import android.media.Image;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.os.Bundle;
+import android.util.Pair;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.rules.Timeout;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(Enclosed.class)
+public class CodecUnitTest {
+    static final int PER_TEST_TIMEOUT_MS = 10000;
+    static final long STALL_TIME_MS = 1000;
+
+    @SmallTest
+    public static class TestApi extends CodecTestBase {
+        @Rule
+        public Timeout timeout = new Timeout(PER_TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+        @After
+        public void hasSeenError() {
+            assertFalse(mAsyncHandle.hasSeenError());
+        }
+
+        public TestApi() {
+            mAsyncHandle = new CodecAsyncHandler();
+        }
+
+        void enqueueInput(int bufferIndex) {
+            fail("something went wrong, shouldn't have reached here");
+        }
+
+        void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
+            if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                mSawOutputEOS = true;
+            }
+            mCodec.releaseOutputBuffer(bufferIndex, false);
+        }
+
+        private MediaFormat getSampleAudioFormat() {
+            MediaFormat format = new MediaFormat();
+            String mime = MediaFormat.MIMETYPE_AUDIO_AAC;
+            format.setString(MediaFormat.KEY_MIME, mime);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, 64000);
+            format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000);
+            format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
+            return format;
+        }
+
+        private MediaFormat getSampleVideoFormat() {
+            MediaFormat format = new MediaFormat();
+            String mime = MediaFormat.MIMETYPE_VIDEO_AVC;
+            format.setString(MediaFormat.KEY_MIME, mime);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, 256000);
+            format.setInteger(MediaFormat.KEY_WIDTH, 352);
+            format.setInteger(MediaFormat.KEY_HEIGHT, 288);
+            format.setInteger(MediaFormat.KEY_FRAME_RATE, 30);
+            format.setFloat(MediaFormat.KEY_I_FRAME_INTERVAL, 1.0f);
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT,
+                    MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible);
+            return format;
+        }
+
+        private Bundle updateBitrate(int bitrate) {
+            final Bundle bitrateUpdate = new Bundle();
+            bitrateUpdate.putInt(MediaCodec.PARAMETER_KEY_VIDEO_BITRATE, bitrate);
+            return bitrateUpdate;
+        }
+
+        void testConfigureCodecForIncompleteFormat(MediaFormat format, String[] keys,
+                boolean isEncoder) throws IOException {
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            if (isEncoder) {
+                mCodec = MediaCodec.createEncoderByType(mime);
+            } else {
+                mCodec = MediaCodec.createDecoderByType(mime);
+            }
+            for (String key : keys) {
+                MediaFormat formatClone = new MediaFormat(format);
+                formatClone.removeKey(key);
+                try {
+                    mCodec.configure(formatClone, null, null,
+                            isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
+                    fail("codec configure succeeds with missing mandatory keys :: " + key);
+                } catch (Exception e) {
+                    if (!(e instanceof IllegalArgumentException)) {
+                        fail("codec configure rec/exp :: " + e.toString() +
+                                " / IllegalArgumentException");
+                    }
+                }
+            }
+            try {
+                mCodec.configure(format, null, null,
+                        isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
+            } catch (Exception e) {
+                fail("configure failed unexpectedly");
+            } finally {
+                mCodec.release();
+            }
+        }
+
+        void testConfigureCodecForBadFlags(boolean isEncoder) throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            if (isEncoder) {
+                mCodec = MediaCodec.createEncoderByType(mime);
+            } else {
+                mCodec = MediaCodec.createDecoderByType(mime);
+            }
+            try {
+                mCodec.configure(format, null, null,
+                        isEncoder ? 0 : MediaCodec.CONFIGURE_FLAG_ENCODE);
+                fail("codec configure succeeds with bad configure flag");
+            } catch (Exception e) {
+                if (!(e instanceof IllegalArgumentException)) {
+                    fail("codec configure rec/exp :: " + e.toString() +
+                            " / IllegalArgumentException");
+                }
+            } finally {
+                mCodec.release();
+            }
+        }
+
+        void tryConfigureCodecInInvalidState(MediaFormat format, boolean isAsync, String msg) {
+            try {
+                configureCodec(format, isAsync, false, true);
+                fail(msg);
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        void tryDequeueInputBufferInInvalidState(String msg) {
+            try {
+                mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US);
+                fail(msg);
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        void tryDequeueOutputBufferInInvalidState(String msg) {
+            try {
+                MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+                mCodec.dequeueOutputBuffer(info, Q_DEQ_TIMEOUT_US);
+                fail(msg);
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        void tryFlushInInvalidState(String msg) {
+            try {
+                flushCodec();
+                fail(msg);
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        void tryGetMetaData(String msg) {
+            try {
+                mCodec.getName();
+            } catch (IllegalStateException e) {
+                fail("get name resulted in" + e.getMessage());
+            }
+
+            try {
+                mCodec.getCanonicalName();
+            } catch (IllegalStateException e) {
+                fail("get canonical name resulted in" + e.getMessage());
+            }
+
+            try {
+                mCodec.getCodecInfo();
+            } catch (IllegalStateException e) {
+                fail("get codec info resulted in" + e.getMessage());
+            }
+
+            try {
+                mCodec.getMetrics();
+            } catch (IllegalStateException e) {
+                fail("get metrics resulted in" + e.getMessage());
+            }
+        }
+
+        void tryGetInputBufferInInvalidState(String msg) {
+            try {
+                mCodec.getInputBuffer(0);
+                fail(msg);
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        void tryGetInputFormatInInvalidState(String msg) {
+            try {
+                mCodec.getInputFormat();
+                fail(msg);
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        void tryGetOutputBufferInInvalidState(String msg) {
+            try {
+                mCodec.getOutputBuffer(0);
+                fail(msg);
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        void tryGetOutputFormatInInvalidState(String msg) {
+            try {
+                mCodec.getOutputFormat();
+                fail(msg);
+            } catch (IllegalStateException e) {
+                // expected
+            }
+
+            try {
+                mCodec.getOutputFormat(0);
+                fail(msg);
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        void tryStartInInvalidState(String msg) {
+            try {
+                mCodec.start();
+                fail(msg);
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        void tryGetInputImageInInvalidState(String msg) {
+            try {
+                mCodec.getInputImage(0);
+                fail(msg);
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        void tryGetOutputImageInInvalidState(String msg) {
+            try {
+                mCodec.getOutputImage(0);
+                fail(msg);
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        void tryQueueInputBufferInInvalidState(String msg) {
+            try {
+                mCodec.queueInputBuffer(0, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                fail(msg);
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        void tryReleaseOutputBufferInInvalidState(String msg) {
+            try {
+                mCodec.releaseOutputBuffer(0, false);
+                fail(msg);
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testCreateByCodecNameForNull() throws IOException {
+            try {
+                mCodec = MediaCodec.createByCodecName(null);
+                fail("createByCodecName succeeds with null argument");
+            } catch (NullPointerException e) {
+                // expected
+            } finally {
+                if (mCodec != null) mCodec.release();
+            }
+        }
+
+        @Test
+        public void testCreateByCodecNameForInvalidName() throws IOException {
+            try {
+                mCodec = MediaCodec.createByCodecName("invalid name");
+                fail("createByCodecName succeeds with invalid name");
+            } catch (IllegalArgumentException e) {
+                // expected
+            } finally {
+                if (mCodec != null) mCodec.release();
+            }
+        }
+
+        @Test
+        public void testCreateDecoderByTypeForNull() throws IOException {
+            try {
+                mCodec = MediaCodec.createDecoderByType(null);
+                fail("createDecoderByType succeeds with null argument");
+            } catch (NullPointerException e) {
+                // expected
+            } finally {
+                if (mCodec != null) mCodec.release();
+            }
+        }
+
+        @Test
+        public void testCreateDecoderByTypeForInvalidMime() throws IOException {
+            try {
+                mCodec = MediaCodec.createDecoderByType("invalid mime");
+                fail("createDecoderByType succeeds with invalid mime");
+            } catch (IllegalArgumentException e) {
+                // expected
+            } finally {
+                if (mCodec != null) mCodec.release();
+            }
+        }
+
+        @Test
+        public void testCreateEncoderByTypeForNull() throws IOException {
+            try {
+                mCodec = MediaCodec.createEncoderByType(null);
+                fail("createEncoderByType succeeds with null argument");
+            } catch (NullPointerException e) {
+                // expected
+            } finally {
+                if (mCodec != null) mCodec.release();
+            }
+        }
+
+        @Test
+        public void testCreateEncoderByTypeForInvalidMime() throws IOException {
+            try {
+                mCodec = MediaCodec.createEncoderByType("invalid mime");
+                fail("createEncoderByType succeeds with invalid mime");
+            } catch (IllegalArgumentException e) {
+                // expected
+            } finally {
+                if (mCodec != null) mCodec.release();
+            }
+        }
+
+        @Test
+        @Ignore("TODO(b/151302868)")
+        public void testConfigureForNullFormat() throws IOException {
+            mCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
+            mCodec.configure(null, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            mCodec.release();
+        }
+
+        @Test
+        @Ignore("TODO(b/151302868)")
+        public void testConfigureForEmptyFormat() throws IOException {
+            mCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC);
+            mCodec.configure(new MediaFormat(), null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
+            mCodec.release();
+        }
+
+        @Test
+        @Ignore("TODO(b/151302868)")
+        public void testConfigureAudioDecodeForIncompleteFormat() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String[] mandatoryKeys =
+                    new String[]{MediaFormat.KEY_MIME, MediaFormat.KEY_CHANNEL_COUNT,
+                            MediaFormat.KEY_SAMPLE_RATE};
+            testConfigureCodecForIncompleteFormat(format, mandatoryKeys, false);
+        }
+
+        @Test
+        @Ignore("TODO(b/151302868)")
+        public void testConfigureAudioEncodeForIncompleteFormat() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String[] mandatoryKeys =
+                    new String[]{MediaFormat.KEY_MIME, MediaFormat.KEY_CHANNEL_COUNT,
+                            MediaFormat.KEY_SAMPLE_RATE, MediaFormat.KEY_BIT_RATE};
+            testConfigureCodecForIncompleteFormat(format, mandatoryKeys, true);
+        }
+
+        @Test
+        @Ignore("TODO(b/151302868)")
+        public void testConfigureVideoDecodeForIncompleteFormat() throws IOException {
+            MediaFormat format = getSampleVideoFormat();
+            String[] mandatoryKeys =
+                    new String[]{MediaFormat.KEY_MIME, MediaFormat.KEY_WIDTH,
+                            MediaFormat.KEY_HEIGHT};
+            testConfigureCodecForIncompleteFormat(format, mandatoryKeys, false);
+        }
+
+        @Test
+        @Ignore("TODO(b/151302868, b/151303041)")
+        public void testConfigureVideoEncodeForIncompleteFormat() throws IOException {
+            MediaFormat format = getSampleVideoFormat();
+            String[] mandatoryKeys =
+                    new String[]{MediaFormat.KEY_MIME, MediaFormat.KEY_WIDTH,
+                            MediaFormat.KEY_HEIGHT, MediaFormat.KEY_I_FRAME_INTERVAL,
+                            MediaFormat.KEY_FRAME_RATE, MediaFormat.KEY_BIT_RATE,
+                            MediaFormat.KEY_COLOR_FORMAT};
+            testConfigureCodecForIncompleteFormat(format, mandatoryKeys, true);
+        }
+
+        @Test
+        @Ignore("TODO(b/151304147)")
+        public void testConfigureEncoderForBadFlags() throws IOException {
+            testConfigureCodecForBadFlags(true);
+        }
+
+        @Test
+        @Ignore("TODO(b/151304147)")
+        public void testConfigureDecoderForBadFlags() throws IOException {
+            testConfigureCodecForBadFlags(false);
+        }
+
+        @Test
+        public void testConfigureInInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                // configure in initialized state
+                tryConfigureCodecInInvalidState(format, isAsync,
+                        "codec configure succeeds in initialized state");
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        @Ignore("TODO(b/151894670)")
+        public void testConfigureAfterStart() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                // configure in running state
+                tryConfigureCodecInInvalidState(format, isAsync,
+                        "codec configure succeeds after Start()");
+                queueEOS();
+                waitForAllOutputs();
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        @Ignore("TODO(b/151894670)")
+        public void testConfigureAfterQueueInputBuffer() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                queueEOS();
+                // configure in running state
+                tryConfigureCodecInInvalidState(format, isAsync,
+                        "codec configure succeeds after QueueInputBuffer()");
+                waitForAllOutputs();
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testConfigureInEOSState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                queueEOS();
+                waitForAllOutputs();
+                // configure in eos state
+                tryConfigureCodecInInvalidState(format, isAsync,
+                        "codec configure succeeds in eos state");
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        @Ignore("TODO(b/147576107)")
+        public void testConfigureInFlushState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                flushCodec();
+                // configure in flush state
+                tryConfigureCodecInInvalidState(format, isAsync,
+                        "codec configure succeeds in flush state");
+                if (mIsCodecInAsyncMode) mCodec.start();
+                queueEOS();
+                waitForAllOutputs();
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testConfigureInUnInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.stop();
+                // configure in uninitialized state
+                try {
+                    configureCodec(format, isAsync, false, true);
+                } catch (Exception e) {
+                    fail("codec configure fails in uninitialized state");
+                }
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testConfigureInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            tryConfigureCodecInInvalidState(format, false,
+                    "codec configure succeeds in release state");
+        }
+
+        @Test
+        public void testDequeueInputBufferInUnInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                // dequeue buffer in uninitialized state
+                tryDequeueInputBufferInInvalidState(
+                        "dequeue input buffer succeeds in uninitialized state");
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                mCodec.stop();
+                // dequeue buffer in stopped state
+                tryDequeueInputBufferInInvalidState(
+                        "dequeue input buffer succeeds in stopped state");
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testDequeueInputBufferInInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                // dequeue buffer in initialized state
+                tryDequeueInputBufferInInvalidState(
+                        "dequeue input buffer succeeds in initialized state");
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testDequeueInputBufferInRunningState()
+                throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                if (mIsCodecInAsyncMode) {
+                    // dequeue buffer in running state
+                    tryDequeueInputBufferInInvalidState(
+                            "dequeue input buffer succeeds in running state, async mode");
+                }
+                queueEOS();
+                waitForAllOutputs();
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testDequeueInputBufferInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            // dequeue buffer in released state
+            tryDequeueInputBufferInInvalidState(
+                    "dequeue input buffer succeeds in release state");
+        }
+
+        @Test
+        public void testDequeueOutputBufferInUnInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                // dequeue buffer in uninitialized state
+                tryDequeueOutputBufferInInvalidState(
+                        "dequeue output buffer succeeds in uninitialized state");
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                mCodec.stop();
+                // dequeue buffer in stopped state
+                tryDequeueOutputBufferInInvalidState(
+                        "dequeue output buffer succeeds in stopped state");
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testDequeueOutputBufferInInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                // dequeue buffer in initialized state
+                tryDequeueOutputBufferInInvalidState(
+                        "dequeue output buffer succeeds in initialized state");
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testDequeueOutputBufferInRunningState()
+                throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                if (mIsCodecInAsyncMode) {
+                    // dequeue buffer in running state
+                    tryDequeueOutputBufferInInvalidState(
+                            "dequeue output buffer succeeds in running state, async mode");
+                }
+                queueEOS();
+                waitForAllOutputs();
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testDequeueOutputBufferInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            // dequeue buffer in released state
+            tryDequeueOutputBufferInInvalidState(
+                    "dequeue output buffer succeeds in release state");
+        }
+
+        @Test
+        public void testFlushInUnInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                // flush uninitialized state
+                tryFlushInInvalidState("codec flush succeeds in uninitialized state");
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                mCodec.stop();
+                // flush in stopped state
+                tryFlushInInvalidState("codec flush succeeds in stopped state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testFlushInInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                // flush in initialized state
+                tryFlushInInvalidState("codec flush succeeds in initialized state");
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        @Ignore("TODO(b/147576107)")
+        public void testFlushInRunningState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            configureCodec(format, true, false, true);
+            mCodec.start();
+            flushCodec();
+            Thread.sleep(STALL_TIME_MS);
+            assertTrue("received input buffer callback before start",
+                    mAsyncHandle.isInputQueueEmpty());
+            mCodec.start();
+            Thread.sleep(STALL_TIME_MS);
+            assertFalse("did not receive input buffer callback after start",
+                    mAsyncHandle.isInputQueueEmpty());
+            mCodec.stop();
+            mCodec.release();
+        }
+
+        @Test
+        public void testFlushInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            tryFlushInInvalidState("codec flush succeeds in release state");
+        }
+
+        @Test
+        public void testGetMetaDataInUnInitState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                tryGetMetaData("codec get metadata call fails in uninitialized state");
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                queueEOS();
+                waitForAllOutputs();
+                mCodec.stop();
+                tryGetMetaData("codec get metadata call fails in stopped state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetMetaDataInInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                tryGetMetaData("codec get metadata call fails in initialized state");
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetMetaDataInRunningState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                tryGetMetaData("codec get metadata call fails in running state");
+                queueEOS();
+                waitForAllOutputs();
+                tryGetMetaData("codec get metadata call fails in eos state");
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetMetaDataInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            try {
+                mCodec.getCanonicalName();
+                fail("get canonical name succeeds after codec release");
+            } catch (IllegalStateException e) {
+                // expected
+            }
+
+            try {
+                mCodec.getCodecInfo();
+                fail("get codec info succeeds after codec release");
+            } catch (IllegalStateException e) {
+                // expected
+            }
+
+            try {
+                mCodec.getName();
+                fail("get name succeeds after codec release");
+            } catch (IllegalStateException e) {
+                // expected
+            }
+
+            try {
+                mCodec.getMetrics();
+                fail("get metrics succeeds after codec release");
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testSetCallBackInUnInitState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+
+            boolean isAsync = true;
+            // set component in async mode
+            mAsyncHandle.setCallBack(mCodec, isAsync);
+            mIsCodecInAsyncMode = isAsync;
+            // configure component to sync mode
+            configureCodec(format, !isAsync, false, true);
+            mCodec.start();
+            queueEOS();
+            waitForAllOutputs();
+            mCodec.stop();
+
+            // set component in sync mode
+            mAsyncHandle.setCallBack(mCodec, !isAsync);
+            mIsCodecInAsyncMode = !isAsync;
+            // configure component in async mode
+            configureCodec(format, isAsync, false, true);
+            mCodec.start();
+            queueEOS();
+            waitForAllOutputs();
+            mCodec.stop();
+            mCodec.release();
+        }
+
+        @Test
+        public void testSetCallBackInInitState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+
+            // configure component in async mode
+            boolean isAsync = true;
+            configureCodec(format, isAsync, false, true);
+            // change component to sync mode
+            mAsyncHandle.setCallBack(mCodec, !isAsync);
+            mIsCodecInAsyncMode = !isAsync;
+            mCodec.start();
+            queueEOS();
+            waitForAllOutputs();
+            mCodec.stop();
+
+            // configure component in sync mode
+            configureCodec(format, !isAsync, false, true);
+            // change the component to operate in async mode
+            mAsyncHandle.setCallBack(mCodec, isAsync);
+            mIsCodecInAsyncMode = isAsync;
+            mCodec.start();
+            queueEOS();
+            waitForAllOutputs();
+            mCodec.stop();
+            mCodec.release();
+        }
+
+        @Test
+        @Ignore("TODO(b/151305056)")
+        public void testSetCallBackInRunningState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean isAsync = false;
+            // configure codec in sync mode
+            configureCodec(format, isAsync, false, true);
+            mCodec.start();
+            // set call back should fail once the component is sailed to running state
+            try {
+                mAsyncHandle.setCallBack(mCodec, !isAsync);
+                mIsCodecInAsyncMode = !isAsync;
+                fail("set call back succeeds in running state");
+            } catch (IllegalStateException e) {
+                // expected
+            }
+            queueEOS();
+            waitForAllOutputs();
+            mCodec.stop();
+
+            // configure codec in async mode
+            configureCodec(format, !isAsync, false, true);
+            mCodec.start();
+            // set call back should fail once the component is sailed to running state
+            try {
+                mAsyncHandle.setCallBack(mCodec, isAsync);
+                mIsCodecInAsyncMode = isAsync;
+                fail("set call back succeeds in running state");
+            } catch (IllegalStateException e) {
+                // expected
+            }
+            queueEOS();
+            waitForAllOutputs();
+            mCodec.stop();
+            mCodec.release();
+        }
+
+        @Test
+        public void testSetCallBackInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            // set callbacks in release state
+            try {
+                mAsyncHandle.setCallBack(mCodec, false);
+                fail("set call back succeeds in released state");
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testGetInputBufferInUnInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                tryGetInputBufferInInvalidState("getInputBuffer succeeds in uninitialized state");
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                mCodec.stop();
+                tryGetInputBufferInInvalidState("getInputBuffer succeeds in stopped state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetInputBufferInInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                tryGetInputBufferInInvalidState("getInputBuffer succeeds in initialized state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        @Ignore("TODO(b/151304147)")
+        public void testGetInputBufferInRunningState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                try {
+                    ByteBuffer buffer = mCodec.getInputBuffer(-1);
+                    assertNull("getInputBuffer succeeds for bad buffer index " + -1, buffer);
+                } catch (Exception e) {
+                    fail("getInputBuffer rec/exp :: " + e.toString() + " / null");
+                }
+                int bufferIndex = mIsCodecInAsyncMode ? mAsyncHandle.getInput().first :
+                        mCodec.dequeueInputBuffer(-1);
+                ByteBuffer buffer = mCodec.getInputBuffer(bufferIndex);
+                assertNotNull(buffer);
+                ByteBuffer bufferDup = mCodec.getInputBuffer(bufferIndex);
+                assertNotNull(bufferDup);
+                enqueueEOS(bufferIndex);
+                waitForAllOutputs();
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetInputBufferInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            tryGetInputBufferInInvalidState("getInputBuffer succeeds in release state");
+        }
+
+        @Test
+        public void testGetInputFormatInUnInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                tryGetInputFormatInInvalidState("getInputFormat succeeds in uninitialized state");
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                mCodec.stop();
+                tryGetInputFormatInInvalidState("getInputFormat succeeds in stopped state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetInputFormatInInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                try {
+                    mCodec.getInputFormat();
+                } catch (Exception e) {
+                    fail("getInputFormat fails in initialized state");
+                }
+                mCodec.start();
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetInputFormatInRunningState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                try {
+                    mCodec.getInputFormat();
+                } catch (Exception e) {
+                    fail("getInputFormat fails in running state");
+                }
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetInputFormatInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            tryGetInputFormatInInvalidState("getInputFormat succeeds in release state");
+        }
+
+        @Test
+        public void testGetOutputBufferInUnInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                tryGetOutputBufferInInvalidState("getOutputBuffer succeeds in uninitialized state");
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                mCodec.stop();
+                tryGetOutputBufferInInvalidState("getOutputBuffer succeeds in stopped state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetOutputBufferInInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                tryGetOutputBufferInInvalidState("getOutputBuffer succeeds in initialized state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        @Ignore("TODO(b/151304147)")
+        public void testGetOutputBufferInRunningState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                try {
+                    ByteBuffer buffer = mCodec.getOutputBuffer(-1);
+                    assertNull("getOutputBuffer succeeds for bad buffer index " + -1, buffer);
+                } catch (Exception e) {
+                    fail("getOutputBuffer rec/exp :: " + e.toString() + " / null");
+                }
+                queueEOS();
+                int bufferIndex = 0;
+                while (!mSawOutputEOS) {
+                    if (mIsCodecInAsyncMode) {
+                        Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getOutput();
+                        bufferIndex = element.first;
+                        ByteBuffer buffer = mCodec.getOutputBuffer(bufferIndex);
+                        assertNotNull(buffer);
+                        dequeueOutput(element.first, element.second);
+                    } else {
+                        bufferIndex = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
+                        if (bufferIndex >= 0) {
+                            ByteBuffer buffer = mCodec.getOutputBuffer(bufferIndex);
+                            assertNotNull(buffer);
+                            dequeueOutput(bufferIndex, outInfo);
+                        }
+                    }
+                }
+                try {
+                    ByteBuffer buffer = mCodec.getOutputBuffer(bufferIndex);
+                    assertNull("getOutputBuffer succeeds for buffer index not owned by client",
+                            buffer);
+                } catch (Exception e) {
+                    fail("getOutputBuffer rec/exp :: " + e.toString() + " / null");
+                }
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetOutputBufferInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            tryGetOutputBufferInInvalidState("getOutputBuffer succeeds in release state");
+        }
+
+        @Test
+        public void testGetOutputFormatInUnInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                tryGetOutputFormatInInvalidState("getOutputFormat succeeds in uninitialized state");
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                mCodec.stop();
+                tryGetOutputFormatInInvalidState("getOutputFormat succeeds in stopped state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetOutputFormatInInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                try {
+                    mCodec.getOutputFormat();
+                } catch (Exception e) {
+                    fail("getOutputFormat fails in initialized state");
+                }
+                try {
+                    mCodec.getOutputFormat(0);
+                    fail("getOutputFormat succeeds in released state");
+                } catch (IllegalStateException e) {
+                    // expected
+                }
+                mCodec.start();
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        @Ignore("TODO(b/151304147)")
+        public void testGetOutputFormatInRunningState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                queueEOS();
+                try {
+                    mCodec.getOutputFormat();
+                } catch (Exception e) {
+                    fail("getOutputFormat fails in running state");
+                }
+                try {
+                    MediaFormat outputFormat = mCodec.getOutputFormat(-1);
+                    assertNull("getOutputFormat succeeds for bad buffer index " + -1, outputFormat);
+                } catch (Exception e) {
+                    fail("getOutputFormat rec/exp :: " + e.toString() + " / null");
+                }
+                int bufferIndex = 0;
+                while (!mSawOutputEOS) {
+                    if (mIsCodecInAsyncMode) {
+                        Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getOutput();
+                        bufferIndex = element.first;
+                        MediaFormat outputFormat = mCodec.getOutputFormat(bufferIndex);
+                        assertNotNull(outputFormat);
+                        dequeueOutput(element.first, element.second);
+                    } else {
+                        bufferIndex = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
+                        if (bufferIndex >= 0) {
+                            MediaFormat outputFormat = mCodec.getOutputFormat(bufferIndex);
+                            assertNotNull(outputFormat);
+                            dequeueOutput(bufferIndex, outInfo);
+                        }
+                    }
+                }
+                try {
+                    MediaFormat outputFormat = mCodec.getOutputFormat(bufferIndex);
+                    assertNull("getOutputFormat succeeds for index not owned by client",
+                            outputFormat);
+                } catch (Exception e) {
+                    fail("getOutputFormat rec/exp :: " + e.toString() + " / null");
+                }
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetOutputFormatInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            tryGetOutputFormatInInvalidState("getOutputFormat succeeds in release state");
+        }
+
+        @Test
+        public void testSetParametersInUnInitState() throws IOException {
+            MediaFormat format = getSampleVideoFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            int bitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            // call set param in uninitialized state
+            mCodec.setParameters(null);
+            mCodec.setParameters(updateBitrate(bitrate >> 1));
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                mCodec.stop();
+                mCodec.setParameters(null);
+                mCodec.setParameters(updateBitrate(bitrate >> 1));
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testSetParametersInInitState() throws IOException {
+            MediaFormat format = getSampleVideoFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            int bitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.setParameters(null);
+                mCodec.setParameters(updateBitrate(bitrate >> 1));
+                mCodec.start();
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testSetParametersInRunningState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleVideoFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            int bitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                mCodec.setParameters(null);
+                mCodec.setParameters(updateBitrate(bitrate >> 1));
+                queueEOS();
+                mCodec.setParameters(null);
+                mCodec.setParameters(updateBitrate(bitrate << 1));
+                waitForAllOutputs();
+                mCodec.setParameters(null);
+                mCodec.setParameters(updateBitrate(bitrate));
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testSetParametersInReleaseState() throws IOException {
+            MediaFormat format = getSampleVideoFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            int bitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            try {
+                mCodec.setParameters(updateBitrate(bitrate >> 1));
+                fail("Codec set parameter succeeds in release mode");
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testStartInUnInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            // call start in uninitialized state
+            tryStartInInvalidState("codec start succeeds before initialization");
+            configureCodec(format, false, false, true);
+            mCodec.start();
+            mCodec.stop();
+            // call start in stopped state
+            tryStartInInvalidState("codec start succeeds in stopped state");
+            mCodec.release();
+        }
+
+        @Test
+        public void testStartInRunningState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            configureCodec(format, false, false, true);
+            mCodec.start();
+            // call start in running state
+            tryStartInInvalidState("codec start succeeds in running state");
+            mCodec.stop();
+            mCodec.release();
+        }
+
+        @Test
+        public void testStartInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            // call start in release state
+            tryStartInInvalidState("codec start succeeds in release state");
+        }
+
+        @Test
+        public void testStopInUnInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.stop();
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                mCodec.stop();
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testStopInInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testStopInRunningState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                queueEOS();
+                mCodec.stop();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testStopInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            try {
+                mCodec.stop();
+                fail("Codec stop succeeds in release mode");
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testResetInUnInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.reset();
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testResetInInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testResetInRunningState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                queueEOS();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testResetInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            try {
+                mCodec.reset();
+                fail("Codec reset succeeds in release mode");
+            } catch (IllegalStateException e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testGetInputImageInUnInitState() throws IOException {
+            MediaFormat format = getSampleVideoFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                tryGetInputImageInInvalidState("getInputImage succeeds in uninitialized state");
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                mCodec.stop();
+                tryGetInputImageInInvalidState("getInputImage succeeds in stopped state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetInputImageInInitState() throws IOException {
+            MediaFormat format = getSampleVideoFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                tryGetInputImageInInvalidState("getInputImage succeeds in initialized state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        @Ignore("TODO(b/151304147)")
+        public void testGetInputImageInRunningStateVideo()
+                throws IOException, InterruptedException {
+            MediaFormat format = getSampleVideoFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                try {
+                    Image img = mCodec.getInputImage(-1);
+                    assertNull("getInputImage succeeds for bad buffer index " + -1, img);
+                } catch (Exception e) {
+                    fail("getInputImage rec/exp :: " + e.toString() + " / null");
+                }
+                int bufferIndex = mIsCodecInAsyncMode ? mAsyncHandle.getInput().first :
+                        mCodec.dequeueInputBuffer(-1);
+                Image img = mCodec.getInputImage(bufferIndex);
+                assertNotNull(img);
+                Image imgDup = mCodec.getInputImage(bufferIndex);
+                assertNotNull(imgDup);
+                enqueueEOS(bufferIndex);
+                waitForAllOutputs();
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        @Ignore("TODO(b/151304147)")
+        public void testGetInputImageInRunningStateAudio()
+                throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                try {
+                    Image img = mCodec.getInputImage(-1);
+                    assertNull("getInputImage succeeds for bad buffer index " + -1, img);
+                } catch (Exception e) {
+                    fail("getInputImage rec/exp :: " + e.toString() + " / null");
+                }
+                int bufferIndex = mIsCodecInAsyncMode ? mAsyncHandle.getInput().first :
+                        mCodec.dequeueInputBuffer(-1);
+                Image img = mCodec.getInputImage(bufferIndex);
+                assertNull("getInputImage returns non null for buffers that do not hold raw img",
+                        img);
+                enqueueEOS(bufferIndex);
+                waitForAllOutputs();
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetInputImageInReleaseState() throws IOException {
+            MediaFormat format = getSampleVideoFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            tryGetInputImageInInvalidState("getInputImage succeeds in release state");
+        }
+
+        @Test
+        public void testGetOutputImageInUnInitState() throws IOException {
+            MediaFormat format = getSampleVideoFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createDecoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                tryGetOutputImageInInvalidState("getOutputImage succeeds in uninitialized state");
+                configureCodec(format, isAsync, false, false);
+                mCodec.start();
+                mCodec.stop();
+                tryGetOutputImageInInvalidState("getOutputImage succeeds in stopped state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetOutputImageInInitState() throws IOException {
+            MediaFormat format = getSampleVideoFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createDecoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, false);
+                tryGetOutputImageInInvalidState("getOutputImage succeeds in initialized state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        @Ignore("TODO(b/151304147)")
+        public void testGetOutputImageInRunningState() throws IOException, InterruptedException {
+            MediaFormat format = getSampleVideoFormat();
+            MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createDecoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, false);
+                mCodec.start();
+                try {
+                    Image img = mCodec.getOutputImage(-1);
+                    assertNull("getOutputImage succeeds for bad buffer index " + -1, img);
+                } catch (Exception e) {
+                    fail("getOutputImage rec/exp :: " + e.toString() + " / null");
+                }
+                queueEOS();
+                int bufferIndex = 0;
+                while (!mSawOutputEOS) {
+                    if (mIsCodecInAsyncMode) {
+                        Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getOutput();
+                        bufferIndex = element.first;
+                        dequeueOutput(element.first, element.second);
+                    } else {
+                        bufferIndex = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
+                        if (bufferIndex >= 0) {
+                            dequeueOutput(bufferIndex, outInfo);
+                        }
+                    }
+                }
+                try {
+                    Image img = mCodec.getOutputImage(bufferIndex);
+                    assertNull("getOutputImage succeeds for buffer index not owned by client", img);
+                } catch (Exception e) {
+                    fail("getOutputBuffer rec/exp :: " + e.toString() + " / null");
+                }
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testGetOutputImageInReleaseState() throws IOException {
+            MediaFormat format = getSampleVideoFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createDecoderByType(mime);
+            mCodec.release();
+            tryGetOutputImageInInvalidState("getOutputImage succeeds in release state");
+        }
+
+        @Test
+        public void testQueueInputBufferInUnInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                tryQueueInputBufferInInvalidState(
+                        "queueInputBuffer succeeds in uninitialized state");
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                mCodec.stop();
+                tryQueueInputBufferInInvalidState("queueInputBuffer succeeds in stopped state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testQueueInputBufferInInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                tryQueueInputBufferInInvalidState("queueInputBuffer succeeds in initialized state");
+                mCodec.start();
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testQueueInputBufferWithBadIndex() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                try {
+                    mCodec.queueInputBuffer(-1, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                    fail("queueInputBuffer succeeds with bad buffer index :: " + -1);
+                } catch (Exception e) {
+                    // expected
+                }
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testQueueInputBufferWithBadSize() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                int bufferIndex = mIsCodecInAsyncMode ? mAsyncHandle.getInput().first :
+                        mCodec.dequeueInputBuffer(-1);
+                ByteBuffer buffer = mCodec.getInputBuffer(bufferIndex);
+                assertNotNull(buffer);
+                try {
+                    mCodec.queueInputBuffer(bufferIndex, 0, buffer.capacity() + 100, 0,
+                            MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                    fail("queueInputBuffer succeeds with bad size param :: " + buffer.capacity() +
+                            100);
+                } catch (Exception e) {
+                    // expected
+                }
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testQueueInputBufferWithBadBuffInfo() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                int bufferIndex = mIsCodecInAsyncMode ? mAsyncHandle.getInput().first :
+                        mCodec.dequeueInputBuffer(-1);
+                ByteBuffer buffer = mCodec.getInputBuffer(bufferIndex);
+                assertNotNull(buffer);
+                try {
+                    mCodec.queueInputBuffer(bufferIndex, 16, buffer.capacity(), 0,
+                            MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                    fail("queueInputBuffer succeeds with bad offset and size param");
+                } catch (Exception e) {
+                    // expected
+                }
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        @Ignore("TODO(b/151305059)")
+        public void testQueueInputBufferWithBadOffset() throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                int bufferIndex = mIsCodecInAsyncMode ? mAsyncHandle.getInput().first :
+                        mCodec.dequeueInputBuffer(-1);
+                ByteBuffer buffer = mCodec.getInputBuffer(bufferIndex);
+                assertNotNull(buffer);
+                try {
+                    mCodec.queueInputBuffer(bufferIndex, -1, buffer.capacity(), 0,
+                            MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+                    fail("queueInputBuffer succeeds with bad offset param :: " + -1);
+                } catch (Exception e) {
+                    // expected
+                }
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testQueueInputBufferInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            tryQueueInputBufferInInvalidState("queueInputBuffer succeeds in release state");
+        }
+
+        @Test
+        public void testReleaseOutputBufferInUnInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                tryReleaseOutputBufferInInvalidState(
+                        "releaseOutputBuffer succeeds in uninitialized state");
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                mCodec.stop();
+                tryReleaseOutputBufferInInvalidState(
+                        "releaseOutputBuffer succeeds in stopped state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testReleaseOutputBufferInInitState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                tryReleaseOutputBufferInInvalidState(
+                        "releaseOutputBuffer succeeds in initialized state");
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testReleaseOutputBufferInRunningState()
+                throws IOException, InterruptedException {
+            MediaFormat format = getSampleAudioFormat();
+            MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            boolean[] boolStates = {true, false};
+            for (boolean isAsync : boolStates) {
+                configureCodec(format, isAsync, false, true);
+                mCodec.start();
+                try {
+                    mCodec.releaseOutputBuffer(-1, false);
+                    fail("releaseOutputBuffer succeeds for bad buffer index " + -1);
+                } catch (MediaCodec.CodecException e) {
+                    // expected
+                }
+                queueEOS();
+                int bufferIndex = 0;
+                while (!mSawOutputEOS) {
+                    if (mIsCodecInAsyncMode) {
+                        Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getOutput();
+                        bufferIndex = element.first;
+                        ByteBuffer buffer = mCodec.getOutputBuffer(bufferIndex);
+                        assertNotNull(buffer);
+                        dequeueOutput(element.first, element.second);
+                    } else {
+                        bufferIndex = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
+                        if (bufferIndex >= 0) {
+                            ByteBuffer buffer = mCodec.getOutputBuffer(bufferIndex);
+                            assertNotNull(buffer);
+                            dequeueOutput(bufferIndex, outInfo);
+                        }
+                    }
+                }
+                try {
+                    mCodec.releaseOutputBuffer(bufferIndex, false);
+                    fail("releaseOutputBuffer succeeds for buffer index not owned by client");
+                } catch (MediaCodec.CodecException e) {
+                    // expected
+                }
+                mCodec.stop();
+                mCodec.reset();
+            }
+            mCodec.release();
+        }
+
+        @Test
+        public void testReleaseOutputBufferInReleaseState() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            tryReleaseOutputBufferInInvalidState(
+                    "releaseOutputBuffer succeeds in release state");
+        }
+
+        @Test
+        public void testReleaseIdempotent() throws IOException {
+            MediaFormat format = getSampleAudioFormat();
+            String mime = format.getString(MediaFormat.KEY_MIME);
+            mCodec = MediaCodec.createEncoderByType(mime);
+            mCodec.release();
+            mCodec.release();
+        }
+    }
+
+    @SmallTest
+    public static class TestApiNative {
+        @Rule
+        public Timeout timeout = new Timeout(PER_TEST_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+
+        static {
+            System.loadLibrary("ctsmediav2codec_jni");
+        }
+
+        @Test
+        public void testCreateByCodecNameForNull() {
+            assertTrue(nativeTestCreateByCodecNameForNull());
+        }
+
+        private native boolean nativeTestCreateByCodecNameForNull();
+
+        @Test
+        public void testCreateByCodecNameForInvalidName() {
+            assertTrue(nativeTestCreateByCodecNameForInvalidName());
+        }
+
+        private native boolean nativeTestCreateByCodecNameForInvalidName();
+
+        @Test
+        public void testCreateDecoderByTypeForNull() {
+            assertTrue(nativeTestCreateDecoderByTypeForNull());
+        }
+
+        private native boolean nativeTestCreateDecoderByTypeForNull();
+
+        @Test
+        public void testCreateDecoderByTypeForInvalidMime() {
+            assertTrue(nativeTestCreateDecoderByTypeForInvalidMime());
+        }
+
+        private native boolean nativeTestCreateDecoderByTypeForInvalidMime();
+
+        @Test
+        public void testCreateEncoderByTypeForNull() {
+            assertTrue(nativeTestCreateEncoderByTypeForNull());
+        }
+
+        private native boolean nativeTestCreateEncoderByTypeForNull();
+
+        @Test
+        public void testCreateEncoderByTypeForInvalidMime() {
+            assertTrue(nativeTestCreateEncoderByTypeForInvalidMime());
+        }
+
+        private native boolean nativeTestCreateEncoderByTypeForInvalidMime();
+
+        @Test
+        @Ignore("TODO(b/151302868)")
+        public void testConfigureForNullFormat() {
+            assertTrue(nativeTestConfigureForNullFormat());
+        }
+
+        private native boolean nativeTestConfigureForNullFormat();
+
+        @Test
+        public void testConfigureForEmptyFormat() {
+            assertTrue(nativeTestConfigureForEmptyFormat());
+        }
+
+        private native boolean nativeTestConfigureForEmptyFormat();
+
+        @Test
+        @Ignore("TODO(b/151303041)")
+        public void testConfigureCodecForIncompleteFormat() {
+            boolean[] boolStates = {false, true};
+            for (boolean isEncoder : boolStates) {
+                for (boolean isAudio : boolStates) {
+                    assertTrue(
+                            "testConfigureCodecForIncompleteFormat failed for isAudio " + isAudio +
+                                    ", isEncoder " + isEncoder,
+                            nativeTestConfigureCodecForIncompleteFormat(isAudio, isEncoder));
+                }
+            }
+        }
+
+        private native boolean nativeTestConfigureCodecForIncompleteFormat(boolean isAudio,
+                boolean isEncoder);
+
+        @Test
+        public void testConfigureEncoderForBadFlags() {
+            assertTrue(nativeTestConfigureEncoderForBadFlags());
+        }
+
+        private native boolean nativeTestConfigureEncoderForBadFlags();
+
+        @Test
+        public void testConfigureDecoderForBadFlags() {
+            assertTrue(nativeTestConfigureDecoderForBadFlags());
+        }
+
+        private native boolean nativeTestConfigureDecoderForBadFlags();
+
+        @Test
+        public void testConfigureInInitState() {
+            assertTrue(nativeTestConfigureInInitState());
+        }
+
+        private native boolean nativeTestConfigureInInitState();
+
+        @Test
+        public void testConfigureInRunningState() {
+            assertTrue(nativeTestConfigureInRunningState());
+        }
+
+        private native boolean nativeTestConfigureInRunningState();
+
+        @Test
+        public void testConfigureInUnInitState() {
+            assertTrue(nativeTestConfigureInUnInitState());
+        }
+
+        private native boolean nativeTestConfigureInUnInitState();
+
+        @Test
+        public void testDequeueInputBufferInInitState() {
+            assertTrue(nativeTestDequeueInputBufferInInitState());
+        }
+
+        private native boolean nativeTestDequeueInputBufferInInitState();
+
+        @Test
+        public void testDequeueInputBufferInRunningState() {
+            assertTrue(nativeTestDequeueInputBufferInRunningState());
+        }
+
+        private native boolean nativeTestDequeueInputBufferInRunningState();
+
+        @Test
+        public void testDequeueInputBufferInUnInitState() {
+            assertTrue(nativeTestDequeueInputBufferInUnInitState());
+        }
+
+        private native boolean nativeTestDequeueInputBufferInUnInitState();
+
+        @Test
+        public void testDequeueOutputBufferInInitState() {
+            assertTrue(nativeTestDequeueOutputBufferInInitState());
+        }
+
+        private native boolean nativeTestDequeueOutputBufferInInitState();
+
+        @Test
+        public void testDequeueOutputBufferInRunningState() {
+            assertTrue(nativeTestDequeueOutputBufferInRunningState());
+        }
+
+        private native boolean nativeTestDequeueOutputBufferInRunningState();
+
+        @Test
+        public void testDequeueOutputBufferInUnInitState() {
+            assertTrue(nativeTestDequeueOutputBufferInUnInitState());
+        }
+
+        private native boolean nativeTestDequeueOutputBufferInUnInitState();
+
+        @Test
+        public void testFlushInInitState() {
+            assertTrue(nativeTestFlushInInitState());
+        }
+
+        private native boolean nativeTestFlushInInitState();
+
+        @Test
+        public void testFlushInRunningState() {
+            assertTrue(nativeTestFlushInRunningState());
+        }
+
+        private native boolean nativeTestFlushInRunningState();
+
+        @Test
+        public void testFlushInUnInitState() {
+            assertTrue(nativeTestFlushInUnInitState());
+        }
+
+        private native boolean nativeTestFlushInUnInitState();
+
+        @Test
+        public void testGetNameInInitState() {
+            assertTrue(nativeTestGetNameInInitState());
+        }
+
+        private native boolean nativeTestGetNameInInitState();
+
+        @Test
+        public void testGetNameInRunningState() {
+            assertTrue(nativeTestGetNameInRunningState());
+        }
+
+        private native boolean nativeTestGetNameInRunningState();
+
+        @Test
+        public void testGetNameInUnInitState() {
+            assertTrue(nativeTestGetNameInUnInitState());
+        }
+
+        private native boolean nativeTestGetNameInUnInitState();
+
+        @Test
+        @Ignore("TODO(b/148523403)")
+        public void testSetAsyncNotifyCallbackInInitState() {
+            assertTrue(nativeTestSetAsyncNotifyCallbackInInitState());
+        }
+
+        private native boolean nativeTestSetAsyncNotifyCallbackInInitState();
+
+        @Test
+        @Ignore("TODO(b/152553625)")
+        public void testSetAsyncNotifyCallbackInRunningState() {
+            assertTrue(nativeTestSetAsyncNotifyCallbackInRunningState());
+        }
+
+        private native boolean nativeTestSetAsyncNotifyCallbackInRunningState();
+
+        @Test
+        public void testSetAsyncNotifyCallbackInUnInitState() {
+            assertTrue(nativeTestSetAsyncNotifyCallbackInUnInitState());
+        }
+
+        private native boolean nativeTestSetAsyncNotifyCallbackInUnInitState();
+
+        @Test
+        public void tesGetInputBufferInInitState() {
+            assertTrue(nativeTestGetInputBufferInInitState());
+        }
+
+        private native boolean nativeTestGetInputBufferInInitState();
+
+        @Test
+        public void testGetInputBufferInRunningState() {
+            assertTrue(nativeTestGetInputBufferInRunningState());
+        }
+
+        private native boolean nativeTestGetInputBufferInRunningState();
+
+        @Test
+        public void testGetInputBufferInUnInitState() {
+            assertTrue(nativeTestGetInputBufferInUnInitState());
+        }
+
+        private native boolean nativeTestGetInputBufferInUnInitState();
+
+        @Test
+        public void testGetInputFormatInInitState() {
+            assertTrue(nativeTestGetInputFormatInInitState());
+        }
+
+        private native boolean nativeTestGetInputFormatInInitState();
+
+        @Test
+        public void testGetInputFormatInRunningState() {
+            assertTrue(nativeTestGetInputFormatInRunningState());
+        }
+
+        private native boolean nativeTestGetInputFormatInRunningState();
+
+        @Test
+        public void testGetInputFormatInUnInitState() {
+            assertTrue(nativeTestGetInputFormatInUnInitState());
+        }
+
+        private native boolean nativeTestGetInputFormatInUnInitState();
+
+        @Test
+        public void testGetOutputBufferInInitState() {
+            assertTrue(nativeTestGetOutputBufferInInitState());
+        }
+
+        private native boolean nativeTestGetOutputBufferInInitState();
+
+        @Test
+        public void testGetOutputBufferInRunningState() {
+            assertTrue(nativeTestGetOutputBufferInRunningState());
+        }
+
+        private native boolean nativeTestGetOutputBufferInRunningState();
+
+        @Test
+        public void testGetOutputBufferInUnInitState() {
+            assertTrue(nativeTestGetOutputBufferInUnInitState());
+        }
+
+        private native boolean nativeTestGetOutputBufferInUnInitState();
+
+        @Test
+        public void testGetOutputFormatInInitState() {
+            assertTrue(nativeTestGetOutputFormatInInitState());
+        }
+
+        private native boolean nativeTestGetOutputFormatInInitState();
+
+        @Test
+        public void testGetOutputFormatInRunningState() {
+            assertTrue(nativeTestGetOutputFormatInRunningState());
+        }
+
+        private native boolean nativeTestGetOutputFormatInRunningState();
+
+        @Test
+        public void testGetOutputFormatInUnInitState() {
+            assertTrue(nativeTestGetOutputFormatInUnInitState());
+        }
+
+        private native boolean nativeTestGetOutputFormatInUnInitState();
+
+        @Test
+        @Ignore("TODO(b/)")
+        public void testSetParametersInInitState() {
+            assertTrue(nativeTestSetParametersInInitState());
+        }
+
+        private native boolean nativeTestSetParametersInInitState();
+
+        @Test
+        public void testSetParametersInRunningState() {
+            assertTrue(nativeTestSetParametersInRunningState());
+        }
+
+        private native boolean nativeTestSetParametersInRunningState();
+
+        @Test
+        @Ignore("TODO(b/)")
+        public void testSetParametersInUnInitState() {
+            assertTrue(nativeTestSetParametersInUnInitState());
+        }
+
+        private native boolean nativeTestSetParametersInUnInitState();
+
+        @Test
+        public void testStartInRunningState() {
+            assertTrue(nativeTestStartInRunningState());
+        }
+
+        private native boolean nativeTestStartInRunningState();
+
+        @Test
+        public void testStartInUnInitState() {
+            assertTrue(nativeTestStartInUnInitState());
+        }
+
+        private native boolean nativeTestStartInUnInitState();
+
+        @Test
+        public void testStopInInitState() {
+            assertTrue(nativeTestStopInInitState());
+        }
+
+        private native boolean nativeTestStopInInitState();
+
+        @Test
+        public void testStopInRunningState() {
+            assertTrue(nativeTestStopInRunningState());
+        }
+
+        private native boolean nativeTestStopInRunningState();
+
+        @Test
+        public void testStopInUnInitState() {
+            assertTrue(nativeTestStopInUnInitState());
+        }
+
+        private native boolean nativeTestStopInUnInitState();
+
+        @Test
+        public void testQueueInputBufferInInitState() {
+            assertTrue(nativeTestQueueInputBufferInInitState());
+        }
+
+        private native boolean nativeTestQueueInputBufferInInitState();
+
+        @Test
+        public void testQueueInputBufferWithBadIndex() {
+            assertTrue(nativeTestQueueInputBufferWithBadIndex());
+        }
+
+        private native boolean nativeTestQueueInputBufferWithBadIndex();
+
+        @Test
+        public void testQueueInputBufferWithBadSize() {
+            assertTrue(nativeTestQueueInputBufferWithBadSize());
+        }
+
+        private native boolean nativeTestQueueInputBufferWithBadSize();
+
+        @Test
+        public void testQueueInputBufferWithBadBuffInfo() {
+            assertTrue(nativeTestQueueInputBufferWithBadBuffInfo());
+        }
+
+        private native boolean nativeTestQueueInputBufferWithBadBuffInfo();
+
+        @Test
+        public void testQueueInputBufferWithBadOffset() {
+            assertTrue(nativeTestQueueInputBufferWithBadOffset());
+        }
+
+        private native boolean nativeTestQueueInputBufferWithBadOffset();
+
+        @Test
+        public void testQueueInputBufferInUnInitState() {
+            assertTrue(nativeTestQueueInputBufferInUnInitState());
+        }
+
+        private native boolean nativeTestQueueInputBufferInUnInitState();
+
+        @Test
+        public void testReleaseOutputBufferInInitState() {
+            assertTrue(nativeTestReleaseOutputBufferInInitState());
+        }
+
+        private native boolean nativeTestReleaseOutputBufferInInitState();
+
+        @Test
+        public void testReleaseOutputBufferInRunningState() {
+            assertTrue(nativeTestReleaseOutputBufferInRunningState());
+        }
+
+        private native boolean nativeTestReleaseOutputBufferInRunningState();
+
+        @Test
+        public void testReleaseOutputBufferInUnInitState() {
+            assertTrue(nativeTestReleaseOutputBufferInUnInitState());
+        }
+
+        private native boolean nativeTestReleaseOutputBufferInUnInitState();
+
+        @Test
+        public void testGetBufferFormatInInitState() {
+            assertTrue(nativeTestGetBufferFormatInInitState());
+        }
+
+        private native boolean nativeTestGetBufferFormatInInitState();
+
+        @Test
+        public void testGetBufferFormatInRunningState() {
+            assertTrue(nativeTestGetBufferFormatInRunningState());
+        }
+
+        private native boolean nativeTestGetBufferFormatInRunningState();
+
+        @Test
+        public void testGetBufferFormatInUnInitState() {
+            assertTrue(nativeTestGetBufferFormatInUnInitState());
+        }
+
+        private native boolean nativeTestGetBufferFormatInUnInitState();
+    }
+}
diff --git a/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java b/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
index 2df3912..7ad9a24 100644
--- a/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
+++ b/tests/rollback/src/com/android/cts/rollback/RollbackManagerTest.java
@@ -17,6 +17,7 @@
 package com.android.cts.rollback;
 
 import static com.android.cts.rollback.lib.RollbackInfoSubject.assertThat;
+import static com.android.cts.rollback.lib.RollbackUtils.getRollbackManager;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -79,8 +80,10 @@
     public void testBasic() throws Exception {
         Install.single(TestApp.A1).commit();
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(1);
-        assertThat(RollbackUtils.getAvailableRollback(TestApp.A)).isNull();
-        assertThat(RollbackUtils.getCommittedRollback(TestApp.A)).isNull();
+        RollbackUtils.waitForRollbackGone(
+                () -> getRollbackManager().getAvailableRollbacks(), TestApp.A);
+        RollbackUtils.waitForRollbackGone(
+                () -> getRollbackManager().getRecentlyCommittedRollbacks(), TestApp.A);
 
         Install.single(TestApp.A2).setEnableRollback().commit();
         assertThat(InstallUtils.getInstalledVersion(TestApp.A)).isEqualTo(2);
diff --git a/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java b/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
index 974d02a..77852f1 100644
--- a/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
+++ b/tests/sensor/src/android/hardware/cts/SensorDirectReportTest.java
@@ -19,7 +19,9 @@
 import android.content.Context;
 import android.hardware.HardwareBuffer;
 import android.hardware.Sensor;
+import android.hardware.SensorAdditionalInfo;
 import android.hardware.SensorDirectChannel;
+import android.hardware.SensorEventCallback;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
@@ -36,6 +38,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -208,13 +211,6 @@
                 SensorDirectChannel.RATE_NORMAL);
     }
 
-    public void testAccelerometerAshmemNormalUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_ACCELEROMETER,
-                SensorDirectChannel.TYPE_MEMORY_FILE,
-                SensorDirectChannel.RATE_NORMAL);
-    }
-
     public void testGyroscopeAshmemNormal() {
         runSensorDirectReportTest(
                 Sensor.TYPE_GYROSCOPE,
@@ -222,13 +218,6 @@
                 SensorDirectChannel.RATE_NORMAL);
     }
 
-    public void testGyroscopeAshmemNormalUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_GYROSCOPE,
-                SensorDirectChannel.TYPE_MEMORY_FILE,
-                SensorDirectChannel.RATE_NORMAL);
-    }
-
     public void testMagneticFieldAshmemNormal() {
         runSensorDirectReportTest(
                 Sensor.TYPE_MAGNETIC_FIELD,
@@ -236,25 +225,12 @@
                 SensorDirectChannel.RATE_NORMAL);
     }
 
-    public void testMagneticFieldAshmemNormalUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_MAGNETIC_FIELD,
-                SensorDirectChannel.TYPE_MEMORY_FILE,
-                SensorDirectChannel.RATE_NORMAL);
-    }
-
     public void testAccelerometerAshmemFast() {
         runSensorDirectReportTest(
                 Sensor.TYPE_ACCELEROMETER,
                 SensorDirectChannel.TYPE_MEMORY_FILE,
                 SensorDirectChannel.RATE_FAST);
-    }
 
-    public void testAccelerometerAshmemFastUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_ACCELEROMETER,
-                SensorDirectChannel.TYPE_MEMORY_FILE,
-                SensorDirectChannel.RATE_FAST);
     }
 
     public void testGyroscopeAshmemFast() {
@@ -264,13 +240,6 @@
                 SensorDirectChannel.RATE_FAST);
     }
 
-    public void testGyroscopeAshmemFastUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_GYROSCOPE,
-                SensorDirectChannel.TYPE_MEMORY_FILE,
-                SensorDirectChannel.RATE_FAST);
-    }
-
     public void testMagneticFieldAshmemFast() {
         runSensorDirectReportTest(
                 Sensor.TYPE_MAGNETIC_FIELD,
@@ -278,25 +247,12 @@
                 SensorDirectChannel.RATE_FAST);
     }
 
-    public void testMagneticFieldAshmemFastUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_MAGNETIC_FIELD,
-                SensorDirectChannel.TYPE_MEMORY_FILE,
-                SensorDirectChannel.RATE_FAST);
-    }
-
     public void testAccelerometerAshmemVeryFast() {
         runSensorDirectReportTest(
                 Sensor.TYPE_ACCELEROMETER,
                 SensorDirectChannel.TYPE_MEMORY_FILE,
                 SensorDirectChannel.RATE_VERY_FAST);
-    }
 
-    public void testAccelerometerAshmemVeryFastUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_ACCELEROMETER,
-                SensorDirectChannel.TYPE_MEMORY_FILE,
-                SensorDirectChannel.RATE_VERY_FAST);
     }
 
     public void testGyroscopeAshmemVeryFast() {
@@ -306,13 +262,6 @@
                 SensorDirectChannel.RATE_VERY_FAST);
     }
 
-    public void testGyroscopeAshmemVeryFastUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_GYROSCOPE,
-                SensorDirectChannel.TYPE_MEMORY_FILE,
-                SensorDirectChannel.RATE_VERY_FAST);
-    }
-
     public void testMagneticFieldAshmemVeryFast() {
         runSensorDirectReportTest(
                 Sensor.TYPE_MAGNETIC_FIELD,
@@ -320,13 +269,6 @@
                 SensorDirectChannel.RATE_VERY_FAST);
     }
 
-    public void testMagneticFieldAshmemVeryFastUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_MAGNETIC_FIELD,
-                SensorDirectChannel.TYPE_MEMORY_FILE,
-                SensorDirectChannel.RATE_VERY_FAST);
-    }
-
     public void testAccelerometerHardwareBufferNormal() {
         runSensorDirectReportTest(
                 Sensor.TYPE_ACCELEROMETER,
@@ -334,13 +276,6 @@
                 SensorDirectChannel.RATE_NORMAL);
     }
 
-    public void testAccelerometerHardwareBufferNormalUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_ACCELEROMETER,
-                SensorDirectChannel.TYPE_HARDWARE_BUFFER,
-                SensorDirectChannel.RATE_NORMAL);
-    }
-
     public void testGyroscopeHardwareBufferNormal() {
         runSensorDirectReportTest(
                 Sensor.TYPE_GYROSCOPE,
@@ -348,13 +283,6 @@
                 SensorDirectChannel.RATE_NORMAL);
     }
 
-    public void testGyroscopeHardwareBufferNormalUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_GYROSCOPE,
-                SensorDirectChannel.TYPE_HARDWARE_BUFFER,
-                SensorDirectChannel.RATE_NORMAL);
-    }
-
     public void testMagneticFieldHardwareBufferNormal() {
         runSensorDirectReportTest(
                 Sensor.TYPE_MAGNETIC_FIELD,
@@ -362,13 +290,6 @@
                 SensorDirectChannel.RATE_NORMAL);
     }
 
-    public void testMagneticFieldHardwareBufferNormalUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_MAGNETIC_FIELD,
-                SensorDirectChannel.TYPE_HARDWARE_BUFFER,
-                SensorDirectChannel.RATE_NORMAL);
-    }
-
     public void testAccelerometerHardwareBufferFast() {
         runSensorDirectReportTest(
                 Sensor.TYPE_ACCELEROMETER,
@@ -376,25 +297,12 @@
                 SensorDirectChannel.RATE_FAST);
     }
 
-    public void testAccelerometerHardwareBufferFastUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_ACCELEROMETER,
-                SensorDirectChannel.TYPE_HARDWARE_BUFFER,
-                SensorDirectChannel.RATE_FAST);
-    }
-
     public void testGyroscopeHardwareBufferFast() {
         runSensorDirectReportTest(
                 Sensor.TYPE_GYROSCOPE,
                 SensorDirectChannel.TYPE_HARDWARE_BUFFER,
                 SensorDirectChannel.RATE_FAST);
     }
-    public void testGyroscopeHardwareBufferFastUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_GYROSCOPE,
-                SensorDirectChannel.TYPE_HARDWARE_BUFFER,
-                SensorDirectChannel.RATE_FAST);
-    }
 
     public void testMagneticFieldHardwareBufferFast() {
         runSensorDirectReportTest(
@@ -403,13 +311,6 @@
                 SensorDirectChannel.RATE_FAST);
     }
 
-    public void testMagneticFieldHardwareBufferFastUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_MAGNETIC_FIELD,
-                SensorDirectChannel.TYPE_HARDWARE_BUFFER,
-                SensorDirectChannel.RATE_FAST);
-    }
-
     public void testAccelerometerHardwareBufferVeryFast() {
         runSensorDirectReportTest(
                 Sensor.TYPE_ACCELEROMETER,
@@ -417,13 +318,6 @@
                 SensorDirectChannel.RATE_VERY_FAST);
     }
 
-    public void testAccelerometerHardwareBufferVeryFastUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_ACCELEROMETER,
-                SensorDirectChannel.TYPE_HARDWARE_BUFFER,
-                SensorDirectChannel.RATE_VERY_FAST);
-    }
-
     public void testGyroscopeHardwareBufferVeryFast() {
         runSensorDirectReportTest(
                 Sensor.TYPE_GYROSCOPE,
@@ -431,13 +325,6 @@
                 SensorDirectChannel.RATE_VERY_FAST);
     }
 
-    public void testGyroscopeHardwareBufferVeryFastUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_GYROSCOPE,
-                SensorDirectChannel.TYPE_HARDWARE_BUFFER,
-                SensorDirectChannel.RATE_VERY_FAST);
-    }
-
     public void testMagneticFieldHardwareBufferVeryFast() {
         runSensorDirectReportTest(
                 Sensor.TYPE_MAGNETIC_FIELD,
@@ -445,13 +332,6 @@
                 SensorDirectChannel.RATE_VERY_FAST);
     }
 
-    public void testMagneticFieldHardwareBufferVeryFastUidIdle() {
-        runSensorDirectReportUidIdleTest(
-                Sensor.TYPE_MAGNETIC_FIELD,
-                SensorDirectChannel.TYPE_HARDWARE_BUFFER,
-                SensorDirectChannel.RATE_VERY_FAST);
-    }
-
     public void testRateIndependencyAccelGyroSingleChannel() {
         runSingleChannelRateIndependencyTestGroup(Sensor.TYPE_ACCELEROMETER,
                                                   Sensor.TYPE_GYROSCOPE);
@@ -756,57 +636,6 @@
         }
     }
 
-    private void runSensorDirectReportUidIdleTest(int sensorType, int memType, int rateLevel) {
-        Sensor s = mSensorManager.getDefaultSensor(sensorType);
-        if (s == null
-                || s.getHighestDirectReportRateLevel() < rateLevel
-                || !s.isDirectChannelTypeSupported(memType)) {
-            return;
-        }
-        resetEvent();
-
-        mChannel = prepareDirectChannel(memType, false /* secondary */);
-        assertTrue("createDirectChannel failed", mChannel != null);
-
-        try {
-            assertTrue("Shared memory is not formatted", isSharedMemoryFormatted(memType));
-            waitBeforeStartSensor();
-
-            int token = mChannel.configure(s, rateLevel);
-            assertTrue("configure direct mChannel failed", token > 0);
-
-            // Make package idle and ensure no sensor events are received
-            try {
-                SensorCtsHelper.makeMyPackageIdle();
-            } catch (IOException e) {
-                fail("IOException while making package idle");
-            }
-
-            int originalEventSize = mBuffer.length;
-            waitSensorCollection();
-
-            assertEquals(mBuffer.length, originalEventSize);
-
-            try {
-                SensorCtsHelper.makeMyPackageActive();
-            } catch (IOException e) {
-                fail("IOException while making package active");
-            }
-
-            // Also verify sensor events can be received after becoming active.
-            resetEvent();
-
-            waitSensorCollection();
-
-            //stop sensor and analyze content
-            mChannel.configure(s, SensorDirectChannel.RATE_STOP);
-            checkSharedMemoryContent(s, memType, rateLevel, token);
-        } finally {
-            mChannel.close();
-            mChannel = null;
-        }
-    }
-
     private void runSingleChannelRateIndependencyTest(
             int type1, int rateLevel1, int type2, int rateLevel2, int memType)
                 throws AssertionError {
diff --git a/tests/sensor/src/android/hardware/cts/SensorTest.java b/tests/sensor/src/android/hardware/cts/SensorTest.java
index 9e94cdd..5096b3d 100644
--- a/tests/sensor/src/android/hardware/cts/SensorTest.java
+++ b/tests/sensor/src/android/hardware/cts/SensorTest.java
@@ -40,13 +40,19 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.PowerManager;
+import android.os.Process;
 import android.os.SystemClock;
-import android.platform.test.annotations.AppModeFull;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.SystemUtil;
+
 import junit.framework.Assert;
 
+import java.io.IOException;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.concurrent.CountDownLatch;
@@ -364,8 +370,7 @@
                     false /* sanitized */, errorsFound);
 
             // If the UID is idle sanitization should be performed
-
-            SensorCtsHelper.makeMyPackageIdle();
+            makeMyPackageIdle();
             try {
                 verifyLongActivation(sensor, 0 /* maxReportLatencyUs */,
                         5 /* duration */, TimeUnit.SECONDS, "continuous event",
@@ -374,7 +379,7 @@
                         5 /* duration */, TimeUnit.SECONDS, "continuous event",
                         true /* sanitized */, errorsFound);
             } finally {
-                SensorCtsHelper.makeMyPackageActive();
+                makeMyPackageActive();
             }
 
             // If the UID is active no sanitization should be performed
@@ -682,6 +687,20 @@
         }
     }
 
+    private static void makeMyPackageActive() throws IOException {
+        final String command = "cmd sensorservice reset-uid-state "
+                +  InstrumentationRegistry.getTargetContext().getPackageName()
+                + " --user " + Process.myUserHandle().getIdentifier();
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+
+    private void makeMyPackageIdle() throws IOException {
+        final String command = "cmd sensorservice set-uid-state "
+                + InstrumentationRegistry.getTargetContext().getPackageName() + " idle"
+                + " --user " + Process.myUserHandle().getIdentifier();
+        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
+    }
+
     /**
      * A delegate that drives the execution of Batch/Flush tests.
      * It performs several operations in order:
@@ -722,16 +741,14 @@
                     listener.waitForEvents(eventLatch, mEventCount, true);
                 }
                 if (mFlushWhileIdle) {
-                    SensorCtsHelper.makeMyPackageIdle();
-                    sensorManager.assertFlushFail();
-                } else {
-                    CountDownLatch flushLatch = sensorManager.requestFlush();
-                    listener.waitForFlushComplete(flushLatch, true);
+                    makeMyPackageIdle();
                 }
+                CountDownLatch flushLatch = sensorManager.requestFlush();
+                listener.waitForFlushComplete(flushLatch, true);
             } finally {
                 sensorManager.unregisterListener();
                 if (mFlushWhileIdle) {
-                    SensorCtsHelper.makeMyPackageActive();
+                    makeMyPackageActive();
                 }
             }
         }
diff --git a/tests/sensor/src/android/hardware/cts/helpers/SensorCtsHelper.java b/tests/sensor/src/android/hardware/cts/helpers/SensorCtsHelper.java
index ecea4b6..86d17e5 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/SensorCtsHelper.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/SensorCtsHelper.java
@@ -17,13 +17,7 @@
 
 import android.hardware.Sensor;
 import android.os.Environment;
-import android.os.Process;
 import android.util.Log;
-
-import androidx.test.InstrumentationRegistry;
-
-import com.android.compatibility.common.util.SystemUtil;
-
 import java.io.File;
 import java.io.IOException;
 import java.util.ArrayList;
@@ -474,18 +468,4 @@
         }
         return new String(hexChars);
     }
-
-    public static void makeMyPackageActive() throws IOException {
-        final String command = "cmd sensorservice reset-uid-state "
-                +  InstrumentationRegistry.getTargetContext().getPackageName()
-                + " --user " + Process.myUserHandle().getIdentifier();
-        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
-    }
-
-    public static void makeMyPackageIdle() throws IOException {
-        final String command = "cmd sensorservice set-uid-state "
-                + InstrumentationRegistry.getTargetContext().getPackageName() + " idle"
-                + " --user " + Process.myUserHandle().getIdentifier();
-        SystemUtil.runShellCommand(InstrumentationRegistry.getInstrumentation(), command);
-    }
 }
diff --git a/tests/sensor/src/android/hardware/cts/helpers/TestSensorManager.java b/tests/sensor/src/android/hardware/cts/helpers/TestSensorManager.java
index aba6d49..7c69649 100644
--- a/tests/sensor/src/android/hardware/cts/helpers/TestSensorManager.java
+++ b/tests/sensor/src/android/hardware/cts/helpers/TestSensorManager.java
@@ -156,20 +156,6 @@
     }
 
     /**
-     * Call {@link SensorManager#flush(SensorEventListener)} and asserts that it fails.
-     */
-    public void assertFlushFail() {
-        if (mTestSensorEventListener == null) {
-            Log.w(LOG_TAG, "No listener registered, returning.");
-            return;
-        }
-        Assert.assertFalse(
-                SensorCtsHelper.formatAssertionMessage(
-                    "Flush succeeded unexpectedly", mEnvironment),
-                mSensorManager.flush(mTestSensorEventListener));
-    }
-
-    /**
      * Call {@link SensorManager#flush(SensorEventListener)}. This method will perform a no-op if
      * the sensor is not registered.
      *
diff --git a/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java b/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
index b6c4b87..14b822b 100644
--- a/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
+++ b/tests/tests/animation/src/android/animation/cts/ValueAnimatorTest.java
@@ -39,6 +39,7 @@
 import android.graphics.Color;
 import android.graphics.PointF;
 import android.os.SystemClock;
+import android.util.Range;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.LinearInterpolator;
 
@@ -689,10 +690,59 @@
         }
     }
 
+    @Test
+    public void testAnimationDurationNoShortenByTinkeredScale() throws Throwable {
+        final long expectedDurationMs = 1000L;
+        final long minDurationMs = expectedDurationMs;
+        final long maxDurationMs = expectedDurationMs + 200L;
+        final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
+
+        final CountDownLatch endLatch = new CountDownLatch(1);
+        long[] startAnimationTime = new long[1];
+        long[] endAnimationTime = new long[1];
+
+        final float durationScale = 1.0f;
+        float currentDurationScale = ValueAnimator.getDurationScale();
+        try {
+            ValueAnimator.setDurationScale(durationScale);
+            assertTrue("The duration scale of ValueAnimator should be 1.0f,"
+                            + " actual=" + ValueAnimator.getDurationScale(),
+                    ValueAnimator.getDurationScale() == durationScale);
+
+            ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+            animator.setInterpolator(new LinearInterpolator());
+            animator.setDuration(expectedDurationMs);
+            assertEquals(animator.getDuration(), expectedDurationMs);
+
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    endAnimationTime[0] = SystemClock.uptimeMillis();
+                    endLatch.countDown();
+                }
+            });
+
+            // Start the animation and verify if the actual animation duration is in the range.
+            mActivityRule.runOnUiThread(() -> {
+                startAnimationTime[0] = SystemClock.uptimeMillis();
+                animator.start();
+            });
+            endLatch.await(2, TimeUnit.SECONDS);
+            final long totalTime = endAnimationTime[0] - startAnimationTime[0];
+            assertTrue("ValueAnimator the duration should be in the range "
+                    + "<" + minDurationMs + ", " + maxDurationMs + "> ms, "
+                    + "actual=" + totalTime, durationRange.contains(totalTime));
+        } finally {
+            // restore scale value to avoid messing up future tests
+            ValueAnimator.setDurationScale(currentDurationScale);
+        }
+    }
+
     private void testAnimatorsEnabledImpl(boolean enabled) throws Throwable {
         final CountDownLatch startLatch = new CountDownLatch(1);
         final CountDownLatch endLatch = new CountDownLatch(1);
         final ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+
         animator.setDuration(1000);
         animator.addListener(new AnimatorListenerAdapter() {
             @Override
diff --git a/tests/tests/app.usage/Android.bp b/tests/tests/app.usage/Android.bp
index a050dff..2fc66d5 100644
--- a/tests/tests/app.usage/Android.bp
+++ b/tests/tests/app.usage/Android.bp
@@ -28,7 +28,7 @@
         "android.test.base.stubs",
         "android.test.runner.stubs",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: ["src/**/*.java", "TestApp1/**/*.java", "TestApp1/**/*.aidl"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
diff --git a/tests/tests/app.usage/TestApp1/Android.bp b/tests/tests/app.usage/TestApp1/Android.bp
index 5b3a0a9..019d685 100644
--- a/tests/tests/app.usage/TestApp1/Android.bp
+++ b/tests/tests/app.usage/TestApp1/Android.bp
@@ -28,11 +28,12 @@
         "android.test.base.stubs",
         "android.test.runner.stubs",
     ],
-    srcs: ["src/**/*.java"],
+    srcs: ["src/**/*.java", "aidl/**/*.aidl"],
     // Tag this module as a cts test artifact
     test_suites: [
         "cts",
         "vts10",
         "general-tests",
     ],
+    sdk_version: "test_current"
 }
diff --git a/tests/tests/app.usage/TestApp1/AndroidManifest.xml b/tests/tests/app.usage/TestApp1/AndroidManifest.xml
index 8d18d86..1cb7e1f 100644
--- a/tests/tests/app.usage/TestApp1/AndroidManifest.xml
+++ b/tests/tests/app.usage/TestApp1/AndroidManifest.xml
@@ -25,5 +25,8 @@
         <activity android:name=".SomeActivityWithLocus"
                   android:exported="true"
         />
+        <service android:name=".TestService"
+                  android:exported="true"
+        />
     </application>
 </manifest>
diff --git a/tests/tests/app.usage/TestApp1/aidl/android/app/usage/cts/ITestReceiver.aidl b/tests/tests/app.usage/TestApp1/aidl/android/app/usage/cts/ITestReceiver.aidl
new file mode 100644
index 0000000..d16a12b
--- /dev/null
+++ b/tests/tests/app.usage/TestApp1/aidl/android/app/usage/cts/ITestReceiver.aidl
@@ -0,0 +1,20 @@
+/*
+ * 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.app.usage.cts;
+
+interface ITestReceiver {
+    boolean isAppInactive(String pkg);
+}
\ No newline at end of file
diff --git a/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/SomeActivity.java b/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/SomeActivity.java
index 9d2ca8e..c68e4fe 100644
--- a/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/SomeActivity.java
+++ b/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/SomeActivity.java
@@ -15,7 +15,7 @@
  */
 package android.app.usage.cts.test1;
 
-import android.annotation.Nullable;
+import androidx.annotation.Nullable;
 import android.app.Activity;
 import android.os.Bundle;
 import android.view.WindowManager;
diff --git a/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/SomeActivityWithLocus.java b/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/SomeActivityWithLocus.java
index f49e0da..dda803c 100644
--- a/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/SomeActivityWithLocus.java
+++ b/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/SomeActivityWithLocus.java
@@ -15,7 +15,7 @@
  */
 package android.app.usage.cts.test1;
 
-import android.annotation.Nullable;
+import androidx.annotation.Nullable;
 import android.app.Activity;
 import android.content.LocusId;
 import android.os.Bundle;
diff --git a/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/TestService.java b/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/TestService.java
new file mode 100644
index 0000000..eb34e95
--- /dev/null
+++ b/tests/tests/app.usage/TestApp1/src/android/app/usage/cts/test1/TestService.java
@@ -0,0 +1,40 @@
+/*
+ * 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.app.usage.cts.test1;
+
+import android.app.Service;
+import android.app.usage.UsageStatsManager;
+import android.app.usage.cts.ITestReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.IBinder;
+
+public class TestService extends Service {
+    @Override
+    public IBinder onBind(Intent intent) {
+        return new TestReceiver();
+    }
+
+    private class TestReceiver extends ITestReceiver.Stub {
+        @Override
+        public boolean isAppInactive(String pkg) {
+            UsageStatsManager usm = (UsageStatsManager) getSystemService(
+                    Context.USAGE_STATS_SERVICE);
+            return usm.isAppInactive(pkg);
+        }
+    }
+}
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index 88db551..d0008dc 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
@@ -33,14 +33,18 @@
 import android.app.NotificationChannel;
 import android.app.NotificationManager;
 import android.app.PendingIntent;
+import android.app.usage.cts.ITestReceiver;
 import android.app.usage.EventStats;
 import android.app.usage.UsageEvents;
 import android.app.usage.UsageEvents.Event;
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.SystemClock;
 import android.platform.test.annotations.AppModeFull;
@@ -53,7 +57,6 @@
 import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseLongArray;
-import android.view.KeyEvent;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
@@ -75,6 +78,8 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.function.BooleanSupplier;
 
@@ -113,6 +118,8 @@
     private static final String TEST_APP_CLASS = "android.app.usage.cts.test1.SomeActivity";
     private static final String TEST_APP_CLASS_LOCUS
             = "android.app.usage.cts.test1.SomeActivityWithLocus";
+    private static final String TEST_APP_CLASS_SERVICE
+            = "android.app.usage.cts.test1.TestService";
 
     private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
     private static final long MINUTE = TimeUnit.MINUTES.toMillis(1);
@@ -123,8 +130,11 @@
     private static final long TIME_DIFF_THRESHOLD = 200;
     private static final String CHANNEL_ID = "my_channel";
 
+    private static final long TIMEOUT_BINDER_SERVICE_SEC = 2;
+
     private Context mContext;
     private UiDevice mUiDevice;
+    private ActivityManager mAm;
     private UsageStatsManager mUsageStatsManager;
     private KeyguardManager mKeyguardManager;
     private String mTargetPackage;
@@ -134,6 +144,7 @@
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getInstrumentation().getContext();
         mUiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        mAm = mContext.getSystemService(ActivityManager.class);
         mUsageStatsManager = (UsageStatsManager) mContext.getSystemService(
                 Context.USAGE_STATS_SERVICE);
         mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
@@ -186,6 +197,14 @@
         mUiDevice.wait(Until.hasObject(By.clazz(clazz)), TIMEOUT);
     }
 
+    private void launchTestActivity(String pkgName, String className) {
+        Intent intent = new Intent();
+        intent.setClassName(pkgName, className);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+        mUiDevice.wait(Until.hasObject(By.clazz(pkgName, className)), TIMEOUT);
+    }
+
     private void launchSubActivities(Class<? extends Activity>[] activityClasses) {
         for (Class<? extends Activity> clazz : activityClasses) {
             launchSubActivity(clazz);
@@ -693,6 +712,48 @@
     // TODO(148887416): get this test to work for instant apps
     @AppModeFull(reason = "Test APK Activity not found when installed as an instant app")
     @Test
+    public void testIsAppInactive() throws Exception {
+        setStandByBucket(mTargetPackage, "rare");
+        setStandByBucket(TEST_APP_PKG, "rare");
+
+        try {
+            BatteryUtils.runDumpsysBatteryUnplug();
+
+            waitUntil(() -> mUsageStatsManager.isAppInactive(mTargetPackage), true);
+            assertFalse(
+                    "App without PACKAGE_USAGE_STATS permission should always receive false for "
+                            + "isAppInactive",
+                    isAppInactiveAsPermissionlessApp(mTargetPackage));
+
+            launchSubActivity(Activities.ActivityOne.class);
+
+            waitUntil(() -> mUsageStatsManager.isAppInactive(mTargetPackage), false);
+            assertFalse(
+                    "App without PACKAGE_USAGE_STATS permission should always receive false for "
+                            + "isAppInactive",
+                    isAppInactiveAsPermissionlessApp(mTargetPackage));
+
+            // Querying for self does not require the PACKAGE_USAGE_STATS
+            waitUntil(() -> mUsageStatsManager.isAppInactive(TEST_APP_PKG), true);
+            assertTrue(
+                    "App without PACKAGE_USAGE_STATS permission should be able to call "
+                            + "isAppInactive for itself",
+                    isAppInactiveAsPermissionlessApp(TEST_APP_PKG));
+
+            launchTestActivity(TEST_APP_PKG, TEST_APP_CLASS);
+
+            waitUntil(() -> mUsageStatsManager.isAppInactive(TEST_APP_PKG), false);
+            assertFalse(
+                    "App without PACKAGE_USAGE_STATS permission should be able to call "
+                            + "isAppInactive for itself",
+                    isAppInactiveAsPermissionlessApp(TEST_APP_PKG));
+
+        } finally {
+            BatteryUtils.runDumpsysBatteryReset();
+        }
+    }
+
+    @Test
     public void testIsAppInactive_Charging() throws Exception {
         setStandByBucket(TEST_APP_PKG, "rare");
 
@@ -1241,7 +1302,6 @@
         mUiDevice.pressHome();
 
         final long startTime = System.currentTimeMillis();
-        final ActivityManager mAm = mContext.getSystemService(ActivityManager.class);
 
         Intent intent = new Intent();
         intent.setClassName(TEST_APP_PKG, TEST_APP_CLASS);
@@ -1298,8 +1358,6 @@
     }
 
     private void startAndDestroyActivityWithLocus() {
-        final ActivityManager mAm = mContext.getSystemService(ActivityManager.class);
-
         Intent intent = new Intent();
         intent.setClassName(TEST_APP_PKG, TEST_APP_CLASS_LOCUS);
         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -1379,4 +1437,34 @@
         return SystemUtil.runWithShellPermissionIdentity(() ->
                 mUsageStatsManager.queryEvents(start, end));
     }
+
+    private ITestReceiver bindToTestService() throws Exception {
+        final TestServiceConnection connection = new TestServiceConnection();
+        final Intent intent = new Intent().setComponent(
+                new ComponentName(TEST_APP_PKG, TEST_APP_CLASS_SERVICE));
+        mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE);
+        return ITestReceiver.Stub.asInterface(connection.getService());
+    }
+
+    private class TestServiceConnection implements ServiceConnection {
+        private BlockingQueue<IBinder> mBlockingQueue = new LinkedBlockingQueue<>();
+
+        public void onServiceConnected(ComponentName componentName, IBinder service) {
+            mBlockingQueue.offer(service);
+        }
+
+        public void onServiceDisconnected(ComponentName componentName) {
+        }
+
+        public IBinder getService() throws Exception {
+            final IBinder service = mBlockingQueue.poll(TIMEOUT_BINDER_SERVICE_SEC,
+                    TimeUnit.SECONDS);
+            return service;
+        }
+    }
+
+    private boolean isAppInactiveAsPermissionlessApp(String pkg) throws Exception {
+        final ITestReceiver testService = bindToTestService();
+        return testService.isAppInactive(pkg);
+    }
 }
diff --git a/tests/tests/appop/aidl/AppOpsUserService/Android.bp b/tests/tests/appop/aidl/AppOpsUserService/Android.bp
index ed5b3ff..d894734 100644
--- a/tests/tests/appop/aidl/AppOpsUserService/Android.bp
+++ b/tests/tests/appop/aidl/AppOpsUserService/Android.bp
@@ -14,6 +14,7 @@
 
 aidl_interface {
     name: "AppOpsUserServiceAidlNative",
+    unstable: true,
 
     local_include_dir: "src",
 
@@ -34,4 +35,4 @@
     srcs: [
         "src/**/*.aidl"
     ],
-}
\ No newline at end of file
+}
diff --git a/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt b/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
index 6643e4f..5adf8e0 100644
--- a/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/AppOpsLoggingTest.kt
@@ -499,7 +499,7 @@
 
             assertThat(asyncNoted[0].message).contains(locationListener::class.java.name)
             assertThat(asyncNoted[0].message).contains(
-                Integer.toHexString(System.identityHashCode(locationListener)))
+                Integer.toString(System.identityHashCode(locationListener)))
         }
     }
 
@@ -540,12 +540,7 @@
                 eventually {
                     assertThat(asyncNoted.map { it.op }).contains(OPSTR_FINE_LOCATION)
                     assertThat(asyncNoted[0].attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
-
-                    assertThat(asyncNoted[0].message).contains(
-                        proximityAlertReceiverPendingIntent::class.java.name)
-                    assertThat(asyncNoted[0].message).contains(
-                        Integer.toHexString(
-                            System.identityHashCode(proximityAlertReceiverPendingIntent)))
+                    assertThat(asyncNoted[0].message).contains(PROXIMITY_ALERT_ACTION)
                 }
             } finally {
                 locationManager.removeProximityAlert(proximityAlertReceiverPendingIntent)
diff --git a/tests/tests/appop/src/android/app/appops/cts/RuntimeMessageCollectionTest.kt b/tests/tests/appop/src/android/app/appops/cts/RuntimeMessageCollectionTest.kt
index d5e8748..e5c05ce 100644
--- a/tests/tests/appop/src/android/app/appops/cts/RuntimeMessageCollectionTest.kt
+++ b/tests/tests/appop/src/android/app/appops/cts/RuntimeMessageCollectionTest.kt
@@ -20,7 +20,7 @@
 import android.app.AppOpsManager
 import android.platform.test.annotations.AppModeFull
 import androidx.test.platform.app.InstrumentationRegistry
-import com.android.internal.util.FrameworkStatsLog.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED
+import com.android.internal.util.FrameworkStatsLog.RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
@@ -71,7 +71,7 @@
                         assertThat(message.attributionTag).isEqualTo(TEST_ATTRIBUTION_TAG)
                         assertThat(message.message).isEqualTo(MESSAGE)
                         assertThat(message.samplingStrategy)
-                                .isEqualTo(RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__RARELY_USED)
+                                .isNotEqualTo(RUNTIME_APP_OP_ACCESS__SAMPLING_STRATEGY__UNIFORM)
                         return
                     }
                 }
diff --git a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java
index be6d057..e7a3272 100644
--- a/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java
+++ b/tests/tests/batterysaving/src/android/os/cts/batterysaving/BatterySaverLocationTest.java
@@ -17,6 +17,7 @@
 
 import static android.provider.Settings.Global.BATTERY_SAVER_CONSTANTS;
 import static android.provider.Settings.Secure.LOCATION_MODE_OFF;
+
 import static androidx.test.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.compatibility.common.util.BatteryUtils.enableBatterySaver;
@@ -30,6 +31,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.app.UiModeManager;
 import android.content.IntentFilter;
 import android.content.pm.PackageManager;
 import android.location.Criteria;
@@ -45,7 +47,6 @@
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.Secure;
-import android.util.Log;
 
 import androidx.test.filters.MediumTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -121,6 +122,8 @@
         SettingsUtils.set(SettingsUtils.NAMESPACE_GLOBAL,
                 Settings.Global.LOCATION_IGNORE_SETTINGS_PACKAGE_WHITELIST,
                 "android.os.cts.batterysaving");
+
+        getContext().getSystemService(UiModeManager.class).disableCarMode(0);
     }
 
     @After
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java
index fa126ff..8616a6d 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BasicAdapterTest.java
@@ -140,7 +140,7 @@
         assertTrue(BluetoothAdapter.checkBluetoothAddress(adapter.getAddress()));
     }
 
-    public void test_setName_getName() {
+    public void test_getName() {
         if (!mHasBluetooth) {
             // Skip the test if bluetooth is not present.
             return;
@@ -150,10 +150,6 @@
 
         String name = adapter.getName();
         assertNotNull(name);
-
-        String genericName = "Generic Device 1";
-        assertTrue(adapter.setName(genericName));
-        assertEquals(genericName, adapter.getName());
     }
 
     public void test_getBondedDevices() {
diff --git a/tests/tests/car/src/android/car/cts/CarUserManagerTest.java b/tests/tests/car/src/android/car/cts/CarUserManagerTest.java
index f62ef35..d5b5ea9 100644
--- a/tests/tests/car/src/android/car/cts/CarUserManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarUserManagerTest.java
@@ -56,7 +56,9 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicIntegerArray;
 import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
 
 public final class CarUserManagerTest extends CarApiTestBase {
 
@@ -77,6 +79,12 @@
      */
     private static final int SUSPEND_TIMEOUT_MS = 5_000;
 
+    private static final List<Integer> EXPECTED_SWITCH_USER_EVENTS = Arrays.asList(
+            CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING,
+            CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING,
+            CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKING,
+            CarUserManager.USER_LIFECYCLE_EVENT_TYPE_UNLOCKED);
+
     /**
      * How long to sleep (multiple times) while waiting for a condition.
      */
@@ -153,6 +161,9 @@
         AtomicReference<Exception> bgExceptionRef = new AtomicReference<>();
         AtomicBoolean expectingEventRef = new AtomicBoolean(true);
 
+        // Track events, STARTING, SWITCHING, UNLOCKING, UNLOCKED
+        List<Integer> receivedEventsType = new ArrayList<>();
+
         UserLifecycleListener listener = (event) -> {
             boolean expectingEvent = expectingEventRef.get();
             Log.d(TAG, "received event (expecting=" + expectingEvent + "): "  + event);
@@ -165,25 +176,27 @@
             // Verify event
             List<String> errors = new ArrayList<>();
             int actualType = event.getEventType();
-            if (actualType != CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
-                errors.add("wrong event; expected SWITCHING, got "
-                        + CarUserManager.lifecycleEventTypeToString(actualType));
-            }
+
             UserHandle actualUserHandle = event.getUserHandle();
             if (actualUserHandle == null) {
                 errors.add("no user handle");
             } else if (actualUserHandle.getIdentifier() != newUserId) {
                 errors.add("wrong user: expected " + newUserId + ", got " + actualUserHandle);
             }
-
-            // TODO(b/144120654): check for previous handle (not set yet)
-            if (false) {
-                UserHandle previousUserHandle = event.getPreviousUserHandle();
-                if (previousUserHandle == null) {
-                    errors.add("no previous user handle");
-                } else if (previousUserHandle.getIdentifier() != oldUserId) {
-                    errors.add("wrong previous user: expected " + oldUserId + ", got "
-                            + previousUserHandle);
+            UserHandle previousUserHandle = null;
+            if (actualType == CarUserManager.USER_LIFECYCLE_EVENT_TYPE_SWITCHING) {
+                try {
+                    previousUserHandle = event.getPreviousUserHandle();
+                    if (previousUserHandle == null) {
+                        errors.add("no previous user handle");
+                    } else if (previousUserHandle.getIdentifier() != oldUserId) {
+                        errors.add("wrong previous user: expected " + oldUserId + ", got "
+                                + previousUserHandle);
+                    }        
+                } catch (Exception e) {
+                    String msg = "failed to get previous user handle: ";
+                    errors.add(msg + e);
+                    Log.d(TAG, msg, e);
                 }
             }
 
@@ -191,10 +204,11 @@
                 bgExceptionRef.set(new IllegalArgumentException(
                         "Received wrong event (" + event + "): " + errors));
             }
+            receivedEventsType.add(actualType);
         };
         Log.d(TAG, "registering listener: " + listener);
-
         AtomicBoolean executedRef = new AtomicBoolean();
+
         sCarUserManager.addListener((r) -> {
             executedRef.set(true);
             r.run();
@@ -209,12 +223,16 @@
         // Make sure it was executed in the proper threaqd
         assertWithMessage("not executed on executor").that(executedRef.get()).isTrue();
 
-         // Then switch back when it isn't
-        // TODO(b/144120654): the current mechanism is not thread safe because if an event is
-        // received before this line, it wouldn't be detected. But that's fine for now, as this test
-        // will be refactored once it's expecting more events (like STARTING before SWITCHING)
-        expectingEventRef.set(false);
+        // Then switch back when it isn't
+        waitUntil("Timeout: receive list of events for switch user: " + newUserId, 
+                SWITCH_TIMEOUT_USING_CHECK_MS,
+                () -> (receivedEventsType.size() == EXPECTED_SWITCH_USER_EVENTS.size()));
+        assertWithMessage("wrong events, expecting STARTING, SWITCHING, UNLOCKING, UNLOCKED; got"
+                + lifecycleEventsTypeToString(receivedEventsType))
+                .that(receivedEventsType).containsExactlyElementsIn(EXPECTED_SWITCH_USER_EVENTS)
+                .inOrder();
 
+        expectingEventRef.set(false);
         Log.d(TAG, "unregistering listener: " + listener);
         sCarUserManager.removeListener(listener);
         switchUser(oldUserId);
@@ -412,4 +430,10 @@
         fail(msg + " after: " + timeoutMs + "ms");
         return false;
     }
+
+    private List<String> lifecycleEventsTypeToString(List<Integer> events) {
+        return events.stream()
+                .map(event -> CarUserManager.lifecycleEventTypeToString(event))
+                .collect(Collectors.toList());
+    }
 }
diff --git a/tests/tests/content/src/android/content/cts/IntentFilterTest.java b/tests/tests/content/src/android/content/cts/IntentFilterTest.java
index 36e5788..096068b 100644
--- a/tests/tests/content/src/android/content/cts/IntentFilterTest.java
+++ b/tests/tests/content/src/android/content/cts/IntentFilterTest.java
@@ -51,6 +51,8 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Set;
@@ -742,14 +744,25 @@
                 new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, null, null, null, null),
                 new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, "action1", null, null, null),
                 new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, "action2", null, null, null),
-                new MatchCondition(IntentFilter.NO_MATCH_ACTION, "action3", null, null, null));
+                new MatchCondition(IntentFilter.NO_MATCH_ACTION, "action3", null, null, null),
+                new MatchCondition(IntentFilter.NO_MATCH_ACTION, "action1", null, null, null, false,
+                        Arrays.asList("action1", "action2")),
+                new MatchCondition(IntentFilter.NO_MATCH_ACTION, "action2", null, null, null, false,
+                        Arrays.asList("action1", "action2")),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, "action1", null, null, null,
+                        false, Arrays.asList("action2")));
     }
 
     public void testActionWildCards() throws Exception {
-        IntentFilter filter = new Match(new String[]{"action1"}, null, null, null, null, null);
+        IntentFilter filter =
+                new Match(new String[]{"action1", "action2"}, null, null, null, null, null);
         checkMatches(filter,
                 new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, null, null, null, null, true),
                 new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, "*", null, null, null, true),
+                new MatchCondition(IntentFilter.MATCH_CATEGORY_EMPTY, "*", null, null, null, true,
+                        Arrays.asList("action1")),
+                new MatchCondition(IntentFilter.NO_MATCH_ACTION, "*", null, null, null, true,
+                        Arrays.asList("action1", "action2")),
                 new MatchCondition(
                         IntentFilter.NO_MATCH_ACTION, "action3", null, null, null, true));
 
@@ -1289,26 +1302,36 @@
         public final Uri data;
         public final String[] categories;
         public final boolean wildcardSupported;
+        public final Collection<String> ignoredActions;
 
         public static MatchCondition data(int result, String data) {
             return new MatchCondition(result, null, null, null, data);
         }
         public static MatchCondition data(int result, String data, boolean wildcardSupported) {
-            return new MatchCondition(result, null, null, null, data, wildcardSupported);
+            return new MatchCondition(result, null, null, null, data, wildcardSupported, null);
         }
-
+        public static MatchCondition data(int result, String data, boolean wildcardSupported,
+                Collection<String> ignoredActions) {
+            return new MatchCondition(result, null, null, null, data, wildcardSupported,
+                    ignoredActions);
+        }
         MatchCondition(int result, String action, String[] categories, String mimeType,
                 String data) {
-            this(result, action, categories, mimeType, data, false);
+            this(result, action, categories, mimeType, data, false, null);
         }
         MatchCondition(int result, String action, String[] categories, String mimeType,
                 String data, boolean wildcardSupported) {
+            this(result, action, categories, mimeType, data, wildcardSupported, null);
+        }
+        MatchCondition(int result, String action, String[] categories, String mimeType,
+                String data, boolean wildcardSupported, Collection<String> ignoredActions) {
             this.result = result;
             this.action = action;
             this.mimeType = mimeType;
             this.data = data != null ? Uri.parse(data) : null;
             this.categories = categories;
             this.wildcardSupported = wildcardSupported;
+            this.ignoredActions = ignoredActions;
         }
     }
 
@@ -1325,7 +1348,7 @@
                 }
             }
             int result = filter.match(mc.action, mc.mimeType, mc.data != null ? mc.data.getScheme()
-                    : null, mc.data, categories, "test", mc.wildcardSupported);
+                    : null, mc.data, categories, "test", mc.wildcardSupported, mc.ignoredActions);
             if ((result & IntentFilter.MATCH_CATEGORY_MASK) !=
                     (mc.result & IntentFilter.MATCH_CATEGORY_MASK)) {
                 StringBuilder msg = new StringBuilder();
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
index 6722dda..0045cb3 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerShellCommandIncrementalTest.java
@@ -104,6 +104,7 @@
 
     @Before
     public void onBefore() throws Exception {
+        checkIncrementalDeliveryFeature();
         uninstallPackageSilently(TEST_APP_PACKAGE);
         assertFalse(isAppInstalled(TEST_APP_PACKAGE));
     }
@@ -115,8 +116,7 @@
         assertEquals(null, getSplits(TEST_APP_PACKAGE));
     }
 
-    @Test
-    public void testIncrementalDelivery() throws Exception {
+    private void checkIncrementalDeliveryFeature() throws Exception {
         final Context context = InstrumentationRegistry.getInstrumentation().getContext();
         Assume.assumeTrue(context.getPackageManager().hasSystemFeature(
                 PackageManager.FEATURE_INCREMENTAL_DELIVERY));
diff --git a/tests/tests/database/src/android/database/cts/SQLiteCantOpenDatabaseExceptionTest.java b/tests/tests/database/src/android/database/cts/SQLiteCantOpenDatabaseExceptionTest.java
new file mode 100644
index 0000000..69a8bbe
--- /dev/null
+++ b/tests/tests/database/src/android/database/cts/SQLiteCantOpenDatabaseExceptionTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.database.cts;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteCantOpenDatabaseException;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDatabase.OpenParams;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.File;
+
+public class SQLiteCantOpenDatabaseExceptionTest  extends AndroidTestCase {
+    private static final String TAG = "SQLiteCantOpenDatabaseExceptionTest";
+
+    private File getDatabaseFile(String name) {
+        final Context c = getContext();
+        c.deleteDatabase(name);
+
+        // getDatabasePath() doesn't like a filename with a / in it, so we can't use it directly.
+        return new File(getContext().getDatabasePath("a").getParentFile(), name);
+    }
+
+    private void callWithExceptedMessage(File file, String expectedMessagePatter) {
+        try {
+            SQLiteDatabase.openDatabase(file, new OpenParams.Builder().build());
+            fail("SQLiteCantOpenDatabaseException was not thrown");
+        } catch (SQLiteCantOpenDatabaseException e) {
+            Log.i(TAG, "Caught excepted exception: " + e.getMessage(), e);
+            if (e.getMessage().startsWith("Cannot open database") &&
+                    e.getMessage().matches(expectedMessagePatter)) {
+                return; // pass
+            }
+            fail("Unexpected exception message: " + e.getMessage());
+        }
+    }
+
+    /** DB's directory doesn't exist. */
+    public void testDirectoryDoesNotExist() {
+        final File file = getDatabaseFile("nonexisitentdir/mydb.db");
+
+        callWithExceptedMessage(file, ".*: Directory .* doesn't exist");
+    }
+
+    /** File doesn't exist */
+    public void testFileDoesNotExist() {
+        final File file = getDatabaseFile("mydb.db");
+
+        callWithExceptedMessage(file, ".*: File .* doesn't exist");
+    }
+
+    /** File exists, but not readable. */
+    public void testFileNotReadable() {
+        final File file = getDatabaseFile("mydb.db");
+
+        SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(file, null);
+        db.close();
+
+        file.setReadable(false);
+
+        callWithExceptedMessage(file, ".*: File .* not readable");
+    }
+
+    /** Directory with the given name exists already. */
+    public void testPathIsADirectory() {
+        final File file = getDatabaseFile("mydb.db");
+
+        file.mkdirs();
+
+        callWithExceptedMessage(file, ".*: Path .* is a directory");
+    }
+}
diff --git a/tests/tests/graphics/assets/gimp-d65-grayscale.jpg b/tests/tests/graphics/assets/gimp-d65-grayscale.jpg
new file mode 100644
index 0000000..0e0924b
--- /dev/null
+++ b/tests/tests/graphics/assets/gimp-d65-grayscale.jpg
Binary files differ
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
index 5603b9d..6e7c5c9 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapColorSpaceTest.java
@@ -1177,4 +1177,61 @@
                     dst.getColor(0, 0), .001f);
         }
     }
+
+    @Test
+    public void testGrayscaleProfile() throws IOException {
+        ImageDecoder.Source source = ImageDecoder.createSource(mResources.getAssets(),
+                "gimp-d65-grayscale.jpg");
+        Bitmap bm = ImageDecoder.decodeBitmap(source, (decoder, info, s) -> {
+            decoder.setAllocator(ImageDecoder.ALLOCATOR_SOFTWARE);
+        });
+        ColorSpace cs = bm.getColorSpace();
+        assertNotNull(cs);
+        assertTrue(cs instanceof ColorSpace.Rgb);
+        ColorSpace.Rgb rgbCs = (ColorSpace.Rgb) cs;
+
+        // A gray color space uses a special primaries array of all 1s.
+        float[] primaries = rgbCs.getPrimaries();
+        assertNotNull(primaries);
+        assertEquals(6, primaries.length);
+        for (float primary : primaries) {
+            assertEquals(0, Float.compare(primary, 1.0f));
+        }
+
+        // A gray color space will have all zeroes in the transform
+        // and inverse transform, except for the diagonal.
+        for (float[] transform : new float[][]{rgbCs.getTransform(), rgbCs.getInverseTransform()}) {
+            assertNotNull(transform);
+            assertEquals(9, transform.length);
+            for (int index : new int[] { 1, 2, 3, 5, 6, 7 }) {
+                assertEquals(0, Float.compare(0.0f, transform[index]));
+            }
+        }
+
+        // When creating another Bitmap with the same ColorSpace, the two
+        // ColorSpaces should be equal.
+        Bitmap otherBm = Bitmap.createBitmap(null, 100, 100, Bitmap.Config.ARGB_8888, true, cs);
+        assertEquals(cs, otherBm.getColorSpace());
+
+        // Same for a scaled bitmap.
+        Bitmap scaledBm = Bitmap.createScaledBitmap(bm, bm.getWidth() / 4, bm.getHeight() / 4,
+                true);
+        assertEquals(cs, scaledBm.getColorSpace());
+
+        // A previous ColorSpace bug resulted in a Bitmap created like scaledBm
+        // having all black pixels. Verify that the Bitmap contains colors other
+        // than black and white.
+        boolean foundOtherColor = false;
+        final int width = scaledBm.getWidth();
+        final int height = scaledBm.getHeight();
+        int[] pixels = new int[width * height];
+        scaledBm.getPixels(pixels, 0, width, 0, 0, width, height);
+        for (int pixel : pixels) {
+            if (pixel != Color.BLACK && pixel != Color.WHITE) {
+                foundOtherColor = true;
+                break;
+            }
+        }
+        assertTrue(foundOtherColor);
+    }
 }
diff --git a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
index 3d2c0a2..ed1d0c9 100644
--- a/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/BitmapTest.java
@@ -1694,6 +1694,36 @@
         p.recycle();
     }
 
+    /**
+     * Although not specified as required behavior, it's something that some apps appear
+     * to rely upon when sending bitmaps between themselves
+     */
+    @Test
+    public void testWriteToParcelPreserveMutability() {
+        Bitmap source = Bitmap.createBitmap(100, 100, Config.ARGB_8888);
+        assertTrue(source.isMutable());
+        Parcel p = Parcel.obtain();
+        source.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        Bitmap result = Bitmap.CREATOR.createFromParcel(p);
+        p.recycle();
+        assertTrue(result.isMutable());
+    }
+
+    @Test
+    public void testWriteToParcelPreserveImmutability() {
+        // Kinda silly way to create an immutable bitmap but it works
+        Bitmap source = Bitmap.createBitmap(100, 100, Config.ARGB_8888)
+                .copy(Config.ARGB_8888, false);
+        assertFalse(source.isMutable());
+        Parcel p = Parcel.obtain();
+        source.writeToParcel(p, 0);
+        p.setDataPosition(0);
+        Bitmap result = Bitmap.CREATOR.createFromParcel(p);
+        p.recycle();
+        assertFalse(result.isMutable());
+    }
+
     @Test
     public void testWriteHwBitmapToParcel() {
         mBitmap = BitmapFactory.decodeResource(mRes, R.drawable.robot, HARDWARE_OPTIONS);
diff --git a/tests/tests/hardware/src/android/hardware/biometrics/cts/BiometricPromptTest.java b/tests/tests/hardware/src/android/hardware/biometrics/cts/BiometricPromptTest.java
index 8e2f9e4..00e1b42 100644
--- a/tests/tests/hardware/src/android/hardware/biometrics/cts/BiometricPromptTest.java
+++ b/tests/tests/hardware/src/android/hardware/biometrics/cts/BiometricPromptTest.java
@@ -17,6 +17,7 @@
 package android.hardware.biometrics.cts;
 
 import android.content.pm.PackageManager;
+import android.hardware.biometrics.BiometricManager.Authenticators;
 import android.hardware.biometrics.BiometricPrompt;
 import android.os.CancellationSignal;
 import android.os.Handler;
@@ -72,17 +73,25 @@
         }
 
         boolean exceptionTaken = false;
+        final String title = "Title";
+        final String subtitle = "Subtitle";
+        final String description = "Description";
+        final String negativeButtonText = "Negative Button";
         CancellationSignal cancellationSignal = new CancellationSignal();
         try {
-            BiometricPrompt.Builder builder = new BiometricPrompt.Builder(getContext());
-            builder.setTitle("Title");
-            builder.setSubtitle("Subtitle");
-            builder.setDescription("Description");
-            builder.setNegativeButton("Negative", mExecutor, (dialog, which) -> {
+            final BiometricPrompt.Builder builder = new BiometricPrompt.Builder(getContext());
+            builder.setTitle(title);
+            builder.setSubtitle(subtitle);
+            builder.setDescription(description);
+            builder.setNegativeButton(negativeButtonText, mExecutor, (dialog, which) -> {
                 // Do nothing
             });
 
-            BiometricPrompt prompt = builder.build();
+            final BiometricPrompt prompt = builder.build();
+            assertEquals(title, prompt.getTitle());
+            assertEquals(subtitle, prompt.getSubtitle());
+            assertEquals(description, prompt.getDescription());
+            assertEquals(negativeButtonText, prompt.getNegativeButtonText());
 
             prompt.authenticate(cancellationSignal, mExecutor, mAuthenticationCallback);
             mLatch.await(AWAIT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
@@ -95,4 +104,54 @@
         }
     }
 
+    @Presubmit
+    public void test_isConfirmationRequired() {
+        final BiometricPrompt.Builder promptBuilder = new BiometricPrompt.Builder(getContext())
+                .setTitle("Title")
+                .setNegativeButton("Cancel", mExecutor, (dialog, which) -> {
+                    // Do nothing.
+                });
+        assertTrue(promptBuilder.build().isConfirmationRequired());
+        assertTrue(promptBuilder.setConfirmationRequired(true).build().isConfirmationRequired());
+        assertFalse(promptBuilder.setConfirmationRequired(false).build().isConfirmationRequired());
+    }
+
+    @Presubmit
+    public void test_setAllowedAuthenticators_withoutDeviceCredential() {
+        final BiometricPrompt.Builder promptBuilder = new BiometricPrompt.Builder(getContext())
+                .setTitle("Title")
+                .setNegativeButton("Cancel", mExecutor, (dialog, which) -> {
+                    // Do nothing.
+                });
+        assertEquals(0, promptBuilder.build().getAllowedAuthenticators());
+
+        final int[] authenticatorCombinations = {
+                Authenticators.BIOMETRIC_WEAK,
+                Authenticators.BIOMETRIC_STRONG
+        };
+        for (final int authenticators : authenticatorCombinations) {
+            final BiometricPrompt prompt = promptBuilder
+                    .setAllowedAuthenticators(authenticators)
+                    .build();
+            assertEquals(authenticators, prompt.getAllowedAuthenticators());
+        }
+    }
+
+    @Presubmit
+    public void test_setAllowedAuthenticators_withDeviceCredential() {
+        final BiometricPrompt.Builder promptBuilder = new BiometricPrompt.Builder(getContext())
+                .setTitle("Title");
+
+        final int[] authenticatorCombinations = {
+                Authenticators.DEVICE_CREDENTIAL,
+                Authenticators.BIOMETRIC_WEAK | Authenticators.DEVICE_CREDENTIAL,
+                Authenticators.BIOMETRIC_STRONG | Authenticators.DEVICE_CREDENTIAL
+        };
+        for (final int authenticators : authenticatorCombinations) {
+            final BiometricPrompt prompt = promptBuilder
+                    .setAllowedAuthenticators(authenticators)
+                    .build();
+            assertEquals(authenticators, prompt.getAllowedAuthenticators());
+        }
+    }
 }
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
index efa6f98..6239026 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/InputTestCase.java
@@ -108,8 +108,7 @@
         }
         assertEquals(mCurrentTestCase + " (action)",
                 expectedKeyEvent.getAction(), receivedKeyEvent.getAction());
-        assertEquals(mCurrentTestCase + " (source)",
-                expectedKeyEvent.getSource(), receivedKeyEvent.getSource());
+        assertSource(mCurrentTestCase, expectedKeyEvent.getSource(), receivedKeyEvent.getSource());
         assertEquals(mCurrentTestCase + " (keycode)",
                 expectedKeyEvent.getKeyCode(), receivedKeyEvent.getKeyCode());
         assertEquals(mCurrentTestCase + " (meta state)",
@@ -135,8 +134,7 @@
         }
         assertEquals(mCurrentTestCase + " (action)",
                 expectedEvent.getAction(), event.getAction());
-        assertEquals(mCurrentTestCase + " (source)",
-                expectedEvent.getSource(), event.getSource());
+        assertSource(mCurrentTestCase, expectedEvent.getSource(), event.getSource());
         assertEquals(mCurrentTestCase + " (button state)",
                 expectedEvent.getButtonState(), event.getButtonState());
         if (event.getActionMasked() == MotionEvent.ACTION_BUTTON_PRESS
@@ -155,6 +153,17 @@
     }
 
     /**
+     * Asserts source flags. Separate this into a different method to allow individual test case to
+     * specify it.
+     *
+     * @param expectedSource expected source flag specified in JSON files.
+     * @param actualSource actual source flag received in the test app.
+     */
+    void assertSource(String testCase, int expectedSource, int actualSource) {
+        assertEquals(testCase + " (source)", expectedSource, actualSource);
+    }
+
+    /**
      * Assert that no more events have been received by the application.
      *
      * If any more events have been received by the application, this will cause failure.
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java
index 4092320..5712ca7 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/MicrosoftDesignerKeyboardTest.java
@@ -16,6 +16,8 @@
 
 package android.hardware.input.cts.tests;
 
+import static org.junit.Assert.assertEquals;
+
 import android.hardware.cts.R;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -36,4 +38,17 @@
     public void testAllKeys() {
         testInputEvents(R.raw.microsoft_designer_keyboard_keyeventtests);
     }
+
+    /**
+     * Relax the source check on this test because we encountered a Linux kernel behavior change in
+     * 4.18 or later that splits the device into multiple devices according to its applications in
+     * HID descriptor. That change further lets Android framework split the KeyboardInputMapper
+     * because it thinks they are different devices which in turn split the source flags. Therefore
+     * we relax the test so that it can pass with both behaviors until we reach a consensus with
+     * upstream Linux on the desired behavior.
+     */
+    @Override
+    void assertSource(String testCase, int expectedSource, int actualSource) {
+        assertEquals(testCase + " (source)", expectedSource & actualSource, actualSource);
+    }
 }
diff --git a/tests/tests/jni/Android.bp b/tests/tests/jni/Android.bp
index 19a41ed..386b2ae 100644
--- a/tests/tests/jni/Android.bp
+++ b/tests/tests/jni/Android.bp
@@ -38,7 +38,7 @@
         "libnativehelper_compat_libc++",
     ],
     srcs: ["src/**/*.java"],
-    sdk_version: "current",
+    sdk_version: "test_current",
     use_embedded_native_libs: false,
 }
 
diff --git a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
index 13c6165..208b43b 100644
--- a/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
+++ b/tests/tests/jni/libjnitest/android_jni_cts_LinkerNamespacesTest.cpp
@@ -55,6 +55,7 @@
 static const std::vector<std::regex> kSystemPathRegexes = {
     std::regex("/system/lib(64)?"),
     std::regex("/apex/com\\.android\\.[^/]*/lib(64)?"),
+    std::regex("/system/lib/arm(64)?"), // when CTS runs in ARM ABI on non-ARM CPU. http://b/149852946
 };
 
 static const std::string kWebViewPlatSupportLib = "libwebviewchromium_plat_support.so";
@@ -450,3 +451,19 @@
     }
     return nullptr;
 }
+
+extern "C" JNIEXPORT jint JNICALL Java_android_jni_cts_LinkerNamespacesHelper_getLibAbi(
+        JNIEnv* env,
+        jclass clazz) {
+#ifdef __aarch64__
+    return 1; // ARM64
+#elif __arm__
+    return 2;
+#elif __x86_64__
+    return 3;
+#elif i386
+    return 4;
+#else
+    return 0;
+#endif
+}
diff --git a/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java b/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
index 527ffc1..14fc592 100644
--- a/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
+++ b/tests/tests/jni/src/android/jni/cts/LinkerNamespacesHelper.java
@@ -19,6 +19,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.os.Build;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -187,6 +188,10 @@
 
         Collections.addAll(systemLibs, PUBLIC_SYSTEM_LIBRARIES);
         Collections.addAll(systemLibs, OPTIONAL_SYSTEM_LIBRARIES);
+	// System path could contain public ART libraries on foreign arch. http://b/149852946
+        if (isForeignArchitecture()) {
+            Collections.addAll(systemLibs, PUBLIC_ART_LIBRARIES);
+        }
 
         if (InstrumentationRegistry.getContext().getPackageManager().
                 hasSystemFeature(PackageManager.FEATURE_WEBVIEW)) {
@@ -407,6 +412,22 @@
     }
 
     public static native String tryDlopen(String lib);
+
+    private static boolean isForeignArchitecture() {
+        int libAbi = getLibAbi();
+        String cpuAbi = android.os.SystemProperties.get("ro.product.cpu.abi");
+        if ((libAbi == 1 || libAbi == 2) && !cpuAbi.startsWith("arm")) {
+            return true;
+        } else if ((libAbi == 3 || libAbi == 4) && !cpuAbi.startsWith("x86")) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * @return ABI type of the JNI library. 1: ARM64, 2:ARM, 3: x86_64, 4: x86, 0: others
+     */
+    private static native int getLibAbi();
 }
 
 class ClassNamespaceA1 {
diff --git a/tests/tests/media/Android.bp b/tests/tests/media/Android.bp
index 02acf97..4c76abd 100644
--- a/tests/tests/media/Android.bp
+++ b/tests/tests/media/Android.bp
@@ -37,6 +37,7 @@
         "hamcrest-library",
         "ctstestserver",
         "junit",
+        "junit-params",
         "ndkaudio",
         "testng",
         "truth-prebuilt",
diff --git a/tests/tests/media/OWNERS b/tests/tests/media/OWNERS
index 6614de9..ecf36fa 100644
--- a/tests/tests/media/OWNERS
+++ b/tests/tests/media/OWNERS
@@ -1,9 +1,7 @@
 # Bug component: 1344
+include ../../media/OWNERS
 andrewlewis@google.com
-chz@google.com
-dwkang@google.com
 elaurent@google.com
-essick@google.com
 etalvala@google.com
 gkasten@google.com
 hdmoon@google.com
@@ -12,7 +10,4 @@
 jaewan@google.com
 jmtrivi@google.com
 jsharkey@android.com
-lajos@google.com
-marcone@google.com
 sungsoo@google.com
-wjia@google.com
diff --git a/tests/tests/media/libndkaudio/Android.bp b/tests/tests/media/libndkaudio/Android.bp
index 13f907c..7af57af 100644
--- a/tests/tests/media/libndkaudio/Android.bp
+++ b/tests/tests/media/libndkaudio/Android.bp
@@ -42,6 +42,7 @@
         "-Wno-deprecated-declarations",
     ],
     gtest: false,
+    sdk_version: "29",
 }
 
 //
diff --git a/tests/tests/media/res/raw/multi0.mp4 b/tests/tests/media/res/raw/multi0.mp4
new file mode 100644
index 0000000..c1e68a6
--- /dev/null
+++ b/tests/tests/media/res/raw/multi0.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/orientation_0.jpg b/tests/tests/media/res/raw/orientation_0.jpg
new file mode 100644
index 0000000..9b4d44a
--- /dev/null
+++ b/tests/tests/media/res/raw/orientation_0.jpg
Binary files differ
diff --git a/tests/tests/media/res/raw/orientation_180.jpg b/tests/tests/media/res/raw/orientation_180.jpg
new file mode 100644
index 0000000..005fee2
--- /dev/null
+++ b/tests/tests/media/res/raw/orientation_180.jpg
Binary files differ
diff --git a/tests/tests/media/res/raw/orientation_270.jpg b/tests/tests/media/res/raw/orientation_270.jpg
new file mode 100644
index 0000000..b2c269f
--- /dev/null
+++ b/tests/tests/media/res/raw/orientation_270.jpg
Binary files differ
diff --git a/tests/tests/media/res/raw/orientation_90.jpg b/tests/tests/media/res/raw/orientation_90.jpg
new file mode 100644
index 0000000..aefca34
--- /dev/null
+++ b/tests/tests/media/res/raw/orientation_90.jpg
Binary files differ
diff --git a/tests/tests/media/res/raw/orientation_heic_0.HEIC b/tests/tests/media/res/raw/orientation_heic_0.HEIC
new file mode 100644
index 0000000..44493a2
--- /dev/null
+++ b/tests/tests/media/res/raw/orientation_heic_0.HEIC
Binary files differ
diff --git a/tests/tests/media/res/raw/orientation_heic_180.HEIC b/tests/tests/media/res/raw/orientation_heic_180.HEIC
new file mode 100644
index 0000000..c1113ce
--- /dev/null
+++ b/tests/tests/media/res/raw/orientation_heic_180.HEIC
Binary files differ
diff --git a/tests/tests/media/res/raw/orientation_heic_270.HEIC b/tests/tests/media/res/raw/orientation_heic_270.HEIC
new file mode 100644
index 0000000..542618c
--- /dev/null
+++ b/tests/tests/media/res/raw/orientation_heic_270.HEIC
Binary files differ
diff --git a/tests/tests/media/res/raw/orientation_heic_90.HEIC b/tests/tests/media/res/raw/orientation_heic_90.HEIC
new file mode 100644
index 0000000..0ec74c1
--- /dev/null
+++ b/tests/tests/media/res/raw/orientation_heic_90.HEIC
Binary files differ
diff --git a/tests/tests/media/res/raw/orientation_stripped_0.jpg b/tests/tests/media/res/raw/orientation_stripped_0.jpg
new file mode 100644
index 0000000..4656475
--- /dev/null
+++ b/tests/tests/media/res/raw/orientation_stripped_0.jpg
Binary files differ
diff --git a/tests/tests/media/res/raw/orientation_stripped_180.jpg b/tests/tests/media/res/raw/orientation_stripped_180.jpg
new file mode 100644
index 0000000..eb77283
--- /dev/null
+++ b/tests/tests/media/res/raw/orientation_stripped_180.jpg
Binary files differ
diff --git a/tests/tests/media/res/raw/orientation_stripped_270.jpg b/tests/tests/media/res/raw/orientation_stripped_270.jpg
new file mode 100644
index 0000000..606520d
--- /dev/null
+++ b/tests/tests/media/res/raw/orientation_stripped_270.jpg
Binary files differ
diff --git a/tests/tests/media/res/raw/orientation_stripped_90.jpg b/tests/tests/media/res/raw/orientation_stripped_90.jpg
new file mode 100644
index 0000000..43cc074
--- /dev/null
+++ b/tests/tests/media/res/raw/orientation_stripped_90.jpg
Binary files differ
diff --git a/tests/tests/media/res/raw/sinesweepid3v23ext.mp3 b/tests/tests/media/res/raw/sinesweepid3v23ext.mp3
new file mode 100644
index 0000000..8249f4a
--- /dev/null
+++ b/tests/tests/media/res/raw/sinesweepid3v23ext.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/sinesweepid3v23extbe.mp3 b/tests/tests/media/res/raw/sinesweepid3v23extbe.mp3
new file mode 100644
index 0000000..8d04b44
--- /dev/null
+++ b/tests/tests/media/res/raw/sinesweepid3v23extbe.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/sinesweepid3v24ext.mp3 b/tests/tests/media/res/raw/sinesweepid3v24ext.mp3
new file mode 100644
index 0000000..40b392f
--- /dev/null
+++ b/tests/tests/media/res/raw/sinesweepid3v24ext.mp3
Binary files differ
diff --git a/tests/tests/media/res/raw/sinesweepoggalbumart.ogg b/tests/tests/media/res/raw/sinesweepoggalbumart.ogg
new file mode 100644
index 0000000..00ad4ad
--- /dev/null
+++ b/tests/tests/media/res/raw/sinesweepoggalbumart.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/swirl_128x128_h264_albumart.mp4 b/tests/tests/media/res/raw/swirl_128x128_h264_albumart.mp4
new file mode 100644
index 0000000..f493f25
--- /dev/null
+++ b/tests/tests/media/res/raw/swirl_128x128_h264_albumart.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/testac3mp4.mp4 b/tests/tests/media/res/raw/testac3mp4.mp4
index b078673..af527b1 100644
--- a/tests/tests/media/res/raw/testac3mp4.mp4
+++ b/tests/tests/media/res/raw/testac3mp4.mp4
Binary files differ
diff --git a/tests/tests/media/res/raw/testac3ts.ts b/tests/tests/media/res/raw/testac3ts.ts
index 1e7bc14..a67b8b8 100644
--- a/tests/tests/media/res/raw/testac3ts.ts
+++ b/tests/tests/media/res/raw/testac3ts.ts
Binary files differ
diff --git a/tests/tests/media/res/raw/voice12_32k_128kbps_15s_ac3_spdif.raw b/tests/tests/media/res/raw/voice12_32k_128kbps_15s_ac3_spdif.raw
new file mode 100644
index 0000000..a2610bd
--- /dev/null
+++ b/tests/tests/media/res/raw/voice12_32k_128kbps_15s_ac3_spdif.raw
Binary files differ
diff --git a/tests/tests/media/res/raw/voice12_44k_128kbps_15s_ac3_spdif.raw b/tests/tests/media/res/raw/voice12_44k_128kbps_15s_ac3_spdif.raw
new file mode 100644
index 0000000..4023a5c
--- /dev/null
+++ b/tests/tests/media/res/raw/voice12_44k_128kbps_15s_ac3_spdif.raw
Binary files differ
diff --git a/tests/tests/media/res/raw/voice12_48k_128kbps_15s_ac3_spdif.raw b/tests/tests/media/res/raw/voice12_48k_128kbps_15s_ac3_spdif.raw
new file mode 100644
index 0000000..3215769
--- /dev/null
+++ b/tests/tests/media/res/raw/voice12_48k_128kbps_15s_ac3_spdif.raw
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java b/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
index 91e09c2..0449d65 100644
--- a/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackSurroundTest.java
@@ -33,7 +33,11 @@
 import com.android.compatibility.common.util.CtsAndroidTestCase;
 
 import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
 import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.ShortBuffer;
 import java.util.ArrayList;
 import java.util.Random;
 
@@ -62,6 +66,9 @@
     private final static int MILLIS_PER_SECOND = 1000;
     private final static long NANOS_PER_SECOND = NANOS_PER_MILLISECOND * MILLIS_PER_SECOND;
 
+    private final static int RES_AC3_SPDIF_VOICE_32000 = R.raw.voice12_32k_128kbps_15s_ac3_spdif;
+    private final static int RES_AC3_SPDIF_VOICE_44100 = R.raw.voice12_44k_128kbps_15s_ac3_spdif;
+    private final static int RES_AC3_SPDIF_VOICE_48000 = R.raw.voice12_48k_128kbps_15s_ac3_spdif;
     private final static int RES_AC3_VOICE_48000 = R.raw.voice12_48k_128kbps_15s_ac3;
 
     private static int mLastPlayedEncoding = AudioFormat.ENCODING_INVALID;
@@ -158,31 +165,26 @@
 
     // Load a resource into a byte[]
     private byte[] loadRawResourceBytes(@RawRes int id) throws Exception {
-        AssetFileDescriptor masterFd = getContext().getResources().openRawResourceFd(id);
-        long masterLength = masterFd.getLength();
-        byte[] masterBuffer = new byte[(int) masterLength];
-        InputStream is = masterFd.createInputStream();
-        BufferedInputStream bis = new BufferedInputStream(is);
-        int result = bis.read(masterBuffer);
-        bis.close();
-        masterFd.close();
-        return masterBuffer;
+        InputStream is = getContext().getResources().openRawResource(id);
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        try (BufferedInputStream bis = new BufferedInputStream(is)) {
+            for (int b = bis.read(); b != -1; b = bis.read()) {
+                bos.write(b);
+            }
+        }
+        return bos.toByteArray();
     }
 
     // Load a resource into a short[]
     private short[] loadRawResourceShorts(@RawRes int id) throws Exception {
-        AssetFileDescriptor masterFd = getContext().getResources().openRawResourceFd(id);
-        long masterLength = masterFd.getLength();
-        short[] masterBuffer = new short[(int) (masterLength / 2)];
-        InputStream is = masterFd.createInputStream();
-        BufferedInputStream bis = new BufferedInputStream(is);
+        byte[] byteBuffer = loadRawResourceBytes(id);
+        ShortBuffer shortBuffer =
+                ByteBuffer.wrap(byteBuffer).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer();
+        // Unfortunately, ShortBuffer.array() works with allocated buffers only.
+        short[] masterBuffer = new short[byteBuffer.length / 2];
         for (int i = 0; i < masterBuffer.length; i++) {
-            int lo = bis.read(); // assume Little Endian
-            int hi = bis.read();
-            masterBuffer[i] = (short) (hi * 256 + lo);
+            masterBuffer[i] = shortBuffer.get();
         }
-        bis.close();
-        masterFd.close();
         return masterBuffer;
     }
 
@@ -541,13 +543,11 @@
         }
     }
 
-    // Note that for testing IEC61937, the Audio framework does not look at the
-    // wrapped data. It just passes it through over HDMI. See we can just use
-    // zeros instead of real data.
     public void testPlayIEC61937_32000() throws Exception {
         if (mInfoIEC61937 != null) {
             SamplePlayerShorts player = new SamplePlayerShorts(
-                    32000, AudioFormat.ENCODING_IEC61937, AudioFormat.CHANNEL_OUT_STEREO);
+                    32000, AudioFormat.ENCODING_IEC61937, AudioFormat.CHANNEL_OUT_STEREO,
+                    RES_AC3_SPDIF_VOICE_32000);
             player.playAndMeasureRate();
         }
     }
@@ -555,7 +555,8 @@
     public void testPlayIEC61937_44100() throws Exception {
         if (mInfoIEC61937 != null) {
             SamplePlayerShorts player = new SamplePlayerShorts(
-                    44100, AudioFormat.ENCODING_IEC61937, AudioFormat.CHANNEL_OUT_STEREO);
+                    44100, AudioFormat.ENCODING_IEC61937, AudioFormat.CHANNEL_OUT_STEREO,
+                    RES_AC3_SPDIF_VOICE_44100);
             player.playAndMeasureRate();
         }
     }
@@ -563,7 +564,8 @@
     public void testPlayIEC61937_48000() throws Exception {
         if (mInfoIEC61937 != null) {
             SamplePlayerShorts player = new SamplePlayerShorts(
-                    48000, AudioFormat.ENCODING_IEC61937, AudioFormat.CHANNEL_OUT_STEREO);
+                    48000, AudioFormat.ENCODING_IEC61937, AudioFormat.CHANNEL_OUT_STEREO,
+                    RES_AC3_SPDIF_VOICE_48000);
             player.playAndMeasureRate();
         }
     }
diff --git a/tests/tests/media/src/android/media/cts/AudioTrackTest.java b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
index d55268c..d9c2f50 100755
--- a/tests/tests/media/src/android/media/cts/AudioTrackTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioTrackTest.java
@@ -36,6 +36,7 @@
 import android.media.AudioTrack;
 import android.media.PlaybackParams;
 import android.os.PersistableBundle;
+import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
 import android.util.Log;
 
@@ -2466,6 +2467,8 @@
             Thread.sleep(300 /* millis */); // warm up track
 
             int anticipatedPosition = track.getPlaybackHeadPosition();
+            long timeMs = SystemClock.elapsedRealtime();
+            final long startTimeMs = timeMs;
             for (int j = 0; j < testSteps; ++j) {
                 // set playback settings
                 final float pitch = playbackParams.getPitch();
@@ -2482,14 +2485,17 @@
 
                 // sleep for playback
                 Thread.sleep(TEST_DELTA_MS);
+                final long newTimeMs = SystemClock.elapsedRealtime();
                 // Log.d(TAG, "position[" + j + "] " + track.getPlaybackHeadPosition());
                 anticipatedPosition +=
-                        playbackParams.getSpeed() * TEST_DELTA_MS * TEST_SR / 1000;
+                        playbackParams.getSpeed() * (newTimeMs - timeMs) * TEST_SR / 1000;
+                timeMs = newTimeMs;
                 playbackParams.setPitch(playbackParams.getPitch() + pitchInc);
                 playbackParams.setSpeed(playbackParams.getSpeed() + speedInc);
             }
             final int endPosition = track.getPlaybackHeadPosition();
             final int tolerance100MsInFrames = 100 * TEST_SR / 1000;
+            Log.d(TAG, "Total playback time: " + (timeMs - startTimeMs));
             assertEquals(TAG, anticipatedPosition, endPosition, tolerance100MsInFrames);
             track.stop();
 
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
index df74df3..36c24ef 100644
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayWithCompositionTestImpl.java
@@ -79,8 +79,8 @@
     private static final boolean DBG = false;
     private static final String MIME_TYPE = MediaFormat.MIMETYPE_VIDEO_AVC;
 
-    private static final long DEFAULT_WAIT_TIMEOUT_MS = 3000;
-    private static final long DEFAULT_WAIT_TIMEOUT_US = 3000000;
+    private static final long DEFAULT_WAIT_TIMEOUT_MS = 5000;
+    private static final long DEFAULT_WAIT_TIMEOUT_US = DEFAULT_WAIT_TIMEOUT_MS * 1000;
 
     private static final int COLOR_RED =  makeColor(100, 0, 0);
     private static final int COLOR_GREEN =  makeColor(0, 100, 0);
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecBlockModelTest.java b/tests/tests/media/src/android/media/cts/MediaCodecBlockModelTest.java
index a5413fd..ce1ac91 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecBlockModelTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecBlockModelTest.java
@@ -37,9 +37,14 @@
 
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.function.BooleanSupplier;
 
 /**
@@ -63,9 +68,9 @@
             R.raw.video_480x360_mp4_h264_1350kbps_30fps_aac_stereo_192kbps_44100hz;
     private static final long LAST_BUFFER_TIMESTAMP_US = 166666;
 
-    // The test should fail if the decoder never produces output frames for the truncated input.
-    // Time out decoding, as we have no way to query whether the decoder will produce output.
-    private static final int DECODING_TIMEOUT_MS = 2000;
+    // The test should fail if the codec never produces output frames for the truncated input.
+    // Time out processing, as we have no way to query whether the decoder will produce output.
+    private static final int TIMEOUT_MS = 2000;
 
     /**
      * Tests whether decoding a short group-of-pictures succeeds. The test queues a few video frames
@@ -105,19 +110,47 @@
         runThread(() -> runEncodeShortAudio());
     }
 
+    public void testFormatChange() throws InterruptedException {
+        List<FormatChangeEvent> events = new ArrayList<>();
+        runThread(() -> runDecodeShortVideo(
+                INPUT_RESOURCE_ID,
+                LAST_BUFFER_TIMESTAMP_US,
+                true /* obtainBlockForEachBuffer */,
+                MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, 320, 240),
+                events));
+        int width = 320;
+        int height = 240;
+        for (FormatChangeEvent event : events) {
+            if (event.changedKeys.contains(MediaFormat.KEY_WIDTH)) {
+                width = event.format.getInteger(MediaFormat.KEY_WIDTH);
+            }
+            if (event.changedKeys.contains(MediaFormat.KEY_HEIGHT)) {
+                height = event.format.getInteger(MediaFormat.KEY_HEIGHT);
+            }
+        }
+        assertEquals("Width should have been updated", 480, width);
+        assertEquals("Height should have been updated", 360, height);
+    }
+
     private void runThread(BooleanSupplier supplier) throws InterruptedException {
-        final AtomicBoolean completed = new AtomicBoolean();
-        completed.set(false);
-        Thread videoDecodingThread = new Thread(new Runnable() {
+        final AtomicBoolean completed = new AtomicBoolean(false);
+        Thread thread = new Thread(new Runnable() {
             public void run() {
                 completed.set(supplier.getAsBoolean());
             }
         });
-        videoDecodingThread.start();
-        videoDecodingThread.join(DECODING_TIMEOUT_MS);
-        if (!completed.get()) {
-            throw new RuntimeException("timed out decoding to end-of-stream");
+        final AtomicReference<Throwable> throwable = new AtomicReference<>();
+        thread.setUncaughtExceptionHandler((Thread t, Throwable e) -> {
+            throwable.set(e);
+        });
+        thread.start();
+        thread.join(TIMEOUT_MS);
+        Throwable t = throwable.get();
+        if (t != null) {
+            Log.e(TAG, "There was an error while running the thread:", t);
+            fail("There was an error while running the thread; please check log");
         }
+        assertTrue("timed out decoding to end-of-stream", completed.get());
     }
 
     private static class InputBlock {
@@ -134,10 +167,12 @@
         public ExtractorInputSlotListener(
                 MediaExtractor extractor,
                 long lastBufferTimestampUs,
-                boolean obtainBlockForEachBuffer) {
+                boolean obtainBlockForEachBuffer,
+                LinkedBlockingQueue<Long> timestampQueue) {
             mExtractor = extractor;
             mLastBufferTimestampUs = lastBufferTimestampUs;
             mObtainBlockForEachBuffer = obtainBlockForEachBuffer;
+            mTimestampQueue = timestampQueue;
         }
 
         @Override
@@ -151,6 +186,8 @@
             if (mObtainBlockForEachBuffer) {
                 input.block.recycle();
                 input.block = MediaCodec.LinearBlock.obtain(Math.toIntExact(size), codecNames);
+                assertTrue("Blocks obtained through LinearBlock.obtain must be mappable",
+                        input.block.isMappable());
                 input.buffer = input.block.map();
                 input.offset = 0;
             }
@@ -158,6 +195,8 @@
                 input.block.recycle();
                 input.block = MediaCodec.LinearBlock.obtain(
                         Math.toIntExact(size * 2), codecNames);
+                assertTrue("Blocks obtained through LinearBlock.obtain must be mappable",
+                        input.block.isMappable());
                 input.buffer = input.block.map();
                 input.offset = 0;
             } else if (input.buffer.capacity() - input.offset < size) {
@@ -165,6 +204,8 @@
                 input.block.recycle();
                 input.block = MediaCodec.LinearBlock.obtain(
                         Math.toIntExact(capacity), codecNames);
+                assertTrue("Blocks obtained through LinearBlock.obtain must be mappable",
+                        input.block.isMappable());
                 input.buffer = input.block.map();
                 input.offset = 0;
             }
@@ -173,22 +214,31 @@
             mExtractor.advance();
             mSignaledEos = mExtractor.getSampleTrackIndex() == -1
                     || timestampUs >= mLastBufferTimestampUs;
-            codec.getQueueRequest(index)
-                    .setLinearBlock(
-                            input.block,
-                            input.offset,
-                            written)
-                    .setPresentationTimeUs(timestampUs)
-                    .setFlags(
-                            mSignaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0)
-                    .queue();
+            MediaCodec.QueueRequest request = codec.getQueueRequest(index);
+            request.setLinearBlock(input.block, input.offset, written);
+            request.setPresentationTimeUs(timestampUs);
+            request.setFlags(mSignaledEos ? MediaCodec.BUFFER_FLAG_END_OF_STREAM : 0);
+            if (mSetParams) {
+                request.setIntegerParameter("vendor.int", 0);
+                request.setLongParameter("vendor.long", 0);
+                request.setFloatParameter("vendor.float", (float)0);
+                request.setStringParameter("vendor.string", "str");
+                request.setByteBufferParameter("vendor.buffer", ByteBuffer.allocate(1));
+                mSetParams = false;
+            }
+            request.queue();
             input.offset += written;
+            if (mTimestampQueue != null) {
+                mTimestampQueue.offer(timestampUs);
+            }
         }
 
         private final MediaExtractor mExtractor;
         private final long mLastBufferTimestampUs;
         private final boolean mObtainBlockForEachBuffer;
+        private final LinkedBlockingQueue<Long> mTimestampQueue;
         private boolean mSignaledEos = false;
+        private boolean mSetParams = true;
     }
 
     private static interface OutputSlotListener {
@@ -197,8 +247,9 @@
     }
 
     private static class DummyOutputSlotListener implements OutputSlotListener {
-        public DummyOutputSlotListener(boolean graphic) {
+        public DummyOutputSlotListener(boolean graphic, LinkedBlockingQueue<Long> timestampQueue) {
             mGraphic = graphic;
+            mTimestampQueue = timestampQueue;
         }
 
         @Override
@@ -212,17 +263,29 @@
             if (!mGraphic && frame.getLinearBlock() != null) {
                 frame.getLinearBlock().recycle();
             }
+
+            Long ts = mTimestampQueue.peek();
+            if (ts != null && ts == frame.getPresentationTimeUs()) {
+                mTimestampQueue.poll();
+            }
+
             codec.releaseOutputBuffer(index, false);
 
             return eos;
         }
 
         private final boolean mGraphic;
+        private final LinkedBlockingQueue<Long> mTimestampQueue;
     }
 
     private static class SurfaceOutputSlotListener implements OutputSlotListener {
-        public SurfaceOutputSlotListener(OutputSurface surface) {
+        public SurfaceOutputSlotListener(
+                OutputSurface surface,
+                LinkedBlockingQueue<Long> timestampQueue,
+                List<FormatChangeEvent> events) {
             mOutputSurface = surface;
+            mTimestampQueue = timestampQueue;
+            mEvents = (events != null) ? events : new ArrayList<>();
         }
 
         @Override
@@ -235,6 +298,16 @@
                 frame.getHardwareBuffer().close();
                 render = true;
             }
+
+            Long ts = mTimestampQueue.peek();
+            if (ts != null && ts == frame.getPresentationTimeUs()) {
+                mTimestampQueue.poll();
+            }
+
+            if (!frame.getChangedKeys().isEmpty()) {
+                mEvents.add(new FormatChangeEvent(ts, frame.getChangedKeys(), frame.getFormat()));
+            }
+
             codec.releaseOutputBuffer(index, render);
             if (render) {
                 mOutputSurface.awaitNewImage();
@@ -244,6 +317,8 @@
         }
 
         private final OutputSurface mOutputSurface;
+        private final LinkedBlockingQueue<Long> mTimestampQueue;
+        private final List<FormatChangeEvent> mEvents;
     }
 
     private static class SlotEvent {
@@ -259,14 +334,51 @@
             int inputResourceId,
             long lastBufferTimestampUs,
             boolean obtainBlockForEachBuffer) {
+        return runDecodeShortVideo(
+                inputResourceId, lastBufferTimestampUs, obtainBlockForEachBuffer, null, null);
+    }
+
+    private static class FormatChangeEvent {
+        FormatChangeEvent(long ts, Set<String> keys, MediaFormat fmt) {
+            timestampUs = ts;
+            changedKeys = new HashSet<>(keys);
+            format = new MediaFormat(fmt);
+        }
+
+        long timestampUs;
+        Set<String> changedKeys;
+        MediaFormat format;
+
+        @Override
+        public String toString() {
+            return Long.toString(timestampUs) + "us: changed keys=" + changedKeys
+                + " format=" + format;
+        }
+    }
+
+    private boolean runDecodeShortVideo(
+            int inputResourceId,
+            long lastBufferTimestampUs,
+            boolean obtainBlockForEachBuffer,
+            MediaFormat format,
+            List<FormatChangeEvent> events) {
         OutputSurface outputSurface = null;
         MediaExtractor mediaExtractor = null;
         MediaCodec mediaCodec = null;
         try {
             outputSurface = new OutputSurface(1, 1);
             mediaExtractor = getMediaExtractorForMimeType(inputResourceId, "video/");
-            MediaFormat mediaFormat =
-                    mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
+            MediaFormat mediaFormat = mediaExtractor.getTrackFormat(mediaExtractor.getSampleTrackIndex());
+            if (format != null) {
+                // copy CSD
+                for (int i = 0; i < 3; ++i) {
+                    String key = "csd-" + i;
+                    if (mediaFormat.containsKey(key)) {
+                        format.setByteBuffer(key, mediaFormat.getByteBuffer(key));
+                    }
+                }
+                mediaFormat = format;
+            }
             // TODO: b/147748978
             String[] codecs = MediaUtils.getDecoderNames(true /* isGoog */, mediaFormat);
             if (codecs.length == 0) {
@@ -275,14 +387,23 @@
             }
             mediaCodec = MediaCodec.createByCodecName(codecs[0]);
 
-            return runComponentWithLinearInput(
+            LinkedBlockingQueue<Long> timestampQueue = new LinkedBlockingQueue<>();
+            boolean result = runComponentWithLinearInput(
                     mediaCodec,
                     mediaFormat,
                     outputSurface.getSurface(),
                     false,  // encoder
                     new ExtractorInputSlotListener(
-                            mediaExtractor, lastBufferTimestampUs, obtainBlockForEachBuffer),
-                    new SurfaceOutputSlotListener(outputSurface));
+                            mediaExtractor,
+                            lastBufferTimestampUs,
+                            obtainBlockForEachBuffer,
+                            timestampQueue),
+                    new SurfaceOutputSlotListener(outputSurface, timestampQueue, events));
+            if (result) {
+                assertTrue("Timestamp should match between input / output",
+                        timestampQueue.isEmpty());
+            }
+            return result;
         } catch (IOException e) {
             throw new RuntimeException("error reading input resource", e);
         } catch (Exception e) {
@@ -319,14 +440,23 @@
             }
             mediaCodec = MediaCodec.createByCodecName(codecs[0]);
 
-            return runComponentWithLinearInput(
+            LinkedBlockingQueue<Long> timestampQueue = new LinkedBlockingQueue<>();
+            boolean result = runComponentWithLinearInput(
                     mediaCodec,
                     mediaFormat,
                     null,  // surface
                     false,  // encoder
                     new ExtractorInputSlotListener(
-                            mediaExtractor, lastBufferTimestampUs, obtainBlockForEachBuffer),
-                    new DummyOutputSlotListener(false /* graphic */));
+                            mediaExtractor,
+                            lastBufferTimestampUs,
+                            obtainBlockForEachBuffer,
+                            timestampQueue),
+                    new DummyOutputSlotListener(false /* graphic */, timestampQueue));
+            if (result) {
+                assertTrue("Timestamp should match between input / output",
+                        timestampQueue.isEmpty());
+            }
+            return result;
         } catch (IOException e) {
             throw new RuntimeException("error reading input resource", e);
         } catch (Exception e) {
@@ -360,14 +490,23 @@
             }
             mediaCodec = MediaCodec.createByCodecName(codecs[0]);
 
-            return runComponentWithLinearInput(
+            LinkedBlockingQueue<Long> timestampQueue = new LinkedBlockingQueue<>();
+            boolean result = runComponentWithLinearInput(
                     mediaCodec,
                     mediaFormat,
                     null,  // surface
                     true,  // encoder
                     new ExtractorInputSlotListener(
-                            mediaExtractor, LAST_BUFFER_TIMESTAMP_US, false),
-                    new DummyOutputSlotListener(false /* graphic */));
+                            mediaExtractor,
+                            LAST_BUFFER_TIMESTAMP_US,
+                            false,
+                            timestampQueue),
+                    new DummyOutputSlotListener(false /* graphic */, timestampQueue));
+            if (result) {
+                assertTrue("Timestamp should match between input / output",
+                        timestampQueue.isEmpty());
+            }
+            return result;
         } catch (IOException e) {
             throw new RuntimeException("error reading input resource", e);
         } catch (Exception e) {
@@ -413,8 +552,14 @@
         });
         String[] codecNames = new String[]{ mediaCodec.getName() };
         InputBlock input = new InputBlock();
+        if (!mediaCodec.getCodecInfo().isVendor() && mediaCodec.getName().startsWith("c2.")) {
+            assertTrue("Google default c2.* codecs are copy-free compatible with LinearBlocks",
+                    MediaCodec.LinearBlock.isCodecCopyFreeCompatible(codecNames));
+        }
         input.block = MediaCodec.LinearBlock.obtain(
                 APP_BUFFER_SIZE, codecNames);
+        assertTrue("Blocks obtained through LinearBlock.obtain must be mappable",
+                input.block.isMappable());
         input.buffer = input.block.map();
         input.offset = 0;
 
diff --git a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
index af64374..4594ead 100644
--- a/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaExtractorTest.java
@@ -535,6 +535,7 @@
         mExtractor.setDataSource(url);
         long cachedDurationUs = mExtractor.getCachedDuration();
         assertTrue("cached duration should be non-negative", cachedDurationUs >= 0);
+        foo.shutdown();
     }
 
     public void testExtractorHasCacheReachedEndOfStream() throws Exception {
@@ -925,19 +926,60 @@
         doTestAdvance(R.raw.video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz);
     }
 
+    private void readAllData() {
+        // 1MB is enough for any sample.
+        final ByteBuffer buf = ByteBuffer.allocate(1024*1024);
+        final int trackCount = mExtractor.getTrackCount();
+
+        for (int i = 0; i < trackCount; i++) {
+            mExtractor.selectTrack(i);
+        }
+        do {
+            mExtractor.readSampleData(buf, 0);
+        } while (mExtractor.advance());
+        mExtractor.seekTo(0, MediaExtractor.SEEK_TO_NEXT_SYNC);
+        do {
+            mExtractor.readSampleData(buf, 0);
+        } while (mExtractor.advance());
+    }
+
     public void testAC3inMP4() throws Exception {
         setDataSource(R.raw.testac3mp4);
+        readAllData();
     }
 
     public void testEAC3inMP4() throws Exception {
         setDataSource(R.raw.testeac3mp4);
+        readAllData();
     }
 
     public void testAC3inTS() throws Exception {
         setDataSource(R.raw.testac3ts);
+        readAllData();
     }
 
     public void testEAC3inTS() throws Exception {
         setDataSource(R.raw.testeac3ts);
+        readAllData();
     }
+
+    public void testAC4inMP4() throws Exception {
+        setDataSource(R.raw.multi0);
+        readAllData();
+    }
+
+    public void testFragmentedRead() throws Exception {
+        setDataSource(R.raw.psshtest);
+        readAllData();
+    }
+
+    public void testFragmentedHttpRead() throws Exception {
+        CtsTestServer server = new CtsTestServer(getContext());
+        String rname = mResources.getResourceEntryName(R.raw.psshtest);
+        String url = server.getAssetUrl("raw/" + rname);
+        mExtractor.setDataSource(url);
+        readAllData();
+        server.shutdown();
+    }
+
 }
diff --git a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
index 0a431d8..845cc9c 100644
--- a/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaMetadataRetrieverTest.java
@@ -333,6 +333,58 @@
                 mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_WRITER));
     }
 
+    public void testID3v2Unsynchronization() {
+        setDataSourceFd(R.raw.testmp3_4);
+        assertEquals("Mime type was other than expected",
+                "audio/mpeg",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+    }
+
+    public void testID3v240ExtHeader() {
+        setDataSourceFd(R.raw.sinesweepid3v24ext);
+        assertEquals("Mime type was other than expected",
+                "audio/mpeg",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+        assertEquals("Title was other than expected",
+                "sinesweep",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
+        assertNotNull("no album art",
+                mRetriever.getEmbeddedPicture());
+    }
+
+    public void testID3v230ExtHeader() {
+        setDataSourceFd(R.raw.sinesweepid3v23ext);
+        assertEquals("Mime type was other than expected",
+                "audio/mpeg",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+        assertEquals("Title was other than expected",
+                "sinesweep",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
+        assertNotNull("no album art",
+                mRetriever.getEmbeddedPicture());
+    }
+
+    public void testID3v230ExtHeaderBigEndian() {
+        setDataSourceFd(R.raw.sinesweepid3v23extbe);
+        assertEquals("Mime type was other than expected",
+                "audio/mpeg",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+        assertEquals("Title was other than expected",
+                "sinesweep",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE));
+        assertNotNull("no album art",
+                mRetriever.getEmbeddedPicture());
+    }
+
+    public void testMp4AlbumArt() {
+        setDataSourceFd(R.raw.swirl_128x128_h264_albumart);
+        assertEquals("Mime type was other than expected",
+                "video/mp4",
+                mRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_MIMETYPE));
+        assertNotNull("no album art",
+                mRetriever.getEmbeddedPicture());
+    }
+
     public void testGenreParsing() {
         Object [][] genres = {
             { R.raw.id3test0, null },
@@ -375,6 +427,11 @@
         assertNotNull("couldn't retrieve album art", mRetriever.getEmbeddedPicture());
     }
 
+    public void testAlbumArtInOgg() throws Exception {
+        setDataSourceFd(R.raw.sinesweepoggalbumart);
+        assertNotNull("couldn't retrieve album art from ogg", mRetriever.getEmbeddedPicture());
+    }
+
     public void testSetDataSourcePath() {
         copyMeidaFile();
         File file = new File(Environment.getExternalStorageDirectory(), TEST_MEDIA_FILE);
diff --git a/tests/tests/media/src/android/media/cts/MediaRouter2Test.java b/tests/tests/media/src/android/media/cts/MediaRouter2Test.java
index 5e8ea6d..566ebdd 100644
--- a/tests/tests/media/src/android/media/cts/MediaRouter2Test.java
+++ b/tests/tests/media/src/android/media/cts/MediaRouter2Test.java
@@ -1006,6 +1006,9 @@
         transferCallback.onTransfer(null, null);
         transferCallback.onTransferFailure(null);
 
+        ControllerCallback controllerCallback = new ControllerCallback() {};
+        controllerCallback.onControllerUpdated(null);
+
         OnGetControllerHintsListener listener = route -> null;
         listener.onGetControllerHints(null);
     }
diff --git a/tests/tests/media/src/android/media/cts/MediaSessionTest.java b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
index a5c5a9e..5cf1d3b 100644
--- a/tests/tests/media/src/android/media/cts/MediaSessionTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaSessionTest.java
@@ -335,6 +335,18 @@
     }
 
     /**
+     * Test public APIs of {@link VolumeProvider}.
+     */
+    public void testVolumeProvider() {
+        VolumeProvider vp = new VolumeProvider(VolumeProvider.VOLUME_CONTROL_RELATIVE,
+                TEST_MAX_VOLUME, TEST_CURRENT_VOLUME, TEST_VOLUME_CONTROL_ID) {};
+        assertEquals(VolumeProvider.VOLUME_CONTROL_RELATIVE, vp.getVolumeControl());
+        assertEquals(TEST_MAX_VOLUME, vp.getMaxVolume());
+        assertEquals(TEST_CURRENT_VOLUME, vp.getCurrentVolume());
+        assertEquals(TEST_VOLUME_CONTROL_ID, vp.getVolumeControlId());
+    }
+
+    /**
      * Test {@link MediaSession#setPlaybackToLocal} and {@link MediaSession#setPlaybackToRemote}.
      */
     public void testPlaybackToLocalAndRemote() throws Exception {
diff --git a/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java b/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
index 6ae9e19..660dd34 100644
--- a/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
+++ b/tests/tests/media/src/android/media/cts/ThumbnailUtilsTest.java
@@ -16,16 +16,18 @@
 
 package android.media.cts;
 
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+import android.annotation.ColorInt;
 import android.content.Context;
 import android.graphics.Bitmap;
+import android.graphics.Color;
 import android.media.ThumbnailUtils;
 import android.platform.test.annotations.AppModeFull;
 import android.util.Size;
 
 import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
 
 import org.junit.After;
 import org.junit.Before;
@@ -38,8 +40,11 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 @AppModeFull(reason = "Instant apps cannot access the SD card")
-@RunWith(AndroidJUnit4.class)
+@RunWith(JUnitParamsRunner.class)
 public class ThumbnailUtilsTest {
     private static final Size[] TEST_SIZES = new Size[] {
             new Size(50, 50),
@@ -49,6 +54,35 @@
 
     private File mDir;
 
+    private Object[] getSampleWithThumbnailForCreateImageThumbnail() {
+        return new Object[] {
+                R.raw.orientation_0,
+                R.raw.orientation_90,
+                R.raw.orientation_180,
+                R.raw.orientation_270,
+        };
+    }
+
+    private Object[] getStrippedSampleForCreateImageThumbnail() {
+        return new Object[] {
+                R.raw.orientation_stripped_0,
+                R.raw.orientation_stripped_90,
+                R.raw.orientation_stripped_180,
+                R.raw.orientation_stripped_270
+
+        };
+    }
+
+    private Object[] getHEICSampleForCreateImageThumbnail() {
+        return new Object[] {
+                R.raw.orientation_heic_0,
+                R.raw.orientation_heic_90,
+                R.raw.orientation_heic_180,
+                R.raw.orientation_heic_270
+
+        };
+    }
+
     @Before
     public void setUp() {
         mDir = InstrumentationRegistry.getTargetContext().getExternalCacheDir();
@@ -97,6 +131,42 @@
         }
     }
 
+    private static void assertOrientationForThumbnail(Bitmap bitmap) {
+        // All callers are expected to pass a Bitmap with an image of a black cup in the middle
+        // (left-to-right) upper portion, on a mostly non-black background. They use different
+        // EXIF orientations to achieve the same final image, and this verifies that the EXIF
+        // orientation was applied properly.
+        assertColorMostlyInRange(bitmap.getPixel(bitmap.getWidth() / 2, bitmap.getHeight() / 3),
+                0xFF202020 /* upperBound */, Color.BLACK);
+    }
+
+    @Test
+    @Parameters(method = "getSampleWithThumbnailForCreateImageThumbnail")
+    public void testCreateImageThumbnail_sampleWithThumbnail(int resId) throws Exception {
+        final File file = stageFile(resId, new File(mDir, "cts.jpg"));
+        final Bitmap bitmap = ThumbnailUtils.createImageThumbnail(file, TEST_SIZES[0], null);
+
+        assertOrientationForThumbnail(bitmap);
+    }
+
+    @Test
+    @Parameters(method = "getStrippedSampleForCreateImageThumbnail")
+    public void testCreateImageThumbnail_strippedSample(int resId) throws Exception {
+        final File file = stageFile(resId, new File(mDir, "cts.jpg"));
+        final Bitmap bitmap = ThumbnailUtils.createImageThumbnail(file, TEST_SIZES[0], null);
+
+        assertOrientationForThumbnail(bitmap);
+    }
+
+    @Test
+    @Parameters(method = "getHEICSampleForCreateImageThumbnail")
+    public void testCreateImageThumbnail_HEICSample(int resId) throws Exception {
+        final File file = stageFile(resId, new File(mDir, "cts.heic"));
+        final Bitmap bitmap = ThumbnailUtils.createImageThumbnail(file, TEST_SIZES[0], null);
+
+        assertOrientationForThumbnail(bitmap);
+    }
+
     @Test
     public void testCreateVideoThumbnail() throws Exception {
         final File file = stageFile(
@@ -136,4 +206,16 @@
             fail("Actual " + actual + " differs too much from expected " + expected);
         }
     }
+
+    private static void assertColorMostlyInRange(@ColorInt int actual, @ColorInt int upperBound,
+            @ColorInt int lowerBound) {
+        assertTrue(Color.alpha(lowerBound) <= Color.alpha(actual)
+                && Color.alpha(actual) <= Color.alpha(upperBound));
+        assertTrue(Color.red(lowerBound) <= Color.red(actual)
+                && Color.red(actual) <= Color.red(upperBound));
+        assertTrue(Color.green(lowerBound) <= Color.green(actual)
+                && Color.green(actual) <= Color.green(upperBound));
+        assertTrue(Color.blue(lowerBound) <= Color.blue(actual)
+                && Color.blue(actual) <= Color.blue(upperBound));
+    }
 }
diff --git a/tests/tests/mediaparser/assets/ts/sample_h264_dts_audio.ts b/tests/tests/mediaparser/assets/ts/sample_h264_dts_audio.ts
new file mode 100644
index 0000000..e9aafd7
--- /dev/null
+++ b/tests/tests/mediaparser/assets/ts/sample_h264_dts_audio.ts
Binary files differ
diff --git a/tests/tests/mediaparser/assets/ts/sample_h264_no_access_unit_delimiters.ts b/tests/tests/mediaparser/assets/ts/sample_h264_no_access_unit_delimiters.ts
new file mode 100644
index 0000000..a1cc40b
--- /dev/null
+++ b/tests/tests/mediaparser/assets/ts/sample_h264_no_access_unit_delimiters.ts
Binary files differ
diff --git a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
index 8d644de..d62bc6a 100644
--- a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
+++ b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MediaParserTest.java
@@ -38,6 +38,8 @@
 import org.junit.runner.RunWith;
 
 import java.io.IOException;
+import java.util.Collections;
+import java.util.Map;
 
 @RunWith(AndroidJUnit4.class)
 public class MediaParserTest {
@@ -211,32 +213,32 @@
     // OGG.
 
     @Test
-    public void testBearVorbisOgg() throws IOException, InterruptedException {
+    public void testOggBearVorbis() throws IOException, InterruptedException {
         testExtractAsset("ogg/bear_vorbis.ogg");
     }
 
     @Test
-    public void testBearOgg() throws IOException, InterruptedException {
+    public void testOggBear() throws IOException, InterruptedException {
         testExtractAsset("ogg/bear.opus");
     }
 
     @Test
-    public void testBearFlacOgg() throws IOException, InterruptedException {
+    public void testOggBearFlac() throws IOException, InterruptedException {
         testExtractAsset("ogg/bear_flac.ogg");
     }
 
     @Test
-    public void testNoFlacSeekTableOgg() throws IOException, InterruptedException {
+    public void testOggNoFlacSeekTable() throws IOException, InterruptedException {
         testExtractAsset("ogg/bear_flac_noseektable.ogg");
     }
 
     @Test
-    public void testFlacHeaderOggSniff() throws IOException, InterruptedException {
+    public void testOggFlacHeaderSniff() throws IOException, InterruptedException {
         testSniffAsset("ogg/flac_header", /* expectedExtractorName= */ MediaParser.PARSER_NAME_OGG);
     }
 
     @Test
-    public void testOpusHeaderOggSniff() throws IOException, InterruptedException {
+    public void testOggOpusHeaderSniff() throws IOException, InterruptedException {
         try {
             testSniffAsset(
                     "ogg/opus_header", /* expectedExtractorName= */ MediaParser.PARSER_NAME_OGG);
@@ -247,7 +249,7 @@
     }
 
     @Test
-    public void testInvalidHeaderOggSniff() throws IOException, InterruptedException {
+    public void testOggInvalidHeaderSniff() throws IOException, InterruptedException {
         try {
             testSniffAsset(
                     "ogg/invalid_ogg_header",
@@ -256,10 +258,6 @@
         } catch (MediaParser.UnrecognizedInputFormatException e) {
             // Expected.
         }
-    }
-
-    @Test
-    public void testInvalidHeaderSniff() throws IOException, InterruptedException {
         try {
             testSniffAsset(
                     "ogg/invalid_header", /* expectedExtractorName= */ MediaParser.PARSER_NAME_OGG);
@@ -272,65 +270,75 @@
     // FLAC.
 
     @Test
-    public void testBearUncommonSampleRateFlac() throws IOException, InterruptedException {
+    public void testFlacUncommonSampleRateFlac() throws IOException, InterruptedException {
         testExtractAsset("flac/bear_uncommon_sample_rate.flac");
     }
 
     @Test
-    public void testBearNoSeekTableAndNoNumSamplesFlac() throws IOException, InterruptedException {
+    public void testFlacNoSeekTableAndNoNumSamples() throws IOException, InterruptedException {
         testExtractAsset("flac/bear_no_seek_table_no_num_samples.flac");
     }
 
     @Test
-    public void testBearWithPictureFlac() throws IOException, InterruptedException {
+    public void testFlacWithPicture() throws IOException, InterruptedException {
         testExtractAsset("flac/bear_with_picture.flac");
     }
 
     @Test
-    public void testBearWithVorbisCommentsFlac() throws IOException, InterruptedException {
+    public void testFlacWithVorbisComments() throws IOException, InterruptedException {
         testExtractAsset("flac/bear_with_vorbis_comments.flac");
     }
 
     @Test
-    public void testOneMetadataBlockFlac() throws IOException, InterruptedException {
+    public void testFlacOneMetadataBlock() throws IOException, InterruptedException {
         testExtractAsset("flac/bear_one_metadata_block.flac");
     }
 
     @Test
-    public void testBearNoMinMaxFrameSizeFlac() throws IOException, InterruptedException {
+    public void testFlacNoMinMaxFrameSize() throws IOException, InterruptedException {
         testExtractAsset("flac/bear_no_min_max_frame_size.flac");
     }
 
     @Test
-    public void testNoNumSamplesFlac() throws IOException, InterruptedException {
+    public void testFlacNoNumSamples() throws IOException, InterruptedException {
         testExtractAsset("flac/bear_no_num_samples.flac");
     }
 
     @Test
-    public void testBearNoId3Flac() throws IOException, InterruptedException {
-        testExtractAsset("flac/bear_with_id3_disabled.flac");
+    public void testFlacWithId3() throws IOException, InterruptedException {
+        testExtractAsset("flac/bear_with_id3.flac");
     }
 
     @Test
-    public void testBearWithId3Flac() throws IOException, InterruptedException {
-        testExtractAsset("flac/bear_with_id3_enabled.flac");
-    }
-
-    @Test
-    public void testBearFlac() throws IOException, InterruptedException {
+    public void testFlacSample() throws IOException, InterruptedException {
         testExtractAsset("flac/bear.flac");
     }
 
     // MP3.
 
     @Test
-    public void testTrimmedMp3() throws IOException, InterruptedException {
+    public void testMp3WithNoSeekTableVariableFrameSize() throws IOException, InterruptedException {
+        testExtractAsset("mp3/bear-cbr-variable-frame-size-no-seek-table.mp3");
+    }
+
+    @Test
+    public void testMp3WithVariableBitrateAndXingHeader() throws IOException, InterruptedException {
+        testExtractAsset("mp3/bear-vbr-xing-header.mp3");
+    }
+
+    @Test
+    public void testMp3WithNoSeekTableVariableBitrate() throws IOException, InterruptedException {
+        testExtractAsset("mp3/bear-vbr-no-seek-table.mp3");
+    }
+
+    @Test
+    public void testMp3WithTrimmedSample() throws IOException, InterruptedException {
         testExtractAsset("mp3/play-trimmed.mp3");
     }
 
     @Test
-    public void testBearMp3() throws IOException, InterruptedException {
-        testExtractAsset("mp3/bear.mp3");
+    public void testMp3WithId3() throws IOException, InterruptedException {
+        testExtractAsset("mp3/bear-id3.mp3");
     }
 
     // WAV.
@@ -348,23 +356,23 @@
     // AMR.
 
     @Test
-    public void testNarrowBandSamplesWithConstantBitrateSeeking()
+    public void testAmrNarrowBandSamplesWithConstantBitrateSeeking()
             throws IOException, InterruptedException {
         testExtractAsset("amr/sample_nb_cbr.amr");
     }
 
     @Test
-    public void testNarrowBandSamples() throws IOException, InterruptedException {
+    public void testAmrNarrowBandSamples() throws IOException, InterruptedException {
         testExtractAsset("amr/sample_nb.amr");
     }
 
     @Test
-    public void testWideBandSamples() throws IOException, InterruptedException {
+    public void testAmrWideBandSamples() throws IOException, InterruptedException {
         testExtractAsset("amr/sample_wb.amr");
     }
 
     @Test
-    public void testWideBandSamplesWithConstantBitrateSeeking()
+    public void testAmrWideBandSamplesWithConstantBitrateSeeking()
             throws IOException, InterruptedException {
         testExtractAsset("amr/sample_wb_cbr.amr");
     }
@@ -381,21 +389,28 @@
     // TODO: Enable once the timeout is fixed.
     @Test
     @Ignore
-    public void testElphantsDreamPs() throws IOException, InterruptedException {
+    public void testPsElphantsDream() throws IOException, InterruptedException {
         testExtractAsset("ts/elephants_dream.mpg");
     }
 
     @Test
-    public void testProgramStream() throws IOException, InterruptedException {
-        testExtractAsset("ts/sample.ps");
+    public void testPsWithAc3() throws IOException, InterruptedException {
+        testExtractAsset("ts/sample_ac3.ps");
+    }
+
+    @Test
+    public void testPsWithH262MpegAudio() throws IOException, InterruptedException {
+        testExtractAsset("ts/sample_h262_mpeg_audio.ps");
     }
 
     // ADTS.
 
     @Test
-    public void testTruncatedAdtsWithConstantBitrateSeeking()
+    public void testAdtsTruncatedWithConstantBitrateSeeking()
             throws IOException, InterruptedException {
-        testExtractAsset("ts/sample_cbs_truncated.adts");
+        testExtractAsset(
+                "ts/sample_cbs_truncated.adts",
+                Collections.singletonMap(MediaParser.PARAMETER_ADTS_ENABLE_CBR_SEEKING, true));
     }
 
     @Test
@@ -405,7 +420,9 @@
 
     @Test
     public void testAdtsWithConstantBitrateSeeking() throws IOException, InterruptedException {
-        testExtractAsset("ts/sample_cbs.adts");
+        testExtractAsset(
+                "ts/sample_cbs.adts",
+                Collections.singletonMap(MediaParser.PARAMETER_ADTS_ENABLE_CBR_SEEKING, true));
     }
 
     // AC-3.
@@ -432,24 +449,54 @@
     // TS.
 
     @Test
-    public void testBigBuckBunnyTs() throws IOException, InterruptedException {
+    public void testTsBigBuckBunny() throws IOException, InterruptedException {
         testExtractAsset("ts/bbb_2500ms.ts");
     }
 
     @Test
-    public void testTransportStream() throws IOException, InterruptedException {
-        testExtractAsset("ts/sample.ts");
+    public void testTsWithH262MpegAudio() throws IOException, InterruptedException {
+        testExtractAsset("ts/sample_h262_mpeg_audio.ts");
     }
 
     @Test
-    public void testTransportStreamWithSdt() throws IOException, InterruptedException {
+    public void testTsWithH264MpegAudio() throws IOException, InterruptedException {
+        testExtractAsset("ts/sample_h264_mpeg_audio.ts");
+    }
+
+    @Test
+    public void testTsWithH264DetectAccessUnits() throws IOException, InterruptedException {
+        testExtractAsset(
+                "ts/sample_h264_no_access_unit_delimiters.ts",
+                Collections.singletonMap(MediaParser.PARAMETER_TS_DETECT_ACCESS_UNITS, true));
+    }
+
+    @Test
+    public void testTsWithH264DtsAudio() throws IOException, InterruptedException {
+        testExtractAsset(
+                "ts/sample_h264_dts_audio.ts",
+                Collections.singletonMap(
+                        MediaParser.PARAMETER_TS_ENABLE_HDMV_DTS_AUDIO_STREAMS, true));
+    }
+
+    @Test
+    public void testTsWithLatm() throws IOException, InterruptedException {
+        testExtractAsset("ts/sample_latm.ts");
+    }
+
+    @Test
+    public void testTsWithSdt() throws IOException, InterruptedException {
         testExtractAsset("ts/sample_with_sdt.ts");
     }
 
+    @Test
+    public void testTsWithH265() throws IOException, InterruptedException {
+        testExtractAsset("ts/sample_h265.ts");
+    }
+
     // MKV.
 
     @Test
-    public void testSubsampleEncryptedNoAltref() throws IOException, InterruptedException {
+    public void testMatroskaSubsampleEncryptedNoAltref() throws IOException, InterruptedException {
         testExtractAsset("mkv/subsample_encrypted_noaltref.webm");
     }
 
@@ -459,29 +506,29 @@
     }
 
     @Test
-    public void testFullBlocks() throws IOException, InterruptedException {
+    public void testMatroskaFullBlocks() throws IOException, InterruptedException {
         testExtractAsset("mkv/full_blocks.mkv");
     }
 
     @Test
-    public void testSubsampleEncryptedAltref() throws IOException, InterruptedException {
+    public void testMatroskaSubsampleEncryptedAltref() throws IOException, InterruptedException {
         testExtractAsset("mkv/subsample_encrypted_altref.webm");
     }
 
     // MP4.
 
     @Test
-    public void testAc4Fragmented() throws IOException, InterruptedException {
+    public void testMp4Ac4Fragmented() throws IOException, InterruptedException {
         testExtractAsset("mp4/sample_ac4_fragmented.mp4");
     }
 
     @Test
-    public void testAndrdoidSlowMotion() throws IOException, InterruptedException {
+    public void testMp4AndrdoidSlowMotion() throws IOException, InterruptedException {
         testExtractAsset("mp4/sample_android_slow_motion.mp4");
     }
 
     @Test
-    public void testFragmentedSei() throws IOException, InterruptedException {
+    public void testMp4FragmentedSei() throws IOException, InterruptedException {
         testExtractAsset("mp4/sample_fragmented_sei.mp4");
     }
 
@@ -491,12 +538,12 @@
     }
 
     @Test
-    public void testFragmentedSeekable() throws IOException, InterruptedException {
+    public void testMp4FragmentedSeekable() throws IOException, InterruptedException {
         testExtractAsset("mp4/sample_fragmented_seekable.mp4");
     }
 
     @Test
-    public void testAc4Protected() throws IOException, InterruptedException {
+    public void testMp4WithProtectedAc4() throws IOException, InterruptedException {
         testExtractAsset("mp4/sample_ac4_protected.mp4");
     }
 
@@ -506,15 +553,17 @@
     }
 
     @Test
-    public void testMdatTooLong() throws IOException, InterruptedException {
+    public void testMp4MdatTooLong() throws IOException, InterruptedException {
         testExtractAsset("mp4/sample_mdat_too_long.mp4");
     }
 
     @Test
-    public void testFragmented() throws IOException, InterruptedException {
+    public void testMp4Fragmented() throws IOException, InterruptedException {
         testExtractAsset("mp4/sample_fragmented.mp4");
     }
 
+    // Internal methods.
+
     private static void testCreationByName(String name) {
         MediaParser.createByName(name, new MockMediaParserOutputConsumer(new FakeExtractorOutput()))
                 .release();
@@ -549,15 +598,21 @@
 
     private static void testSniffAsset(String assetPath, String expectedParserName)
             throws IOException, InterruptedException {
-        extractAsset(assetPath, expectedParserName);
+        extractAsset(assetPath, Collections.emptyMap(), expectedParserName);
     }
 
     private static void testExtractAsset(String assetPath)
             throws IOException, InterruptedException {
-        extractAsset(assetPath, /* expectedParserName= */ null);
+        testExtractAsset(assetPath, Collections.emptyMap());
     }
 
-    private static void extractAsset(String assetPath, String expectedParserName)
+    private static void testExtractAsset(String assetPath, Map<String, Object> parameters)
+            throws IOException, InterruptedException {
+        extractAsset(assetPath, parameters, /* expectedParserName= */ null);
+    }
+
+    private static void extractAsset(
+            String assetPath, Map<String, Object> parameters, String expectedParserName)
             throws IOException, InterruptedException {
         byte[] assetBytes =
                 TestUtil.getByteArray(
@@ -568,6 +623,9 @@
         MockMediaParserOutputConsumer outputConsumer =
                 new MockMediaParserOutputConsumer(new FakeExtractorOutput());
         MediaParser mediaParser = MediaParser.create(outputConsumer);
+        for (Map.Entry<String, Object> entry : parameters.entrySet()) {
+            mediaParser.setParameter(entry.getKey(), entry.getValue());
+        }
 
         mediaParser.advance(mockInput);
         if (expectedParserName != null) {
@@ -607,6 +665,8 @@
         mediaParser.release();
     }
 
+    // Internal classes.
+
     private static FluentMediaParserSubject assertParsers(String... names) {
         return new FluentMediaParserSubject(names);
     }
diff --git a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java
index a20e327..b87d825 100644
--- a/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java
+++ b/tests/tests/mediaparser/src/android/media/mediaparser/cts/MockMediaParserOutputConsumer.java
@@ -21,19 +21,19 @@
 import android.media.MediaParser;
 import android.util.Pair;
 
+import androidx.annotation.Nullable;
+
 import com.google.android.exoplayer2.C;
 import com.google.android.exoplayer2.Format;
 import com.google.android.exoplayer2.drm.DrmInitData;
-import com.google.android.exoplayer2.extractor.ExtractorInput;
 import com.google.android.exoplayer2.extractor.SeekMap;
 import com.google.android.exoplayer2.extractor.SeekPoint;
 import com.google.android.exoplayer2.extractor.TrackOutput;
 import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
-import com.google.android.exoplayer2.util.MimeTypes;
+import com.google.android.exoplayer2.upstream.DataReader;
 import com.google.android.exoplayer2.video.ColorInfo;
 
 import java.io.IOException;
-import java.io.InterruptedIOException;
 import java.util.ArrayList;
 
 public class MockMediaParserOutputConsumer implements MediaParser.OutputConsumer {
@@ -92,18 +92,10 @@
     @Override
     public void onSampleDataFound(int trackIndex, MediaParser.InputReader inputReader)
             throws IOException {
-        try {
-            mFakeExtractorOutput
-                    .track(trackIndex, C.TRACK_TYPE_UNKNOWN)
-                    .sampleData(
-                            new ExtractorInputAdapter(inputReader),
-                            (int) inputReader.getLength(),
-                            false);
-        } catch (InterruptedException e) {
-            // TODO: Remove this exception replacement once we update the ExoPlayer
-            // version.
-            throw new InterruptedIOException();
-        }
+        mFakeExtractorOutput
+                .track(trackIndex, C.TRACK_TYPE_UNKNOWN)
+                .sampleData(
+                        new DataReaderAdapter(inputReader), (int) inputReader.getLength(), false);
     }
 
     @Override
@@ -174,7 +166,9 @@
                         ? Format.NO_VALUE
                         : pixelAspectWidth / pixelAspectHeight;
         ColorInfo colorInfo = getExoPlayerColorInfo(mediaFormat);
-        DrmInitData drmInitData = getExoPlayerDrmInitData(trackData.drmInitData);
+        DrmInitData drmInitData =
+                getExoPlayerDrmInitData(
+                        mediaFormat.getString("crypto-mode-fourcc"), trackData.drmInitData);
 
         int selectionFlags =
                 mediaFormat.getInteger(MediaFormat.KEY_IS_AUTOSELECT, /* defaultValue= */ 0) != 0
@@ -191,60 +185,55 @@
                         : 0;
 
         String language = mediaFormat.getString(MediaFormat.KEY_LANGUAGE, /* defaultValue= */ null);
+        int channels =
+                mediaFormat.getInteger(
+                        MediaFormat.KEY_CHANNEL_COUNT, /* defaultValue= */ Format.NO_VALUE);
+        int sampleRate =
+                mediaFormat.getInteger(
+                        MediaFormat.KEY_SAMPLE_RATE, /* defaultValue= */ Format.NO_VALUE);
+        int accessibilityChannel =
+                mediaFormat.getInteger(
+                        MediaFormat.KEY_CAPTION_SERVICE_NUMBER,
+                        /* defaultValue= */ Format.NO_VALUE);
 
-        // TODO: Replace this with Format.Builder once available.
-        if (MimeTypes.isVideo(sampleMimeType)) {
-            return Format.createVideoSampleFormat(
-                    id,
-                    sampleMimeType,
-                    codecs,
-                    bitrate,
-                    maxInputSize,
-                    width,
-                    height,
-                    frameRate,
-                    initData,
-                    rotationDegrees,
-                    pixelAspectRatio,
-                    /* projectionData= */ null,
-                    /* stereoMode= */ Format.NO_VALUE,
-                    colorInfo,
-                    drmInitData);
-        } else if (MimeTypes.isAudio(sampleMimeType)) {
-            int channels = mediaFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
-            int sampleRate = mediaFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
-            return Format.createAudioContainerFormat(
-                    id,
-                    /* label= */ null,
-                    /* containerMimeType= */ null,
-                    sampleMimeType,
-                    codecs,
-                    /* metadata= */ null,
-                    bitrate,
-                    channels,
-                    sampleRate,
-                    initData,
-                    selectionFlags,
-                    /* roleFlags= */ 0,
-                    language);
-        } else { // Application or Text.
-            return Format.createTextSampleFormat(
-                    id,
-                    sampleMimeType,
-                    codecs,
-                    bitrate,
-                    selectionFlags,
-                    language,
-                    /* accessibilityChannel= */ 0, // TODO: Add once ag/9864463 is submitted.
-                    /* drmInitData= */ drmInitData,
-                    /* subsampleOffsetUs= */ Format.OFFSET_SAMPLE_RELATIVE,
-                    initData);
-        }
+        return new Format.Builder()
+                .setId(id)
+                .setSampleMimeType(sampleMimeType)
+                .setCodecs(codecs)
+                .setPeakBitrate(bitrate)
+                .setMaxInputSize(maxInputSize)
+                .setWidth(width)
+                .setHeight(height)
+                .setFrameRate(frameRate)
+                .setInitializationData(initData)
+                .setRotationDegrees(rotationDegrees)
+                .setPixelWidthHeightRatio(pixelAspectRatio)
+                .setColorInfo(colorInfo)
+                .setDrmInitData(drmInitData)
+                .setChannelCount(channels)
+                .setSampleRate(sampleRate)
+                .setSelectionFlags(selectionFlags)
+                .setLanguage(language)
+                .setAccessibilityChannel(accessibilityChannel)
+                .build();
     }
 
-    private static DrmInitData getExoPlayerDrmInitData(android.media.DrmInitData drmInitData) {
-        // TODO: Implement once ag/10253368 is resolved.
-        return null;
+    @Nullable
+    private static DrmInitData getExoPlayerDrmInitData(
+            @Nullable String encryptionScheme, @Nullable android.media.DrmInitData drmInitData) {
+        if (drmInitData == null) {
+            return null;
+        }
+        DrmInitData.SchemeData[] schemeDatas =
+                new DrmInitData.SchemeData[drmInitData.getSchemeInitDataCount()];
+        for (int i = 0; i < schemeDatas.length; i++) {
+            android.media.DrmInitData.SchemeInitData schemeInitData =
+                    drmInitData.getSchemeInitDataAt(i);
+            schemeDatas[i] =
+                    new DrmInitData.SchemeData(
+                            schemeInitData.uuid, schemeInitData.mimeType, schemeInitData.data);
+        }
+        return new DrmInitData(encryptionScheme, schemeDatas);
     }
 
     private static ColorInfo getExoPlayerColorInfo(MediaFormat mediaFormat) {
@@ -321,100 +310,17 @@
 
     // Internal classes.
 
-    private class ExtractorInputAdapter implements ExtractorInput {
+    private static class DataReaderAdapter implements DataReader {
 
         private final MediaParser.InputReader mInputReader;
 
-        private ExtractorInputAdapter(MediaParser.InputReader inputReader) {
+        private DataReaderAdapter(MediaParser.InputReader inputReader) {
             mInputReader = inputReader;
         }
 
         @Override
-        public int read(byte[] target, int offset, int length)
-                throws IOException, InterruptedException {
+        public int read(byte[] target, int offset, int length) throws IOException {
             return mInputReader.read(target, offset, length);
         }
-
-        @Override
-        public boolean readFully(byte[] target, int offset, int length, boolean allowEndOfInput)
-                throws IOException, InterruptedException {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void readFully(byte[] target, int offset, int length)
-                throws IOException, InterruptedException {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public int skip(int length) throws IOException, InterruptedException {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public boolean skipFully(int length, boolean allowEndOfInput)
-                throws IOException, InterruptedException {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void skipFully(int length) throws IOException, InterruptedException {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public int peek(byte[] target, int offset, int length)
-                throws IOException, InterruptedException {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public boolean peekFully(byte[] target, int offset, int length, boolean allowEndOfInput)
-                throws IOException, InterruptedException {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void peekFully(byte[] target, int offset, int length)
-                throws IOException, InterruptedException {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public boolean advancePeekPosition(int length, boolean allowEndOfInput)
-                throws IOException, InterruptedException {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void advancePeekPosition(int length) throws IOException, InterruptedException {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public void resetPeekPosition() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public long getPeekPosition() {
-            throw new UnsupportedOperationException();
-        }
-
-        @Override
-        public long getPosition() {
-            return mInputReader.getPosition();
-        }
-
-        @Override
-        public long getLength() {
-            return mInputReader.getLength();
-        }
-
-        @Override
-        public <E extends Throwable> void setRetryPosition(long position, E e) throws E {
-            throw new UnsupportedOperationException();
-        }
     }
 }
diff --git a/tests/tests/mediastress/Android.bp b/tests/tests/mediastress/Android.bp
index d3dff75..689fec0 100644
--- a/tests/tests/mediastress/Android.bp
+++ b/tests/tests/mediastress/Android.bp
@@ -40,4 +40,5 @@
     host_required: ["cts-dynamic-config"],
     sdk_version: "test_current",
     min_sdk_version: "29",
+    required: ["CtsMediaPreparerApp"],
 }
diff --git a/tests/tests/net/AndroidManifest.xml b/tests/tests/net/AndroidManifest.xml
index c2b3bf7..baf914f 100644
--- a/tests/tests/net/AndroidManifest.xml
+++ b/tests/tests/net/AndroidManifest.xml
@@ -35,6 +35,11 @@
     <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
     <uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
 
+    <!-- This test also uses signature permissions through adopting the shell identity.
+         The permissions acquired that way include (probably not exhaustive) :
+             android.permission.MANAGE_TEST_NETWORKS
+    -->
+
     <application android:usesCleartextTraffic="true">
         <uses-library android:name="android.test.runner" />
         <uses-library android:name="org.apache.http.legacy" android:required="false" />
diff --git a/tests/tests/net/ipsec/Android.bp b/tests/tests/net/ipsec/Android.bp
new file mode 100644
index 0000000..86969c3
--- /dev/null
+++ b/tests/tests/net/ipsec/Android.bp
@@ -0,0 +1,46 @@
+// 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: "CtsIkeTestCases",
+    defaults: ["cts_defaults"],
+
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
+
+    libs: [
+        "android.net.ipsec.ike.stubs.system",
+        "android.test.base.stubs",
+    ],
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    static_libs: [
+        "androidx.test.ext.junit",
+        "compatibility-device-util-axt",
+        "ctstestrunner-axt",
+    ],
+
+    platform_apis: true,
+
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "mts",
+        "vts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/net/ipsec/AndroidManifest.xml b/tests/tests/net/ipsec/AndroidManifest.xml
new file mode 100644
index 0000000..de7d23c
--- /dev/null
+++ b/tests/tests/net/ipsec/AndroidManifest.xml
@@ -0,0 +1,39 @@
+<?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.net.ipsec.cts"
+    android:targetSandboxVersion="2">
+
+    <!--Allow tests to call ConnectivityManager#getActiveNetwork()-->
+    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
+    <!--Allow tests to create socket -->
+    <uses-permission android:name="android.permission.INTERNET"/>
+
+    <application android:label="CtsIkeTestCases">
+        <uses-library android:name="android.test.runner" />
+        <uses-library android:name="android.net.ipsec.ike" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="android.net.ipsec.cts"
+                     android:label="CTS tests of android.net.ipsec">
+        <meta-data android:name="listener"
+            android:value="com.android.cts.runner.CtsTestRunListener" />
+    </instrumentation>
+
+</manifest>
diff --git a/tests/tests/net/ipsec/AndroidTest.xml b/tests/tests/net/ipsec/AndroidTest.xml
new file mode 100644
index 0000000..09e5c93
--- /dev/null
+++ b/tests/tests/net/ipsec/AndroidTest.xml
@@ -0,0 +1,30 @@
+<!-- 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 CTS IKE test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="networking" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="not-shardable" value="true" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="CtsIkeTestCases.apk" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="android.net.ipsec.cts" />
+        <option name="hidden-api-checks" value="false" />
+    </test>
+</configuration>
diff --git a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/ChildSessionParamsTest.java b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/ChildSessionParamsTest.java
new file mode 100644
index 0000000..7fb1b6d
--- /dev/null
+++ b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/ChildSessionParamsTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.ipsec.ike.cts;
+
+import static android.system.OsConstants.AF_INET;
+import static android.system.OsConstants.AF_INET6;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.LinkAddress;
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.ChildSessionParams;
+import android.net.ipsec.ike.TransportModeChildSessionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Address;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DhcpServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4DnsServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv4Netmask;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6Address;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.ConfigRequestIpv6DnsServer;
+import android.net.ipsec.ike.TunnelModeChildSessionParams.TunnelModeChildConfigRequest;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+public class ChildSessionParamsTest extends IkeTestBase {
+    private static final int HARD_LIFETIME_SECONDS = (int) TimeUnit.HOURS.toSeconds(3L);
+    private static final int SOFT_LIFETIME_SECONDS = (int) TimeUnit.HOURS.toSeconds(1L);
+
+    // Random proposal. Content doesn't matter
+    private final ChildSaProposal mSaProposal =
+            SaProposalTest.buildChildSaProposalWithCombinedModeCipher();
+
+    private void verifyTunnelModeChildParamsWithDefaultValues(ChildSessionParams childParams) {
+        assertTrue(childParams instanceof TunnelModeChildSessionParams);
+        verifyChildParamsWithDefaultValues(childParams);
+    }
+
+    private void verifyTunnelModeChildParamsWithCustomizedValues(ChildSessionParams childParams) {
+        assertTrue(childParams instanceof TunnelModeChildSessionParams);
+        verifyChildParamsWithCustomizedValues(childParams);
+    }
+
+    private void verifyTransportModeChildParamsWithDefaultValues(ChildSessionParams childParams) {
+        assertTrue(childParams instanceof TransportModeChildSessionParams);
+        verifyChildParamsWithDefaultValues(childParams);
+    }
+
+    private void verifyTransportModeChildParamsWithCustomizedValues(
+            ChildSessionParams childParams) {
+        assertTrue(childParams instanceof TransportModeChildSessionParams);
+        verifyChildParamsWithCustomizedValues(childParams);
+    }
+
+    private void verifyChildParamsWithDefaultValues(ChildSessionParams childParams) {
+        assertEquals(Arrays.asList(mSaProposal), childParams.getSaProposals());
+
+        // Do not do assertEquals to the default values to be avoid being a change-detector test
+        assertTrue(childParams.getHardLifetimeSeconds() > childParams.getSoftLifetimeSeconds());
+        assertTrue(childParams.getSoftLifetimeSeconds() > 0);
+
+        assertEquals(
+                Arrays.asList(DEFAULT_V4_TS, DEFAULT_V6_TS),
+                childParams.getInboundTrafficSelectors());
+        assertEquals(
+                Arrays.asList(DEFAULT_V4_TS, DEFAULT_V6_TS),
+                childParams.getOutboundTrafficSelectors());
+    }
+
+    private void verifyChildParamsWithCustomizedValues(ChildSessionParams childParams) {
+        assertEquals(Arrays.asList(mSaProposal), childParams.getSaProposals());
+
+        assertEquals(HARD_LIFETIME_SECONDS, childParams.getHardLifetimeSeconds());
+        assertEquals(SOFT_LIFETIME_SECONDS, childParams.getSoftLifetimeSeconds());
+
+        assertEquals(
+                Arrays.asList(INBOUND_V4_TS, INBOUND_V6_TS),
+                childParams.getInboundTrafficSelectors());
+        assertEquals(
+                Arrays.asList(OUTBOUND_V4_TS, OUTBOUND_V6_TS),
+                childParams.getOutboundTrafficSelectors());
+    }
+
+    @Test
+    public void testBuildTransportModeParamsWithDefaultValues() {
+        TransportModeChildSessionParams childParams =
+                new TransportModeChildSessionParams.Builder().addSaProposal(mSaProposal).build();
+
+        verifyTransportModeChildParamsWithDefaultValues(childParams);
+    }
+
+    @Test
+    public void testBuildTunnelModeParamsWithDefaultValues() {
+        TunnelModeChildSessionParams childParams =
+                new TunnelModeChildSessionParams.Builder().addSaProposal(mSaProposal).build();
+
+        verifyTunnelModeChildParamsWithDefaultValues(childParams);
+        assertTrue(childParams.getConfigurationRequests().isEmpty());
+    }
+
+    @Test
+    public void testBuildTransportModeParamsWithCustomizedValues() {
+        TransportModeChildSessionParams childParams =
+                new TransportModeChildSessionParams.Builder()
+                        .addSaProposal(mSaProposal)
+                        .setLifetimeSeconds(HARD_LIFETIME_SECONDS, SOFT_LIFETIME_SECONDS)
+                        .addInboundTrafficSelectors(INBOUND_V4_TS)
+                        .addInboundTrafficSelectors(INBOUND_V6_TS)
+                        .addOutboundTrafficSelectors(OUTBOUND_V4_TS)
+                        .addOutboundTrafficSelectors(OUTBOUND_V6_TS)
+                        .build();
+
+        verifyTransportModeChildParamsWithCustomizedValues(childParams);
+    }
+
+    @Test
+    public void testBuildTunnelModeParamsWithCustomizedValues() {
+        TunnelModeChildSessionParams childParams =
+                new TunnelModeChildSessionParams.Builder()
+                        .addSaProposal(mSaProposal)
+                        .setLifetimeSeconds(HARD_LIFETIME_SECONDS, SOFT_LIFETIME_SECONDS)
+                        .addInboundTrafficSelectors(INBOUND_V4_TS)
+                        .addInboundTrafficSelectors(INBOUND_V6_TS)
+                        .addOutboundTrafficSelectors(OUTBOUND_V4_TS)
+                        .addOutboundTrafficSelectors(OUTBOUND_V6_TS)
+                        .build();
+
+        verifyTunnelModeChildParamsWithCustomizedValues(childParams);
+    }
+
+    @Test
+    public void testBuildChildSessionParamsWithConfigReq() {
+        TunnelModeChildSessionParams childParams =
+                new TunnelModeChildSessionParams.Builder()
+                        .addSaProposal(mSaProposal)
+                        .addInternalAddressRequest(AF_INET)
+                        .addInternalAddressRequest(AF_INET6)
+                        .addInternalAddressRequest(AF_INET6)
+                        .addInternalAddressRequest(IPV4_ADDRESS_REMOTE)
+                        .addInternalAddressRequest(IPV6_ADDRESS_REMOTE, IP6_PREFIX_LEN)
+                        .addInternalDnsServerRequest(AF_INET)
+                        .addInternalDnsServerRequest(AF_INET6)
+                        .addInternalDhcpServerRequest(AF_INET)
+                        .addInternalDhcpServerRequest(AF_INET)
+                        .build();
+
+        verifyTunnelModeChildParamsWithDefaultValues(childParams);
+
+        // Verify config request types and number of requests for each type
+        Map<Class<? extends TunnelModeChildConfigRequest>, Integer> expectedAttributeCounts =
+                new HashMap<>();
+        expectedAttributeCounts.put(ConfigRequestIpv4Address.class, 2);
+        expectedAttributeCounts.put(ConfigRequestIpv6Address.class, 3);
+        expectedAttributeCounts.put(ConfigRequestIpv4Netmask.class, 1);
+        expectedAttributeCounts.put(ConfigRequestIpv4DnsServer.class, 1);
+        expectedAttributeCounts.put(ConfigRequestIpv6DnsServer.class, 1);
+        expectedAttributeCounts.put(ConfigRequestIpv4DhcpServer.class, 2);
+        verifyConfigRequestTypes(expectedAttributeCounts, childParams.getConfigurationRequests());
+
+        // Verify specific IPv4 address request
+        Set<Inet4Address> expectedV4Addresses = new HashSet<>();
+        expectedV4Addresses.add(IPV4_ADDRESS_REMOTE);
+        verifySpecificV4AddrConfigReq(expectedV4Addresses, childParams);
+
+        // Verify specific IPv6 address request
+        Set<LinkAddress> expectedV6Addresses = new HashSet<>();
+        expectedV6Addresses.add(new LinkAddress(IPV6_ADDRESS_REMOTE, IP6_PREFIX_LEN));
+        verifySpecificV6AddrConfigReq(expectedV6Addresses, childParams);
+    }
+
+    protected void verifySpecificV4AddrConfigReq(
+            Set<Inet4Address> expectedAddresses, TunnelModeChildSessionParams childParams) {
+        for (TunnelModeChildConfigRequest req : childParams.getConfigurationRequests()) {
+            if (req instanceof ConfigRequestIpv4Address
+                    && ((ConfigRequestIpv4Address) req).getAddress() != null) {
+                Inet4Address address = ((ConfigRequestIpv4Address) req).getAddress();
+
+                // Fail if expectedAddresses does not contain this address
+                assertTrue(expectedAddresses.remove(address));
+            }
+        }
+
+        // Fail if any expected address is not found in result
+        assertTrue(expectedAddresses.isEmpty());
+    }
+
+    protected void verifySpecificV6AddrConfigReq(
+            Set<LinkAddress> expectedAddresses, TunnelModeChildSessionParams childParams) {
+        for (TunnelModeChildConfigRequest req : childParams.getConfigurationRequests()) {
+            if (req instanceof ConfigRequestIpv6Address
+                    && ((ConfigRequestIpv6Address) req).getAddress() != null) {
+                ConfigRequestIpv6Address ipv6AddrReq = (ConfigRequestIpv6Address) req;
+
+                // Fail if expectedAddresses does not contain this address
+                LinkAddress address =
+                        new LinkAddress(ipv6AddrReq.getAddress(), ipv6AddrReq.getPrefixLength());
+                assertTrue(expectedAddresses.remove(address));
+            }
+        }
+
+        // Fail if any expected address is not found in result
+        assertTrue(expectedAddresses.isEmpty());
+    }
+}
diff --git a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java
new file mode 100644
index 0000000..d3aa8d0
--- /dev/null
+++ b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/IkeTestBase.java
@@ -0,0 +1,120 @@
+/*
+ * 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.ipsec.ike.cts;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import android.net.InetAddresses;
+import android.net.ipsec.ike.IkeTrafficSelector;
+
+import java.net.Inet4Address;
+import java.net.Inet6Address;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Shared parameters and util methods for testing different components of IKE */
+abstract class IkeTestBase {
+    private static final int MIN_PORT = 0;
+    private static final int MAX_PORT = 65535;
+    private static final int INBOUND_TS_START_PORT = MIN_PORT;
+    private static final int INBOUND_TS_END_PORT = 65520;
+    private static final int OUTBOUND_TS_START_PORT = 16;
+    private static final int OUTBOUND_TS_END_PORT = MAX_PORT;
+
+    static final int IP4_PREFIX_LEN = 32;
+    static final int IP6_PREFIX_LEN = 64;
+
+    static final Inet4Address IPV4_ADDRESS_LOCAL =
+            (Inet4Address) (InetAddresses.parseNumericAddress("192.0.2.100"));
+    static final Inet4Address IPV4_ADDRESS_REMOTE =
+            (Inet4Address) (InetAddresses.parseNumericAddress("198.51.100.100"));
+    static final Inet6Address IPV6_ADDRESS_LOCAL =
+            (Inet6Address) (InetAddresses.parseNumericAddress("2001:db8::100"));
+    static final Inet6Address IPV6_ADDRESS_REMOTE =
+            (Inet6Address) (InetAddresses.parseNumericAddress("2001:db8:255::100"));
+
+    static final IkeTrafficSelector DEFAULT_V4_TS =
+            new IkeTrafficSelector(
+                    MIN_PORT,
+                    MAX_PORT,
+                    InetAddresses.parseNumericAddress("0.0.0.0"),
+                    InetAddresses.parseNumericAddress("255.255.255.255"));
+    static final IkeTrafficSelector DEFAULT_V6_TS =
+            new IkeTrafficSelector(
+                    MIN_PORT,
+                    MAX_PORT,
+                    InetAddresses.parseNumericAddress("::"),
+                    InetAddresses.parseNumericAddress("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"));
+    static final IkeTrafficSelector INBOUND_V4_TS =
+            new IkeTrafficSelector(
+                    INBOUND_TS_START_PORT,
+                    INBOUND_TS_END_PORT,
+                    InetAddresses.parseNumericAddress("192.0.2.10"),
+                    InetAddresses.parseNumericAddress("192.0.2.20"));
+    static final IkeTrafficSelector OUTBOUND_V4_TS =
+            new IkeTrafficSelector(
+                    OUTBOUND_TS_START_PORT,
+                    OUTBOUND_TS_END_PORT,
+                    InetAddresses.parseNumericAddress("198.51.100.0"),
+                    InetAddresses.parseNumericAddress("198.51.100.255"));
+
+    static final IkeTrafficSelector INBOUND_V6_TS =
+            new IkeTrafficSelector(
+                    INBOUND_TS_START_PORT,
+                    INBOUND_TS_END_PORT,
+                    InetAddresses.parseNumericAddress("2001:db8::10"),
+                    InetAddresses.parseNumericAddress("2001:db8::128"));
+    static final IkeTrafficSelector OUTBOUND_V6_TS =
+            new IkeTrafficSelector(
+                    OUTBOUND_TS_START_PORT,
+                    OUTBOUND_TS_END_PORT,
+                    InetAddresses.parseNumericAddress("2001:db8:255::64"),
+                    InetAddresses.parseNumericAddress("2001:db8:255::255"));
+
+    // Verify Config requests in TunnelModeChildSessionParams and IkeSessionParams
+    <T> void verifyConfigRequestTypes(
+            Map<Class<? extends T>, Integer> expectedReqCntMap, List<? extends T> resultReqList) {
+        Map<Class<? extends T>, Integer> resultReqCntMap = new HashMap<>();
+
+        // Verify that every config request type in resultReqList is expected, and build
+        // resultReqCntMap at the same time
+        for (T resultReq : resultReqList) {
+            boolean isResultReqExpected = false;
+
+            for (Class<? extends T> expectedReqInterface : expectedReqCntMap.keySet()) {
+                if (expectedReqInterface.isInstance(resultReq)) {
+                    isResultReqExpected = true;
+
+                    resultReqCntMap.put(
+                            expectedReqInterface,
+                            resultReqCntMap.getOrDefault(expectedReqInterface, 0) + 1);
+                }
+            }
+
+            if (!isResultReqExpected) {
+                fail("Failed due to unexpected config request " + resultReq);
+            }
+        }
+
+        assertEquals(expectedReqCntMap, resultReqCntMap);
+
+        // TODO: Think of a neat way to validate both counts and values in this method. Probably can
+        // build Runnables as validators for count and values.
+    }
+}
diff --git a/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/SaProposalTest.java b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/SaProposalTest.java
new file mode 100644
index 0000000..e0d3be0
--- /dev/null
+++ b/tests/tests/net/ipsec/src/android/net/ipsec/ike/cts/SaProposalTest.java
@@ -0,0 +1,256 @@
+/*
+ * 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.ipsec.ike.cts;
+
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_1024_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_2048_BIT_MODP;
+import static android.net.ipsec.ike.SaProposal.DH_GROUP_NONE;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_3DES;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_CBC;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_12;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_16;
+import static android.net.ipsec.ike.SaProposal.ENCRYPTION_ALGORITHM_AES_GCM_8;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_AES_XCBC_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA1_96;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_256_128;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_384_192;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_HMAC_SHA2_512_256;
+import static android.net.ipsec.ike.SaProposal.INTEGRITY_ALGORITHM_NONE;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_128;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_192;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_AES_256;
+import static android.net.ipsec.ike.SaProposal.KEY_LEN_UNUSED;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_AES128_XCBC;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_HMAC_SHA1;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_SHA2_256;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_SHA2_384;
+import static android.net.ipsec.ike.SaProposal.PSEUDORANDOM_FUNCTION_SHA2_512;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.net.ipsec.ike.ChildSaProposal;
+import android.net.ipsec.ike.IkeSaProposal;
+import android.util.Pair;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+public class SaProposalTest {
+    private static final List<Pair<Integer, Integer>> NORMAL_MODE_CIPHERS = new ArrayList<>();
+    private static final List<Pair<Integer, Integer>> COMBINED_MODE_CIPHERS = new ArrayList<>();
+    private static final List<Integer> INTEGRITY_ALGOS = new ArrayList<>();
+    private static final List<Integer> DH_GROUPS = new ArrayList<>();
+    private static final List<Integer> DH_GROUPS_WITH_NONE = new ArrayList<>();
+    private static final List<Integer> PRFS = new ArrayList<>();
+
+    static {
+        NORMAL_MODE_CIPHERS.add(new Pair<>(ENCRYPTION_ALGORITHM_3DES, KEY_LEN_UNUSED));
+        NORMAL_MODE_CIPHERS.add(new Pair<>(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_128));
+        NORMAL_MODE_CIPHERS.add(new Pair<>(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_192));
+        NORMAL_MODE_CIPHERS.add(new Pair<>(ENCRYPTION_ALGORITHM_AES_CBC, KEY_LEN_AES_256));
+
+        COMBINED_MODE_CIPHERS.add(new Pair<>(ENCRYPTION_ALGORITHM_AES_GCM_8, KEY_LEN_AES_128));
+        COMBINED_MODE_CIPHERS.add(new Pair<>(ENCRYPTION_ALGORITHM_AES_GCM_12, KEY_LEN_AES_192));
+        COMBINED_MODE_CIPHERS.add(new Pair<>(ENCRYPTION_ALGORITHM_AES_GCM_16, KEY_LEN_AES_256));
+
+        INTEGRITY_ALGOS.add(INTEGRITY_ALGORITHM_HMAC_SHA1_96);
+        INTEGRITY_ALGOS.add(INTEGRITY_ALGORITHM_AES_XCBC_96);
+        INTEGRITY_ALGOS.add(INTEGRITY_ALGORITHM_HMAC_SHA2_256_128);
+        INTEGRITY_ALGOS.add(INTEGRITY_ALGORITHM_HMAC_SHA2_384_192);
+        INTEGRITY_ALGOS.add(INTEGRITY_ALGORITHM_HMAC_SHA2_512_256);
+
+        DH_GROUPS.add(DH_GROUP_1024_BIT_MODP);
+        DH_GROUPS.add(DH_GROUP_2048_BIT_MODP);
+
+        DH_GROUPS_WITH_NONE.add(DH_GROUP_NONE);
+        DH_GROUPS_WITH_NONE.addAll(DH_GROUPS);
+
+        PRFS.add(PSEUDORANDOM_FUNCTION_HMAC_SHA1);
+        PRFS.add(PSEUDORANDOM_FUNCTION_AES128_XCBC);
+        PRFS.add(PSEUDORANDOM_FUNCTION_SHA2_256);
+        PRFS.add(PSEUDORANDOM_FUNCTION_SHA2_384);
+        PRFS.add(PSEUDORANDOM_FUNCTION_SHA2_512);
+    }
+
+    // Package private
+    static IkeSaProposal buildIkeSaProposalWithNormalModeCipher() {
+        return buildIkeSaProposal(NORMAL_MODE_CIPHERS, INTEGRITY_ALGOS, PRFS, DH_GROUPS);
+    }
+
+    // Package private
+    static IkeSaProposal buildIkeSaProposalWithCombinedModeCipher() {
+        return buildIkeSaProposalWithCombinedModeCipher(true /* hasIntegrityNone */);
+    }
+
+    private static IkeSaProposal buildIkeSaProposalWithCombinedModeCipher(
+            boolean hasIntegrityNone) {
+        List<Integer> integerAlgos = new ArrayList<>();
+        if (hasIntegrityNone) {
+            integerAlgos.add(INTEGRITY_ALGORITHM_NONE);
+        }
+        return buildIkeSaProposal(COMBINED_MODE_CIPHERS, integerAlgos, PRFS, DH_GROUPS);
+    }
+
+    private static IkeSaProposal buildIkeSaProposal(
+            List<Pair<Integer, Integer>> ciphers,
+            List<Integer> integrityAlgos,
+            List<Integer> prfs,
+            List<Integer> dhGroups) {
+        IkeSaProposal.Builder builder = new IkeSaProposal.Builder();
+
+        for (Pair<Integer, Integer> pair : ciphers) {
+            builder.addEncryptionAlgorithm(pair.first, pair.second);
+        }
+        for (int algo : integrityAlgos) {
+            builder.addIntegrityAlgorithm(algo);
+        }
+        for (int algo : prfs) {
+            builder.addPseudorandomFunction(algo);
+        }
+        for (int algo : dhGroups) {
+            builder.addDhGroup(algo);
+        }
+
+        return builder.build();
+    }
+
+    // Package private
+    static ChildSaProposal buildChildSaProposalWithNormalModeCipher() {
+        return buildChildSaProposal(NORMAL_MODE_CIPHERS, INTEGRITY_ALGOS, DH_GROUPS_WITH_NONE);
+    }
+
+    // Package private
+    static ChildSaProposal buildChildSaProposalWithCombinedModeCipher() {
+        return buildChildSaProposalWithCombinedModeCipher(true /* hasIntegrityNone */);
+    }
+
+    private static ChildSaProposal buildChildSaProposalWithCombinedModeCipher(
+            boolean hasIntegrityNone) {
+        List<Integer> integerAlgos = new ArrayList<>();
+        if (hasIntegrityNone) {
+            integerAlgos.add(INTEGRITY_ALGORITHM_NONE);
+        }
+
+        return buildChildSaProposal(COMBINED_MODE_CIPHERS, integerAlgos, DH_GROUPS_WITH_NONE);
+    }
+
+    private static ChildSaProposal buildChildSaProposal(
+            List<Pair<Integer, Integer>> ciphers,
+            List<Integer> integrityAlgos,
+            List<Integer> dhGroups) {
+        ChildSaProposal.Builder builder = new ChildSaProposal.Builder();
+
+        for (Pair<Integer, Integer> pair : ciphers) {
+            builder.addEncryptionAlgorithm(pair.first, pair.second);
+        }
+        for (int algo : integrityAlgos) {
+            builder.addIntegrityAlgorithm(algo);
+        }
+        for (int algo : dhGroups) {
+            builder.addDhGroup(algo);
+        }
+
+        return builder.build();
+    }
+
+    // Package private
+    static ChildSaProposal buildChildSaProposalWithOnlyCiphers() {
+        return buildChildSaProposal(
+                COMBINED_MODE_CIPHERS, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
+    }
+
+    @Test
+    public void testBuildIkeSaProposalWithNormalModeCipher() {
+        IkeSaProposal saProposal = buildIkeSaProposalWithNormalModeCipher();
+
+        assertEquals(NORMAL_MODE_CIPHERS, saProposal.getEncryptionAlgorithms());
+        assertEquals(INTEGRITY_ALGOS, saProposal.getIntegrityAlgorithms());
+        assertEquals(PRFS, saProposal.getPseudorandomFunctions());
+        assertEquals(DH_GROUPS, saProposal.getDhGroups());
+    }
+
+    @Test
+    public void testBuildIkeSaProposalWithCombinedModeCipher() {
+        IkeSaProposal saProposal =
+                buildIkeSaProposalWithCombinedModeCipher(false /* hasIntegrityNone */);
+
+        assertEquals(COMBINED_MODE_CIPHERS, saProposal.getEncryptionAlgorithms());
+        assertEquals(PRFS, saProposal.getPseudorandomFunctions());
+        assertEquals(DH_GROUPS, saProposal.getDhGroups());
+        assertTrue(saProposal.getIntegrityAlgorithms().isEmpty());
+    }
+
+    @Test
+    public void testBuildIkeSaProposalWithCombinedModeCipherAndIntegrityNone() {
+        IkeSaProposal saProposal =
+                buildIkeSaProposalWithCombinedModeCipher(true /* hasIntegrityNone */);
+
+        assertEquals(COMBINED_MODE_CIPHERS, saProposal.getEncryptionAlgorithms());
+        assertEquals(PRFS, saProposal.getPseudorandomFunctions());
+        assertEquals(DH_GROUPS, saProposal.getDhGroups());
+        assertEquals(Arrays.asList(INTEGRITY_ALGORITHM_NONE), saProposal.getIntegrityAlgorithms());
+    }
+
+    @Test
+    public void testBuildChildSaProposalWithNormalModeCipher() {
+        ChildSaProposal saProposal = buildChildSaProposalWithNormalModeCipher();
+
+        assertEquals(NORMAL_MODE_CIPHERS, saProposal.getEncryptionAlgorithms());
+        assertEquals(INTEGRITY_ALGOS, saProposal.getIntegrityAlgorithms());
+        assertEquals(DH_GROUPS_WITH_NONE, saProposal.getDhGroups());
+    }
+
+    @Test
+    public void testBuildChildProposalWithCombinedModeCipher() {
+        ChildSaProposal saProposal =
+                buildChildSaProposalWithCombinedModeCipher(false /* hasIntegrityNone */);
+
+        assertEquals(COMBINED_MODE_CIPHERS, saProposal.getEncryptionAlgorithms());
+        assertTrue(saProposal.getIntegrityAlgorithms().isEmpty());
+        assertEquals(DH_GROUPS_WITH_NONE, saProposal.getDhGroups());
+    }
+
+    @Test
+    public void testBuildChildProposalWithCombinedModeCipherAndIntegrityNone() {
+        ChildSaProposal saProposal =
+                buildChildSaProposalWithCombinedModeCipher(true /* hasIntegrityNone */);
+
+        assertEquals(COMBINED_MODE_CIPHERS, saProposal.getEncryptionAlgorithms());
+        assertEquals(Arrays.asList(INTEGRITY_ALGORITHM_NONE), saProposal.getIntegrityAlgorithms());
+        assertEquals(DH_GROUPS_WITH_NONE, saProposal.getDhGroups());
+    }
+
+    @Test
+    public void testBuildChildSaProposalWithOnlyCiphers() {
+        ChildSaProposal saProposal = buildChildSaProposalWithOnlyCiphers();
+
+        assertEquals(COMBINED_MODE_CIPHERS, saProposal.getEncryptionAlgorithms());
+        assertTrue(saProposal.getIntegrityAlgorithms().isEmpty());
+        assertTrue(saProposal.getDhGroups().isEmpty());
+    }
+
+    // TODO(b/148689509): Test throwing exception when algorithm combination is invalid
+}
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
index fa7e138..1ee08ff 100644
--- a/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
+++ b/tests/tests/net/src/android/net/cts/ConnectivityManagerTest.java
@@ -639,11 +639,14 @@
         }
     }
 
-    private void waitForActiveNetworkMetered(boolean requestedMeteredness) throws Exception {
+    private void waitForActiveNetworkMetered(int targetTransportType, boolean requestedMeteredness)
+            throws Exception {
         final CountDownLatch latch = new CountDownLatch(1);
         final NetworkCallback networkCallback = new NetworkCallback() {
             @Override
             public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) {
+                if (!nc.hasTransport(targetTransportType)) return;
+
                 final boolean metered = !nc.hasCapability(NET_CAPABILITY_NOT_METERED);
                 if (metered == requestedMeteredness) {
                     latch.countDown();
@@ -709,7 +712,7 @@
     @AppModeFull(reason = "Cannot get WifiManager in instant app mode")
     public void testGetMultipathPreference() throws Exception {
         final ContentResolver resolver = mContext.getContentResolver();
-        final Network network = ensureWifiConnected();
+        ensureWifiConnected();
         final String ssid = unquoteSSID(mWifiManager.getConnectionInfo().getSSID());
         final String oldMeteredSetting = getWifiMeteredStatus(ssid);
         final String oldMeteredMultipathPreference = Settings.Global.getString(
@@ -720,7 +723,11 @@
             Settings.Global.putString(resolver, NETWORK_METERED_MULTIPATH_PREFERENCE,
                     Integer.toString(newMeteredPreference));
             setWifiMeteredStatus(ssid, "true");
-            waitForActiveNetworkMetered(true);
+            waitForActiveNetworkMetered(TRANSPORT_WIFI, true);
+            // Wifi meterness changes from unmetered to metered will disconnect and reconnect since
+            // R.
+            final Network network = ensureWifiConnected();
+            assertEquals(ssid, unquoteSSID(mWifiManager.getConnectionInfo().getSSID()));
             assertEquals(mCm.getNetworkCapabilities(network).hasCapability(
                     NET_CAPABILITY_NOT_METERED), false);
             assertMultipathPreferenceIsEventually(network, initialMeteredPreference,
@@ -736,7 +743,8 @@
                     oldMeteredPreference, newMeteredPreference);
 
             setWifiMeteredStatus(ssid, "false");
-            waitForActiveNetworkMetered(false);
+            // No disconnect from unmetered to metered.
+            waitForActiveNetworkMetered(TRANSPORT_WIFI, false);
             assertEquals(mCm.getNetworkCapabilities(network).hasCapability(
                     NET_CAPABILITY_NOT_METERED), true);
             assertMultipathPreferenceIsEventually(network, newMeteredPreference,
diff --git a/tests/tests/net/src/android/net/cts/DhcpInfoTest.java b/tests/tests/net/src/android/net/cts/DhcpInfoTest.java
index 085fdd91..b8d2392 100644
--- a/tests/tests/net/src/android/net/cts/DhcpInfoTest.java
+++ b/tests/tests/net/src/android/net/cts/DhcpInfoTest.java
@@ -16,48 +16,99 @@
 
 package android.net.cts;
 
+import static android.net.shared.Inet4AddressUtils.inet4AddressToIntHTL;
+
+import static com.android.testutils.MiscAssertsKt.assertFieldCountEquals;
+import static com.android.testutils.ParcelUtilsKt.parcelingRoundTrip;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.annotation.Nullable;
 import android.net.DhcpInfo;
-import android.test.AndroidTestCase;
 
-public class DhcpInfoTest extends AndroidTestCase {
+import androidx.test.runner.AndroidJUnit4;
 
-    public void testConstructor() {
-        new DhcpInfo();
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+
+@RunWith(AndroidJUnit4.class)
+public class DhcpInfoTest {
+    private static final String STR_ADDR1 = "255.255.255.255";
+    private static final String STR_ADDR2 = "127.0.0.1";
+    private static final String STR_ADDR3 = "192.168.1.1";
+    private static final String STR_ADDR4 = "192.168.1.0";
+    private static final int LEASE_TIME = 9999;
+
+    private int ipToInteger(String ipString) throws Exception {
+        return inet4AddressToIntHTL((Inet4Address) InetAddress.getByName(ipString));
     }
 
-    public void testToString() {
-        String expectedDefault = "ipaddr 0.0.0.0 gateway 0.0.0.0 netmask 0.0.0.0 dns1 0.0.0.0 "
-                + "dns2 0.0.0.0 DHCP server 0.0.0.0 lease 0 seconds";
-        String STR_ADDR1 = "255.255.255.255";
-        String STR_ADDR2 = "127.0.0.1";
-        String STR_ADDR3 = "192.168.1.1";
-        String STR_ADDR4 = "192.168.1.0";
-        int leaseTime = 9999;
-        String expected = "ipaddr " + STR_ADDR1 + " gateway " + STR_ADDR2 + " netmask "
-                + STR_ADDR3 + " dns1 " + STR_ADDR4 + " dns2 " + STR_ADDR4 + " DHCP server "
-                + STR_ADDR2 + " lease " + leaseTime + " seconds";
-
-        DhcpInfo dhcpInfo = new DhcpInfo();
-
-        // Test default string.
-        assertEquals(expectedDefault, dhcpInfo.toString());
-
+    private DhcpInfo createDhcpInfoObject() throws Exception {
+        final DhcpInfo dhcpInfo = new DhcpInfo();
         dhcpInfo.ipAddress = ipToInteger(STR_ADDR1);
         dhcpInfo.gateway = ipToInteger(STR_ADDR2);
         dhcpInfo.netmask = ipToInteger(STR_ADDR3);
         dhcpInfo.dns1 = ipToInteger(STR_ADDR4);
         dhcpInfo.dns2 = ipToInteger(STR_ADDR4);
         dhcpInfo.serverAddress = ipToInteger(STR_ADDR2);
-        dhcpInfo.leaseDuration = leaseTime;
+        dhcpInfo.leaseDuration = LEASE_TIME;
+        return dhcpInfo;
+    }
 
+    @Test
+    public void testConstructor() {
+        new DhcpInfo();
+    }
+
+    @Test
+    public void testToString() throws Exception {
+        final String expectedDefault = "ipaddr 0.0.0.0 gateway 0.0.0.0 netmask 0.0.0.0 "
+                + "dns1 0.0.0.0 dns2 0.0.0.0 DHCP server 0.0.0.0 lease 0 seconds";
+
+        DhcpInfo dhcpInfo = new DhcpInfo();
+
+        // Test default string.
+        assertEquals(expectedDefault, dhcpInfo.toString());
+
+        dhcpInfo = createDhcpInfoObject();
+
+        final String expected = "ipaddr " + STR_ADDR1 + " gateway " + STR_ADDR2 + " netmask "
+                + STR_ADDR3 + " dns1 " + STR_ADDR4 + " dns2 " + STR_ADDR4 + " DHCP server "
+                + STR_ADDR2 + " lease " + LEASE_TIME + " seconds";
         // Test with new values
         assertEquals(expected, dhcpInfo.toString());
     }
 
-    private int ipToInteger(String ipString) {
-        String ipSegs[] = ipString.split("[.]");
-        int tmp = Integer.parseInt(ipSegs[3]) << 24 | Integer.parseInt(ipSegs[2]) << 16 |
-            Integer.parseInt(ipSegs[1]) << 8 | Integer.parseInt(ipSegs[0]);
-        return tmp;
+    private boolean dhcpInfoEquals(@Nullable DhcpInfo left, @Nullable DhcpInfo right) {
+        if (left == null && right == null) return true;
+
+        if (left == null || right == null) return false;
+
+        return left.ipAddress == right.ipAddress
+                && left.gateway == right.gateway
+                && left.netmask == right.netmask
+                && left.dns1 == right.dns1
+                && left.dns2 == right.dns2
+                && left.serverAddress == right.serverAddress
+                && left.leaseDuration == right.leaseDuration;
+    }
+
+    @Test
+    public void testParcelDhcpInfo() throws Exception {
+        // Cannot use assertParcelSane() here because this requires .equals() to work as
+        // defined, but DhcpInfo has a different legacy behavior that we cannot change.
+        final DhcpInfo dhcpInfo = createDhcpInfoObject();
+        assertFieldCountEquals(7, DhcpInfo.class);
+
+        final DhcpInfo dhcpInfoRoundTrip = parcelingRoundTrip(dhcpInfo);
+        assertTrue(dhcpInfoEquals(null, null));
+        assertFalse(dhcpInfoEquals(null, dhcpInfoRoundTrip));
+        assertFalse(dhcpInfoEquals(dhcpInfo, null));
+        assertTrue(dhcpInfoEquals(dhcpInfo, dhcpInfoRoundTrip));
     }
 }
diff --git a/tests/tests/net/src/android/net/cts/IpSecManagerTunnelTest.java b/tests/tests/net/src/android/net/cts/IpSecManagerTunnelTest.java
index 999d2f1..1d83dda 100644
--- a/tests/tests/net/src/android/net/cts/IpSecManagerTunnelTest.java
+++ b/tests/tests/net/src/android/net/cts/IpSecManagerTunnelTest.java
@@ -192,9 +192,8 @@
         // Build a network request
         NetworkRequest nr =
                 new NetworkRequest.Builder()
+                        .clearCapabilities()
                         .addTransportType(TRANSPORT_TEST)
-                        .removeCapability(NET_CAPABILITY_TRUSTED)
-                        .removeCapability(NET_CAPABILITY_NOT_VPN)
                         .setNetworkSpecifier(ifname)
                         .build();
 
diff --git a/tests/tests/net/src/android/net/cts/NetworkAgentTest.kt b/tests/tests/net/src/android/net/cts/NetworkAgentTest.kt
new file mode 100644
index 0000000..89d3dff
--- /dev/null
+++ b/tests/tests/net/src/android/net/cts/NetworkAgentTest.kt
@@ -0,0 +1,595 @@
+/*
+ * 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 android.app.Instrumentation
+import android.content.Context
+import android.net.ConnectivityManager
+import android.net.KeepalivePacketData
+import android.net.LinkAddress
+import android.net.LinkProperties
+import android.net.Network
+import android.net.NetworkAgent
+import android.net.NetworkAgent.CMD_ADD_KEEPALIVE_PACKET_FILTER
+import android.net.NetworkAgent.CMD_PREVENT_AUTOMATIC_RECONNECT
+import android.net.NetworkAgent.CMD_REMOVE_KEEPALIVE_PACKET_FILTER
+import android.net.NetworkAgent.CMD_REPORT_NETWORK_STATUS
+import android.net.NetworkAgent.CMD_SAVE_ACCEPT_UNVALIDATED
+import android.net.NetworkAgent.CMD_START_SOCKET_KEEPALIVE
+import android.net.NetworkAgent.CMD_STOP_SOCKET_KEEPALIVE
+import android.net.NetworkAgent.INVALID_NETWORK
+import android.net.NetworkAgent.VALID_NETWORK
+import android.net.NetworkAgentConfig
+import android.net.NetworkCapabilities
+import android.net.NetworkProvider
+import android.net.NetworkRequest
+import android.net.SocketKeepalive
+import android.net.StringNetworkSpecifier
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import android.os.Message
+import android.os.Messenger
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnAddKeepalivePacketFilter
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnAutomaticReconnectDisabled
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnBandwidthUpdateRequested
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnNetworkUnwanted
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnRemoveKeepalivePacketFilter
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSaveAcceptUnvalidated
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnSignalStrengthThresholdsUpdated
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStartSocketKeepalive
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnStopSocketKeepalive
+import android.net.cts.NetworkAgentTest.TestableNetworkAgent.CallbackEntry.OnValidationStatus
+import androidx.test.InstrumentationRegistry
+import androidx.test.runner.AndroidJUnit4
+import com.android.internal.util.AsyncChannel
+import com.android.testutils.ArrayTrackRecord
+import com.android.testutils.DevSdkIgnoreRule
+import com.android.testutils.RecorderCallback.CallbackEntry.Available
+import com.android.testutils.RecorderCallback.CallbackEntry.Lost
+import com.android.testutils.TestableNetworkCallback
+import java.util.UUID
+import org.junit.After
+import org.junit.Assert.assertArrayEquals
+import org.junit.Assert.fail
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import java.net.InetAddress
+import java.time.Duration
+import kotlin.test.assertEquals
+import kotlin.test.assertFalse
+import kotlin.test.assertFailsWith
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.assertTrue
+
+// This test doesn't really have a constraint on how fast the methods should return. If it's
+// going to fail, it will simply wait forever, so setting a high timeout lowers the flake ratio
+// without affecting the run time of successful runs. Thus, set a very high timeout.
+private const val DEFAULT_TIMEOUT_MS = 5000L
+// When waiting for a NetworkCallback to determine there was no timeout, waiting is the
+// only possible thing (the relevant handler is the one in the real ConnectivityService,
+// and then there is the Binder call), so have a short timeout for this as it will be
+// exhausted every time.
+private const val NO_CALLBACK_TIMEOUT = 200L
+// Any legal score (0~99) for the test network would do, as it is going to be kept up by the
+// requests filed by the test and should never match normal internet requests. 70 is the default
+// score of Ethernet networks, it's as good a value as any other.
+private const val TEST_NETWORK_SCORE = 70
+private const val BETTER_NETWORK_SCORE = 75
+private const val FAKE_NET_ID = 1098
+private val instrumentation: Instrumentation
+    get() = InstrumentationRegistry.getInstrumentation()
+private val context: Context
+    get() = InstrumentationRegistry.getContext()
+private fun Message(what: Int, arg1: Int, arg2: Int, obj: Any?) = Message.obtain().also {
+    it.what = what
+    it.arg1 = arg1
+    it.arg2 = arg2
+    it.obj = obj
+}
+
+@RunWith(AndroidJUnit4::class)
+class NetworkAgentTest {
+    @Rule @JvmField
+    val ignoreRule = DevSdkIgnoreRule(ignoreClassUpTo = Build.VERSION_CODES.Q)
+
+    private val LOCAL_IPV4_ADDRESS = InetAddress.parseNumericAddress("192.0.2.1")
+    private val REMOTE_IPV4_ADDRESS = InetAddress.parseNumericAddress("192.0.2.2")
+
+    private val mCM = context.getSystemService(ConnectivityManager::class.java)
+    private val mHandlerThread = HandlerThread("${javaClass.simpleName} handler thread")
+    private val mFakeConnectivityService by lazy { FakeConnectivityService(mHandlerThread.looper) }
+
+    private class Provider(context: Context, looper: Looper) :
+            NetworkProvider(context, looper, "NetworkAgentTest NetworkProvider")
+
+    private val agentsToCleanUp = mutableListOf<NetworkAgent>()
+    private val callbacksToCleanUp = mutableListOf<TestableNetworkCallback>()
+
+    @Before
+    fun setUp() {
+        instrumentation.getUiAutomation().adoptShellPermissionIdentity()
+        mHandlerThread.start()
+    }
+
+    @After
+    fun tearDown() {
+        agentsToCleanUp.forEach { it.unregister() }
+        callbacksToCleanUp.forEach { mCM.unregisterNetworkCallback(it) }
+        mHandlerThread.quitSafely()
+        instrumentation.getUiAutomation().dropShellPermissionIdentity()
+    }
+
+    /**
+     * A fake that helps simulating ConnectivityService talking to a harnessed agent.
+     * This fake only supports speaking to one harnessed agent at a time because it
+     * only keeps track of one async channel.
+     */
+    private class FakeConnectivityService(looper: Looper) {
+        private val CMD_EXPECT_DISCONNECT = 1
+        private var disconnectExpected = false
+        private val msgHistory = ArrayTrackRecord<Message>().newReadHead()
+        private val asyncChannel = AsyncChannel()
+        private val handler = object : Handler(looper) {
+            override fun handleMessage(msg: Message) {
+                msgHistory.add(Message.obtain(msg)) // make a copy as the original will be recycled
+                when (msg.what) {
+                    CMD_EXPECT_DISCONNECT -> disconnectExpected = true
+                    AsyncChannel.CMD_CHANNEL_HALF_CONNECTED ->
+                        asyncChannel.sendMessage(AsyncChannel.CMD_CHANNEL_FULL_CONNECTION)
+                    AsyncChannel.CMD_CHANNEL_DISCONNECTED ->
+                        if (!disconnectExpected) {
+                            fail("Agent unexpectedly disconnected")
+                        } else {
+                            disconnectExpected = false
+                        }
+                }
+            }
+        }
+
+        fun connect(agentMsngr: Messenger) = asyncChannel.connect(context, handler, agentMsngr)
+
+        fun disconnect() = asyncChannel.disconnect()
+
+        fun sendMessage(what: Int, arg1: Int = 0, arg2: Int = 0, obj: Any? = null) =
+            asyncChannel.sendMessage(Message(what, arg1, arg2, obj))
+
+        fun expectMessage(what: Int) =
+            assertNotNull(msgHistory.poll(DEFAULT_TIMEOUT_MS) { it.what == what })
+
+        fun willExpectDisconnectOnce() = handler.sendEmptyMessage(CMD_EXPECT_DISCONNECT)
+    }
+
+    private open class TestableNetworkAgent(
+        val looper: Looper,
+        val nc: NetworkCapabilities,
+        val lp: LinkProperties,
+        conf: NetworkAgentConfig
+    ) : NetworkAgent(context, looper, TestableNetworkAgent::class.java.simpleName /* tag */,
+            nc, lp, TEST_NETWORK_SCORE, conf, Provider(context, looper)) {
+        private val history = ArrayTrackRecord<CallbackEntry>().newReadHead()
+
+        sealed class CallbackEntry {
+            object OnBandwidthUpdateRequested : CallbackEntry()
+            object OnNetworkUnwanted : CallbackEntry()
+            data class OnAddKeepalivePacketFilter(
+                val slot: Int,
+                val packet: KeepalivePacketData
+            ) : CallbackEntry()
+            data class OnRemoveKeepalivePacketFilter(val slot: Int) : CallbackEntry()
+            data class OnStartSocketKeepalive(
+                val slot: Int,
+                val interval: Int,
+                val packet: KeepalivePacketData
+            ) : CallbackEntry()
+            data class OnStopSocketKeepalive(val slot: Int) : CallbackEntry()
+            data class OnSaveAcceptUnvalidated(val accept: Boolean) : CallbackEntry()
+            object OnAutomaticReconnectDisabled : CallbackEntry()
+            data class OnValidationStatus(val status: Int, val uri: Uri?) : CallbackEntry()
+            data class OnSignalStrengthThresholdsUpdated(val thresholds: IntArray) : CallbackEntry()
+        }
+
+        fun getName(): String? = (nc.getNetworkSpecifier() as? StringNetworkSpecifier)?.specifier
+
+        override fun onBandwidthUpdateRequested() {
+            history.add(OnBandwidthUpdateRequested)
+        }
+
+        override fun onNetworkUnwanted() {
+            history.add(OnNetworkUnwanted)
+        }
+
+        override fun onAddKeepalivePacketFilter(slot: Int, packet: KeepalivePacketData) {
+            history.add(OnAddKeepalivePacketFilter(slot, packet))
+        }
+
+        override fun onRemoveKeepalivePacketFilter(slot: Int) {
+            history.add(OnRemoveKeepalivePacketFilter(slot))
+        }
+
+        override fun onStartSocketKeepalive(
+            slot: Int,
+            interval: Duration,
+            packet: KeepalivePacketData
+        ) {
+            history.add(OnStartSocketKeepalive(slot, interval.seconds.toInt(), packet))
+        }
+
+        override fun onStopSocketKeepalive(slot: Int) {
+            history.add(OnStopSocketKeepalive(slot))
+        }
+
+        override fun onSaveAcceptUnvalidated(accept: Boolean) {
+            history.add(OnSaveAcceptUnvalidated(accept))
+        }
+
+        override fun onAutomaticReconnectDisabled() {
+            history.add(OnAutomaticReconnectDisabled)
+        }
+
+        override fun onSignalStrengthThresholdsUpdated(thresholds: IntArray) {
+            history.add(OnSignalStrengthThresholdsUpdated(thresholds))
+        }
+
+        fun expectEmptySignalStrengths() {
+            expectCallback<OnSignalStrengthThresholdsUpdated>().let {
+                // intArrayOf() without arguments makes an empty array
+                assertArrayEquals(intArrayOf(), it.thresholds)
+            }
+        }
+
+        override fun onValidationStatus(status: Int, uri: Uri?) {
+            history.add(OnValidationStatus(status, uri))
+        }
+
+        // Expects the initial validation event that always occurs immediately after registering
+        // a NetworkAgent whose network does not require validation (which test networks do
+        // not, since they lack the INTERNET capability). It always contains the default argument
+        // for the URI.
+        fun expectNoInternetValidationStatus() = expectCallback<OnValidationStatus>().let {
+            assertEquals(it.status, VALID_NETWORK)
+            // The returned Uri is parsed from the empty string, which means it's an
+            // instance of the (private) Uri.StringUri. There are no real good ways
+            // to check this, the least bad is to just convert it to a string and
+            // make sure it's empty.
+            assertEquals("", it.uri.toString())
+        }
+
+        inline fun <reified T : CallbackEntry> expectCallback(): T {
+            val foundCallback = history.poll(DEFAULT_TIMEOUT_MS)
+            assertTrue(foundCallback is T, "Expected ${T::class} but found $foundCallback")
+            return foundCallback
+        }
+
+        fun assertNoCallback() {
+            assertTrue(waitForIdle(DEFAULT_TIMEOUT_MS),
+                    "Handler didn't became idle after ${DEFAULT_TIMEOUT_MS}ms")
+            assertNull(history.peek())
+        }
+    }
+
+    private fun requestNetwork(request: NetworkRequest, callback: TestableNetworkCallback) {
+        mCM.requestNetwork(request, callback)
+        callbacksToCleanUp.add(callback)
+    }
+
+    private fun registerNetworkCallback(
+        request: NetworkRequest,
+        callback: TestableNetworkCallback
+    ) {
+        mCM.registerNetworkCallback(request, callback)
+        callbacksToCleanUp.add(callback)
+    }
+
+    private fun createNetworkAgent(name: String? = null): TestableNetworkAgent {
+        val nc = NetworkCapabilities().apply {
+            addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+            removeCapability(NetworkCapabilities.NET_CAPABILITY_TRUSTED)
+            removeCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
+            addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_SUSPENDED)
+            addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_ROAMING)
+            addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_VPN)
+            if (null != name) {
+                setNetworkSpecifier(StringNetworkSpecifier(name))
+            }
+        }
+        val lp = LinkProperties().apply {
+            addLinkAddress(LinkAddress(LOCAL_IPV4_ADDRESS, 0))
+        }
+        val config = NetworkAgentConfig.Builder().build()
+        return TestableNetworkAgent(mHandlerThread.looper, nc, lp, config).also {
+            agentsToCleanUp.add(it)
+        }
+    }
+
+    private fun createConnectedNetworkAgent(name: String? = null):
+            Pair<TestableNetworkAgent, TestableNetworkCallback> {
+        val request: NetworkRequest = NetworkRequest.Builder()
+                .clearCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+                .build()
+        val callback = TestableNetworkCallback(timeoutMs = DEFAULT_TIMEOUT_MS)
+        requestNetwork(request, callback)
+        val agent = createNetworkAgent(name)
+        agent.register()
+        agent.markConnected()
+        return agent to callback
+    }
+
+    private fun createNetworkAgentWithFakeCS() = createNetworkAgent().also {
+        mFakeConnectivityService.connect(it.registerForTest(Network(FAKE_NET_ID)))
+    }
+
+    @Test
+    fun testConnectAndUnregister() {
+        val (agent, callback) = createConnectedNetworkAgent()
+        callback.expectAvailableThenValidatedCallbacks(agent.network)
+        agent.expectEmptySignalStrengths()
+        agent.expectNoInternetValidationStatus()
+        agent.unregister()
+        callback.expectCallback<Lost>(agent.network)
+        agent.expectCallback<OnNetworkUnwanted>()
+        assertFailsWith<IllegalStateException>("Must not be able to register an agent twice") {
+            agent.register()
+        }
+    }
+
+    @Test
+    fun testOnBandwidthUpdateRequested() {
+        val (agent, callback) = createConnectedNetworkAgent()
+        callback.expectAvailableThenValidatedCallbacks(agent.network)
+        agent.expectEmptySignalStrengths()
+        agent.expectNoInternetValidationStatus()
+        mCM.requestBandwidthUpdate(agent.network)
+        agent.expectCallback<OnBandwidthUpdateRequested>()
+        agent.unregister()
+    }
+
+    @Test
+    fun testSignalStrengthThresholds() {
+        val thresholds = intArrayOf(30, 50, 65)
+        val callbacks = thresholds.map { strength ->
+            val request = NetworkRequest.Builder()
+                    .clearCapabilities()
+                    .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+                    .setSignalStrength(strength)
+                    .build()
+            TestableNetworkCallback(DEFAULT_TIMEOUT_MS).also {
+                registerNetworkCallback(request, it)
+            }
+        }
+        createConnectedNetworkAgent().let { (agent, callback) ->
+            callback.expectAvailableThenValidatedCallbacks(agent.network)
+            agent.expectCallback<OnSignalStrengthThresholdsUpdated>().let {
+                assertArrayEquals(it.thresholds, thresholds)
+            }
+            agent.expectNoInternetValidationStatus()
+
+            // Send signal strength and check that the callbacks are called appropriately.
+            val nc = NetworkCapabilities(agent.nc)
+            nc.setSignalStrength(20)
+            agent.sendNetworkCapabilities(nc)
+            callbacks.forEach { it.assertNoCallback(NO_CALLBACK_TIMEOUT) }
+
+            nc.setSignalStrength(40)
+            agent.sendNetworkCapabilities(nc)
+            callbacks[0].expectAvailableCallbacks(agent.network)
+            callbacks[1].assertNoCallback(NO_CALLBACK_TIMEOUT)
+            callbacks[2].assertNoCallback(NO_CALLBACK_TIMEOUT)
+
+            nc.setSignalStrength(80)
+            agent.sendNetworkCapabilities(nc)
+            callbacks[0].expectCapabilitiesThat(agent.network) { it.signalStrength == 80 }
+            callbacks[1].expectAvailableCallbacks(agent.network)
+            callbacks[2].expectAvailableCallbacks(agent.network)
+
+            nc.setSignalStrength(55)
+            agent.sendNetworkCapabilities(nc)
+            callbacks[0].expectCapabilitiesThat(agent.network) { it.signalStrength == 55 }
+            callbacks[1].expectCapabilitiesThat(agent.network) { it.signalStrength == 55 }
+            callbacks[2].expectCallback<Lost>(agent.network)
+        }
+        callbacks.forEach {
+            mCM.unregisterNetworkCallback(it)
+        }
+    }
+
+    @Test
+    fun testSocketKeepalive(): Unit = createNetworkAgentWithFakeCS().let { agent ->
+        val packet = object : KeepalivePacketData(
+                LOCAL_IPV4_ADDRESS /* srcAddress */, 1234 /* srcPort */,
+                REMOTE_IPV4_ADDRESS /* dstAddress */, 4567 /* dstPort */,
+                ByteArray(100 /* size */) { it.toByte() /* init */ }) {}
+        val slot = 4
+        val interval = 37
+
+        mFakeConnectivityService.sendMessage(CMD_ADD_KEEPALIVE_PACKET_FILTER,
+                arg1 = slot, obj = packet)
+        mFakeConnectivityService.sendMessage(CMD_START_SOCKET_KEEPALIVE,
+                arg1 = slot, arg2 = interval, obj = packet)
+
+        agent.expectCallback<OnAddKeepalivePacketFilter>().let {
+            assertEquals(it.slot, slot)
+            assertEquals(it.packet, packet)
+        }
+        agent.expectCallback<OnStartSocketKeepalive>().let {
+            assertEquals(it.slot, slot)
+            assertEquals(it.interval, interval)
+            assertEquals(it.packet, packet)
+        }
+
+        agent.assertNoCallback()
+
+        // Check that when the agent sends a keepalive event, ConnectivityService receives the
+        // expected message.
+        agent.sendSocketKeepaliveEvent(slot, SocketKeepalive.ERROR_UNSUPPORTED)
+        mFakeConnectivityService.expectMessage(NetworkAgent.EVENT_SOCKET_KEEPALIVE).let() {
+            assertEquals(slot, it.arg1)
+            assertEquals(SocketKeepalive.ERROR_UNSUPPORTED, it.arg2)
+        }
+
+        mFakeConnectivityService.sendMessage(CMD_STOP_SOCKET_KEEPALIVE, arg1 = slot)
+        mFakeConnectivityService.sendMessage(CMD_REMOVE_KEEPALIVE_PACKET_FILTER, arg1 = slot)
+        agent.expectCallback<OnStopSocketKeepalive>().let {
+            assertEquals(it.slot, slot)
+        }
+        agent.expectCallback<OnRemoveKeepalivePacketFilter>().let {
+            assertEquals(it.slot, slot)
+        }
+    }
+
+    @Test
+    fun testSendUpdates(): Unit = createConnectedNetworkAgent().let { (agent, callback) ->
+        callback.expectAvailableThenValidatedCallbacks(agent.network)
+        agent.expectEmptySignalStrengths()
+        agent.expectNoInternetValidationStatus()
+        val ifaceName = "adhocIface"
+        val lp = LinkProperties(agent.lp)
+        lp.setInterfaceName(ifaceName)
+        agent.sendLinkProperties(lp)
+        callback.expectLinkPropertiesThat(agent.network) {
+            it.getInterfaceName() == ifaceName
+        }
+        val nc = NetworkCapabilities(agent.nc)
+        nc.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+        agent.sendNetworkCapabilities(nc)
+        callback.expectCapabilitiesThat(agent.network) {
+            it.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED)
+        }
+    }
+
+    @Test
+    fun testSendScore() {
+        // This test will create two networks and check that the one with the stronger
+        // score wins out for a request that matches them both.
+        // First create requests to make sure both networks are kept up, using the
+        // specifier so they are specific to each network
+        val name1 = UUID.randomUUID().toString()
+        val name2 = UUID.randomUUID().toString()
+        val request1 = NetworkRequest.Builder()
+                .clearCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+                .setNetworkSpecifier(StringNetworkSpecifier(name1))
+                .build()
+        val request2 = NetworkRequest.Builder()
+                .clearCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+                .setNetworkSpecifier(StringNetworkSpecifier(name2))
+                .build()
+        val callback1 = TestableNetworkCallback()
+        val callback2 = TestableNetworkCallback()
+        requestNetwork(request1, callback1)
+        requestNetwork(request2, callback2)
+
+        // Then file the interesting request
+        val request = NetworkRequest.Builder()
+                .clearCapabilities()
+                .addTransportType(NetworkCapabilities.TRANSPORT_TEST)
+                .build()
+        val callback = TestableNetworkCallback()
+        requestNetwork(request, callback)
+
+        // Connect the first Network
+        createConnectedNetworkAgent(name1).let { (agent1, _) ->
+            callback.expectAvailableThenValidatedCallbacks(agent1.network)
+            // Upgrade agent1 to a better score so that there is no ambiguity when
+            // agent2 connects that agent1 is still better
+            agent1.sendNetworkScore(BETTER_NETWORK_SCORE - 1)
+            // Connect the second agent
+            createConnectedNetworkAgent(name2).let { (agent2, _) ->
+                agent2.markConnected()
+                // The callback should not see anything yet
+                callback.assertNoCallback(NO_CALLBACK_TIMEOUT)
+                // Now update the score and expect the callback now prefers agent2
+                agent2.sendNetworkScore(BETTER_NETWORK_SCORE)
+                callback.expectCallback<Available>(agent2.network)
+            }
+        }
+
+        // tearDown() will unregister the requests and agents
+    }
+
+    @Test
+    fun testSetAcceptUnvalidated() {
+        createNetworkAgentWithFakeCS().let { agent ->
+            mFakeConnectivityService.sendMessage(CMD_SAVE_ACCEPT_UNVALIDATED, 1)
+            agent.expectCallback<OnSaveAcceptUnvalidated>().let {
+                assertTrue(it.accept)
+            }
+            agent.assertNoCallback()
+        }
+    }
+
+    @Test
+    fun testSetAcceptUnvalidatedPreventAutomaticReconnect() {
+        createNetworkAgentWithFakeCS().let { agent ->
+            mFakeConnectivityService.sendMessage(CMD_SAVE_ACCEPT_UNVALIDATED, 0)
+            mFakeConnectivityService.sendMessage(CMD_PREVENT_AUTOMATIC_RECONNECT)
+            agent.expectCallback<OnSaveAcceptUnvalidated>().let {
+                assertFalse(it.accept)
+            }
+            agent.expectCallback<OnAutomaticReconnectDisabled>()
+            agent.assertNoCallback()
+            // When automatic reconnect is turned off, the network is torn down and
+            // ConnectivityService sends a disconnect. This in turn causes the agent
+            // to send a DISCONNECTED message to CS.
+            mFakeConnectivityService.willExpectDisconnectOnce()
+            mFakeConnectivityService.disconnect()
+            mFakeConnectivityService.expectMessage(AsyncChannel.CMD_CHANNEL_DISCONNECTED)
+            agent.expectCallback<OnNetworkUnwanted>()
+        }
+    }
+
+    @Test
+    fun testPreventAutomaticReconnect() {
+        createNetworkAgentWithFakeCS().let { agent ->
+            mFakeConnectivityService.sendMessage(CMD_PREVENT_AUTOMATIC_RECONNECT)
+            agent.expectCallback<OnAutomaticReconnectDisabled>()
+            agent.assertNoCallback()
+            mFakeConnectivityService.willExpectDisconnectOnce()
+            mFakeConnectivityService.disconnect()
+            mFakeConnectivityService.expectMessage(AsyncChannel.CMD_CHANNEL_DISCONNECTED)
+            agent.expectCallback<OnNetworkUnwanted>()
+        }
+    }
+
+    @Test
+    fun testValidationStatus() = createNetworkAgentWithFakeCS().let { agent ->
+        val uri = Uri.parse("http://www.google.com")
+        val bundle = Bundle().apply {
+            putString(NetworkAgent.REDIRECT_URL_KEY, uri.toString())
+        }
+        mFakeConnectivityService.sendMessage(CMD_REPORT_NETWORK_STATUS,
+                arg1 = VALID_NETWORK, obj = bundle)
+        agent.expectCallback<OnValidationStatus>().let {
+            assertEquals(it.status, VALID_NETWORK)
+            assertEquals(it.uri, uri)
+        }
+
+        mFakeConnectivityService.sendMessage(CMD_REPORT_NETWORK_STATUS,
+                arg1 = INVALID_NETWORK, obj = Bundle())
+        agent.expectCallback<OnValidationStatus>().let {
+            assertEquals(it.status, INVALID_NETWORK)
+            assertNull(it.uri)
+        }
+    }
+}
diff --git a/tests/tests/net/src/android/net/cts/NetworkRequestTest.java b/tests/tests/net/src/android/net/cts/NetworkRequestTest.java
index 8b97c8c..6a1d9de 100644
--- a/tests/tests/net/src/android/net/cts/NetworkRequestTest.java
+++ b/tests/tests/net/src/android/net/cts/NetworkRequestTest.java
@@ -16,8 +16,11 @@
 
 package android.net.cts;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_MMS;
 import static android.net.NetworkCapabilities.TRANSPORT_BLUETOOTH;
+import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
+import static android.net.NetworkCapabilities.TRANSPORT_VPN;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 
 import static org.junit.Assert.assertEquals;
@@ -26,13 +29,13 @@
 import static org.junit.Assert.assertTrue;
 
 import android.net.MacAddress;
+import android.net.NetworkCapabilities;
 import android.net.NetworkRequest;
 import android.net.NetworkSpecifier;
-import android.net.wifi.WifiConfiguration;
 import android.net.wifi.WifiNetworkSpecifier;
 import android.os.Build;
+import android.os.Process;
 import android.os.PatternMatcher;
-import android.util.Pair;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -49,6 +52,7 @@
     public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
 
     private static final String TEST_SSID = "TestSSID";
+    private static final String OTHER_SSID = "OtherSSID";
     private static final int TEST_UID = 2097;
     private static final String TEST_PACKAGE_NAME = "test.package.name";
     private static final MacAddress ARBITRARY_ADDRESS = MacAddress.fromString("3:5:8:12:9:2");
@@ -59,6 +63,18 @@
                 .hasCapability(NET_CAPABILITY_MMS));
         assertFalse(new NetworkRequest.Builder().removeCapability(NET_CAPABILITY_MMS).build()
                 .hasCapability(NET_CAPABILITY_MMS));
+
+        final NetworkRequest nr = new NetworkRequest.Builder().clearCapabilities().build();
+        // Verify request has no capabilities
+        verifyNoCapabilities(nr);
+    }
+
+    private void verifyNoCapabilities(NetworkRequest nr) {
+        // NetworkCapabilities.mNetworkCapabilities is defined as type long
+        final int MAX_POSSIBLE_CAPABILITY = Long.SIZE;
+        for(int bit = 0; bit < MAX_POSSIBLE_CAPABILITY; bit++) {
+            assertFalse(nr.hasCapability(bit));
+        }
     }
 
     @Test
@@ -83,5 +99,81 @@
                 .build()
                 .getNetworkSpecifier();
         assertEquals(obtainedSpecifier, specifier);
+
+        assertNull(new NetworkRequest.Builder()
+                .clearCapabilities()
+                .build()
+                .getNetworkSpecifier());
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testRequestorPackageName() {
+        assertNull(new NetworkRequest.Builder().build().getRequestorPackageName());
+        final String pkgName = "android.net.test";
+        final NetworkCapabilities nc = new NetworkCapabilities.Builder()
+                .setRequestorPackageName(pkgName)
+                .build();
+        final NetworkRequest nr = new NetworkRequest.Builder()
+                .setCapabilities(nc)
+                .build();
+        assertEquals(pkgName, nr.getRequestorPackageName());
+        assertNull(new NetworkRequest.Builder()
+                .clearCapabilities()
+                .build()
+                .getRequestorPackageName());
+    }
+
+    @Test
+    @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testCanBeSatisfiedBy() {
+        final WifiNetworkSpecifier specifier1 = new WifiNetworkSpecifier.Builder()
+                .setSsidPattern(new PatternMatcher(TEST_SSID, PatternMatcher.PATTERN_LITERAL))
+                .setBssidPattern(ARBITRARY_ADDRESS, ARBITRARY_ADDRESS)
+                .build();
+        final WifiNetworkSpecifier specifier2 = new WifiNetworkSpecifier.Builder()
+                .setSsidPattern(new PatternMatcher(OTHER_SSID, PatternMatcher.PATTERN_LITERAL))
+                .setBssidPattern(ARBITRARY_ADDRESS, ARBITRARY_ADDRESS)
+                .build();
+        final NetworkCapabilities cap = new NetworkCapabilities()
+                .addTransportType(TRANSPORT_WIFI)
+                .addCapability(NET_CAPABILITY_INTERNET);
+        final NetworkCapabilities capWithSp =
+                new NetworkCapabilities(cap).setNetworkSpecifier(specifier1);
+        final NetworkCapabilities cellCap = new NetworkCapabilities()
+                .addTransportType(TRANSPORT_CELLULAR)
+                .addCapability(NET_CAPABILITY_MMS)
+                .addCapability(NET_CAPABILITY_INTERNET);
+        final NetworkRequest request = new NetworkRequest.Builder()
+                .addTransportType(TRANSPORT_WIFI)
+                .addCapability(NET_CAPABILITY_INTERNET)
+                .setNetworkSpecifier(specifier1)
+                .build();
+        assertFalse(request.canBeSatisfiedBy(null));
+        assertFalse(request.canBeSatisfiedBy(new NetworkCapabilities()));
+        assertTrue(request.canBeSatisfiedBy(cap));
+        assertTrue(request.canBeSatisfiedBy(
+                new NetworkCapabilities(cap).addTransportType(TRANSPORT_VPN)));
+        assertTrue(request.canBeSatisfiedBy(capWithSp));
+        assertFalse(request.canBeSatisfiedBy(
+                new NetworkCapabilities(cap).setNetworkSpecifier(specifier2)));
+        assertFalse(request.canBeSatisfiedBy(cellCap));
+        assertEquals(request.canBeSatisfiedBy(capWithSp),
+                new NetworkCapabilities(capWithSp).satisfiedByNetworkCapabilities(capWithSp));
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testRequestorUid() {
+        final NetworkCapabilities nc = new NetworkCapabilities();
+        // Verify default value is INVALID_UID
+        assertEquals(Process.INVALID_UID, new NetworkRequest.Builder()
+                 .setCapabilities(nc).build().getRequestorUid());
+
+        nc.setRequestorUid(1314);
+        final NetworkRequest nr = new NetworkRequest.Builder().setCapabilities(nc).build();
+        assertEquals(1314, nr.getRequestorUid());
+
+        assertEquals(Process.INVALID_UID, new NetworkRequest.Builder()
+                .clearCapabilities().build().getRequestorUid());
     }
 }
diff --git a/tests/tests/net/src/android/net/cts/ProxyInfoTest.java b/tests/tests/net/src/android/net/cts/ProxyInfoTest.java
new file mode 100644
index 0000000..1c5624c
--- /dev/null
+++ b/tests/tests/net/src/android/net/cts/ProxyInfoTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.net.ProxyInfo;
+import android.net.Uri;
+import android.os.Build;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+@RunWith(AndroidJUnit4.class)
+public final class ProxyInfoTest {
+    private static final String TEST_HOST = "test.example.com";
+    private static final int TEST_PORT = 5566;
+    private static final Uri TEST_URI = Uri.parse("https://test.example.com");
+    // This matches android.net.ProxyInfo#LOCAL_EXCL_LIST
+    private static final String LOCAL_EXCL_LIST = "";
+    // This matches android.net.ProxyInfo#LOCAL_HOST
+    private static final String LOCAL_HOST = "localhost";
+    // This matches android.net.ProxyInfo#LOCAL_PORT
+    private static final int LOCAL_PORT = -1;
+
+    @Rule
+    public final DevSdkIgnoreRule ignoreRule = new DevSdkIgnoreRule();
+
+    @Test
+    public void testConstructor() {
+        final ProxyInfo proxy = new ProxyInfo((ProxyInfo) null);
+        checkEmpty(proxy);
+
+        assertEquals(proxy, new ProxyInfo(proxy));
+    }
+
+    @Test
+    public void testBuildDirectProxy() {
+        final ProxyInfo proxy1 = ProxyInfo.buildDirectProxy(TEST_HOST, TEST_PORT);
+
+        assertEquals(TEST_HOST, proxy1.getHost());
+        assertEquals(TEST_PORT, proxy1.getPort());
+        assertArrayEquals(new String[0], proxy1.getExclusionList());
+        assertEquals(Uri.EMPTY, proxy1.getPacFileUrl());
+
+        final List<String> exclList = new ArrayList<>();
+        exclList.add("localhost");
+        exclList.add("*.exclusion.com");
+        final ProxyInfo proxy2 = ProxyInfo.buildDirectProxy(TEST_HOST, TEST_PORT, exclList);
+
+        assertEquals(TEST_HOST, proxy2.getHost());
+        assertEquals(TEST_PORT, proxy2.getPort());
+        assertArrayEquals(exclList.toArray(new String[0]), proxy2.getExclusionList());
+        assertEquals(Uri.EMPTY, proxy2.getPacFileUrl());
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q)
+    public void testBuildPacProxy() {
+        final ProxyInfo proxy1 = ProxyInfo.buildPacProxy(TEST_URI);
+
+        assertEquals(LOCAL_HOST, proxy1.getHost());
+        assertEquals(LOCAL_PORT, proxy1.getPort());
+        assertArrayEquals(LOCAL_EXCL_LIST.toLowerCase(Locale.ROOT).split(","),
+                proxy1.getExclusionList());
+        assertEquals(TEST_URI, proxy1.getPacFileUrl());
+
+        final ProxyInfo proxy2 = ProxyInfo.buildPacProxy(TEST_URI, TEST_PORT);
+
+        assertEquals(LOCAL_HOST, proxy2.getHost());
+        assertEquals(TEST_PORT, proxy2.getPort());
+        assertArrayEquals(LOCAL_EXCL_LIST.toLowerCase(Locale.ROOT).split(","),
+                proxy2.getExclusionList());
+        assertEquals(TEST_URI, proxy2.getPacFileUrl());
+    }
+
+    @Test
+    public void testIsValid() {
+        final ProxyInfo proxy1 = ProxyInfo.buildDirectProxy(TEST_HOST, TEST_PORT);
+        assertTrue(proxy1.isValid());
+
+        // Given empty host
+        final ProxyInfo proxy2 = ProxyInfo.buildDirectProxy("", TEST_PORT);
+        assertFalse(proxy2.isValid());
+        // Given invalid host
+        final ProxyInfo proxy3 = ProxyInfo.buildDirectProxy(".invalid.com", TEST_PORT);
+        assertFalse(proxy3.isValid());
+        // Given invalid port.
+        final ProxyInfo proxy4 = ProxyInfo.buildDirectProxy(TEST_HOST, 0);
+        assertFalse(proxy4.isValid());
+        // Given another invalid port
+        final ProxyInfo proxy5 = ProxyInfo.buildDirectProxy(TEST_HOST, 65536);
+        assertFalse(proxy5.isValid());
+        // Given invalid exclusion list
+        final List<String> exclList = new ArrayList<>();
+        exclList.add(".invalid.com");
+        exclList.add("%.test.net");
+        final ProxyInfo proxy6 = ProxyInfo.buildDirectProxy(TEST_HOST, TEST_PORT, exclList);
+        assertFalse(proxy6.isValid());
+    }
+
+    private void checkEmpty(ProxyInfo proxy) {
+        assertNull(proxy.getHost());
+        assertEquals(0, proxy.getPort());
+        assertNull(proxy.getExclusionList());
+        assertEquals(Uri.EMPTY, proxy.getPacFileUrl());
+    }
+}
diff --git a/tests/tests/net/src/android/net/cts/TrafficStatsTest.java b/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
index 5bd1e20..37bdd44 100755
--- a/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
+++ b/tests/tests/net/src/android/net/cts/TrafficStatsTest.java
@@ -16,14 +16,13 @@
 
 package android.net.cts;
 
-import android.content.pm.PackageManager;
 import android.net.NetworkStats;
 import android.net.TrafficStats;
 import android.os.Process;
-import android.os.SystemProperties;
 import android.platform.test.annotations.AppModeFull;
 import android.test.AndroidTestCase;
 import android.util.Log;
+import android.util.Range;
 
 import java.io.IOException;
 import java.io.InputStream;
@@ -36,6 +35,13 @@
 public class TrafficStatsTest extends AndroidTestCase {
     private static final String LOG_TAG = "TrafficStatsTest";
 
+    /** Verify the given value is in range [lower, upper] */
+    private void assertInRange(String tag, long value, long lower, long upper) {
+        final Range range = new Range(lower, upper);
+        assertTrue(tag + ": " + value + " is not within range [" + lower + ", " + upper + "]",
+                range.contains(value));
+    }
+
     public void testValidMobileStats() {
         // We can't assume a mobile network is even present in this test, so
         // we simply assert that a valid value is returned.
@@ -53,6 +59,11 @@
         assertTrue(TrafficStats.getTotalRxBytes() >= 0);
     }
 
+    public void testValidPacketStats() {
+        assertTrue(TrafficStats.getTxPackets("lo") >= 0);
+        assertTrue(TrafficStats.getRxPackets("lo") >= 0);
+    }
+
     public void testThreadStatsTag() throws Exception {
         TrafficStats.setThreadStatsTag(0xf00d);
         assertTrue("Tag didn't stick", TrafficStats.getThreadStatsTag() == 0xf00d);
@@ -96,6 +107,8 @@
         final long uidRxBytesBefore = TrafficStats.getUidRxBytes(Process.myUid());
         final long uidTxPacketsBefore = TrafficStats.getUidTxPackets(Process.myUid());
         final long uidRxPacketsBefore = TrafficStats.getUidRxPackets(Process.myUid());
+        final long ifaceTxPacketsBefore = TrafficStats.getTxPackets("lo");
+        final long ifaceRxPacketsBefore = TrafficStats.getRxPackets("lo");
 
         // Transfer 1MB of data across an explicitly localhost socket.
         final int byteCount = 1024;
@@ -107,12 +120,12 @@
             @Override
             public void run() {
                 try {
-                    Socket socket = new Socket("localhost", server.getLocalPort());
+                    final Socket socket = new Socket("localhost", server.getLocalPort());
                     // Make sure that each write()+flush() turns into a packet:
                     // disable Nagle.
                     socket.setTcpNoDelay(true);
-                    OutputStream out = socket.getOutputStream();
-                    byte[] buf = new byte[byteCount];
+                    final OutputStream out = socket.getOutputStream();
+                    final byte[] buf = new byte[byteCount];
                     TrafficStats.setThreadStatsTag(0x42);
                     TrafficStats.tagSocket(socket);
                     for (int i = 0; i < packetCount; i++) {
@@ -135,12 +148,12 @@
 
         int read = 0;
         try {
-            Socket socket = server.accept();
+            final Socket socket = server.accept();
             socket.setTcpNoDelay(true);
             TrafficStats.setThreadStatsTag(0x43);
             TrafficStats.tagSocket(socket);
-            InputStream in = socket.getInputStream();
-            byte[] buf = new byte[byteCount];
+            final InputStream in = socket.getInputStream();
+            final byte[] buf = new byte[byteCount];
             while (read < byteCount * packetCount) {
                 int n = in.read(buf);
                 assertTrue("Unexpected EOF", n > 0);
@@ -156,24 +169,28 @@
             Thread.sleep(1000);
         } catch (InterruptedException e) {
         }
-        NetworkStats testStats = TrafficStats.stopDataProfiling(null);
+        final NetworkStats testStats = TrafficStats.stopDataProfiling(null);
 
-        long mobileTxPacketsAfter = TrafficStats.getMobileTxPackets();
-        long mobileRxPacketsAfter = TrafficStats.getMobileRxPackets();
-        long mobileTxBytesAfter = TrafficStats.getMobileTxBytes();
-        long mobileRxBytesAfter = TrafficStats.getMobileRxBytes();
-        long totalTxPacketsAfter = TrafficStats.getTotalTxPackets();
-        long totalRxPacketsAfter = TrafficStats.getTotalRxPackets();
-        long totalTxBytesAfter = TrafficStats.getTotalTxBytes();
-        long totalRxBytesAfter = TrafficStats.getTotalRxBytes();
-        long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
-        long uidRxBytesAfter = TrafficStats.getUidRxBytes(Process.myUid());
-        long uidTxPacketsAfter = TrafficStats.getUidTxPackets(Process.myUid());
-        long uidRxPacketsAfter = TrafficStats.getUidRxPackets(Process.myUid());
-        long uidTxDeltaBytes = uidTxBytesAfter - uidTxBytesBefore;
-        long uidTxDeltaPackets = uidTxPacketsAfter - uidTxPacketsBefore;
-        long uidRxDeltaBytes = uidRxBytesAfter - uidRxBytesBefore;
-        long uidRxDeltaPackets = uidRxPacketsAfter - uidRxPacketsBefore;
+        final long mobileTxPacketsAfter = TrafficStats.getMobileTxPackets();
+        final long mobileRxPacketsAfter = TrafficStats.getMobileRxPackets();
+        final long mobileTxBytesAfter = TrafficStats.getMobileTxBytes();
+        final long mobileRxBytesAfter = TrafficStats.getMobileRxBytes();
+        final long totalTxPacketsAfter = TrafficStats.getTotalTxPackets();
+        final long totalRxPacketsAfter = TrafficStats.getTotalRxPackets();
+        final long totalTxBytesAfter = TrafficStats.getTotalTxBytes();
+        final long totalRxBytesAfter = TrafficStats.getTotalRxBytes();
+        final long uidTxBytesAfter = TrafficStats.getUidTxBytes(Process.myUid());
+        final long uidRxBytesAfter = TrafficStats.getUidRxBytes(Process.myUid());
+        final long uidTxPacketsAfter = TrafficStats.getUidTxPackets(Process.myUid());
+        final long uidRxPacketsAfter = TrafficStats.getUidRxPackets(Process.myUid());
+        final long uidTxDeltaBytes = uidTxBytesAfter - uidTxBytesBefore;
+        final long uidTxDeltaPackets = uidTxPacketsAfter - uidTxPacketsBefore;
+        final long uidRxDeltaBytes = uidRxBytesAfter - uidRxBytesBefore;
+        final long uidRxDeltaPackets = uidRxPacketsAfter - uidRxPacketsBefore;
+        final long ifaceTxPacketsAfter = TrafficStats.getTxPackets("lo");
+        final long ifaceRxPacketsAfter = TrafficStats.getRxPackets("lo");
+        final long ifaceTxDeltaPackets = ifaceTxPacketsAfter - ifaceTxPacketsBefore;
+        final long ifaceRxDeltaPackets = ifaceRxPacketsAfter - ifaceRxPacketsBefore;
 
         // Localhost traffic *does* count against per-UID stats.
         /*
@@ -192,50 +209,46 @@
         // Some other tests don't cleanup connections correctly.
         // They have the same UID, so we discount their lingering traffic
         // which happens only on non-localhost, such as TCP FIN retranmission packets
-        long deltaTxOtherPackets = (totalTxPacketsAfter - totalTxPacketsBefore) - uidTxDeltaPackets;
-        long deltaRxOtherPackets = (totalRxPacketsAfter - totalRxPacketsBefore) - uidRxDeltaPackets;
+        final long deltaTxOtherPackets = (totalTxPacketsAfter - totalTxPacketsBefore)
+                - uidTxDeltaPackets;
+        final long deltaRxOtherPackets = (totalRxPacketsAfter - totalRxPacketsBefore)
+                - uidRxDeltaPackets;
         if (deltaTxOtherPackets > 0 || deltaRxOtherPackets > 0) {
-            Log.i(LOG_TAG, "lingering traffic data: " + deltaTxOtherPackets + "/" + deltaRxOtherPackets);
+            Log.i(LOG_TAG, "lingering traffic data: " + deltaTxOtherPackets + "/"
+                    + deltaRxOtherPackets);
         }
 
-        // Check the per uid stats read from data profiling have the stats expected. The data
-        // profiling snapshot is generated from readNetworkStatsDetail() method in
-        // networkStatsService and in this way we can verify the detail networkStats of a given uid
-        // is correct.
-        NetworkStats.Entry entry = testStats.getTotal(null, Process.myUid());
-        assertTrue("txPackets detail: " + entry.txPackets + " uidTxPackets: " + uidTxDeltaPackets,
-            entry.txPackets >= packetCount + minExpectedExtraPackets
-            && entry.txPackets <= uidTxDeltaPackets);
-        assertTrue("rxPackets detail: " + entry.rxPackets + " uidRxPackets: " + uidRxDeltaPackets,
-            entry.rxPackets >= packetCount + minExpectedExtraPackets
-            && entry.rxPackets <= uidRxDeltaPackets);
-        assertTrue("txBytes detail: " + entry.txBytes + " uidTxDeltaBytes: " + uidTxDeltaBytes,
-            entry.txBytes >= tcpPacketToIpBytes(packetCount, byteCount)
-            + tcpPacketToIpBytes(minExpectedExtraPackets, 0) && entry.txBytes <= uidTxDeltaBytes);
-        assertTrue("rxBytes detail: " + entry.rxBytes + " uidRxDeltaBytes: " + uidRxDeltaBytes,
-            entry.rxBytes >= tcpPacketToIpBytes(packetCount, byteCount)
-            + tcpPacketToIpBytes(minExpectedExtraPackets, 0) && entry.rxBytes <= uidRxDeltaBytes);
-
-        assertTrue("uidtxp: " + uidTxPacketsBefore + " -> " + uidTxPacketsAfter + " delta=" + uidTxDeltaPackets +
-            " Wanted: " + uidTxDeltaPackets + ">=" + packetCount + "+" + minExpectedExtraPackets + " && " +
-            uidTxDeltaPackets + "<=" + packetCount + "+" + packetCount + "+" + maxExpectedExtraPackets + "+" + deltaTxOtherPackets,
-            uidTxDeltaPackets >= packetCount + minExpectedExtraPackets &&
-            uidTxDeltaPackets <= packetCount + packetCount + maxExpectedExtraPackets + deltaTxOtherPackets);
-        assertTrue("uidrxp: " + uidRxPacketsBefore + " -> " + uidRxPacketsAfter + " delta=" + uidRxDeltaPackets +
-            " Wanted: " + uidRxDeltaPackets + ">=" + packetCount + "+" + minExpectedExtraPackets + " && " +
-            uidRxDeltaPackets + "<=" + packetCount + "+" + packetCount + "+" + maxExpectedExtraPackets,
-            uidRxDeltaPackets >= packetCount + minExpectedExtraPackets &&
-            uidRxDeltaPackets <= packetCount + packetCount + maxExpectedExtraPackets + deltaRxOtherPackets);
-        assertTrue("uidtxb: " + uidTxBytesBefore + " -> " + uidTxBytesAfter + " delta=" + uidTxDeltaBytes +
-            " Wanted: " + uidTxDeltaBytes + ">=" + tcpPacketToIpBytes(packetCount, byteCount) + "+" + tcpPacketToIpBytes(minExpectedExtraPackets, 0) + " && " +
-            uidTxDeltaBytes + "<=" + tcpPacketToIpBytes(packetCount, byteCount) + "+" + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets, 0),
-            uidTxDeltaBytes >= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(minExpectedExtraPackets, 0) &&
-            uidTxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets + deltaTxOtherPackets, 0));
-        assertTrue("uidrxb: " + uidRxBytesBefore + " -> " + uidRxBytesAfter + " delta=" + uidRxDeltaBytes +
-            " Wanted: " + uidRxDeltaBytes + ">=" + tcpPacketToIpBytes(packetCount, byteCount) + "+" + tcpPacketToIpBytes(minExpectedExtraPackets, 0) + " && " +
-            uidRxDeltaBytes + "<=" + tcpPacketToIpBytes(packetCount, byteCount) + "+" + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets, 0),
-            uidRxDeltaBytes >= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(minExpectedExtraPackets, 0) &&
-            uidRxDeltaBytes <= tcpPacketToIpBytes(packetCount, byteCount) + tcpPacketToIpBytes(packetCount + maxExpectedExtraPackets + deltaRxOtherPackets, 0));
+        // Check that the per-uid stats obtained from data profiling contain the expected values.
+        // The data profiling snapshot is generated from the readNetworkStatsDetail() method in
+        // networkStatsService, so it's possible to verify that the detailed stats for a given
+        // uid are correct.
+        final NetworkStats.Entry entry = testStats.getTotal(null, Process.myUid());
+        final long pktBytes = tcpPacketToIpBytes(packetCount, byteCount);
+        final long pktWithNoDataBytes = tcpPacketToIpBytes(packetCount, 0);
+        final long minExpExtraPktBytes = tcpPacketToIpBytes(minExpectedExtraPackets, 0);
+        final long maxExpExtraPktBytes = tcpPacketToIpBytes(maxExpectedExtraPackets, 0);
+        final long deltaTxOtherPktBytes = tcpPacketToIpBytes(deltaTxOtherPackets, 0);
+        final long deltaRxOtherPktBytes  = tcpPacketToIpBytes(deltaRxOtherPackets, 0);
+        assertInRange("txPackets detail", entry.txPackets, packetCount + minExpectedExtraPackets,
+                uidTxDeltaPackets);
+        assertInRange("rxPackets detail", entry.rxPackets, packetCount + minExpectedExtraPackets,
+                uidRxDeltaPackets);
+        assertInRange("txBytes detail", entry.txBytes, pktBytes + minExpExtraPktBytes,
+                uidTxDeltaBytes);
+        assertInRange("rxBytes detail", entry.rxBytes, pktBytes + minExpExtraPktBytes,
+                uidRxDeltaBytes);
+        assertInRange("uidtxp", uidTxDeltaPackets, packetCount + minExpectedExtraPackets,
+                packetCount + packetCount + maxExpectedExtraPackets + deltaTxOtherPackets);
+        assertInRange("uidrxp", uidRxDeltaPackets, packetCount + minExpectedExtraPackets,
+                packetCount + packetCount + maxExpectedExtraPackets + deltaRxOtherPackets);
+        assertInRange("uidtxb", uidTxDeltaBytes, pktBytes + minExpExtraPktBytes,
+                pktBytes + pktWithNoDataBytes + maxExpExtraPktBytes + deltaTxOtherPktBytes);
+        assertInRange("uidrxb", uidRxDeltaBytes, pktBytes + minExpExtraPktBytes,
+                pktBytes + pktWithNoDataBytes + maxExpExtraPktBytes + deltaRxOtherPktBytes);
+        assertInRange("iftxp", ifaceTxDeltaPackets, packetCount + minExpectedExtraPackets,
+                packetCount + packetCount + maxExpectedExtraPackets + deltaTxOtherPackets);
+        assertInRange("ifrxp", ifaceRxDeltaPackets, packetCount + minExpectedExtraPackets,
+                packetCount + packetCount + maxExpectedExtraPackets + deltaRxOtherPackets);
 
         // Localhost traffic *does* count against total stats.
         // Check the total stats increased after test data transfer over localhost has been made.
@@ -247,42 +260,20 @@
                 totalTxBytesAfter >= totalTxBytesBefore + uidTxDeltaBytes);
         assertTrue("trxb: " + totalRxBytesBefore + " -> " + totalRxBytesAfter,
                 totalRxBytesAfter >= totalRxBytesBefore + uidRxDeltaBytes);
-
-        // If the adb TCP port is opened, this test may be run by adb over network.
-        // Huge amount of data traffic might go through the network and accounted into total packets
-        // stats. The upper bound check would be meaningless.
-        // TODO: Consider precisely calculate the traffic accounted due to adb over network and
-        //       subtract it when checking upper bound instead of skip checking.
-        final PackageManager pm = mContext.getPackageManager();
-        if (SystemProperties.getInt("persist.adb.tcp.port", -1) > -1
-                || SystemProperties.getInt("service.adb.tcp.port", -1) > -1
-                || !pm.hasSystemFeature(PackageManager.FEATURE_USB_ACCESSORY)) {
-            Log.i(LOG_TAG, "adb is running over the network, skip the upper bound check");
-        } else {
-            // Fudge by 132 packets of 1500 bytes not related to the test.
-            assertTrue("ttxp: " + totalTxPacketsBefore + " -> " + totalTxPacketsAfter,
-                    totalTxPacketsAfter <= totalTxPacketsBefore + uidTxDeltaPackets + 132);
-            assertTrue("trxp: " + totalRxPacketsBefore + " -> " + totalRxPacketsAfter,
-                    totalRxPacketsAfter <= totalRxPacketsBefore + uidRxDeltaPackets + 132);
-            assertTrue("ttxb: " + totalTxBytesBefore + " -> " + totalTxBytesAfter,
-                    totalTxBytesAfter <= totalTxBytesBefore + uidTxDeltaBytes + 132 * 1500);
-            assertTrue("trxb: " + totalRxBytesBefore + " -> " + totalRxBytesAfter,
-                    totalRxBytesAfter <= totalRxBytesBefore + uidRxDeltaBytes + 132 * 1500);
-        }
+        assertTrue("iftxp: " + ifaceTxPacketsBefore + " -> " + ifaceTxPacketsAfter,
+                totalTxPacketsAfter >= totalTxPacketsBefore + ifaceTxDeltaPackets);
+        assertTrue("ifrxp: " + ifaceRxPacketsBefore + " -> " + ifaceRxPacketsAfter,
+                totalRxPacketsAfter >= totalRxPacketsBefore + ifaceRxDeltaPackets);
 
         // Localhost traffic should *not* count against mobile stats,
         // There might be some other traffic, but nowhere near 1MB.
-        assertTrue("mtxp: " + mobileTxPacketsBefore + " -> " + mobileTxPacketsAfter,
-            mobileTxPacketsAfter >= mobileTxPacketsBefore &&
-            mobileTxPacketsAfter <= mobileTxPacketsBefore + 500);
-        assertTrue("mrxp: " + mobileRxPacketsBefore + " -> " + mobileRxPacketsAfter,
-            mobileRxPacketsAfter >= mobileRxPacketsBefore &&
-            mobileRxPacketsAfter <= mobileRxPacketsBefore + 500);
-        assertTrue("mtxb: " + mobileTxBytesBefore + " -> " + mobileTxBytesAfter,
-            mobileTxBytesAfter >= mobileTxBytesBefore &&
-            mobileTxBytesAfter <= mobileTxBytesBefore + 200000);
-        assertTrue("mrxb: " + mobileRxBytesBefore + " -> " + mobileRxBytesAfter,
-            mobileRxBytesAfter >= mobileRxBytesBefore &&
-            mobileRxBytesAfter <= mobileRxBytesBefore + 200000);
+        assertInRange("mtxp", mobileTxPacketsAfter, mobileTxPacketsBefore,
+                mobileTxPacketsBefore + 500);
+        assertInRange("mrxp", mobileRxPacketsAfter, mobileRxPacketsBefore,
+                mobileRxPacketsBefore + 500);
+        assertInRange("mtxb", mobileTxBytesAfter, mobileTxBytesBefore,
+                mobileTxBytesBefore + 200000);
+        assertInRange("mrxb", mobileRxBytesAfter, mobileRxBytesBefore,
+                mobileRxBytesBefore + 200000);
     }
 }
diff --git a/tests/tests/net/src/android/net/cts/UrlQuerySanitizerTest.java b/tests/tests/net/src/android/net/cts/UrlQuerySanitizerTest.java
index 2d615bb..5a70928 100644
--- a/tests/tests/net/src/android/net/cts/UrlQuerySanitizerTest.java
+++ b/tests/tests/net/src/android/net/cts/UrlQuerySanitizerTest.java
@@ -16,15 +16,38 @@
 
 package android.net.cts;
 
-import java.util.List;
-import java.util.Set;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
 import android.net.UrlQuerySanitizer;
 import android.net.UrlQuerySanitizer.IllegalCharacterValueSanitizer;
 import android.net.UrlQuerySanitizer.ParameterValuePair;
 import android.net.UrlQuerySanitizer.ValueSanitizer;
-import android.test.AndroidTestCase;
+import android.os.Build;
 
-public class UrlQuerySanitizerTest extends AndroidTestCase {
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.testutils.DevSdkIgnoreRule;
+import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+import java.util.Set;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class UrlQuerySanitizerTest {
+    @Rule
+    public final DevSdkIgnoreRule mIgnoreRule = new DevSdkIgnoreRule();
+
     private static final int ALL_OK = IllegalCharacterValueSanitizer.ALL_OK;
 
     // URL for test.
@@ -41,6 +64,7 @@
     private static final String AGE = "age";
     private static final String HEIGHT = "height";
 
+    @Test
     public void testUrlQuerySanitizer() {
         MockUrlQuerySanitizer uqs = new MockUrlQuerySanitizer();
         assertFalse(uqs.getAllowUnregisteredParamaters());
@@ -209,6 +233,19 @@
 
     }
 
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q) // Only fixed in R
+    public void testScriptUrlOk_73822755() {
+        ValueSanitizer sanitizer = new UrlQuerySanitizer.IllegalCharacterValueSanitizer(
+                UrlQuerySanitizer.IllegalCharacterValueSanitizer.SCRIPT_URL_OK);
+        assertEquals("javascript:alert()", sanitizer.sanitize("javascript:alert()"));
+    }
+
+    @Test @IgnoreUpTo(Build.VERSION_CODES.Q) // Only fixed in R
+    public void testScriptUrlBlocked_73822755() {
+        ValueSanitizer sanitizer = UrlQuerySanitizer.getUrlAndSpaceLegal();
+        assertEquals("", sanitizer.sanitize("javascript:alert()"));
+    }
+
     private static class MockValueSanitizer implements ValueSanitizer{
 
         public String sanitize(String value) {
diff --git a/tests/tests/nfc/Android.bp b/tests/tests/nfc/Android.bp
new file mode 100644
index 0000000..077ee09
--- /dev/null
+++ b/tests/tests/nfc/Android.bp
@@ -0,0 +1,33 @@
+// 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: "CtsNfcTestCases",
+    defaults: ["cts_defaults"],
+    static_libs: [
+        "ctstestrunner-axt",
+        "compatibility-device-util-axt",
+    ],
+    srcs: ["src/**/*.java"],
+    // was: sdk_version: "current",
+    libs: [
+        "android.test.runner",
+        "android.test.base",
+    ],
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "general-tests",
+    ],
+}
diff --git a/tests/tests/nfc/AndroidManifest.xml b/tests/tests/nfc/AndroidManifest.xml
new file mode 100644
index 0000000..3e1d16a
--- /dev/null
+++ b/tests/tests/nfc/AndroidManifest.xml
@@ -0,0 +1,46 @@
+<?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.nfc.cts"
+          android:targetSandboxVersion="2">
+
+    <uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
+    <uses-permission android:name="android.permission.NFC" />
+    <uses-permission android:name="android.permission.NFC_PREFERRED_PAYMENT_INFO" />
+    <uses-permission android:name="android.permission.WRITE_SECURE_SETTINGS" />
+    <application>
+        <uses-library android:name="android.test.runner"/>
+
+        <service android:name=".CtsMyHostApduService" android:exported="true"
+                 android:permission="android.permission.BIND_NFC_SERVICE">
+            <intent-filter>
+                <action android:name="android.nfc.cardemulation.action.HOST_APDU_SERVICE"/>
+            </intent-filter>
+            <meta-data android:name="android.nfc.cardemulation.host_apdu_service"
+                       android:resource="@xml/payment_aid_list"/>
+        </service>
+    </application>
+
+    <!-- This is a self-instrumenting test package. -->
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:label="CTS tests for Nfc CardEmulation API"
+                     android:targetPackage="android.nfc.cts">
+        <meta-data android:name="listener"
+                   android:value="com.android.cts.runner.CtsTestRunListener"/>
+    </instrumentation>
+
+</manifest>
\ No newline at end of file
diff --git a/tests/tests/nfc/AndroidTest.xml b/tests/tests/nfc/AndroidTest.xml
new file mode 100644
index 0000000..cf798ff
--- /dev/null
+++ b/tests/tests/nfc/AndroidTest.xml
@@ -0,0 +1,31 @@
+<?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 CTS Nfc test cases">
+    <option name="test-suite-tag" value="cts"/>
+    <option name="config-descriptor:metadata" key="component" value="systems"/>
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+    <option name="not-shardable" value="true" />
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true"/>
+        <option name="test-file-name" value="CtsNfcTestCases.apk"/>
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest">
+        <option name="package" value="android.nfc.cts"/>
+        <option name="runtime-hint" value="10m10s"/>
+    </test>
+</configuration>
diff --git a/tests/tests/nfc/OWNERS b/tests/tests/nfc/OWNERS
new file mode 100644
index 0000000..d92b2ab
--- /dev/null
+++ b/tests/tests/nfc/OWNERS
@@ -0,0 +1,5 @@
+# Bug component: 48448
+alisher@google.com
+jackcwyu@google.com
+georgekgchang@google.com
+zachoverflow@google.com
diff --git a/apps/CtsVerifier/res/xml/device_admin_comp_profile.xml b/tests/tests/nfc/res/values/strings.xml
similarity index 66%
copy from apps/CtsVerifier/res/xml/device_admin_comp_profile.xml
copy to tests/tests/nfc/res/values/strings.xml
index 784258d..64f9905 100644
--- a/apps/CtsVerifier/res/xml/device_admin_comp_profile.xml
+++ b/tests/tests/nfc/res/values/strings.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!-- 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.
@@ -13,12 +13,6 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-
-<!-- BEGIN_INCLUDE(meta_data) -->
-<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
-    <uses-policies>
-        <wipe-data />
-        <watch-login />
-    </uses-policies>
-</device-admin>
-<!-- END_INCLUDE(meta_data) -->
+<resources>
+    <string name="CtsPaymentService">CTS Nfc Test Service</string>
+</resources>
diff --git a/tests/tests/nfc/res/xml/payment_aid_list.xml b/tests/tests/nfc/res/xml/payment_aid_list.xml
new file mode 100644
index 0000000..02c6de1
--- /dev/null
+++ b/tests/tests/nfc/res/xml/payment_aid_list.xml
@@ -0,0 +1,8 @@
+<host-apdu-service xmlns:android="http://schemas.android.com/apk/res/android"
+    android:description="@string/CtsPaymentService">
+    <aid-group android:description="@string/CtsPaymentService" android:category="payment">
+        <aid-filter android:name="A000000004101011"/>
+        <aid-filter android:name="A000000004101012"/>
+        <aid-filter android:name="A000000004101013"/>
+    </aid-group>
+</host-apdu-service>
diff --git a/tests/tests/nfc/src/android/nfc/cts/CtsMyHostApduService.java b/tests/tests/nfc/src/android/nfc/cts/CtsMyHostApduService.java
new file mode 100644
index 0000000..0e32bbd
--- /dev/null
+++ b/tests/tests/nfc/src/android/nfc/cts/CtsMyHostApduService.java
@@ -0,0 +1,16 @@
+package android.nfc.cts;
+
+import android.nfc.cardemulation.*;
+import android.os.Bundle;
+
+public class CtsMyHostApduService extends HostApduService {
+    @Override
+    public byte[] processCommandApdu(byte[] apdu, Bundle extras) {
+        return new byte[0];
+    }
+
+    @Override
+    public void onDeactivated(int reason) {
+        return;
+    }
+}
diff --git a/tests/tests/nfc/src/android/nfc/cts/NfcPreferredPaymentTest.java b/tests/tests/nfc/src/android/nfc/cts/NfcPreferredPaymentTest.java
new file mode 100644
index 0000000..c55edc4
--- /dev/null
+++ b/tests/tests/nfc/src/android/nfc/cts/NfcPreferredPaymentTest.java
@@ -0,0 +1,119 @@
+/*
+ * 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.nfc.cts;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.nfc.NfcAdapter;
+import android.nfc.cardemulation.CardEmulation;
+import android.provider.Settings;
+import android.util.Log;
+
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+public class NfcPreferredPaymentTest {
+    private final static String mTag = "Nfc";
+
+    private final static String mRouteDestination = "Host";
+    private final static String mDescription = "CTS Nfc Test Service";
+    private final static List<String> mAids = Arrays.asList("A000000004101011",
+                                                            "A000000004101012",
+                                                            "A000000004101013");
+    private static final ComponentName CtsNfcTestService =
+            new ComponentName("android.nfc.cts", "android.nfc.cts.CtsMyHostApduService");
+
+    private NfcAdapter mAdapter;
+    private CardEmulation mCardEmulation;
+    private Context mContext;
+
+    private boolean supportsHardware() {
+        final PackageManager pm = InstrumentationRegistry.getContext().getPackageManager();
+        return pm.hasSystemFeature(PackageManager.FEATURE_NFC);
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue(supportsHardware());
+        mContext = InstrumentationRegistry.getContext();
+        mAdapter = NfcAdapter.getDefaultAdapter(mContext);
+        assertNotNull(mAdapter);
+        mCardEmulation = CardEmulation.getInstance(mAdapter);
+        Settings.Secure.putString(mContext.getContentResolver(),
+                Settings.Secure.NFC_PAYMENT_DEFAULT_COMPONENT,
+                CtsNfcTestService.flattenToString());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+    }
+
+    /** Tests getAidsForPreferredPaymentService API */
+    @Test
+    public void testAidsForPreferredPaymentService() {
+        try {
+            List<String> aids = mCardEmulation.getAidsForPreferredPaymentService();
+            for (String aid :aids) {
+                Log.i(mTag, "AidsForPreferredPaymentService: " + aid);
+            }
+
+            assertTrue("Retrieve incorrect preferred payment aid list", mAids.equals(aids));
+        } catch (Exception e) {
+            fail("Unexpected Exception " + e);
+        }
+    }
+
+    /** Tests getRouteDestinationForPreferredPaymentService API */
+    @Test
+    public void testRouteDestinationForPreferredPaymentService() {
+        try {
+            String routeDestination =
+                    mCardEmulation.getRouteDestinationForPreferredPaymentService();
+            Log.i(mTag, "RouteDestinationForPreferredPaymentService: " + routeDestination);
+
+            assertTrue("Retrieve incorrect preferred payment route destination",
+                    routeDestination.equals(mRouteDestination));
+        } catch (Exception e) {
+            fail("Unexpected Exception " + e);
+        }
+    }
+
+    /** Tests getDescriptionForPreferredPaymentService API */
+    @Test
+    public void testDescriptionForPreferredPaymentService() {
+        try {
+            CharSequence description = mCardEmulation.getDescriptionForPreferredPaymentService();
+            Log.i(mTag, "DescriptionForPreferredPaymentService: " + description.toString());
+
+            assertTrue("Retrieve incorrect preferred payment description",
+                description.toString().equals(mDescription.toString()));
+        } catch (Exception e) {
+            fail("Unexpected Exception " + e);
+        }
+    }
+
+}
diff --git a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
index cc68322..e8fe4fd 100644
--- a/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
+++ b/tests/tests/os/src/android/os/cts/AutoRevokeTest.kt
@@ -17,23 +17,32 @@
 package android.os.cts
 
 import android.Manifest.permission.READ_CALENDAR
+import android.content.Intent
+import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
+import android.content.pm.PackageManager
 import android.content.pm.PackageManager.PERMISSION_DENIED
 import android.content.pm.PackageManager.PERMISSION_GRANTED
 import android.graphics.Rect
+import android.net.Uri
 import android.platform.test.annotations.AppModeFull
 import android.provider.DeviceConfig
+import android.provider.Settings.*
 import android.support.test.uiautomator.By
 import android.support.test.uiautomator.BySelector
 import android.support.test.uiautomator.UiObject2
 import android.test.InstrumentationTestCase
 import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.Switch
+import com.android.compatibility.common.util.SystemUtil
 import com.android.compatibility.common.util.SystemUtil.*
 import com.android.compatibility.common.util.ThrowingSupplier
 import com.android.compatibility.common.util.UiAutomatorUtils
 import com.android.compatibility.common.util.UiDumpUtils
 import org.hamcrest.CoreMatchers.containsString
 import org.junit.Assert.assertThat
+import java.lang.reflect.Modifier
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicReference
 import java.util.regex.Pattern
 
 private const val APK_PATH = "/data/local/tmp/cts/os/CtsAutoRevokeDummyApp.apk"
@@ -42,8 +51,6 @@
 /**
  * Test for auto revoke
  */
-// TODO test pregrants exempt
-// TODO test manifest whitelist
 class AutoRevokeTest : InstrumentationTestCase() {
 
     companion object {
@@ -53,11 +60,14 @@
     @AppModeFull(reason = "Uses separate apps for testing")
     fun testUnusedApp_getsPermissionRevoked() {
         wakeUpScreen()
-        withDeviceConfig("auto_revoke_unused_threshold_millis", "1") {
+        withUnusedThresholdMs(3L) {
             withDummyApp {
                 // Setup
                 startApp()
                 clickPermissionAllow()
+                eventually {
+                    assertPermission(PERMISSION_GRANTED)
+                }
                 goHome()
                 Thread.sleep(5)
 
@@ -72,35 +82,59 @@
         }
     }
 
-    // TODO once implemented, use:
-    // Intent(Intent.ACTION_AUTO_REVOKE_PERMISSIONS).putExtra(Intent.EXTRA_PACKAGE_NAME, packageName)
+    @AppModeFull(reason = "Uses separate apps for testing")
+    fun testUsedApp_doesntGetPermissionRevoked() {
+        wakeUpScreen()
+        withUnusedThresholdMs(100_000L) {
+            withDummyApp {
+                // Setup
+                startApp()
+                clickPermissionAllow()
+                eventually {
+                    assertPermission(PERMISSION_GRANTED)
+                }
+                goHome()
+                Thread.sleep(5)
+
+                // Run
+                runAutoRevoke()
+                Thread.sleep(500)
+
+                // Verify
+                assertPermission(PERMISSION_GRANTED)
+            }
+        }
+    }
+
     @AppModeFull(reason = "Uses separate apps for testing")
     fun testAutoRevoke_userWhitelisting() {
         wakeUpScreen()
-        withDummyApp {
-            // Setup
-            startApp()
-            clickPermissionAllow()
-            assertWhitelistState(false)
+        withUnusedThresholdMs(TimeUnit.DAYS.toMillis(30)) {
+            withDummyApp {
+                // Setup
+                startApp()
+                clickPermissionAllow()
+                assertWhitelistState(false)
 
-            // Verify
-            waitFindObject(byTextIgnoreCase("Request whitelist")).click()
-            waitFindObject(byTextIgnoreCase("Permissions")).click()
-            val autoRevokeEnabledToggle = getWhitelistToggle()
-            assertTrue(autoRevokeEnabledToggle.isChecked)
+                // Verify
+                waitFindObject(byTextIgnoreCase("Request whitelist")).click()
+                waitFindObject(byTextIgnoreCase("Permissions")).click()
+                val autoRevokeEnabledToggle = getWhitelistToggle()
+                assertTrue(autoRevokeEnabledToggle.isChecked)
 
-            // Grant whitelist
-            autoRevokeEnabledToggle.click()
-            eventually {
-                assertFalse(getWhitelistToggle().isChecked)
+                // Grant whitelist
+                autoRevokeEnabledToggle.click()
+                eventually {
+                    assertFalse(getWhitelistToggle().isChecked)
+                }
+
+                // Verify
+                goBack()
+                goBack()
+                goBack()
+                startApp()
+                assertWhitelistState(true)
             }
-
-            // Verify
-            goBack()
-            goBack()
-            goBack()
-            startApp()
-            assertWhitelistState(true)
         }
     }
 
@@ -108,19 +142,24 @@
     @AppModeFull(reason = "Uses separate apps for testing")
     fun _testInstallGrants_notRevokedImmediately() {
         wakeUpScreen()
-        withDummyApp {
-            // Setup
-            instrumentation.uiAutomation.grantRuntimePermission(APK_PACKAGE_NAME, READ_CALENDAR)
-            eventually {
+        withUnusedThresholdMs(TimeUnit.DAYS.toMillis(30)) {
+            withDummyApp {
+                // Setup
+                runWithShellPermissionIdentity {
+                    instrumentation.uiAutomation
+                            .grantRuntimePermission(APK_PACKAGE_NAME, READ_CALENDAR)
+                }
+                eventually {
+                    assertPermission(PERMISSION_GRANTED)
+                }
+
+                // Run
+                runAutoRevoke()
+                Thread.sleep(500)
+
+                // Verify
                 assertPermission(PERMISSION_GRANTED)
             }
-
-            // Run
-            runAutoRevoke()
-            Thread.sleep(500)
-
-            // Verify
-            assertPermission(PERMISSION_GRANTED)
         }
     }
 
@@ -135,25 +174,31 @@
     }
 
     private inline fun <T> withDeviceConfig(
+        namespace: String,
         name: String,
         value: String,
         action: () -> T
     ): T {
         val oldValue = runWithShellPermissionIdentity(ThrowingSupplier {
-            DeviceConfig.getProperty("permissions", name)
+            DeviceConfig.getProperty(namespace, name)
         })
         try {
             runWithShellPermissionIdentity {
-                DeviceConfig.setProperty("permissions", name, value, false /* makeDefault */)
+                DeviceConfig.setProperty(namespace, name, value, false /* makeDefault */)
             }
             return action()
         } finally {
             runWithShellPermissionIdentity {
-                DeviceConfig.setProperty("permissions", name, oldValue, false /* makeDefault */)
+                DeviceConfig.setProperty(namespace, name, oldValue, false /* makeDefault */)
             }
         }
     }
 
+    private inline fun <T> withUnusedThresholdMs(threshold: Long, action: () -> T): T {
+        return withDeviceConfig(
+                "permissions", "auto_revoke_unused_threshold_millis", threshold.toString(), action)
+    }
+
     private fun installApp() {
         assertThat(runShellCommand("pm install -r $APK_PATH"), containsString("Success"))
     }
@@ -189,37 +234,77 @@
     }
 
     private fun assertPermission(state: Int) {
-        assertEquals(
-                state,
-                context.packageManager.checkPermission(READ_CALENDAR, APK_PACKAGE_NAME))
+        // For some reason this incorrectly always returns PERMISSION_DENIED
+//        runWithShellPermissionIdentity {
+//            assertEquals(
+//                permissionStateToString(state),
+//                permissionStateToString(context.packageManager.checkPermission(READ_CALENDAR, APK_PACKAGE_NAME)))
+//        }
+
+        try {
+            context.startActivity(Intent(ACTION_APPLICATION_DETAILS_SETTINGS)
+                    .setData(Uri.fromParts("package", APK_PACKAGE_NAME, null))
+                    .addFlags(FLAG_ACTIVITY_NEW_TASK))
+
+            waitFindObject(byTextIgnoreCase("Permissions")).click()
+
+            waitForIdle()
+            val ui = instrumentation.uiAutomation.rootInActiveWindow
+            val permStateSection = ui.lowestCommonAncestor(
+                    { textAsString.equals("Allowed", ignoreCase = true) },
+                    { textAsString.equals("Denied", ignoreCase = true) }
+            ).assertNotNull {
+                "Cannot find permissions state section in\n${dumpUi(ui)}"
+            }
+            val sectionHeaderIndex = permStateSection.children.indexOfFirst {
+                it?.depthFirstSearch {
+                    textAsString.equals(
+                            if (state == PERMISSION_GRANTED) "Allowed" else "Denied",
+                            ignoreCase = true)
+                } != null
+            }
+            permStateSection.getChild(sectionHeaderIndex + 1).depthFirstSearch {
+                textAsString.equals("Calendar", ignoreCase = true)
+            }.assertNotNull {
+                "Permission must be ${permissionStateToString(state)}\n${dumpUi(ui)}"
+            }
+        } finally {
+            goBack()
+            goBack()
+        }
     }
 
     private fun assertWhitelistState(state: Boolean) {
         assertThat(
-                waitFindObject(By.textStartsWith("Auto-revoke whitelisted: ")).text,
-                containsString(state.toString()))
+            waitFindObject(By.textStartsWith("Auto-revoke whitelisted: ")).text,
+            containsString(state.toString()))
     }
 
     private fun getWhitelistToggle(): AccessibilityNodeInfo {
         waitForIdle()
-        val ui = instrumentation.uiAutomation.rootInActiveWindow
-        return ui.depthFirstSearch {
-            depthFirstSearch {
-                (text as CharSequence?).toString() == "Remove permissions if app isn’t used"
-            } != null &&
-                    depthFirstSearch { className == Switch::class.java.name } != null
-        }.assertNotNull {
-            "No auto-revoke whitelist toggle found in\n" +
-                    buildString { UiDumpUtils.dumpNodes(ui, this) }
-        }.depthFirstSearch { className == Switch::class.java.name }!!
+        return eventually {
+            val ui = instrumentation.uiAutomation.rootInActiveWindow
+            return@eventually ui.lowestCommonAncestor(
+                { textAsString == "Remove permissions if app isn’t used" },
+                { className == Switch::class.java.name }
+            ).assertNotNull {
+                "No auto-revoke whitelist toggle found in\n${dumpUi(ui)}"
+            }.depthFirstSearch { className == Switch::class.java.name }!!
+        }
     }
 
     private fun waitForIdle() {
         instrumentation.uiAutomation.waitForIdle(2000, 5000)
+        Thread.sleep(500)
+        instrumentation.uiAutomation.waitForIdle(2000, 5000)
     }
 
-    private fun <T> T?.assertNotNull(errorMsg: () -> String): T {
-        return if (this == null) throw AssertionError(errorMsg()) else this
+    private inline fun <T> eventually(crossinline action: () -> T): T {
+        val res = AtomicReference<T>()
+        SystemUtil.eventually {
+            res.set(action())
+        }
+        return res.get()
     }
 
     private fun waitFindObject(selector: BySelector): UiObject2 {
@@ -228,7 +313,7 @@
         } catch (e: RuntimeException) {
             val ui = instrumentation.uiAutomation.rootInActiveWindow
 
-            val title = ui.depthFirstSearch { viewIdResourceName?.contains("alertTitle") == true  }
+            val title = ui.depthFirstSearch { viewIdResourceName?.contains("alertTitle") == true }
             val okButton = ui.depthFirstSearch {
                 (text as CharSequence?)?.toString()?.equals("OK", ignoreCase = true) ?: false
             }
@@ -247,6 +332,10 @@
     private fun byTextIgnoreCase(txt: String): BySelector {
         return By.text(Pattern.compile(txt, Pattern.CASE_INSENSITIVE))
     }
+
+    private fun permissionStateToString(state: Int): String {
+        return constToString<PackageManager>("PERMISSION_", state)
+    }
 }
 
 val AccessibilityNodeInfo.bounds: Rect get() = Rect().also { getBoundsInScreen(it) }
@@ -259,10 +348,42 @@
     condition: AccessibilityNodeInfo.() -> Boolean
 ): AccessibilityNodeInfo? {
     for (child in children) {
-        child.depthFirstSearch(condition)?.let { return it }
+        child?.depthFirstSearch(condition)?.let { return it }
     }
     if (this.condition()) return this
     return null
 }
 
-val AccessibilityNodeInfo.children get() = List(childCount) { i -> getChild(i) }
+fun AccessibilityNodeInfo.lowestCommonAncestor(
+    condition1: AccessibilityNodeInfo.() -> Boolean,
+    condition2: AccessibilityNodeInfo.() -> Boolean
+): AccessibilityNodeInfo? {
+    return depthFirstSearch {
+        depthFirstSearch(condition1) != null &&
+            depthFirstSearch(condition2) != null
+    }
+}
+
+val AccessibilityNodeInfo.children: List<AccessibilityNodeInfo?> get() =
+    List(childCount) { i -> getChild(i) }
+
+val AccessibilityNodeInfo.textAsString: String? get() = (text as CharSequence?).toString()
+
+inline fun <reified T> constToString(prefix: String, value: Int): String {
+    return T::class.java.declaredFields.filter {
+        Modifier.isStatic(it.modifiers) && it.name.startsWith(prefix)
+    }.map {
+        it.isAccessible = true
+        it.name to it.get(null)
+    }.find { (k, v) ->
+        v == value
+    }.assertNotNull {
+        "None of ${T::class.java.simpleName}.$prefix* == $value"
+    }.first
+}
+
+inline fun <T> T?.assertNotNull(errorMsg: () -> String): T {
+    return if (this == null) throw AssertionError(errorMsg()) else this
+}
+
+fun dumpUi(ui: AccessibilityNodeInfo?) = buildString { UiDumpUtils.dumpNodes(ui, this) }
\ No newline at end of file
diff --git a/tests/tests/os/src/android/os/cts/EnvironmentTest.java b/tests/tests/os/src/android/os/cts/EnvironmentTest.java
index 0b02291..a23854a 100644
--- a/tests/tests/os/src/android/os/cts/EnvironmentTest.java
+++ b/tests/tests/os/src/android/os/cts/EnvironmentTest.java
@@ -15,11 +15,17 @@
  */
 package android.os.cts;
 
+import static androidx.test.InstrumentationRegistry.getContext;
+
+import android.app.AppOpsManager;
 import android.os.Environment;
+import android.os.Process;
 import android.platform.test.annotations.AppModeFull;
 import android.system.Os;
 import android.system.StructStatVfs;
 
+import androidx.test.InstrumentationRegistry;
+
 import junit.framework.TestCase;
 
 import java.io.BufferedReader;
@@ -127,4 +133,50 @@
               + minInodes + "," + maxInodes + "]");
         }
     }
+
+    public void testIsExternalStorageManager() throws Exception {
+        final int initialMode = getContext().getSystemService(AppOpsManager.class)
+                .unsafeCheckOpNoThrow(AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE, Process.myUid(),
+                        getContext().getPackageName());
+
+        try {
+            setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_DEFAULT,
+                    AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE);
+            // By default, this test app is not an external storage manager
+            assertFalse(Environment.isExternalStorageManager());
+            // Allow the external storage manager app-op to the test app. This mirrors what happens
+            // when the user grants this special app access to an app.
+            setAppOpsModeForUid(Process.myUid(), AppOpsManager.MODE_ALLOWED,
+                    AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE);
+            // Once we allow the right app-op for the test, it becomes an external storage manager
+            assertTrue(Environment.isExternalStorageManager());
+        } finally {
+            setAppOpsModeForUid(Process.myUid(), initialMode,
+                    AppOpsManager.OPSTR_MANAGE_EXTERNAL_STORAGE);
+        }
+    }
+
+    /**
+     * Sets {@code mode} for the given {@code ops} and the given {@code uid}.
+     *
+     * <p>This method drops shell permission identity.
+     */
+    private static void setAppOpsModeForUid(int uid, int mode, String... ops) {
+        if (ops == null) {
+            return;
+        }
+        InstrumentationRegistry.getInstrumentation()
+                .getUiAutomation()
+                .adoptShellPermissionIdentity();
+        try {
+            for (String op : ops) {
+                InstrumentationRegistry.getContext().getSystemService(AppOpsManager.class)
+                        .setUidMode(op, uid, mode);
+            }
+        } finally {
+            InstrumentationRegistry.getInstrumentation()
+                    .getUiAutomation()
+                    .dropShellPermissionIdentity();
+        }
+    }
 }
diff --git a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/InstallSourceInfoTest.kt b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/InstallSourceInfoTest.kt
index 50853e3..42fddd8 100644
--- a/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/InstallSourceInfoTest.kt
+++ b/tests/tests/packageinstaller/install/src/android/packageinstaller/install/cts/InstallSourceInfoTest.kt
@@ -16,7 +16,11 @@
 package android.packageinstaller.install.cts
 
 import android.app.Activity
+import android.content.Intent
+import android.content.Intent.ACTION_INSTALL_PACKAGE
 import android.content.pm.PackageInstaller
+import android.content.pm.PackageManager.MATCH_DEFAULT_ONLY
+import android.net.Uri
 import android.platform.test.annotations.AppModeFull
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.platform.app.InstrumentationRegistry
@@ -26,7 +30,6 @@
 import java.util.concurrent.TimeUnit
 
 private const val INSTALL_BUTTON_ID = "button1"
-private const val NEW_PACKAGE_INSTALLER_PACKAGE_NAME = "com.google.android.packageinstaller"
 
 @RunWith(AndroidJUnit4::class)
 @AppModeFull(reason = "Instant apps cannot install packages")
@@ -37,6 +40,8 @@
 
     @Test
     fun installViaIntent() {
+        val packageInstallerPackageName = getPackageInstallerPackageName()
+
         val installation = startInstallationViaIntent()
         clickInstallerUIButton(INSTALL_BUTTON_ID)
 
@@ -44,8 +49,8 @@
         assertThat(installation.get(TIMEOUT, TimeUnit.MILLISECONDS)).isEqualTo(Activity.RESULT_OK)
 
         val info = pm.getInstallSourceInfo(TEST_APK_PACKAGE_NAME)
-        assertThat(info.getInstallingPackageName()).isEqualTo(NEW_PACKAGE_INSTALLER_PACKAGE_NAME)
-        assertThat(info.getInitiatingPackageName()).isEqualTo(NEW_PACKAGE_INSTALLER_PACKAGE_NAME)
+        assertThat(info.getInstallingPackageName()).isEqualTo(packageInstallerPackageName)
+        assertThat(info.getInitiatingPackageName()).isEqualTo(packageInstallerPackageName)
         assertThat(info.getOriginatingPackageName()).isNull()
     }
 
@@ -62,4 +67,11 @@
         assertThat(info.getInitiatingPackageName()).isEqualTo(ourPackageName)
         assertThat(info.getOriginatingPackageName()).isNull()
     }
+
+    private fun getPackageInstallerPackageName(): String {
+        val installerIntent = Intent(ACTION_INSTALL_PACKAGE)
+        installerIntent.setDataAndType(Uri.parse("content://com.example/"),
+                "application/vnd.android.package-archive")
+        return installerIntent.resolveActivityInfo(pm, MATCH_DEFAULT_ONLY).packageName
+    }
 }
diff --git a/tests/tests/permission/AndroidTest.xml b/tests/tests/permission/AndroidTest.xml
index e37b956..102741c 100644
--- a/tests/tests/permission/AndroidTest.xml
+++ b/tests/tests/permission/AndroidTest.xml
@@ -58,6 +58,8 @@
         <option name="push" value="CtsVictimPermissionDefinerApp.apk->/data/local/tmp/cts/permissions/CtsVictimPermissionDefinerApp.apk" />
         <option name="push" value="CtsRuntimePermissionDefinerApp.apk->/data/local/tmp/cts/permissions/CtsRuntimePermissionDefinerApp.apk" />
         <option name="push" value="CtsRuntimePermissionUserApp.apk->/data/local/tmp/cts/permissions/CtsRuntimePermissionUserApp.apk" />
+        <option name="push" value="CtsInstalltimePermissionDefinerApp.apk->/data/local/tmp/cts/permissions/CtsInstalltimePermissionDefinerApp.apk" />
+        <option name="push" value="CtsInstalltimePermissionUserApp.apk->/data/local/tmp/cts/permissions/CtsInstalltimePermissionUserApp.apk" />
         <option name="push" value="CtsAppThatRequestsOneTimePermission.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestsOneTimePermission.apk" />
     </target_preparer>
 
diff --git a/tests/tests/permission/AppThatRequestOneTimePermission/src/android/permission/cts/appthatrequestpermission/KeepAliveForegroundService.java b/tests/tests/permission/AppThatRequestOneTimePermission/src/android/permission/cts/appthatrequestpermission/KeepAliveForegroundService.java
index af296a2..e41a473 100644
--- a/tests/tests/permission/AppThatRequestOneTimePermission/src/android/permission/cts/appthatrequestpermission/KeepAliveForegroundService.java
+++ b/tests/tests/permission/AppThatRequestOneTimePermission/src/android/permission/cts/appthatrequestpermission/KeepAliveForegroundService.java
@@ -30,9 +30,14 @@
     private static final String EXTRA_FOREGROUND_SERVICE_LIFESPAN =
             "android.permission.cts.OneTimePermissionTest.EXTRA_FOREGROUND_SERVICE_LIFESPAN";
 
+    private static final String EXTRA_FOREGROUND_SERVICE_STICKY =
+            "android.permission.cts.OneTimePermissionTest.EXTRA_FOREGROUND_SERVICE_STICKY";
+
     private static final String CHANNEL_ID = "channelId";
     private static final String CHANNEL_NAME = "channelName";
 
+    private static final long DEFAULT_LIFESPAN = 5000;
+
     @Override
     public IBinder onBind(Intent intent) {
         return null;
@@ -40,7 +45,15 @@
 
     @Override
     public int onStartCommand(Intent intent, int flags, int startId) {
-        long lifespan = intent.getLongExtra(EXTRA_FOREGROUND_SERVICE_LIFESPAN, 5000);
+        long lifespan;
+        boolean sticky;
+        if (intent == null) {
+            lifespan = DEFAULT_LIFESPAN;
+            sticky = false;
+        } else {
+            lifespan = intent.getLongExtra(EXTRA_FOREGROUND_SERVICE_LIFESPAN, DEFAULT_LIFESPAN);
+            sticky = intent.getBooleanExtra(EXTRA_FOREGROUND_SERVICE_STICKY, false);
+        }
         NotificationManager notificationManager = getSystemService(NotificationManager.class);
         notificationManager.createNotificationChannel(
                 new NotificationChannel(CHANNEL_ID, CHANNEL_NAME,
@@ -51,6 +64,9 @@
         startForeground(1, notification);
         new Handler(Looper.getMainLooper()).postDelayed(
                 () -> stopForeground(Service.STOP_FOREGROUND_REMOVE), lifespan);
+        if (sticky) {
+            return START_STICKY;
+        }
         return super.onStartCommand(intent, flags, startId);
     }
 }
diff --git a/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java b/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java
index 86e1260..a884a94 100644
--- a/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java
+++ b/tests/tests/permission/permissionTestUtilLib/src/android/permission/cts/PermissionUtils.java
@@ -210,7 +210,7 @@
             }
         } else if (permission.equals(PACKAGE_USAGE_STATS)) {
             setAppOpByName(packageName, OPSTR_GET_USAGE_STATS, MODE_ALLOWED);
-        } else {
+        } else if (permissionToOp(permission) != null) {
             setAppOp(packageName, permission, MODE_ALLOWED);
         }
     }
@@ -237,7 +237,7 @@
             }
         } else if (permission.equals(PACKAGE_USAGE_STATS)) {
             setAppOpByName(packageName, OPSTR_GET_USAGE_STATS, MODE_IGNORED);
-        } else {
+        } else if (permissionToOp(permission) != null) {
             setAppOp(packageName, permission, MODE_IGNORED);
         }
     }
diff --git a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
index c578c19..2ac729a 100644
--- a/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
+++ b/tests/tests/permission/src/android/permission/cts/LocationAccessCheckTest.java
@@ -21,6 +21,7 @@
 import static android.app.Notification.EXTRA_TITLE;
 import static android.content.Context.BIND_AUTO_CREATE;
 import static android.content.Intent.ACTION_BOOT_COMPLETED;
+import static android.content.Intent.FLAG_RECEIVER_FOREGROUND;
 import static android.location.Criteria.ACCURACY_FINE;
 import static android.provider.Settings.RESET_MODE_PACKAGE_DEFAULTS;
 import static android.provider.Settings.Secure.LOCATION_ACCESS_CHECK_DELAY_MILLIS;
@@ -449,21 +450,23 @@
             String pkg = ri.activityInfo.packageName;
 
             if (pkg.equals(PERMISSION_CONTROLLER_PKG)) {
-                permissionControllerSetupIntent = new Intent();
-                permissionControllerSetupIntent.setClassName(pkg, ri.activityInfo.name);
-            }
-        }
+                permissionControllerSetupIntent = new Intent()
+                        .setClassName(pkg, ri.activityInfo.name)
+                        .setFlags(FLAG_RECEIVER_FOREGROUND)
+                        .setPackage(PERMISSION_CONTROLLER_PKG);
 
-        if (permissionControllerSetupIntent != null) {
-            sContext.sendBroadcast(permissionControllerSetupIntent);
+                sContext.sendBroadcast(permissionControllerSetupIntent);
+            }
         }
 
         // Wait until jobs are set up
         eventually(() -> {
             JobSchedulerServiceDumpProto dump = getJobSchedulerDump();
+
             for (RegisteredJob job : dump.registeredJobs) {
                 if (job.dump.sourceUserId == currentUserId
-                        && job.dump.sourcePackageName.equals(PERMISSION_CONTROLLER_PKG)) {
+                        && job.dump.sourcePackageName.equals(PERMISSION_CONTROLLER_PKG)
+                        && job.dump.jobInfo.service.className.contains("LocationAccessCheck")) {
                     return;
                 }
             }
diff --git a/tests/tests/permission/src/android/permission/cts/OneTimePermissionTest.java b/tests/tests/permission/src/android/permission/cts/OneTimePermissionTest.java
index bf20718..656f6ad 100644
--- a/tests/tests/permission/src/android/permission/cts/OneTimePermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/OneTimePermissionTest.java
@@ -17,7 +17,9 @@
 package android.permission.cts;
 
 import static android.Manifest.permission.ACCESS_FINE_LOCATION;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
 import static com.android.compatibility.common.util.SystemUtil.eventually;
@@ -52,9 +54,11 @@
             "/data/local/tmp/cts/permissions/CtsAppThatRequestsOneTimePermission.apk";
     private static final String EXTRA_FOREGROUND_SERVICE_LIFESPAN =
             "android.permission.cts.OneTimePermissionTest.EXTRA_FOREGROUND_SERVICE_LIFESPAN";
+    private static final String EXTRA_FOREGROUND_SERVICE_STICKY =
+            "android.permission.cts.OneTimePermissionTest.EXTRA_FOREGROUND_SERVICE_STICKY";
 
     private static final long ONE_TIME_TIMEOUT_MILLIS = 5000;
-    private static final long ONE_TIME_TIMER_LOWER_GRACE_PERIOD = 500;
+    private static final long ONE_TIME_TIMER_LOWER_GRACE_PERIOD = 1000;
     private static final long ONE_TIME_TIMER_UPPER_GRACE_PERIOD = 10000;
 
     private final Context mContext =
@@ -126,7 +130,7 @@
         clickOneTimeButton();
 
         long expectedLifespanMillis = 2 * ONE_TIME_TIMEOUT_MILLIS;
-        startAppForegroundService(expectedLifespanMillis);
+        startAppForegroundService(expectedLifespanMillis, false);
 
         exitApp();
 
@@ -149,14 +153,48 @@
         assertGranted(5000);
 
         mUiDevice.waitForIdle();
-
-        ActivityManager activityManager = mContext.getSystemService(ActivityManager.class);
         SystemUtil.runWithShellPermissionIdentity(() ->
-                activityManager.killBackgroundProcesses(APP_PKG_NAME));
+                mActivityManager.killBackgroundProcesses(APP_PKG_NAME));
 
+        runWithShellPermissionIdentity(
+                () -> Thread.sleep(DeviceConfig.getLong(DeviceConfig.NAMESPACE_PERMISSIONS,
+                "one_time_permissions_killed_delay_millis", 5000L)));
         assertDenied(500);
     }
 
+    @Test
+    public void testStickyServiceMaintainsPermissionOnRestart() throws Throwable {
+        startApp();
+
+        clickOneTimeButton();
+
+        startAppForegroundService(2 * ONE_TIME_TIMEOUT_MILLIS, true);
+
+        exitApp();
+
+        assertGranted(5000);
+        mUiDevice.waitForIdle();
+        Thread.sleep(ONE_TIME_TIMEOUT_MILLIS);
+
+        runShellCommand("am crash " + APP_PKG_NAME);
+
+        eventually(() -> runWithShellPermissionIdentity(() -> {
+            if (mActivityManager.getPackageImportance(APP_PKG_NAME) <= IMPORTANCE_CACHED) {
+                throw new AssertionError("App was never killed");
+            }
+        }));
+
+        eventually(() -> runWithShellPermissionIdentity(() -> {
+            if (mActivityManager.getPackageImportance(APP_PKG_NAME)
+                    > IMPORTANCE_FOREGROUND_SERVICE) {
+                throw new AssertionError("Foreground service never resumed");
+            }
+            Assert.assertEquals("Service resumed without permission",
+                    PackageManager.PERMISSION_GRANTED, mContext.getPackageManager()
+                            .checkPermission(ACCESS_FINE_LOCATION, APP_PKG_NAME));
+        }));
+    }
+
     private void assertGrantedState(String s, int permissionGranted, long timeoutMillis) {
         eventually(() -> Assert.assertEquals(s,
                 permissionGranted, mContext.getPackageManager()
@@ -214,11 +252,12 @@
         mContext.startActivity(startApp);
     }
 
-    private void startAppForegroundService(long lifespanMillis) {
-        Intent intent = new Intent();
-        intent.setComponent(new ComponentName(
-                APP_PKG_NAME, APP_PKG_NAME + ".KeepAliveForegroundService"));
-        intent.putExtra(EXTRA_FOREGROUND_SERVICE_LIFESPAN, lifespanMillis);
+    private void startAppForegroundService(long lifespanMillis, boolean sticky) {
+        Intent intent = new Intent()
+                .setComponent(new ComponentName(
+                APP_PKG_NAME, APP_PKG_NAME + ".KeepAliveForegroundService"))
+                .putExtra(EXTRA_FOREGROUND_SERVICE_LIFESPAN, lifespanMillis)
+                .putExtra(EXTRA_FOREGROUND_SERVICE_STICKY, sticky);
         mContext.startService(intent);
     }
 
diff --git a/tests/tests/permission/src/android/permission/cts/RemovePermissionTest.java b/tests/tests/permission/src/android/permission/cts/RemovePermissionTest.java
index ff0204b..466fb7f 100644
--- a/tests/tests/permission/src/android/permission/cts/RemovePermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/RemovePermissionTest.java
@@ -22,6 +22,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 
 import android.app.Instrumentation;
 import android.content.Context;
@@ -39,17 +40,44 @@
 
 @AppModeFull(reason = "Instant apps cannot read state of other packages.")
 public class RemovePermissionTest {
-    private static final String APP_PKG_NAME = "android.permission.cts.revokepermissionwhenremoved";
-    private static final String USER_PKG_NAME =
-            "android.permission.cts.revokepermissionwhenremoved.userapp";
+    private static final String APP_PKG_NAME_BASE =
+            "android.permission.cts.revokepermissionwhenremoved";
+    private static final String ADVERSARIAL_PERMISSION_DEFINER_PKG_NAME =
+            APP_PKG_NAME_BASE + ".AdversarialPermissionDefinerApp";
+    private static final String VICTIM_PERMISSION_DEFINER_PKG_NAME =
+            APP_PKG_NAME_BASE + ".VictimPermissionDefinerApp";
+    private static final String ADVERSARIAL_PERMISSION_USER_PKG_NAME =
+            APP_PKG_NAME_BASE + ".userapp";
+    private static final String RUNTIME_PERMISSION_USER_PKG_NAME =
+            APP_PKG_NAME_BASE + ".runtimepermissionuserapp";
+    private static final String RUNTIME_PERMISSION_DEFINER_PKG_NAME =
+            APP_PKG_NAME_BASE + ".runtimepermissiondefinerapp";
+    private static final String INSTALLTIME_PERMISSION_USER_PKG_NAME =
+            APP_PKG_NAME_BASE + ".installtimepermissionuserapp";
+    private static final String INSTALLTIME_PERMISSION_DEFINER_PKG_NAME =
+            APP_PKG_NAME_BASE + ".installtimepermissiondefinerapp";
+
     private static final String TEST_PERMISSION =
             "android.permission.cts.revokepermissionwhenremoved.TestPermission";
-    private static final String RUNTIME_PERMISSION_USER_PKG_NAME =
-            "android.permission.cts.revokepermissionwhenremoved.runtimepermissionuserapp";
-    private static final String RUNTIME_PERMISSION_DEFINER_PKG_NAME =
-            "android.permission.cts.revokepermissionwhenremoved.runtimepermissiondefinerapp";
     private static final String TEST_RUNTIME_PERMISSION =
-            "android.permission.cts.revokepermissionwhenremoved.TestRuntimePermission";
+            APP_PKG_NAME_BASE + ".TestRuntimePermission";
+    private static final String TEST_INSTALLTIME_PERMISSION =
+            APP_PKG_NAME_BASE + ".TestInstalltimePermission";
+
+    private static final String ADVERSARIAL_PERMISSION_DEFINER_APK_NAME =
+            "CtsAdversarialPermissionDefinerApp";
+    private static final String ADVERSARIAL_PERMISSION_USER_APK_NAME =
+            "CtsAdversarialPermissionUserApp";
+    private static final String VICTIM_PERMISSION_DEFINER_APK_NAME =
+            "CtsVictimPermissionDefinerApp";
+    private static final String RUNTIME_PERMISSION_DEFINER_APK_NAME =
+            "CtsRuntimePermissionDefinerApp";
+    private static final String RUNTIME_PERMISSION_USER_APK_NAME =
+            "CtsRuntimePermissionUserApp";
+    private static final String INSTALLTIME_PERMISSION_DEFINER_APK_NAME =
+            "CtsInstalltimePermissionDefinerApp";
+    private static final String INSTALLTIME_PERMISSION_USER_APK_NAME =
+            "CtsInstalltimePermissionUserApp";
 
     private Context mContext;
     private Instrumentation mInstrumentation;
@@ -107,34 +135,74 @@
     @SecurityTest
     @Test
     public void permissionShouldBeRevokedIfRemoved() throws Throwable {
-        installApp("CtsAdversarialPermissionDefinerApp");
-        installApp("CtsAdversarialPermissionUserApp");
+        installApp(ADVERSARIAL_PERMISSION_DEFINER_APK_NAME);
+        installApp(ADVERSARIAL_PERMISSION_USER_APK_NAME);
 
-        grantPermission(USER_PKG_NAME, TEST_PERMISSION);
-        assertTrue(permissionGranted(USER_PKG_NAME, TEST_PERMISSION));
+        grantPermission(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION);
+        assertTrue(permissionGranted(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION));
 
         // Uninstall app which defines a permission with the same name as in victim app.
         // Install the victim app.
-        uninstallApp(APP_PKG_NAME + ".AdversarialPermissionDefinerApp");
-        installApp("CtsVictimPermissionDefinerApp");
-        assertFalse(permissionGranted(USER_PKG_NAME, TEST_PERMISSION));
-        uninstallApp(APP_PKG_NAME + ".userapp");
-        uninstallApp(APP_PKG_NAME + ".VictimPermissionDefinerApp");
+        uninstallApp(ADVERSARIAL_PERMISSION_DEFINER_PKG_NAME);
+        installApp(VICTIM_PERMISSION_DEFINER_APK_NAME);
+        assertFalse(permissionGranted(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION));
+        uninstallApp(ADVERSARIAL_PERMISSION_USER_PKG_NAME);
+        uninstallApp(VICTIM_PERMISSION_DEFINER_PKG_NAME);
     }
 
     @Test
     public void permissionShouldRemainGrantedAfterAppUpdate() throws Throwable {
-        installApp("CtsRuntimePermissionDefinerApp");
-        installApp("CtsRuntimePermissionUserApp");
+        installApp(RUNTIME_PERMISSION_DEFINER_APK_NAME);
+        installApp(RUNTIME_PERMISSION_USER_APK_NAME);
 
         grantPermission(RUNTIME_PERMISSION_USER_PKG_NAME, TEST_RUNTIME_PERMISSION);
         assertTrue(permissionGranted(RUNTIME_PERMISSION_USER_PKG_NAME, TEST_RUNTIME_PERMISSION));
 
         // Install app which defines a permission. This is similar to update the app
         // operation
-        installApp("CtsRuntimePermissionDefinerApp");
+        installApp(RUNTIME_PERMISSION_DEFINER_APK_NAME);
         assertTrue(permissionGranted(RUNTIME_PERMISSION_USER_PKG_NAME, TEST_RUNTIME_PERMISSION));
         uninstallApp(RUNTIME_PERMISSION_USER_PKG_NAME);
         uninstallApp(RUNTIME_PERMISSION_DEFINER_PKG_NAME);
     }
+
+    @Test
+    public void runtimePermissionDependencyTest() throws Throwable {
+        installApp(ADVERSARIAL_PERMISSION_USER_APK_NAME);
+        // Should fail to grant permission because its definer is not installed yet
+        try {
+            grantPermission(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION);
+            fail("Should have thrown security exception above");
+        } catch (SecurityException expected) {
+        }
+        assertFalse(permissionGranted(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION));
+        // Now install the permission definer; should be able to grant permission to user package
+        installApp(ADVERSARIAL_PERMISSION_DEFINER_APK_NAME);
+        grantPermission(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION);
+        assertTrue(permissionGranted(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION));
+        // Now uninstall the permission definer; the user packages' permission should be revoked
+        uninstallApp(ADVERSARIAL_PERMISSION_DEFINER_PKG_NAME);
+        assertFalse(permissionGranted(ADVERSARIAL_PERMISSION_USER_PKG_NAME, TEST_PERMISSION));
+
+        uninstallApp(ADVERSARIAL_PERMISSION_USER_PKG_NAME);
+    }
+
+    @Test
+    public void installtimePermissionDependencyTest() throws Throwable {
+        installApp(INSTALLTIME_PERMISSION_USER_APK_NAME);
+        // Should not have the permission auto-granted
+        assertFalse(permissionGranted(
+                INSTALLTIME_PERMISSION_USER_PKG_NAME, TEST_INSTALLTIME_PERMISSION));
+        // Now install the permission definer; user package should have the permission auto granted
+        installApp(INSTALLTIME_PERMISSION_DEFINER_APK_NAME);
+        installApp(INSTALLTIME_PERMISSION_USER_APK_NAME);
+        assertTrue(permissionGranted(
+                INSTALLTIME_PERMISSION_USER_PKG_NAME, TEST_INSTALLTIME_PERMISSION));
+        // Now uninstall the permission definer; the user packages' permission will not be revoked
+        uninstallApp(INSTALLTIME_PERMISSION_DEFINER_PKG_NAME);
+        assertTrue(permissionGranted(
+                INSTALLTIME_PERMISSION_USER_PKG_NAME, TEST_INSTALLTIME_PERMISSION));
+
+        uninstallApp(INSTALLTIME_PERMISSION_USER_PKG_NAME);
+    }
 }
diff --git a/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java b/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
index 5b38996..b53bf6a 100644
--- a/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/SplitPermissionTest.java
@@ -42,6 +42,7 @@
 
 import android.app.UiAutomation;
 import android.platform.test.annotations.AppModeFull;
+import android.platform.test.annotations.FlakyTest;
 
 import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
@@ -252,6 +253,7 @@
      * If a permission was granted before the split happens, the new permission should inherit the
      * granted state.
      */
+    @FlakyTest(bugId = 152580253)
     @Test
     public void inheritGrantedPermissionState() throws Exception {
         install(APK_LOCATION_29);
@@ -300,6 +302,7 @@
      *
      * <p>(Pre-M version of test)
      */
+    @FlakyTest(bugId = 152580253)
     @Test
     public void inheritFlagsPreM() {
         install(APK_CONTACTS_16);
@@ -316,6 +319,7 @@
      * If a permission has flags before the split happens, the new permission should inherit the
      * flags.
      */
+    @FlakyTest(bugId = 152580253)
     @Test
     public void inheritFlags() {
         install(APK_LOCATION_29);
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionDefinerApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionDefinerApp/Android.bp
new file mode 100644
index 0000000..94fa99e
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionDefinerApp/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsInstalltimePermissionDefinerApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey1",
+}
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionDefinerApp/AndroidManifest.xml b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionDefinerApp/AndroidManifest.xml
new file mode 100644
index 0000000..75ce567
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionDefinerApp/AndroidManifest.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission.cts.revokepermissionwhenremoved.installtimepermissiondefinerapp">
+
+    <permission android:name="android.permission.cts.revokepermissionwhenremoved.TestInstalltimePermission"
+        android:protectionLevel="normal"
+        android:label="TestInstalltimePermission"
+        android:description="@string/test_permission" />
+
+    <application>
+    </application>
+</manifest>
diff --git a/apps/CtsVerifier/res/xml/device_admin_comp_profile.xml b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionDefinerApp/res/values/strings.xml
similarity index 66%
rename from apps/CtsVerifier/res/xml/device_admin_comp_profile.xml
rename to tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionDefinerApp/res/values/strings.xml
index 784258d..0943be7 100644
--- a/apps/CtsVerifier/res/xml/device_admin_comp_profile.xml
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionDefinerApp/res/values/strings.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2017 The Android Open Source Project
+<!-- Copyright (C) 2019 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -14,11 +14,6 @@
      limitations under the License.
 -->
 
-<!-- BEGIN_INCLUDE(meta_data) -->
-<device-admin xmlns:android="http://schemas.android.com/apk/res/android">
-    <uses-policies>
-        <wipe-data />
-        <watch-login />
-    </uses-policies>
-</device-admin>
-<!-- END_INCLUDE(meta_data) -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="test_permission">Test Installtime Permission</string>
+</resources>
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionUserApp/Android.bp b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionUserApp/Android.bp
new file mode 100644
index 0000000..6531cec
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionUserApp/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+android_test_helper_app {
+    name: "CtsInstalltimePermissionUserApp",
+    defaults: ["cts_defaults"],
+    sdk_version: "current",
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts10",
+        "general-tests",
+    ],
+    certificate: ":cts-testkey2",
+}
diff --git a/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionUserApp/AndroidManifest.xml b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionUserApp/AndroidManifest.xml
new file mode 100644
index 0000000..7a0e405
--- /dev/null
+++ b/tests/tests/permission/testapps/RevokePermissionWhenRemoved/InstalltimePermissionUserApp/AndroidManifest.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="android.permission.cts.revokepermissionwhenremoved.installtimepermissionuserapp">
+
+    <uses-permission android:name="android.permission.cts.revokepermissionwhenremoved.TestInstalltimePermission" />
+
+    <application>
+    </application>
+</manifest>
diff --git a/tests/tests/permission2/AndroidManifest.xml b/tests/tests/permission2/AndroidManifest.xml
index c8b594a..50dba1fe 100755
--- a/tests/tests/permission2/AndroidManifest.xml
+++ b/tests/tests/permission2/AndroidManifest.xml
@@ -32,8 +32,8 @@
     <!--  need ability to send sms, to test that SMS's cannot be received -->
     <uses-permission android:name="android.permission.SEND_SMS"/>
 
-    <!-- needs read phone state to get current phone number -->
-    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
+    <!-- needs read phone numbers to get current phone number for R+ -->
+    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>
 
     <!--  need app that has WRITE_SETTINGS but not WRITE_SECURE_SETTINGS -->
     <uses-permission android:name="android.permission.WRITE_SETTINGS"/>
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index c1ed4fc..6e500e2 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -1091,9 +1091,9 @@
         android:description="@string/permgroupdesc_phone"
         android:priority="500" />
 
-    <!-- Allows read only access to phone state, including the phone number of the device,
-         current cellular network information, the status of any ongoing calls, and a list of any
-         {@link android.telecom.PhoneAccount}s registered on the device.
+    <!-- Allows read only access to phone state, including the current cellular network information,
+         the status of any ongoing calls, and a list of any {@link android.telecom.PhoneAccount}s
+         registered on the device.
          <p class="note"><strong>Note:</strong> If <em>both</em> your <a
          href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code
          minSdkVersion}</a> and <a
@@ -1102,12 +1102,13 @@
          grants your app this permission. If you don't need this permission, be sure your <a
          href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code
          targetSdkVersion}</a> is 4 or higher.
-         <p>Protection level: normal
+         <p>Protection level: dangerous
     -->
     <permission android:name="android.permission.READ_PHONE_STATE"
+        android:permissionGroup="android.permission-group.UNDEFINED"
         android:label="@string/permlab_readPhoneState"
         android:description="@string/permdesc_readPhoneState"
-        android:protectionLevel="normal" />
+        android:protectionLevel="dangerous" />
 
     <!-- Allows read access to the device's phone number(s). This is a subset of the capabilities
          granted by {@link #READ_PHONE_STATE} but is exposed to instant applications.
diff --git a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
index 96c04d5..26c6cc8 100644
--- a/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
+++ b/tests/tests/permission2/src/android/permission2/cts/RuntimePermissionProperties.kt
@@ -34,6 +34,7 @@
 import android.Manifest.permission.READ_CONTACTS
 import android.Manifest.permission.READ_EXTERNAL_STORAGE
 import android.Manifest.permission.READ_PHONE_NUMBERS
+import android.Manifest.permission.READ_PHONE_STATE
 import android.Manifest.permission.READ_SMS
 import android.Manifest.permission.RECEIVE_MMS
 import android.Manifest.permission.RECEIVE_SMS
@@ -122,7 +123,7 @@
             WRITE_CALENDAR, SEND_SMS, RECEIVE_SMS, READ_SMS, RECEIVE_MMS, RECEIVE_WAP_PUSH,
             READ_CELL_BROADCASTS, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE,
             ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION, READ_CALL_LOG, WRITE_CALL_LOG,
-            PROCESS_OUTGOING_CALLS, READ_PHONE_NUMBERS, CALL_PHONE,
+            PROCESS_OUTGOING_CALLS, READ_PHONE_STATE, READ_PHONE_NUMBERS, CALL_PHONE,
             ADD_VOICEMAIL, USE_SIP, ANSWER_PHONE_CALLS, ACCEPT_HANDOVER, RECORD_AUDIO, CAMERA,
             BODY_SENSORS)
 
diff --git a/tests/tests/permission3/res/values/strings.xml b/tests/tests/permission3/res/values/strings.xml
index 275c49d..7bd97be 100755
--- a/tests/tests/permission3/res/values/strings.xml
+++ b/tests/tests/permission3/res/values/strings.xml
@@ -22,4 +22,6 @@
     <string name="ask">Ask every time</string>
     <string name="allow">Allow</string>
     <string name="allow_foreground">Allow only while using the app</string>
+    <string name="allow_media_storage">Allow access to media only</string>
+    <string name="allow_external_storage">Allow management of all files</string>
 </resources>
diff --git a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
index b596c93..e689188 100644
--- a/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/BaseUsePermissionTest.kt
@@ -22,6 +22,7 @@
 import android.content.Intent
 import android.content.pm.PackageManager
 import android.net.Uri
+import android.os.Build
 import android.provider.Settings
 import android.support.test.uiautomator.By
 import android.support.test.uiautomator.BySelector
@@ -312,20 +313,25 @@
         )
     )
 
-    protected fun grantAppPermissions(vararg permissions: String) {
-        setAppPermissionState(*permissions, state = PermissionState.ALLOWED, isLegacyApp = false)
+    protected fun grantAppPermissions(vararg permissions: String, targetSdk: Int = 30) {
+        setAppPermissionState(*permissions, state = PermissionState.ALLOWED, isLegacyApp = false,
+                targetSdk = targetSdk)
     }
 
-    protected fun revokeAppPermissions(vararg permissions: String, isLegacyApp: Boolean = false) {
-        setAppPermissionState(
-            *permissions, state = PermissionState.DENIED, isLegacyApp = isLegacyApp
-        )
+    protected fun revokeAppPermissions(
+        vararg permissions: String,
+        isLegacyApp: Boolean = false,
+        targetSdk: Int = 30
+    ) {
+        setAppPermissionState(*permissions, state = PermissionState.DENIED,
+                isLegacyApp = isLegacyApp, targetSdk = targetSdk)
     }
 
     private fun setAppPermissionState(
         vararg permissions: String,
         state: PermissionState,
-        isLegacyApp: Boolean
+        isLegacyApp: Boolean,
+        targetSdk: Int
     ) {
         pressBack()
         pressBack()
@@ -366,6 +372,10 @@
                             PermissionState.ALLOWED ->
                                 if (showsForegroundOnlyButton(permission)) {
                                     R.string.allow_foreground
+                                } else if (isMediaStorageButton(permission, targetSdk)) {
+                                    R.string.allow_media_storage
+                                } else if (isAllStorageButton(permission, targetSdk)) {
+                                    R.string.allow_external_storage
                                 } else {
                                     R.string.allow
                                 }
@@ -428,6 +438,27 @@
             else -> false
         }
 
+    private fun isMediaStorageButton(permission: String, targetSdk: Int): Boolean =
+            when (permission) {
+                android.Manifest.permission.READ_EXTERNAL_STORAGE,
+                android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                android.Manifest.permission.ACCESS_MEDIA_LOCATION ->
+                    // Default behavior, can cause issues if OPSTR_LEGACY_STORAGE is set
+                    targetSdk >= Build.VERSION_CODES.P
+                else -> false
+            }
+
+    private fun isAllStorageButton(permission: String, targetSdk: Int): Boolean =
+            when (permission) {
+                android.Manifest.permission.READ_EXTERNAL_STORAGE,
+                android.Manifest.permission.WRITE_EXTERNAL_STORAGE,
+                android.Manifest.permission.ACCESS_MEDIA_LOCATION ->
+                    // Default behavior, can cause issues if OPSTR_LEGACY_STORAGE is set
+                    targetSdk < Build.VERSION_CODES.P
+                android.Manifest.permission.MANAGE_EXTERNAL_STORAGE -> true
+                else -> false
+            }
+
     private fun scrollToBottom() {
         val scrollable = UiScrollable(UiSelector().scrollable(true)).apply {
             swipeDeadZonePercentage = 0.25
diff --git a/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt b/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
index fd3b573..59cb9aa 100644
--- a/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
+++ b/tests/tests/permission3/src/android/permission3/cts/PermissionTest23.kt
@@ -241,7 +241,7 @@
             android.Manifest.permission.RECORD_AUDIO,
             android.Manifest.permission.BODY_SENSORS,
             android.Manifest.permission.CAMERA,
-            android.Manifest.permission.READ_EXTERNAL_STORAGE
+            android.Manifest.permission.READ_EXTERNAL_STORAGE, targetSdk = 23
         )
         // Don't use UI for granting location permission as this shows another dialog
         uiAutomation.grantRuntimePermission(
@@ -311,6 +311,7 @@
             android.Manifest.permission.WRITE_CALENDAR,
             android.Manifest.permission.WRITE_CONTACTS,
             android.Manifest.permission.READ_SMS,
+            android.Manifest.permission.READ_PHONE_STATE,
             android.Manifest.permission.READ_CALL_LOG,
             android.Manifest.permission.WRITE_CALL_LOG,
             android.Manifest.permission.ADD_VOICEMAIL,
diff --git a/tests/tests/print/printTestUtilLib/src/android/print/test/BasePrintTest.java b/tests/tests/print/printTestUtilLib/src/android/print/test/BasePrintTest.java
index 0a9f237..3128b17 100755
--- a/tests/tests/print/printTestUtilLib/src/android/print/test/BasePrintTest.java
+++ b/tests/tests/print/printTestUtilLib/src/android/print/test/BasePrintTest.java
@@ -62,7 +62,6 @@
 import android.printservice.CustomPrinterIconCallback;
 import android.printservice.PrintJob;
 import android.provider.Settings;
-import android.support.test.uiautomator.UiDevice;
 import android.util.Log;
 import android.util.SparseArray;
 
@@ -134,15 +133,6 @@
 
     protected ICtsPrintHelper mPrintHelper;
 
-    /**
-     * Return the UI device
-     *
-     * @return the UI device
-     */
-    public static UiDevice getUiDevice() {
-        return UiDevice.getInstance(getInstrumentation());
-    }
-
     private CallCounter mCancelOperationCounter;
     private CallCounter mLayoutCallCounter;
     private CallCounter mWriteCallCounter;
diff --git a/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java b/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java
index be03e83..3d85709 100644
--- a/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java
+++ b/tests/tests/print/src/android/print/cts/CustomPrintOptionsTest.java
@@ -370,9 +370,9 @@
         }
 
         // Abort printing
-        getUiDevice().pressBack();
-        getUiDevice().pressBack();
-        getUiDevice().pressBack();
+        mPrintHelper.closeCustomPrintOptions();
+        mPrintHelper.closePrintOptions();
+        mPrintHelper.cancelPrinting();
 
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
     }
diff --git a/tests/tests/print/src/android/print/cts/InstallBehavior.java b/tests/tests/print/src/android/print/cts/InstallBehavior.java
index bbb581a..c3be659 100644
--- a/tests/tests/print/src/android/print/cts/InstallBehavior.java
+++ b/tests/tests/print/src/android/print/cts/InstallBehavior.java
@@ -87,6 +87,6 @@
         selectPrinter("ExternalServicePrinter", OPERATION_TIMEOUT_MILLIS);
 
         // Exit print preview and thereby end printing
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
     }
 }
diff --git a/tests/tests/print/src/android/print/cts/InteractionBetweenPrintDocumentAndPrinterDiscovery.java b/tests/tests/print/src/android/print/cts/InteractionBetweenPrintDocumentAndPrinterDiscovery.java
index 469d27d..46366af 100644
--- a/tests/tests/print/src/android/print/cts/InteractionBetweenPrintDocumentAndPrinterDiscovery.java
+++ b/tests/tests/print/src/android/print/cts/InteractionBetweenPrintDocumentAndPrinterDiscovery.java
@@ -183,7 +183,7 @@
         // Finish test: Fail pending write, exit print activity and wait for print subsystem to shut
         // down
         writeCallBack[0].onWriteFailed(null);
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
     }
 }
diff --git a/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java b/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java
index 3e483f5..90f8822 100644
--- a/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java
+++ b/tests/tests/print/src/android/print/cts/InterfaceForAppsTest.java
@@ -259,7 +259,7 @@
         eventually(() -> assertEquals(PrintJobInfo.STATE_CREATED, job.getInfo().getState()));
 
         // Cancel printing by exiting print activity
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
         eventually(() -> assertTrue(job.isCancelled()));
 
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
diff --git a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
index 39dfdfe..771df27 100644
--- a/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
+++ b/tests/tests/print/src/android/print/cts/PageRangeAdjustmentTest.java
@@ -391,8 +391,7 @@
         waitForWriteAdapterCallback(1);
 
         // Cancel printing.
-        getUiDevice().pressBack(); // wakes up the device.
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         // Wait for finish.
         waitForAdapterFinishCallbackCalled();
diff --git a/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java b/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
index 2e849f5..5ab6ad7 100644
--- a/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintDocumentAdapterContractTest.java
@@ -203,8 +203,7 @@
         waitForWriteAdapterCallback(1);
 
         // Cancel the printing.
-        getUiDevice().pressBack(); // wakes up the device.
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         // Wait for finish.
         waitForAdapterFinishCallbackCalled();
@@ -307,8 +306,7 @@
 
         assertNoPrintButton();
 
-        getUiDevice().pressBack();
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
     }
@@ -1176,8 +1174,7 @@
         waitForLayoutAdapterCallbackCount(1);
 
         // Cancel printing.
-        getUiDevice().pressBack(); // wakes up the device.
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         // Wait for the cancellation request.
         waitForCancelOperationCallbackCalled();
@@ -1253,8 +1250,7 @@
         waitForWriteAdapterCallback(1);
 
         // Cancel printing.
-        getUiDevice().pressBack(); // wakes up the device.
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         // Wait for the cancellation request.
         waitForCancelOperationCallbackCalled();
@@ -1317,8 +1313,7 @@
         waitForLayoutAdapterCallbackCount(1);
 
         // Cancel printing.
-        getUiDevice().pressBack(); // wakes up the device.
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         // Wait for a finish.
         waitForAdapterFinishCallbackCalled();
@@ -1385,8 +1380,7 @@
         waitForWriteAdapterCallback(1);
 
         // Cancel printing.
-        getUiDevice().pressBack(); // wakes up the device.
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         // Wait for a finish.
         waitForAdapterFinishCallbackCalled();
@@ -1598,8 +1592,7 @@
         waitForWriteAdapterCallback(1);
 
         // Cancel printing.
-        getUiDevice().pressBack(); // wakes up the device.
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         // Wait for a finish.
         waitForAdapterFinishCallbackCalled();
@@ -1658,8 +1651,7 @@
         waitForLayoutAdapterCallbackCount(1);
 
         // Cancel printing.
-        getUiDevice().pressBack(); // wakes up the device.
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         // Wait for a finish.
         waitForAdapterFinishCallbackCalled();
@@ -1723,8 +1715,7 @@
         waitForWriteAdapterCallback(1);
 
         // Cancel printing.
-        getUiDevice().pressBack(); // wakes up the device.
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         // Wait for a finish.
         waitForAdapterFinishCallbackCalled();
@@ -1800,8 +1791,7 @@
         waitForWriteAdapterCallback(1);
 
         // Cancel printing.
-        getUiDevice().pressBack(); // wakes up the device.
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         // Wait for a finish.
         waitForAdapterFinishCallbackCalled();
@@ -1888,8 +1878,7 @@
         waitForWriteAdapterCallback(1);
 
         // Cancel printing.
-        getUiDevice().pressBack(); // wakes up the device.
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         // Wait for a finish.
         waitForAdapterFinishCallbackCalled();
diff --git a/tests/tests/print/src/android/print/cts/PrintServicesTest.java b/tests/tests/print/src/android/print/cts/PrintServicesTest.java
index 0bf7892..20a4a6f 100644
--- a/tests/tests/print/src/android/print/cts/PrintServicesTest.java
+++ b/tests/tests/print/src/android/print/cts/PrintServicesTest.java
@@ -414,8 +414,8 @@
 
         assertThatIconIs(renderDrawable(mIcon.loadDrawable(getActivity())));
 
-        getUiDevice().pressBack();
-        getUiDevice().pressBack();
+        mPrintHelper.closePrinterList();
+        mPrintHelper.cancelPrinting();
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
     }
 
@@ -453,8 +453,7 @@
         assertException(() -> serviceCallbacks.getService().callAttachBaseContext(getActivity()),
                 IllegalStateException.class);
 
-        getUiDevice().pressBack();
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
     }
 
@@ -685,10 +684,10 @@
                 InfoActivity.clearObservers();
             }
         } finally {
-            getUiDevice().pressBack();
+            mPrintHelper.closePrinterList();
         }
 
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
     }
 }
diff --git a/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java b/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java
index f321d84..2922661 100644
--- a/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterCapabilitiesChangeTest.java
@@ -258,7 +258,7 @@
         waitForMediaSizeChange(mLayoutAttributes, MediaSize.NA_LETTER);
         waitForMediaSizeChange(mWriteAttributes, MediaSize.NA_LETTER);
 
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
     }
diff --git a/tests/tests/print/src/android/print/cts/PrinterCapabilitiesTest.java b/tests/tests/print/src/android/print/cts/PrinterCapabilitiesTest.java
index fe182c5..5fc663e 100644
--- a/tests/tests/print/src/android/print/cts/PrinterCapabilitiesTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterCapabilitiesTest.java
@@ -305,7 +305,7 @@
 
         waitForPrinterDiscoverySessionCreateCallbackCalled();
 
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
     }
diff --git a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
index 63a5027..290bcf8 100644
--- a/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterDiscoverySessionLifecycleTest.java
@@ -512,8 +512,7 @@
         waitForLayoutAdapterCallbackCount(4);
 
         // Cancel the printing.
-        getUiDevice().pressBack(); // wakes up the device.
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         // Wait for all print jobs to be handled after which the is session destroyed.
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
@@ -634,7 +633,7 @@
         runOnMainThread(() -> session[0].removePrinters(printerIdsToRemove));
         eventually(() -> runOnMainThread(() -> assertEquals(0, session[0].getPrinters().size())));
 
-        getUiDevice().pressBack();
+        mPrintHelper.cancelPrinting();
 
         waitForPrinterDiscoverySessionDestroyCallbackCalled(1);
     }
diff --git a/tests/tests/print/src/android/print/cts/PrinterInfoTest.java b/tests/tests/print/src/android/print/cts/PrinterInfoTest.java
index 2f66f87..d82f47c 100644
--- a/tests/tests/print/src/android/print/cts/PrinterInfoTest.java
+++ b/tests/tests/print/src/android/print/cts/PrinterInfoTest.java
@@ -343,8 +343,7 @@
         mPrintHelper.displayPrinterList();
 
         // Exit print spooler
-        getUiDevice().pressBack();
-        getUiDevice().pressBack();
-        getUiDevice().pressBack();
+        mPrintHelper.closePrinterList();
+        mPrintHelper.cancelPrinting();
     }
 }
diff --git a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
index 1f29fb2..b494527 100644
--- a/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
+++ b/tests/tests/role/src/android/app/role/cts/RoleManagerTest.java
@@ -45,13 +45,11 @@
 import android.support.test.uiautomator.BySelector;
 import android.support.test.uiautomator.UiObject2;
 import android.support.test.uiautomator.UiObjectNotFoundException;
-import android.telecom.TelecomManager;
 import android.util.Pair;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.test.InstrumentationRegistry;
-import androidx.test.filters.FlakyTest;
 import androidx.test.rule.ActivityTestRule;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -185,7 +183,6 @@
         assertThat(intent.getStringExtra(Intent.EXTRA_ROLE_NAME)).isEqualTo(ROLE_NAME);
     }
 
-    @FlakyTest
     @Test
     public void requestRoleAndDenyThenIsNotRoleHolder() throws Exception {
         requestRole(ROLE_NAME);
@@ -194,7 +191,6 @@
         assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, false);
     }
 
-    @FlakyTest
     @Test
     public void requestRoleAndAllowThenIsRoleHolder() throws Exception {
         requestRole(ROLE_NAME);
@@ -203,7 +199,6 @@
         assertIsRoleHolder(ROLE_NAME, APP_PACKAGE_NAME, true);
     }
 
-    @FlakyTest
     @Test
     public void requestRoleFirstTimeNoDontAskAgain() throws Exception {
         requestRole(ROLE_NAME);
@@ -214,7 +209,6 @@
         respondToRoleRequest(false);
     }
 
-    @FlakyTest
     @Test
     public void requestRoleAndDenyThenHasDontAskAgain() throws Exception {
         requestRole(ROLE_NAME);
@@ -228,7 +222,6 @@
         respondToRoleRequest(false);
     }
 
-    @FlakyTest
     @Test
     public void requestRoleAndDenyWithDontAskAgainReturnsCanceled() throws Exception {
         requestRole(ROLE_NAME);
@@ -241,7 +234,6 @@
         assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
     }
 
-    @FlakyTest
     @Test
     public void requestRoleAndDenyWithDontAskAgainThenDeniedAutomatically() throws Exception {
         requestRole(ROLE_NAME);
@@ -257,7 +249,6 @@
         assertThat(result.first).isEqualTo(Activity.RESULT_CANCELED);
     }
 
-    @FlakyTest
     @Test
     public void requestRoleAndDenyWithDontAskAgainAndClearDataThenShowsUiWithoutDontAskAgain()
             throws Exception {
@@ -288,7 +279,6 @@
         });
     }
 
-    @FlakyTest
     @Test
     public void requestRoleAndDenyWithDontAskAgainAndReinstallThenShowsUiWithoutDontAskAgain()
             throws Exception {
diff --git a/tests/tests/secure_element/omapi/src/android/omapi/cts/OmapiTest.java b/tests/tests/secure_element/omapi/src/android/omapi/cts/OmapiTest.java
index 9a862d7..da99ff0 100644
--- a/tests/tests/secure_element/omapi/src/android/omapi/cts/OmapiTest.java
+++ b/tests/tests/secure_element/omapi/src/android/omapi/cts/OmapiTest.java
@@ -265,6 +265,17 @@
 
             if (supportUICCReaders()) {
                 assertGreaterOrEqual(uiccReaders.size(), 1);
+                // Test API getUiccReader(int slotNumber)
+                // The result should be the same as getReaders() with UICC reader prefix
+                for (int i = 1; i <= uiccReaders.size(); i++) {
+                    try {
+                        Reader uiccReader = seService.getUiccReader(i);
+                        if (!uiccReaders.contains(uiccReader))
+                            fail("Incorrect reader object - getUiccReader(" + i + ")");
+                    } catch (IllegalArgumentException e) {
+                        fail("Fail to get Reader object by calling getUiccReader(" + i + ")");
+                    }
+                }
             } else {
                 assertTrue(uiccReaders.size() == 0);
             }
diff --git a/tests/tests/security/AndroidTest.xml b/tests/tests/security/AndroidTest.xml
index 45220d9..ad7fa33 100644
--- a/tests/tests/security/AndroidTest.xml
+++ b/tests/tests/security/AndroidTest.xml
@@ -24,6 +24,14 @@
         <option name="test-file-name" value="CtsSecurityTestCases.apk" />
     </target_preparer>
     <target_preparer class="com.android.compatibility.common.tradefed.targetprep.CrashReporter" />
+
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="run-command"
+            value="appops set android.security.cts REQUEST_INSTALL_PACKAGES allow" />
+        <option name="teardown-command"
+            value="appops reset android.security.cts" />
+    </target_preparer>
+
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.security.cts" />
         <option name="runtime-hint" value="1h40m18s" />
diff --git a/tests/tests/security/assets/fsverity-debug.x509.der b/tests/tests/security/assets/fsverity-debug.x509.der
new file mode 100644
index 0000000..fbee6f1
--- /dev/null
+++ b/tests/tests/security/assets/fsverity-debug.x509.der
Binary files differ
diff --git a/tests/tests/security/assets/fsverity-release.x509.der b/tests/tests/security/assets/fsverity-release.x509.der
new file mode 100644
index 0000000..cd8cd79
--- /dev/null
+++ b/tests/tests/security/assets/fsverity-release.x509.der
Binary files differ
diff --git a/tests/tests/security/native/encryption/FileBasedEncryptionPolicyTest.cpp b/tests/tests/security/native/encryption/FileBasedEncryptionPolicyTest.cpp
index f1b9b25..7229574 100644
--- a/tests/tests/security/native/encryption/FileBasedEncryptionPolicyTest.cpp
+++ b/tests/tests/security/native/encryption/FileBasedEncryptionPolicyTest.cpp
@@ -201,7 +201,11 @@
         res = ioctl(fd, FS_IOC_GET_ENCRYPTION_POLICY, &arg.policy.v1);
     }
     if (res != 0) {
-        if (errno == ENODATA || errno == ENOENT) {  // Directory is unencrypted
+        if (errno == ENODATA ||     // Directory is unencrypted
+            errno == ENOENT ||      // Directory is unencrypted (on older kernels)
+            errno == EOPNOTSUPP ||  // Filesystem encryption feature not enabled
+            errno == ENOTTY) {      // Very old kernel, doesn't know about encryption at all
+
             // Starting with Android 10, file-based encryption is required on
             // new devices [CDD 9.9.2/C-0-3].
             if (first_api_level < Q_API_LEVEL) {
diff --git a/tests/tests/security/src/android/security/cts/FileIntegrityManagerTest.java b/tests/tests/security/src/android/security/cts/FileIntegrityManagerTest.java
new file mode 100644
index 0000000..2d65976
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/FileIntegrityManagerTest.java
@@ -0,0 +1,105 @@
+/*
+ * 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.security.cts;
+
+import android.content.Context;
+import android.security.FileIntegrityManager;
+import android.platform.test.annotations.RestrictedBuildTest;
+import android.platform.test.annotations.SecurityTest;
+import android.util.Log;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.CddTest;
+import com.android.compatibility.common.util.CtsAndroidTestCase;
+import com.android.compatibility.common.util.PropertyUtil;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+
+@SecurityTest
+public class FileIntegrityManagerTest extends CtsAndroidTestCase {
+
+    private static final String TAG = "FileIntegrityManagerTest";
+    private static final int MIN_REQUIRED_API_LEVEL = 30;
+
+    private Context mContext;
+    private FileIntegrityManager mFileIntegrityManager;
+    private CertificateFactory mCertFactory;
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        mFileIntegrityManager = mContext.getSystemService(FileIntegrityManager.class);
+        mCertFactory = CertificateFactory.getInstance("X.509");
+    }
+
+
+    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    public void testSupportedOnDevicesFirstLaunchedWithR() throws Exception {
+        if (PropertyUtil.getFirstApiLevel() >= MIN_REQUIRED_API_LEVEL) {
+            assertTrue(mFileIntegrityManager.isApkVeritySupported());
+        }
+    }
+
+    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    public void testCtsReleaseCertificateTrusted() throws Exception {
+        boolean isReleaseCertTrusted = mFileIntegrityManager.isAppSourceCertificateTrusted(
+                readAssetAsX509Certificate("fsverity-release.x509.der"));
+        if (mFileIntegrityManager.isApkVeritySupported()) {
+            assertTrue(isReleaseCertTrusted);
+        } else {
+            assertFalse(isReleaseCertTrusted);
+        }
+    }
+
+    @CddTest(requirement="9.10/C-0-3,C-1-1")
+    @RestrictedBuildTest
+    public void testPlatformDebugCertificateNotTrusted() throws Exception {
+        boolean isDebugCertTrusted = mFileIntegrityManager.isAppSourceCertificateTrusted(
+                readAssetAsX509Certificate("fsverity-debug.x509.der"));
+        assertFalse(isDebugCertTrusted);
+    }
+
+    private X509Certificate readAssetAsX509Certificate(String assetName)
+            throws CertificateException, IOException {
+        InputStream is = mContext.getAssets().open(assetName);
+        return toX509Certificate(readAllBytes(is));
+    }
+
+    // TODO: Switch to InputStream#readAllBytes when Java 9 is supported
+    private byte[] readAllBytes(InputStream is) throws IOException {
+        ByteArrayOutputStream output = new ByteArrayOutputStream();
+        byte[] buf = new byte[8192];
+        int len;
+        while ((len = is.read(buf, 0, buf.length)) > 0) {
+            output.write(buf, 0, len);
+        }
+        return output.toByteArray();
+    }
+
+    private X509Certificate toX509Certificate(byte[] bytes) throws CertificateException {
+        return (X509Certificate) mCertFactory.generateCertificate(new ByteArrayInputStream(bytes));
+    }
+}
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index ea858f5..61b9d47 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -1464,10 +1464,10 @@
         String rname = resources.getResourceEntryName(rid);
         String url = server.getAssetUrl("raw/" + rname);
         verifyServer(rid, url);
-        policy.setCleartextTrafficPermitted(false);
         doStagefrightTestMediaPlayer(url, config);
         doStagefrightTestMediaCodec(url, config);
         doStagefrightTestMediaMetadataRetriever(url, config);
+        policy.setCleartextTrafficPermitted(false);
         server.shutdown();
     }
 
diff --git a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
index 5505c2c7..12c0fe2 100644
--- a/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
+++ b/tests/tests/telecom/src/android/telecom/cts/BaseTelecomTestWithMockServices.java
@@ -505,11 +505,15 @@
         placeAndVerifyCall(null);
     }
 
+    void placeAndVerifyCallByRedirection(boolean wasCancelled) {
+        placeAndVerifyCallByRedirection(null, wasCancelled);
+    }
+
     /**
      *  Puts Telecom in a state where there is an active call provided by the
      *  {@link CtsConnectionService} which can be tested.
      */
-    void placeAndVerifyCallByRedirection(boolean wasCancelled) {
+    void placeAndVerifyCallByRedirection(Bundle extras, boolean wasCancelled) {
         int currentCallCount = (getInCallService() == null) ? 0 : getInCallService().getCallCount();
         int currentConnections = getNumberOfConnections();
         // We expect a new connection if it wasn't cancelled.
@@ -517,7 +521,7 @@
             currentConnections++;
             currentCallCount++;
         }
-        placeAndVerifyCall(null, VideoProfile.STATE_AUDIO_ONLY, currentConnections,
+        placeAndVerifyCall(extras, VideoProfile.STATE_AUDIO_ONLY, currentConnections,
                 currentCallCount);
         // Ensure the new outgoing call broadcast fired for the outgoing call.
         assertOutgoingCallBroadcastReceived(true);
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallRedirectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/CallRedirectionServiceTest.java
index 2fcedf5..5e8f3f5 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallRedirectionServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallRedirectionServiceTest.java
@@ -27,6 +27,7 @@
 import android.content.ServiceConnection;
 
 import android.net.Uri;
+import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -58,7 +59,18 @@
 
     private static final Uri SAMPLE_HANDLE = Uri.fromParts(PhoneAccount.SCHEME_TEL, "0001112222",
             null);
-
+    private static final Uri SAMPLE_HANDLE_WITH_POST_DIAL = new Uri.Builder()
+            .scheme(PhoneAccount.SCHEME_TEL)
+            .encodedOpaquePart("6505551212,1234567890")
+            .build();
+    private static final Uri SAMPLE_REDIRECT_HANDLE = new Uri.Builder()
+            .scheme(PhoneAccount.SCHEME_TEL)
+            .encodedOpaquePart("6505551213")
+            .build();
+    private static final Uri SAMPLE_REDIRECT_HANDLE_WITH_POST_DIAL = new Uri.Builder()
+            .scheme(PhoneAccount.SCHEME_TEL)
+            .encodedOpaquePart("6505551213,1234567890")
+            .build();
     private static final int ASYNC_TIMEOUT = 10000;
     private RoleManager mRoleManager;
     private Handler mHandler;
@@ -120,6 +132,34 @@
         assertTrue(Call.STATE_DISCONNECTED != mCall.getState());
     }
 
+    /**
+     * Verifies that post-dial digits will be re-added to a number after redirection.
+     * @throws Exception
+     */
+    public void testRedirectedCallWithPostDialDigits() throws Exception {
+        if (!shouldTestTelecom(mContext)) {
+            return;
+        }
+        mCallRedirectionServiceController.setRedirectCall(SAMPLE_REDIRECT_HANDLE, null, false);
+        Bundle extras = new Bundle();
+        extras.putParcelable(TestUtils.EXTRA_PHONE_NUMBER, SAMPLE_HANDLE_WITH_POST_DIAL);
+        placeAndVerifyCallByRedirection(extras, false /* cancelledByCallRedirection */);
+        mInCallService = mInCallCallbacks.getService();
+        assertCallGatewayConstructed(mInCallService.getLastCall(), true);
+        mCall = mInCallService.getLastCall();
+        assertEquals(SAMPLE_REDIRECT_HANDLE_WITH_POST_DIAL,
+                mCall.getDetails().getGatewayInfo().getGatewayAddress());
+        // The , (pause) separators get URI encoded in the call intent; compare decoded scheme to
+        // ensure proper equality for what it essentially the same thing.
+        assertEquals(Uri.decode(SAMPLE_HANDLE_WITH_POST_DIAL.getSchemeSpecificPart()),
+                Uri.decode(mCall.getDetails().getGatewayInfo().getOriginalAddress()
+                        .getSchemeSpecificPart()));
+        assertEquals(SAMPLE_REDIRECT_HANDLE_WITH_POST_DIAL,
+                mCall.getDetails().getHandle());
+        assertEquals(TestUtils.TEST_PHONE_ACCOUNT_HANDLE, mCall.getDetails().getAccountHandle());
+        assertTrue(Call.STATE_DISCONNECTED != mCall.getState());
+    }
+
     public void testRedirectedCallWithRedirectedPhoneAccount()
             throws Exception {
         if (!shouldTestTelecom(mContext)) {
diff --git a/tests/tests/telephony/current/Android.bp b/tests/tests/telephony/current/Android.bp
index 215b13d..b570919 100644
--- a/tests/tests/telephony/current/Android.bp
+++ b/tests/tests/telephony/current/Android.bp
@@ -32,7 +32,6 @@
         "telephony-common",
         "android.test.runner.stubs",
         "android.test.base.stubs",
-        "framework-telephony-stubs",
         "voip-common",
     ],
     // uncomment when EuiccService tests do not use hidden APIs (Binder instances)
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastIntentsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastIntentsTest.java
index 4c4d93d..9c10532 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastIntentsTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastIntentsTest.java
@@ -19,6 +19,9 @@
 
 import static junit.framework.Assert.fail;
 
+import static org.junit.Assume.assumeTrue;
+
+import android.content.pm.PackageManager;
 import android.os.UserHandle;
 import android.telephony.CbGeoUtils.Geometry;
 import android.telephony.CellBroadcastIntents;
@@ -30,6 +33,7 @@
 
 import com.android.internal.telephony.gsm.SmsCbConstants;
 
+import org.junit.Before;
 import org.junit.Test;
 
 import java.util.ArrayList;
@@ -58,6 +62,12 @@
     private static final int TEST_SLOT = 0;
     private static final int TEST_SUB_ID = 1;
 
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+    }
+
     @Test
     public void testGetIntentForBackgroundReceivers() {
         try {
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
index 517b97c..06e2b59 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
@@ -43,6 +43,7 @@
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
 import android.telephony.SmsManager;
+import android.telephony.TelephonyDisplayInfo;
 import android.telephony.TelephonyManager;
 import android.telephony.emergency.EmergencyNumber;
 import android.telephony.ims.ImsReasonInfo;
@@ -89,6 +90,7 @@
     private boolean mOnBarringInfoChangedCalled;
     private boolean mSecurityExceptionThrown;
     private boolean mOnRegistrationFailedCalled;
+    private boolean mOnTelephonyDisplayInfoChanged;
     @RadioPowerState private int mRadioPowerState;
     @SimActivationState private int mVoiceActivationState;
     private BarringInfo mBarringInfo;
@@ -693,6 +695,37 @@
     }
 
     @Test
+    public void testOnDisplayInfoChanged() throws Exception {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+        assertThat(mOnTelephonyDisplayInfoChanged).isFalse();
+
+        mHandler.post(() -> {
+            mListener = new PhoneStateListener() {
+                @Override
+                public void onDisplayInfoChanged(TelephonyDisplayInfo displayInfo) {
+                    synchronized (mLock) {
+                        mOnTelephonyDisplayInfoChanged = true;
+                        mLock.notify();
+                    }
+                }
+            };
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                    (tm) -> tm.listen(mListener,
+                            PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED));
+        });
+
+        synchronized (mLock) {
+            if (!mOnTelephonyDisplayInfoChanged) {
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        assertTrue(mOnTelephonyDisplayInfoChanged);
+    }
+
+    @Test
     public void testOnCallForwardingIndicatorChanged() throws Throwable {
         if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
             Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
index 56c9d55..474fc72 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsManagerTest.java
@@ -34,6 +34,7 @@
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import android.app.AppOpsManager;
 import android.app.PendingIntent;
@@ -59,6 +60,8 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.test.InstrumentationRegistry;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -133,6 +136,9 @@
 
     @Before
     public void setUp() throws Exception {
+        assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+
         mContext = getContext();
         mTelephonyManager =
             (TelephonyManager) getContext().getSystemService(
@@ -163,9 +169,6 @@
 
     @Test
     public void testDivideMessage() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
         ArrayList<String> dividedMessages = divideMessage(LONG_TEXT);
         assertNotNull(dividedMessages);
         if (TelephonyUtils.isSkt(mTelephonyManager)) {
@@ -181,9 +184,6 @@
 
     @Test
     public void testDivideUnicodeMessage() {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
         ArrayList<String> dividedMessages = divideMessage(LONG_TEXT_WITH_32BIT_CHARS);
         assertNotNull(dividedMessages);
         assertTrue(isComplete(dividedMessages, 3, LONG_TEXT_WITH_32BIT_CHARS));
@@ -207,10 +207,6 @@
 
     @Test
     public void testSmsRetriever() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-
         assertFalse("[RERUN] SIM card does not provide phone number. Use a suitable SIM Card.",
                 TextUtils.isEmpty(mDestAddr));
 
@@ -302,10 +298,6 @@
 
     @Test
     public void testSendAndReceiveMessages() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-
         assertFalse("[RERUN] SIM card does not provide phone number. Use a suitable SIM Card.",
                 TextUtils.isEmpty(mDestAddr));
 
@@ -334,10 +326,6 @@
 
     @Test
     public void testSmsBlocking() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-
         assertFalse("[RERUN] SIM card does not provide phone number. Use a suitable SIM Card.",
                 TextUtils.isEmpty(mDestAddr));
 
@@ -427,10 +415,6 @@
 
     @Test
     public void testSmsNotPersisted_failsWithoutCarrierPermissions() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
-
         assertFalse("[RERUN] SIM card does not provide phone number. Use a suitable SIM Card.",
                 TextUtils.isEmpty(mDestAddr));
 
@@ -445,9 +429,6 @@
 
     @Test
     public void testContentProviderAccessRestriction() throws Exception {
-        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
-            return;
-        }
         Uri dummySmsUri = null;
         Context context = getInstrumentation().getContext();
         ContentResolver contentResolver = context.getContentResolver();
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
index cf58926..92b6bec 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -18,8 +18,8 @@
 
 import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_CONGESTED;
-import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED;
+import static android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED;
 import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR;
 import static android.telephony.TelephonyManager.SET_OPPORTUNISTIC_SUB_SUCCESS;
 
@@ -312,6 +312,8 @@
 
     @Test
     public void testSetDefaultVoiceSubId() {
+        if (!isSupported()) return;
+
         int oldSubId = SubscriptionManager.getDefaultVoiceSubscriptionId();
         InstrumentationRegistry.getInstrumentation().getUiAutomation()
                 .adoptShellPermissionIdentity();
@@ -341,12 +343,13 @@
         mSm.setSubscriptionPlans(mSubId, Arrays.asList(buildValidSubscriptionPlan()));
 
         // Cellular is metered by default
-        assertFalse(cm.getNetworkCapabilities(net).hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertFalse(cm.getNetworkCapabilities(net).hasCapability(
+                NET_CAPABILITY_TEMPORARILY_NOT_METERED));
 
-        // Override should make it go unmetered
+        // Override should make it go temporarily unmetered
         {
             final CountDownLatch latch = waitForNetworkCapabilities(net, caps -> {
-                return caps.hasCapability(NET_CAPABILITY_NOT_METERED);
+                return caps.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
             });
             mSm.setSubscriptionOverrideUnmetered(mSubId, true, 0);
             assertTrue(latch.await(10, TimeUnit.SECONDS));
@@ -355,7 +358,7 @@
         // Clearing override should make it go metered
         {
             final CountDownLatch latch = waitForNetworkCapabilities(net, caps -> {
-                return !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
+                return !caps.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
             });
             mSm.setSubscriptionOverrideUnmetered(mSubId, false, 0);
             assertTrue(latch.await(10, TimeUnit.SECONDS));
@@ -376,7 +379,8 @@
         mSm.setSubscriptionPlans(mSubId, Arrays.asList(buildValidSubscriptionPlan()));
 
         // Cellular is metered by default
-        assertFalse(cm.getNetworkCapabilities(net).hasCapability(NET_CAPABILITY_NOT_METERED));
+        assertFalse(cm.getNetworkCapabilities(net).hasCapability(
+                NET_CAPABILITY_TEMPORARILY_NOT_METERED));
 
         SubscriptionPlan unmeteredPlan = SubscriptionPlan.Builder
                 .createRecurring(ZonedDateTime.parse("2007-03-14T00:00:00.000Z"),
@@ -389,7 +393,7 @@
         // Unmetered plan should make it go unmetered
         {
             final CountDownLatch latch = waitForNetworkCapabilities(net, caps -> {
-                return caps.hasCapability(NET_CAPABILITY_NOT_METERED);
+                return caps.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
             });
             mSm.setSubscriptionPlans(mSubId, Arrays.asList(unmeteredPlan));
             assertTrue(latch.await(10, TimeUnit.SECONDS));
@@ -398,7 +402,7 @@
         // Metered plan should make it go metered
         {
             final CountDownLatch latch = waitForNetworkCapabilities(net, caps -> {
-                return !caps.hasCapability(NET_CAPABILITY_NOT_METERED);
+                return !caps.hasCapability(NET_CAPABILITY_TEMPORARILY_NOT_METERED);
             });
             mSm.setSubscriptionPlans(mSubId, Arrays.asList(buildValidSubscriptionPlan()));
             assertTrue(latch.await(10, TimeUnit.SECONDS));
@@ -677,6 +681,22 @@
     }
 
     @Test
+    public void testGetActiveDataSubscriptionId() {
+        if (!isSupported()) return;
+
+        int activeDataSubIdCurrent = executeWithShellPermissionAndDefault(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID, mSm,
+                (sm) -> sm.getActiveDataSubscriptionId());
+
+        if (activeDataSubIdCurrent != SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
+            List<SubscriptionInfo> subscriptionInfos = mSm.getCompleteActiveSubscriptionInfoList();
+            boolean foundSub = subscriptionInfos.stream()
+                    .anyMatch(x -> x.getSubscriptionId() == activeDataSubIdCurrent);
+            assertTrue(foundSub);
+        }
+    }
+
+    @Test
     public void testSetPreferredDataSubscriptionId() {
         if (!isSupported()) return;
         int preferredSubId = executeWithShellPermissionAndDefault(-1, mSm,
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 d090ad7..1953833c 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -171,6 +171,8 @@
     private static final String TEST_FORWARD_NUMBER = "54321";
     private static final String TESTING_PLMN = "12345";
 
+    private static final int RADIO_HAL_VERSION_1_3 = makeRadioVersion(1, 3);
+
     static {
         EMERGENCY_NUMBER_SOURCE_SET = new HashSet<Integer>();
         EMERGENCY_NUMBER_SOURCE_SET.add(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING);
@@ -201,6 +203,7 @@
 
     private int mTestSub;
     private TelephonyManagerTest.CarrierConfigReceiver mReceiver;
+    private int mRadioVersion;
 
     private static class CarrierConfigReceiver extends BroadcastReceiver {
         private CountDownLatch mLatch = new CountDownLatch(1);
@@ -242,6 +245,8 @@
         mTelephonyManager = getContext().getSystemService(TelephonyManager.class)
                 .createForSubscriptionId(mTestSub);
         mReceiver = new CarrierConfigReceiver(mTestSub);
+        Pair<Integer, Integer> radioVersion = mTelephonyManager.getRadioHalVersion();
+        mRadioVersion = makeRadioVersion(radioVersion.first, radioVersion.second);
         IntentFilter filter = new IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
         // ACTION_CARRIER_CONFIG_CHANGED is sticky, so we will get a callback right away.
         getContext().registerReceiver(mReceiver, filter);
@@ -335,6 +340,10 @@
 
     @Test
     public void testDevicePolicyApn() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
+            return;
+        }
         // These methods aren't accessible to anything except system and phone by design, so we just
         // look for security exceptions here.
         try {
@@ -1156,6 +1165,10 @@
 
     @Test
     public void testSetSystemSelectionChannels() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
+            return;
+        }
         LinkedBlockingQueue<Boolean> queue = new LinkedBlockingQueue<>(1);
         final UiAutomation uiAutomation =
                 InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -1166,8 +1179,12 @@
             mTelephonyManager.setSystemSelectionChannels(Collections.emptyList(),
                     getContext().getMainExecutor(), queue::offer);
             Boolean result = queue.poll(1000, TimeUnit.MILLISECONDS);
+            // Ensure we get a result
             assertNotNull(result);
-            assertTrue(result);
+            // Only verify the result for supported devices on IRadio 1.3+
+            if (mRadioVersion >= RADIO_HAL_VERSION_1_3) {
+                assertTrue(result);
+            }
         } catch (InterruptedException e) {
             fail("interrupted");
         } finally {
@@ -1876,6 +1893,10 @@
      */
     @Test
     public void testGetUiccCardsInfoException() {
+        if (!mPackageManager.hasSystemFeature(PackageManager.FEATURE_TELEPHONY)) {
+            Log.d(TAG, "skipping test on device without FEATURE_TELEPHONY present");
+            return;
+        }
         try {
             // Requires READ_PRIVILEGED_PHONE_STATE or carrier privileges
             List<UiccCardInfo> infos = mTelephonyManager.getUiccCardsInfo();
@@ -2836,5 +2857,10 @@
         }
         return mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM;
     }
+
+    private static int makeRadioVersion(int major, int minor) {
+        if (major < 0 || minor < 0) return 0;
+        return major * 100 + minor;
+    }
 }
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyProtectedBroadcastsTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyProtectedBroadcastsTest.java
index fedd639..922572b 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyProtectedBroadcastsTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyProtectedBroadcastsTest.java
@@ -16,12 +16,15 @@
 package android.telephony.cts;
 
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageManager;
 
 import androidx.test.InstrumentationRegistry;
 
+import org.junit.Before;
 import org.junit.Test;
 
 
@@ -46,6 +49,12 @@
             "android.telephony.action.SIM_SLOT_STATUS_CHANGED",
     };
 
+    @Before
+    public void setUp() throws Exception {
+        assumeTrue(getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+    }
+
     @Test
     public void testBroadcasts() {
         StringBuilder errorMessage = new StringBuilder(); //Fail on all missing broadcasts
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyRegistryManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyRegistryManagerTest.java
index 946efe6b..45a4a1c 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyRegistryManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyRegistryManagerTest.java
@@ -4,8 +4,10 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.telephony.PhoneStateListener;
 import android.telephony.ServiceState;
 import android.telephony.SignalStrength;
@@ -34,6 +36,9 @@
 
     @Before
     public void setUp() throws Exception {
+        assumeTrue(InstrumentationRegistry.getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_TELEPHONY));
+
         mTelephonyRegistryMgr = (TelephonyRegistryManager) InstrumentationRegistry.getContext()
             .getSystemService(Context.TELEPHONY_REGISTRY_SERVICE);
     }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
index f96928a..cddf19e 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsMmTelManagerTest.java
@@ -20,6 +20,7 @@
 
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -98,9 +99,7 @@
 
     @BeforeClass
     public static void beforeAllTests() {
-        if (!ImsUtils.shouldTestImsService()) {
-            return;
-        }
+        assumeTrue(ImsUtils.shouldTestImsService());
 
         sTestSub = ImsUtils.getPreferredActiveSubId();
 
@@ -117,10 +116,6 @@
 
     @AfterClass
     public static void afterAllTests() {
-        if (!ImsUtils.shouldTestImsService()) {
-            return;
-        }
-
         if (sReceiver != null) {
             getContext().unregisterReceiver(sReceiver);
             sReceiver = null;
@@ -129,10 +124,6 @@
 
     @Before
     public void beforeTest() {
-        if (!ImsUtils.shouldTestImsService()) {
-            return;
-        }
-
         if (!SubscriptionManager.isValidSubscriptionId(sTestSub)) {
             fail("This test requires that there is a SIM in the device!");
         }
@@ -159,9 +150,6 @@
      */
     @Test
     public void testAdvancedCallingSetting() throws Exception {
-        if (!ImsUtils.shouldTestImsService()) {
-            return;
-        }
         // Ensure advanced calling setting is editable.
         PersistableBundle bundle = new PersistableBundle();
         bundle.putBoolean(CarrierConfigManager.KEY_EDITABLE_ENHANCED_4G_LTE_BOOL, true);
@@ -199,10 +187,6 @@
      */
     @Test
     public void testVtSetting() throws Exception {
-        if (!ImsUtils.shouldTestImsService()) {
-            return;
-        }
-
         // Register Observer
         Uri callingUri = Uri.withAppendedPath(
                 SubscriptionManager.VT_ENABLED_CONTENT_URI, "" + sTestSub);
@@ -233,10 +217,6 @@
      */
     @Test
     public void testVoWiFiSetting() throws Exception {
-        if (!ImsUtils.shouldTestImsService()) {
-            return;
-        }
-
         PersistableBundle bundle = new PersistableBundle();
         // Do not worry about provisioning for this test
         bundle.putBoolean(KEY_CARRIER_VOLTE_OVERRIDE_WFC_PROVISIONING_BOOL, false);
@@ -274,10 +254,6 @@
      */
     @Test
     public void testVoWiFiRoamingSetting() throws Exception {
-        if (!ImsUtils.shouldTestImsService()) {
-            return;
-        }
-
         Uri callingUri = Uri.withAppendedPath(
                 SubscriptionManager.WFC_ROAMING_ENABLED_CONTENT_URI, "" + sTestSub);
         CountDownLatch contentObservedLatch = new CountDownLatch(1);
@@ -306,9 +282,6 @@
      */
     @Test
     public void testGetVoWiFiModeSetting_noPermission() throws Exception {
-        if (!ImsUtils.shouldTestImsService()) {
-            return;
-        }
         try {
             ImsManager imsManager = getContext().getSystemService(ImsManager.class);
             ImsMmTelManager mMmTelManager = imsManager.getImsMmTelManager(sTestSub);
@@ -325,9 +298,6 @@
      */
     @Test
     public void testGetVoWiFiRoamingModeSetting_noPermission() throws Exception {
-        if (!ImsUtils.shouldTestImsService()) {
-            return;
-        }
         try {
             ImsManager imsManager = getContext().getSystemService(ImsManager.class);
             ImsMmTelManager mMmTelManager = imsManager.getImsMmTelManager(sTestSub);
@@ -345,10 +315,6 @@
      */
     @Test
     public void testVoWiFiModeSetting() throws Exception {
-        if (!ImsUtils.shouldTestImsService()) {
-            return;
-        }
-
         PersistableBundle bundle = new PersistableBundle();
         bundle.putBoolean(KEY_EDITABLE_WFC_MODE_BOOL, true);
         overrideCarrierConfig(bundle);
@@ -383,10 +349,6 @@
      */
     @Test
     public void testVoWiFiRoamingModeSetting() throws Exception {
-        if (!ImsUtils.shouldTestImsService()) {
-            return;
-        }
-
         PersistableBundle bundle = new PersistableBundle();
         // Ensure the WFC roaming mode will be changed properly
         bundle.putBoolean(KEY_USE_WFC_HOME_NETWORK_MODE_IN_ROAMING_NETWORK_BOOL, false);
@@ -424,10 +386,6 @@
      */
     @Test
     public void testMethodPermissions() throws Exception {
-        if (!ImsUtils.shouldTestImsService()) {
-            return;
-        }
-
         ImsManager imsManager = getContext().getSystemService(ImsManager.class);
         ImsMmTelManager mMmTelManager = imsManager.getImsMmTelManager(sTestSub);
         // setRttCapabilitySetting
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
index 5e28767..7c92b15 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/ImsServiceTest.java
@@ -1746,6 +1746,29 @@
                 sServiceConnector.getCarrierService().getMmTelFeature());
     }
 
+    // Waiting for ImsRcsManager to become public before implementing RegistrationManager,
+    // Duplicate these methods for now.
+    private void verifyRegistrationState(ImsRcsManager regManager, int expectedState)
+            throws Exception {
+        LinkedBlockingQueue<Integer> mQueue = new LinkedBlockingQueue<>();
+        assertTrue(ImsUtils.retryUntilTrue(() -> {
+            ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(regManager,
+                    (m) -> m.getRegistrationState(getContext().getMainExecutor(), mQueue::offer));
+            return waitForIntResult(mQueue) == expectedState;
+        }));
+    }
+
+    // Waiting for ImsRcsManager to become public before implementing RegistrationManager,
+    // Duplicate these methods for now.
+    private void verifyRegistrationTransportType(ImsRcsManager regManager,
+            int expectedTransportType) throws Exception {
+        LinkedBlockingQueue<Integer> mQueue = new LinkedBlockingQueue<>();
+        ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(regManager,
+                (m) -> m.getRegistrationTransportType(getContext().getMainExecutor(),
+                        mQueue::offer));
+        assertEquals(expectedTransportType, waitForIntResult(mQueue));
+    }
+
     private void verifyRegistrationState(RegistrationManager regManager, int expectedState)
             throws Exception {
         LinkedBlockingQueue<Integer> mQueue = new LinkedBlockingQueue<>();
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
index 769a588..a1816d8 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsUceAdapterTest.java
@@ -19,10 +19,14 @@
 import static junit.framework.Assert.assertTrue;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.fail;
 
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Handler;
@@ -33,6 +37,7 @@
 import android.telephony.SubscriptionManager;
 import android.telephony.ims.ImsException;
 import android.telephony.ims.ImsManager;
+import android.telephony.ims.ImsRcsManager;
 import android.telephony.ims.RcsUceAdapter;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -101,6 +106,22 @@
     }
 
     @Test
+    public void testCapabilityDiscoveryIntentReceiverExists() {
+        if (!ImsUtils.shouldTestImsService()) {
+            return;
+        }
+
+        PackageManager packageManager = getContext().getPackageManager();
+        ResolveInfo info = packageManager.resolveActivity(
+                new Intent(ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN),
+                PackageManager.MATCH_DEFAULT_ONLY);
+        assertNotNull(ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN
+                + " Intent action must be handled by an appropriate settings application.", info);
+        assertNotEquals(ImsRcsManager.ACTION_SHOW_CAPABILITY_DISCOVERY_OPT_IN
+                + " activity intent filter must have a > 0 priority.", 0, info.priority);
+    }
+
+    @Test
     public void testGetAndSetUceSetting() throws Exception {
         if (!ImsUtils.shouldTestImsService()) {
             return;
diff --git a/tests/tests/tethering/Android.bp b/tests/tests/tethering/Android.bp
index 0f98125..63de301 100644
--- a/tests/tests/tethering/Android.bp
+++ b/tests/tests/tethering/Android.bp
@@ -25,12 +25,19 @@
     ],
 
     static_libs: [
+        "TetheringIntegrationTestsLib",
         "compatibility-device-util-axt",
         "ctstestrunner-axt",
         "junit",
         "junit-params",
     ],
 
+    jni_libs: [
+        // For mockito extended
+        "libdexmakerjvmtiagent",
+        "libstaticjvmtiagent",
+    ],
+
     // Change to system current when TetheringManager move to bootclass path.
     platform_apis: true,
 
@@ -41,4 +48,6 @@
         "mts",
     ],
 
+    // Include both the 32 and 64 bit versions
+    compile_multilib: "both",
 }
diff --git a/tests/tests/tethering/OWNERS b/tests/tests/tethering/OWNERS
new file mode 100644
index 0000000..cd6abeb
--- /dev/null
+++ b/tests/tests/tethering/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 31808
+lorenzo@google.com
+satk@google.com
+
diff --git a/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 86fe54c..ccad14c 100644
--- a/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -15,8 +15,13 @@
  */
 package android.tethering.test;
 
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_FAILED;
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STARTED;
+import static android.net.TetheringManager.TETHER_HARDWARE_OFFLOAD_STOPPED;
 import static android.net.TetheringManager.TETHERING_USB;
 import static android.net.TetheringManager.TETHERING_WIFI;
+import static android.net.TetheringManager.TETHERING_WIFI_P2P;
+import static android.net.TetheringManager.TETHER_ERROR_ENTITLEMENT_UNKNOWN;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -29,9 +34,17 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.net.LinkAddress;
+import android.net.Network;
+import android.net.TetheredClient;
 import android.net.TetheringManager;
+import android.net.TetheringManager.OnTetheringEntitlementResultListener;
+import android.net.TetheringManager.TetheringEventCallback;
+import android.net.TetheringManager.TetheringInterfaceRegexps;
 import android.net.TetheringManager.TetheringRequest;
+import android.os.Bundle;
+import android.os.ResultReceiver;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -42,8 +55,14 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
 
 @RunWith(AndroidJUnit4.class)
 public class TetheringManagerTest {
@@ -195,8 +214,12 @@
         }
     }
 
-    private static boolean isIfaceMatch(final String[] ifaceRegexs,
-            final ArrayList<String> ifaces) {
+    private static boolean isIfaceMatch(final List<String> ifaceRegexs,
+            final List<String> ifaces) {
+        return isIfaceMatch(ifaceRegexs.toArray(new String[0]), ifaces);
+    }
+
+    private static boolean isIfaceMatch(final String[] ifaceRegexs, final List<String> ifaces) {
         if (ifaceRegexs == null) fail("ifaceRegexs should not be null");
 
         if (ifaces == null) return false;
@@ -251,4 +274,267 @@
         assertTrue(tr2.isExemptFromEntitlementCheck());
         assertFalse(tr2.getShouldShowEntitlementUi());
     }
+
+    // Must poll the callback before looking at the member.
+    private static class TestTetheringEventCallback implements TetheringEventCallback {
+        public enum CallbackType {
+            ON_SUPPORTED,
+            ON_UPSTREAM,
+            ON_TETHERABLE_REGEX,
+            ON_TETHERABLE_IFACES,
+            ON_TETHERED_IFACES,
+            ON_ERROR,
+            ON_CLIENTS,
+            ON_OFFLOAD_STATUS,
+        };
+
+        public static class CallbackValue {
+            public final CallbackType callbackType;
+            public final Object callbackParam;
+            public final int callbackParam2;
+
+            private CallbackValue(final CallbackType type, final Object param, final int param2) {
+                this.callbackType = type;
+                this.callbackParam = param;
+                this.callbackParam2 = param2;
+            }
+        }
+        private final LinkedBlockingQueue<CallbackValue> mCallbacks = new LinkedBlockingQueue<>();
+
+        private TetheringInterfaceRegexps mTetherableRegex;
+        private List<String> mTetherableIfaces;
+        private List<String> mTetheredIfaces;
+
+        @Override
+        public void onTetheringSupported(boolean supported) {
+            mCallbacks.add(new CallbackValue(CallbackType.ON_SUPPORTED, null, 0));
+        }
+
+        @Override
+        public void onUpstreamChanged(Network network) {
+            mCallbacks.add(new CallbackValue(CallbackType.ON_UPSTREAM, network, 0));
+        }
+
+        @Override
+        public void onTetherableInterfaceRegexpsChanged(TetheringInterfaceRegexps reg) {
+            mTetherableRegex = reg;
+            mCallbacks.add(new CallbackValue(CallbackType.ON_TETHERABLE_REGEX, reg, 0));
+        }
+
+        @Override
+        public void onTetherableInterfacesChanged(List<String> interfaces) {
+            mTetherableIfaces = interfaces;
+            mCallbacks.add(new CallbackValue(CallbackType.ON_TETHERABLE_IFACES, interfaces, 0));
+        }
+
+        @Override
+        public void onTetheredInterfacesChanged(List<String> interfaces) {
+            mTetheredIfaces = interfaces;
+            mCallbacks.add(new CallbackValue(CallbackType.ON_TETHERED_IFACES, interfaces, 0));
+        }
+
+        @Override
+        public void onError(String ifName, int error) {
+            mCallbacks.add(new CallbackValue(CallbackType.ON_ERROR, ifName, error));
+        }
+
+        @Override
+        public void onClientsChanged(Collection<TetheredClient> clients) {
+            mCallbacks.add(new CallbackValue(CallbackType.ON_CLIENTS, clients, 0));
+        }
+
+        @Override
+        public void onOffloadStatusChanged(int status) {
+            mCallbacks.add(new CallbackValue(CallbackType.ON_OFFLOAD_STATUS, status, 0));
+        }
+
+        public CallbackValue pollCallback() {
+            try {
+                return mCallbacks.poll(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS);
+            } catch (InterruptedException e) {
+                fail("Callback not seen");
+            }
+            return null;
+        }
+
+        public void expectTetherableInterfacesChanged(@NonNull List<String> regexs) {
+            while (true) {
+                final CallbackValue cv = pollCallback();
+                if (cv == null) fail("No expected tetherable ifaces callback");
+                if (cv.callbackType != CallbackType.ON_TETHERABLE_IFACES) continue;
+
+                final List<String> interfaces = (List<String>) cv.callbackParam;
+                if (isIfaceMatch(regexs, interfaces)) break;
+            }
+        }
+
+        public void expectTetheredInterfacesChanged(@NonNull List<String> regexs) {
+            while (true) {
+                final CallbackValue cv = pollCallback();
+                if (cv == null) fail("No expected tethered ifaces callback");
+                if (cv.callbackType != CallbackType.ON_TETHERED_IFACES) continue;
+
+                final List<String> interfaces = (List<String>) cv.callbackParam;
+
+                // Null regexs means no active tethering.
+                if (regexs == null) {
+                    if (interfaces.size() == 0) break;
+                } else if (isIfaceMatch(regexs, interfaces)) {
+                    break;
+                }
+            }
+        }
+
+        public void expectCallbackStarted() {
+            // The each bit represent a type from CallbackType.ON_*.
+            // Expect all of callbacks except for ON_ERROR.
+            final int expectedBitMap = 0x7f ^ (1 << CallbackType.ON_ERROR.ordinal());
+            int receivedBitMap = 0;
+            while (receivedBitMap != expectedBitMap) {
+                final CallbackValue cv = pollCallback();
+                if (cv == null) {
+                    fail("No expected callbacks, " + "expected bitmap: "
+                            + expectedBitMap + ", actual: " + receivedBitMap);
+                }
+                receivedBitMap = receivedBitMap | (1 << cv.callbackType.ordinal());
+            }
+        }
+
+        public void expectOneOfOffloadStatusChanged(int... offloadStatuses) {
+            while (true) {
+                final CallbackValue cv = pollCallback();
+                if (cv == null) fail("No expected offload status change callback");
+                if (cv.callbackType != CallbackType.ON_OFFLOAD_STATUS) continue;
+
+                final int status = (int) cv.callbackParam;
+                for (int offloadStatus : offloadStatuses) if (offloadStatus == status) return;
+            }
+        }
+
+        public TetheringInterfaceRegexps getTetheringInterfaceRegexps() {
+            return mTetherableRegex;
+        }
+
+        public List<String> getTetherableInterfaces() {
+            return mTetherableIfaces;
+        }
+
+        public List<String> getTetheredInterfaces() {
+            return mTetheredIfaces;
+        }
+    }
+
+    @Test
+    public void testRegisterTetheringEventCallback() throws Exception {
+        if (!mTM.isTetheringSupported()) return;
+
+        final TestTetheringEventCallback tetherEventCallback = new TestTetheringEventCallback();
+
+        mTM.registerTetheringEventCallback(c -> c.run(), tetherEventCallback);
+        tetherEventCallback.expectCallbackStarted();
+        tetherEventCallback.expectOneOfOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
+
+        final TetheringInterfaceRegexps tetherableRegexs =
+                tetherEventCallback.getTetheringInterfaceRegexps();
+        final List<String> wifiRegexs = tetherableRegexs.getTetherableWifiRegexs();
+        if (wifiRegexs.size() == 0) return;
+
+        final boolean isIfaceAvailWhenNoTethering =
+                isIfaceMatch(wifiRegexs, tetherEventCallback.getTetherableInterfaces());
+
+        mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(), c -> c.run(),
+                new StartTetheringCallback());
+
+        // If interface is already available before starting tethering, the available callback may
+        // not be sent after tethering enabled.
+        if (!isIfaceAvailWhenNoTethering) {
+            tetherEventCallback.expectTetherableInterfacesChanged(wifiRegexs);
+        }
+
+        tetherEventCallback.expectTetheredInterfacesChanged(wifiRegexs);
+        tetherEventCallback.expectOneOfOffloadStatusChanged(
+                TETHER_HARDWARE_OFFLOAD_STARTED,
+                TETHER_HARDWARE_OFFLOAD_FAILED);
+
+        mTM.stopTethering(TETHERING_WIFI);
+
+        tetherEventCallback.expectTetheredInterfacesChanged(null);
+        tetherEventCallback.expectOneOfOffloadStatusChanged(TETHER_HARDWARE_OFFLOAD_STOPPED);
+        mTM.unregisterTetheringEventCallback(tetherEventCallback);
+    }
+
+    @Test
+    public void testGetTetherableInterfaceRegexps() {
+        if (!mTM.isTetheringSupported()) return;
+
+        final TestTetheringEventCallback tetherEventCallback = new TestTetheringEventCallback();
+        mTM.registerTetheringEventCallback(c -> c.run(), tetherEventCallback);
+        tetherEventCallback.expectCallbackStarted();
+
+        final TetheringInterfaceRegexps tetherableRegexs =
+                tetherEventCallback.getTetheringInterfaceRegexps();
+        final List<String> wifiRegexs = tetherableRegexs.getTetherableWifiRegexs();
+        final List<String> usbRegexs = tetherableRegexs.getTetherableUsbRegexs();
+        final List<String> btRegexs = tetherableRegexs.getTetherableBluetoothRegexs();
+
+        assertEquals(wifiRegexs, Arrays.asList(mTM.getTetherableWifiRegexs()));
+        assertEquals(usbRegexs, Arrays.asList(mTM.getTetherableUsbRegexs()));
+        assertEquals(btRegexs, Arrays.asList(mTM.getTetherableBluetoothRegexs()));
+
+        //Verify that any regex name should only contain in one array.
+        wifiRegexs.forEach(s -> assertFalse(usbRegexs.contains(s)));
+        wifiRegexs.forEach(s -> assertFalse(btRegexs.contains(s)));
+        usbRegexs.forEach(s -> assertFalse(btRegexs.contains(s)));
+
+        mTM.unregisterTetheringEventCallback(tetherEventCallback);
+    }
+
+    private class EntitlementResultListener implements OnTetheringEntitlementResultListener {
+        private final CompletableFuture<Integer> future = new CompletableFuture<>();
+
+        @Override
+        public void onTetheringEntitlementResult(int result) {
+            future.complete(result);
+        }
+
+        public int get(long timeout, TimeUnit unit) throws Exception {
+            return future.get(timeout, unit);
+        }
+
+    }
+
+    private void assertEntitlementResult(final Consumer<EntitlementResultListener> functor,
+            final int expect) throws Exception {
+        final EntitlementResultListener listener = new EntitlementResultListener();
+        functor.accept(listener);
+
+        assertEquals(expect, listener.get(DEFAULT_TIMEOUT_MS, TimeUnit.MILLISECONDS));
+    }
+
+    @Test
+    public void testRequestLatestEntitlementResult() throws Exception {
+        // Verify that requestLatestTetheringEntitlementResult() can get entitlement
+        // result(TETHER_ERROR_ENTITLEMENT_UNKNOWN due to invalid downstream type) via listener.
+        assertEntitlementResult(listener -> mTM.requestLatestTetheringEntitlementResult(
+                TETHERING_WIFI_P2P, false, c -> c.run(), listener),
+                TETHER_ERROR_ENTITLEMENT_UNKNOWN);
+
+        // Verify that requestLatestTetheringEntitlementResult() can get entitlement
+        // result(TETHER_ERROR_ENTITLEMENT_UNKNOWN due to invalid downstream type) via receiver.
+        assertEntitlementResult(listener -> mTM.requestLatestTetheringEntitlementResult(
+                TETHERING_WIFI_P2P,
+                new ResultReceiver(null /* handler */) {
+                    @Override
+                    public void onReceiveResult(int resultCode, Bundle resultData) {
+                        listener.onTetheringEntitlementResult(resultCode);
+                    }
+                }, false),
+                TETHER_ERROR_ENTITLEMENT_UNKNOWN);
+
+        // Verify that null listener will cause IllegalArgumentException.
+        try {
+            mTM.requestLatestTetheringEntitlementResult(
+                    TETHERING_WIFI, false, c -> c.run(), null);
+        } catch (IllegalArgumentException expect) { }
+    }
 }
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionsTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionsTest.java
index 843891f..3434aba 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionsTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/ConversationActionsTest.java
@@ -150,9 +150,9 @@
                 new ConversationActions.Message.Builder(PERSON)
                         .setText(TEXT)
                         .build();
-
         ConversationActions.Request request =
                 new ConversationActions.Request.Builder(Collections.singletonList(message))
+                        .setMaxSuggestions(-1) // The default value.
                         .build();
 
         ConversationActions.Request recovered =
diff --git a/tests/tests/tv/AndroidManifest.xml b/tests/tests/tv/AndroidManifest.xml
index 8cb2b79..a3429bb 100644
--- a/tests/tests/tv/AndroidManifest.xml
+++ b/tests/tests/tv/AndroidManifest.xml
@@ -24,6 +24,10 @@
 
     <uses-permission android:name="com.android.providers.tv.permission.WRITE_EPG_DATA" />
 
+    <queries>
+        <package android:name="com.android.providers.tv" />
+    </queries>
+
     <application>
         <uses-library android:name="android.test.runner" />
 
diff --git a/tests/tests/tv/src/android/media/tv/cts/TvTrackInfoTest.java b/tests/tests/tv/src/android/media/tv/cts/TvTrackInfoTest.java
index ed3789d..4800296 100644
--- a/tests/tests/tv/src/android/media/tv/cts/TvTrackInfoTest.java
+++ b/tests/tests/tv/src/android/media/tv/cts/TvTrackInfoTest.java
@@ -20,12 +20,15 @@
 
 import static org.testng.Assert.assertThrows;
 
+import android.content.Context;
 import android.media.tv.TvTrackInfo;
 import android.os.Bundle;
 
-import androidx.test.core.app.ApplicationProvider;
 import androidx.test.core.os.Parcelables;
 
+import com.android.compatibility.common.util.RequiredServiceRule;
+
+import org.junit.Rule;
 import org.junit.Test;
 
 /**
@@ -33,11 +36,12 @@
  */
 public class TvTrackInfoTest {
 
+    @Rule
+    public final RequiredServiceRule requiredServiceRule = new RequiredServiceRule(
+            Context.TV_INPUT_SERVICE);
+
     @Test
     public void setHardOfHearing_invalid() {
-        if (!Utils.hasTvInputFramework(ApplicationProvider.getApplicationContext())) {
-            return;
-        }
         assertThrows(
                 IllegalStateException.class,
                 () -> new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "invalid")
@@ -47,9 +51,6 @@
 
     @Test
     public void newAudioTrack_default() {
-        if (!Utils.hasTvInputFramework(ApplicationProvider.getApplicationContext())) {
-            return;
-        }
         final TvTrackInfo info = new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "default")
                 .build();
         assertThat(info).hasType(TvTrackInfo.TYPE_AUDIO);
@@ -70,9 +71,6 @@
 
     @Test
     public void newAudioTrack_everything() {
-        if (!Utils.hasTvInputFramework(ApplicationProvider.getApplicationContext())) {
-            return;
-        }
         final Bundle bundle = new Bundle();
         final TvTrackInfo info = new TvTrackInfo.Builder(TvTrackInfo.TYPE_AUDIO, "id_audio")
                 .setAudioChannelCount(2)
@@ -102,9 +100,6 @@
 
     @Test
     public void newVideoTrack_default() {
-        if (!Utils.hasTvInputFramework(ApplicationProvider.getApplicationContext())) {
-            return;
-        }
         final TvTrackInfo info = new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "default")
                 .build();
         assertThat(info).hasType(TvTrackInfo.TYPE_VIDEO);
@@ -126,9 +121,6 @@
 
     @Test
     public void newVideoTrack_everything() {
-        if (!Utils.hasTvInputFramework(ApplicationProvider.getApplicationContext())) {
-            return;
-        }
         final Bundle bundle = new Bundle();
         bundle.putBoolean("testTrue", true);
         final TvTrackInfo info = new TvTrackInfo.Builder(TvTrackInfo.TYPE_VIDEO, "id_video")
@@ -161,9 +153,6 @@
 
     @Test
     public void newSubtitleTrack_default() {
-        if (!Utils.hasTvInputFramework(ApplicationProvider.getApplicationContext())) {
-            return;
-        }
         final Bundle bundle = new Bundle();
         bundle.putBoolean("testTrue", true);
         final TvTrackInfo info = new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, "default")
@@ -183,9 +172,6 @@
 
     @Test
     public void newSubtitleTrack_everything() {
-        if (!Utils.hasTvInputFramework(ApplicationProvider.getApplicationContext())) {
-            return;
-        }
         final Bundle bundle = new Bundle();
         bundle.putBoolean("testTrue", true);
         final TvTrackInfo info = new TvTrackInfo.Builder(TvTrackInfo.TYPE_SUBTITLE, "id_subtitle")
diff --git a/tests/tests/tv/src/android/media/tv/cts/Utils.java b/tests/tests/tv/src/android/media/tv/cts/Utils.java
index 91f49b1..01a93f6 100644
--- a/tests/tests/tv/src/android/media/tv/cts/Utils.java
+++ b/tests/tests/tv/src/android/media/tv/cts/Utils.java
@@ -2,11 +2,20 @@
 
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.util.Log;
+
+import org.junit.Assume;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
 
 public class Utils {
-    private Utils() { }
+
+    private Utils() {
+    }
 
     public static boolean hasTvInputFramework(Context context) {
         return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LIVE_TV);
     }
+
 }
diff --git a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
index af5239c..aa8be38 100644
--- a/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
+++ b/tests/tests/tv/src/android/media/tv/tuner/cts/TunerTest.java
@@ -18,6 +18,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
 
 import android.content.Context;
 import android.media.tv.tuner.Descrambler;
@@ -32,12 +33,20 @@
 import android.media.tv.tuner.filter.FilterEvent;
 import android.media.tv.tuner.filter.Filter;
 import android.media.tv.tuner.filter.TimeFilter;
+import android.media.tv.tuner.frontend.Atsc3PlpInfo;
+import android.media.tv.tuner.frontend.AtscFrontendSettings;
+import android.media.tv.tuner.frontend.FrontendInfo;
+import android.media.tv.tuner.frontend.FrontendSettings;
+import android.media.tv.tuner.frontend.ScanCallback;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
+import java.util.concurrent.TimeUnit;
 
 import org.junit.After;
 import org.junit.Before;
@@ -49,31 +58,70 @@
 public class TunerTest {
     private static final String TAG = "MediaTunerTest";
 
+    private static final int TIMEOUT_MS = 10000;
+
     private Context mContext;
+    private Tuner mTuner;
+    private CountDownLatch mLockLatch = new CountDownLatch(1);
 
     @Before
     public void setUp() throws Exception {
         mContext = InstrumentationRegistry.getTargetContext();
         InstrumentationRegistry
                 .getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+        if (!hasTuner()) return;
+        mTuner = new Tuner(mContext, null, 100);
     }
 
     @After
     public void tearDown() {
+        if (mTuner != null) {
+          mTuner.close();
+          mTuner = null;
+        }
     }
 
     @Test
     public void testTunerConstructor() throws Exception {
         if (!hasTuner()) return;
-        Tuner tuner = new Tuner(mContext, null, 100);
-        assertNotNull(tuner);
+        assertNotNull(mTuner);
+    }
+
+    @Test
+    public void testTuning() throws Exception {
+        if (!hasTuner()) return;
+        int res = mTuner.tune(getFrontendSettings());
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+        res = mTuner.cancelTuning();
+        assertEquals(Tuner.RESULT_SUCCESS, res);
+    }
+
+    @Test
+    public void testScanning() throws Exception {
+        if (!hasTuner()) return;
+        List<Integer> ids = mTuner.getFrontendIds();
+        for (int id : ids) {
+            FrontendInfo info = mTuner.getFrontendInfoById(id);
+            if (info != null && info.getType() == FrontendSettings.TYPE_ATSC) {
+                mLockLatch = new CountDownLatch(1);
+                int res = mTuner.scan(
+                        getFrontendSettings(),
+                        Tuner.SCAN_TYPE_AUTO,
+                        getExecutor(),
+                        getScanCallback());
+               assertEquals(Tuner.RESULT_SUCCESS, res);
+               assertTrue(mLockLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+               res = mTuner.cancelScanning();
+               assertEquals(Tuner.RESULT_SUCCESS, res);
+            }
+        }
+        mLockLatch = null;
     }
 
     @Test
     public void testOpenLnb() throws Exception {
         if (!hasTuner()) return;
-        Tuner tuner = new Tuner(mContext, null, 100);
-        Lnb lnb = tuner.openLnb(getExecutor(), getLnbCallback());
+        Lnb lnb = mTuner.openLnb(getExecutor(), getLnbCallback());
         assertNotNull(lnb);
     }
 
@@ -81,24 +129,21 @@
     public void testLnbSetVoltage() throws Exception {
         // TODO: move lnb-related tests to a separate file.
         if (!hasTuner()) return;
-        Tuner tuner = new Tuner(mContext, null, 100);
-        Lnb lnb = tuner.openLnb(getExecutor(), getLnbCallback());
+        Lnb lnb = mTuner.openLnb(getExecutor(), getLnbCallback());
         assertEquals(lnb.setVoltage(Lnb.VOLTAGE_5V), Tuner.RESULT_SUCCESS);
     }
 
     @Test
     public void testLnbSetTone() throws Exception {
         if (!hasTuner()) return;
-        Tuner tuner = new Tuner(mContext, null, 100);
-        Lnb lnb = tuner.openLnb(getExecutor(), getLnbCallback());
+        Lnb lnb = mTuner.openLnb(getExecutor(), getLnbCallback());
         assertEquals(lnb.setTone(Lnb.TONE_NONE), Tuner.RESULT_SUCCESS);
     }
 
     @Test
     public void testLnbSetPosistion() throws Exception {
         if (!hasTuner()) return;
-        Tuner tuner = new Tuner(mContext, null, 100);
-        Lnb lnb = tuner.openLnb(getExecutor(), getLnbCallback());
+        Lnb lnb = mTuner.openLnb(getExecutor(), getLnbCallback());
         assertEquals(
                 lnb.setSatellitePosition(Lnb.POSITION_A), Tuner.RESULT_SUCCESS);
     }
@@ -106,8 +151,7 @@
     @Test
     public void testOpenFilter() throws Exception {
         if (!hasTuner()) return;
-        Tuner tuner = new Tuner(mContext, null, 100);
-        Filter f = tuner.openFilter(
+        Filter f = mTuner.openFilter(
                 Filter.TYPE_TS, Filter.SUBTYPE_SECTION, 1000, getExecutor(), getFilterCallback());
         assertNotNull(f);
     }
@@ -115,32 +159,28 @@
     @Test
     public void testOpenTimeFilter() throws Exception {
         if (!hasTuner()) return;
-        Tuner tuner = new Tuner(mContext, null, 100);
-        TimeFilter f = tuner.openTimeFilter();
+        TimeFilter f = mTuner.openTimeFilter();
         assertNotNull(f);
     }
 
     @Test
     public void testOpenDescrambler() throws Exception {
         if (!hasTuner()) return;
-        Tuner tuner = new Tuner(mContext, null, 100);
-        Descrambler d = tuner.openDescrambler();
+        Descrambler d = mTuner.openDescrambler();
         assertNotNull(d);
     }
 
     @Test
     public void testOpenDvrRecorder() throws Exception {
         if (!hasTuner()) return;
-        Tuner tuner = new Tuner(mContext, null, 100);
-        DvrRecorder d = tuner.openDvrRecorder(100, getExecutor(), getRecordListener());
+        DvrRecorder d = mTuner.openDvrRecorder(100, getExecutor(), getRecordListener());
         assertNotNull(d);
     }
 
     @Test
     public void testOpenDvPlayback() throws Exception {
         if (!hasTuner()) return;
-        Tuner tuner = new Tuner(mContext, null, 100);
-        DvrPlayback d = tuner.openDvrPlayback(100, getExecutor(), getPlaybackListener());
+        DvrPlayback d = mTuner.openDvrPlayback(100, getExecutor(), getPlaybackListener());
         assertNotNull(d);
     }
 
@@ -176,10 +216,69 @@
             public void onRecordStatusChanged(int status) {}
         };
     }
+
     private OnPlaybackStatusChangedListener getPlaybackListener() {
         return new OnPlaybackStatusChangedListener() {
             @Override
             public void onPlaybackStatusChanged(int status) {}
         };
     }
+
+    private FrontendSettings getFrontendSettings() {
+        return AtscFrontendSettings
+                .builder()
+                .setFrequency(2000)
+                .setModulation(AtscFrontendSettings.MODULATION_AUTO)
+                .build();
+    }
+
+    private ScanCallback getScanCallback() {
+        return new ScanCallback() {
+            @Override
+            public void onLocked() {
+                if (mLockLatch != null) {
+                    mLockLatch.countDown();
+                }
+            }
+
+            @Override
+            public void onScanStopped() {}
+
+            @Override
+            public void onProgress(int percent) {}
+
+            @Override
+            public void onFrequenciesReported(int[] frequency) {}
+
+            @Override
+            public void onSymbolRatesReported(int[] rate) {}
+
+            @Override
+            public void onPlpIdsReported(int[] plpIds) {}
+
+            @Override
+            public void onGroupIdsReported(int[] groupIds) {}
+
+            @Override
+            public void onInputStreamIdsReported(int[] inputStreamIds) {}
+
+            @Override
+            public void onDvbsStandardReported(int dvbsStandard) {}
+
+            @Override
+            public void onDvbtStandardReported(int dvbtStandard) {}
+
+            @Override
+            public void onAnalogSifStandardReported(int sif) {}
+
+            @Override
+            public void onAtsc3PlpInfosReported(Atsc3PlpInfo[] atsc3PlpInfos) {}
+
+            @Override
+            public void onHierarchyReported(int hierarchy) {}
+
+            @Override
+            public void onSignalTypeReported(int signalType) {}
+        };
+    }
 }
diff --git a/tests/tests/uirendering/AndroidManifest.xml b/tests/tests/uirendering/AndroidManifest.xml
index d7adaf7..f43818a 100644
--- a/tests/tests/uirendering/AndroidManifest.xml
+++ b/tests/tests/uirendering/AndroidManifest.xml
@@ -32,7 +32,8 @@
                   android:theme="@style/DefaultTheme"
                   android:screenOrientation="locked"
                   android:resizeableActivity="false"
-                  android:configChanges="uiMode" />
+                  android:configChanges="uiMode"
+                  android:requestLegacyExternalStorage="true" />
         <uses-library android:name="android.test.runner" />
     </application>
 
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/runner/UiRenderingRunner.java b/tests/tests/uirendering/src/android/uirendering/cts/runner/UiRenderingRunner.java
index a238699..0215e16 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/runner/UiRenderingRunner.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/runner/UiRenderingRunner.java
@@ -16,10 +16,10 @@
 
 package android.uirendering.cts.runner;
 
+import android.content.Intent;
 import android.os.Bundle;
 import android.support.test.uiautomator.UiDevice;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnitRunner;
 
 /**
@@ -36,10 +36,11 @@
     public void onCreate(Bundle arguments) {
         super.onCreate(arguments);
 
-        final UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+        final UiDevice device = UiDevice.getInstance(this);
         try {
             device.wakeUp();
             device.executeShellCommand("wm dismiss-keyguard");
+            getContext().sendBroadcast(new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS));
         } catch (Exception e) {
         }
     }
diff --git a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java
index de47fbf..84cbd00 100644
--- a/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java
+++ b/tests/tests/view/surfacevalidator/src/android/view/cts/surfacevalidator/CapturedActivity.java
@@ -130,12 +130,6 @@
         bindMediaProjectionService();
     }
 
-    @Override
-    public void onStop() {
-        super.onStop();
-        mSettingsSession.close();
-    }
-
     public void dismissPermissionDialog() {
         // The permission dialog will be auto-opened by the activity - find it and accept
         UiDevice uiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
@@ -180,6 +174,7 @@
             unbindService(mConnection);
             mProjectionServiceBound = false;
         }
+        mSettingsSession.close();
     }
 
     @Override
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
index fdd6a51..81ea695 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebViewClientTest.java
@@ -915,8 +915,9 @@
             // TODO(ntfschr): propagate these exceptions to the instrumentation thread.
             assertTrue("Expected onPageStarted to be called before onPageFinished",
                     mOnPageStartedCalled);
-            assertTrue("Expected onLoadResource to be called before onPageFinished",
-                    mOnLoadResourceCalled);
+            assertTrue(
+                    "Expected onLoadResource or onReceivedError to be called before onPageFinished",
+                    mOnLoadResourceCalled || mOnReceivedResourceError != null);
             mOnPageFinishedCalled = true;
         }
 
diff --git a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
index 507cbbe..dd9b971 100644
--- a/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
+++ b/tests/tests/widget/src/android/widget/cts/PopupWindowTest.java
@@ -29,6 +29,8 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.animation.Animator;
+import android.animation.ValueAnimator;
 import android.app.Instrumentation;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
@@ -40,11 +42,14 @@
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.os.SystemClock;
+import android.transition.Fade;
 import android.transition.Transition;
 import android.transition.Transition.TransitionListener;
+import android.transition.TransitionListenerAdapter;
 import android.transition.TransitionValues;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
+import android.util.Range;
 import android.view.Display;
 import android.view.Gravity;
 import android.view.MotionEvent;
@@ -56,6 +61,7 @@
 import android.view.Window;
 import android.view.WindowInsets;
 import android.view.WindowManager;
+import android.view.animation.LinearInterpolator;
 import android.widget.ImageView;
 import android.widget.PopupWindow;
 import android.widget.PopupWindow.OnDismissListener;
@@ -2016,6 +2022,71 @@
         }
     }
 
+    @Test
+    public void testWinAnimationDurationNoShortenByTinkeredScale() throws Throwable {
+        final long expectedDurationMs = 1500;
+        final long minDurationMs = expectedDurationMs;
+        final long maxDurationMs = expectedDurationMs + 200L;
+        final Range<Long> durationRange = new Range<>(minDurationMs, maxDurationMs);
+
+        final CountDownLatch latch = new CountDownLatch(1);
+        long[] transitionStartTime = new long[1];
+        long[] transitionEndTime = new long[1];
+
+        final float durationScale = 1.0f;
+        float currentDurationScale = ValueAnimator.getDurationScale();
+        try {
+            ValueAnimator.setDurationScale(durationScale);
+            assertTrue("The duration scale of ValueAnimator should be 1.0f,"
+                            + " actual=" + ValueAnimator.getDurationScale(),
+                    ValueAnimator.getDurationScale() == durationScale);
+
+            ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
+            animator.setInterpolator(new LinearInterpolator());
+
+            // Verify the actual transition duration is in expected range.
+            Fade enterTransition = new Fade(Fade.IN) {
+                @Override
+                public Animator onAppear(ViewGroup sceneRoot, View view,
+                        TransitionValues startValues, TransitionValues endValues) {
+                    return animator;
+                }
+            };
+            enterTransition.addListener(new TransitionListenerAdapter() {
+                @Override
+                public void onTransitionEnd(Transition transition) {
+                    transitionEndTime[0] = System.currentTimeMillis();
+                    latch.countDown();
+                }
+            });
+            enterTransition.setDuration(expectedDurationMs);
+            assertEquals("Transition duration should be as expected", enterTransition.getDuration(),
+                    expectedDurationMs);
+
+            mActivityRule.runOnUiThread(() -> {
+                mPopupWindow = createPopupWindow(createPopupContent(
+                        CONTENT_SIZE_DP, CONTENT_SIZE_DP));
+                mPopupWindow.setEnterTransition(enterTransition);
+            });
+            mInstrumentation.waitForIdleSync();
+
+            final View upperAnchor = mActivity.findViewById(R.id.anchor_upper);
+            mActivityRule.runOnUiThread(() -> {
+                transitionStartTime[0] = System.currentTimeMillis();
+                mPopupWindow.showAsDropDown(upperAnchor);
+            });
+            latch.await(2, TimeUnit.SECONDS);
+
+            final long totalTime = transitionEndTime[0] - transitionStartTime[0];
+            assertTrue("Actual transition duration should be in the range "
+                    + "<" + minDurationMs + ", " + maxDurationMs + "> ms, "
+                    + "actual=" + totalTime, durationRange.contains(totalTime));
+        } finally {
+            // restore scale value to avoid messing up future tests
+            ValueAnimator.setDurationScale(currentDurationScale);
+        }
+    }
+
     private void verifySubPopupPosition(PopupWindow subPopup, int mainAnchorId, int subAnchorId,
             int contentEdgeX, int operatorX, int anchorEdgeX,
             int contentEdgeY, int operatorY, int anchorEdgeY) throws Throwable {
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
index 1ebb556..71831de 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/WifiManagerTest.java
@@ -16,9 +16,9 @@
 
 package android.net.wifi.cts;
 
+import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED;
 import static android.net.NetworkCapabilities.TRANSPORT_WIFI;
 import static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
-import static android.net.wifi.WifiManager.TrafficStateCallback.DATA_ACTIVITY_INOUT;
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
@@ -1774,20 +1774,22 @@
         TestNetworkCallback networkCallbackListener = new TestNetworkCallback(mLock);
         synchronized (mLock) {
             try {
+                NetworkRequest.Builder networkRequestBuilder = new NetworkRequest.Builder()
+                        .addTransportType(TRANSPORT_WIFI);
+                if (expectMetered) {
+                    networkRequestBuilder.removeCapability(NET_CAPABILITY_NOT_METERED);
+                } else {
+                    networkRequestBuilder.addCapability(NET_CAPABILITY_NOT_METERED);
+                }
                 // File a request for wifi network.
                 mConnectivityManager.registerNetworkCallback(
-                        new NetworkRequest.Builder()
-                                .addTransportType(TRANSPORT_WIFI)
-                                .build(),
-                        networkCallbackListener);
+                        networkRequestBuilder.build(), networkCallbackListener);
                 // now wait for callback
                 mLock.wait(TEST_WAIT_DURATION_MS);
             } catch (InterruptedException e) {
             }
         }
         assertTrue(networkCallbackListener.onAvailableCalled);
-        assertNotEquals(expectMetered, networkCallbackListener.networkCapabilities.hasCapability(
-                NetworkCapabilities.NET_CAPABILITY_NOT_METERED));
     }
 
     /**
@@ -1835,6 +1837,9 @@
             }
             // check if we got the success callback
             assertTrue(actionListener.onSuccessCalled);
+            // Ensure we disconnected on marking the network metered & connect back.
+            waitForDisconnection();
+            waitForConnection();
             // Check the network capabilities to ensure that the network is marked metered now.
             waitForNetworkCallbackAndCheckForMeteredness(true);
 
@@ -2233,40 +2238,28 @@
     }
 
     /**
-     * Tests {@link WifiManager#factoryReset()}.
+     * Tests {@link WifiManager#factoryReset()} cannot be invoked from a non-privileged app.
      *
-     * Note: This test assumes that the device only has 1 or more saved networks before the test.
-     * The test will restore those when the test exits. But, it does not restore the softap
-     * configuration, suggestions, etc which will also have been lost on factory reset.
-     * TODO(b/152637504): Re-enabel this test.
+     * Note: This intentionally does not test the full reset functionality because it causes
+     * the existing saved networks on the device to be lost after the test. If you add the
+     * networks back after reset, the ownership of saved networks will change.
      */
-    public void ignoreTestFactoryReset() throws Exception {
+    public void testFactoryReset() throws Exception {
         if (!WifiFeature.isWifiSupported(getContext())) {
             // skip the test if WiFi is not supported
             return;
         }
-        UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
-        List<WifiConfiguration> savedNetworks = null;
+        List<WifiConfiguration> beforeSavedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+                mWifiManager::getConfiguredNetworks);
         try {
-            uiAutomation.adoptShellPermissionIdentity();
-            // These below API's only work with privileged permissions (obtained via shell identity
-            // for test)
-            savedNetworks = mWifiManager.getPrivilegedConfiguredNetworks();
-
             mWifiManager.factoryReset();
-            // Ensure all the saved networks are removed.
-            assertEquals(0, mWifiManager.getConfiguredNetworks().size());
-        } finally {
-            // Restore the original saved networks.
-            if (savedNetworks != null) {
-                for (WifiConfiguration network : savedNetworks) {
-                    network.networkId = WifiConfiguration.INVALID_NETWORK_ID;
-                    int networkId = mWifiManager.addNetwork(network);
-                    mWifiManager.enableNetwork(networkId, false);
-                }
-            }
-            uiAutomation.dropShellPermissionIdentity();
+            fail("Factory reset should not be allowed for non-privileged apps");
+        } catch (SecurityException e) {
+            // expected
         }
+        List<WifiConfiguration> afterSavedNetworks = ShellIdentityUtils.invokeWithShellPermissions(
+                mWifiManager::getConfiguredNetworks);
+        assertEquals(beforeSavedNetworks.size(), afterSavedNetworks.size());
     }
 
     /**
diff --git a/tests/video/OWNERS b/tests/video/OWNERS
index ecaa5dd..c0794b9 100644
--- a/tests/video/OWNERS
+++ b/tests/video/OWNERS
@@ -1,2 +1,3 @@
 # Bug component: 1344
-include platform/frameworks/av:/media/OWNERS
\ No newline at end of file
+# Same as the new media tests
+include ../media/OWNERS