Merge "Hdmi Cec Remote Control Interrupted Press and Hold"
diff --git a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
index fb753b3..48ebced 100644
--- a/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
+++ b/apps/CameraITS/tests/scene4/test_multi_camera_alignment.py
@@ -299,12 +299,33 @@
         # do captures on 2 cameras
         caps = {}
         for i, fmt in enumerate(fmts):
+            physicalSizes = {}
+
             capture_cam_ids = physical_ids
             if fmt == 'raw':
                 capture_cam_ids = physical_raw_ids
+
+            for physical_id in capture_cam_ids:
+                configs = props_physical[physical_id]['android.scaler.streamConfigurationMap']\
+                                   ['availableStreamConfigurations']
+                if fmt == 'raw':
+                    fmt_codes = 0x20
+                    fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_codes]
+                else:
+                    fmt_codes = 0x23
+                    fmt_configs = [cfg for cfg in configs if cfg['format'] == fmt_codes]
+
+                out_configs = [cfg for cfg in fmt_configs if cfg['input'] is False]
+                out_sizes = [(cfg['width'], cfg['height']) for cfg in out_configs]
+                physicalSizes[physical_id] = max(out_sizes, key=lambda item: item[1])
+
             out_surfaces = [{'format': 'yuv', 'width': w, 'height': h},
-                            {'format': fmt, 'physicalCamera': capture_cam_ids[0]},
-                            {'format': fmt, 'physicalCamera': capture_cam_ids[1]}]
+                            {'format': fmt, 'physicalCamera': capture_cam_ids[0],
+                             'width': physicalSizes[capture_cam_ids[0]][0],
+                             'height': physicalSizes[capture_cam_ids[0]][1]},
+                            {'format': fmt, 'physicalCamera': capture_cam_ids[1],
+                             'width': physicalSizes[capture_cam_ids[1]][0],
+                             'height': physicalSizes[capture_cam_ids[1]][1]},]
 
             out_surfaces_supported = cam.is_stream_combination_supported(out_surfaces)
             its.caps.skip_unless(out_surfaces_supported)
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/BiometricPromptBoundKeysTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/BiometricPromptBoundKeysTest.java
index 126611f..bae8e81 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/BiometricPromptBoundKeysTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/BiometricPromptBoundKeysTest.java
@@ -26,14 +26,20 @@
 
 import com.android.cts.verifier.R;
 
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
 import java.util.concurrent.Executor;
 
+import javax.crypto.Cipher;
+
 /**
  * Test for {@link BiometricPrompt}. This test runs twice, once with confirmation required,
  * once without. Both tests use crypto objects.
  */
 public class BiometricPromptBoundKeysTest extends FingerprintBoundKeysTest {
 
+    private static final String TAG = "BiometricPromptBoundKeysTest";
+
     private static final int STATE_TEST_REQUIRE_CONFIRMATION = 1; // confirmation required
     private static final int STATE_TEST_NO_CONFIRMATION = 2; // no confirmation required
 
@@ -79,18 +85,84 @@
     }
 
     @Override
+    protected String getTag() {
+        return TAG;
+    }
+
+    @Override
     protected void onPermissionsGranted() {
+        Button startTestButton = findViewById(R.id.sec_start_test_button);
         mBiometricManager = getSystemService(BiometricManager.class);
         final int result = mBiometricManager.canAuthenticate();
 
-        if (result == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
+        if (result == BiometricManager.BIOMETRIC_SUCCESS) {
+
+            // The Android 10 CDD allows for integration of Strong, Weak, and Convenience
+            // biometrics on the device. A subset (Strong, Weak) are allowed for application
+            // integration (e.g. with the public APIs). However, the Android 10 API surface is not
+            // wide enough to support this.
+            //
+            // Although this was addressed in Android 11 with Authenticators.BIOMETRIC_STRONG,
+            // Authenticators.BIOMETRIC_WEAK, and the canAuthenticate(int) APIs etc, we still need
+            // to patch the hole in Android 10. The POR is as follows.
+            //
+            // Devices with weak biometrics should implement a @hide API with the following
+            // signature: `int BiometricManager#canAuthenticate(BiometricPrompt.CryptoObject)`.
+            // On devices with weak-only, the method should return BIOMETRIC_ERROR_NO_HARDWARE. The
+            // CryptoObject does not need to be initialized/valid, and is used only to find the
+            // method signature. (Yes, in hindsight, we should have settled on the @hide API being
+            // more like `int BiometricManager#canAuthenticateWithCrypto()`
+            //
+            // The androidx.biometric library can then bring Android11-like functionality to
+            // developers by similarly checking for the existence/result of this @hide API.
+
+            try {
+                final Method canAuthenticateCrypto = BiometricManager.class.getMethod(
+                        "canAuthenticate",
+                        BiometricPrompt.CryptoObject.class);
+                final Object canAuthenticateResult = canAuthenticateCrypto
+                        .invoke(mBiometricManager, (Cipher) null);
+
+                if (canAuthenticateResult instanceof Integer) {
+                    switch ((int) canAuthenticateResult) {
+                        case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
+                            // Device does not have a strong authenticator, but has a weak one.
+                            // Allow the test to pass.
+                            startTestButton.setEnabled(false);
+                            getPassButton().setEnabled(true);
+
+                            showToast("The device has WEAK biometrics but no STRONG biometrics."
+                                    + " You can skip the keystore integration test.");
+                            break;
+                        case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
+                            // HAL/Hardware has an issue. This is definitely not expected. In this
+                            // case, we should assume it should be in the enrolled/available state.
+                            // Test for Keystore integration in this case.
+                        case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
+                            // The user can be asked to enroll. Test for Keystore integration in
+                            // this case.
+                        case BiometricManager.BIOMETRIC_SUCCESS:
+                            // Device reports that a Strong authenticator is available and enrolled.
+                            // Test for Keystore integration in this case.
+                        default:
+                            showToast("canAuthenticateCrypto result: " + canAuthenticateResult
+                                    + " Please proceed with the keystore integration test");
+                            break;
+                    }
+                }
+            } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
+                // Proceed with the keystore integration test as usual.
+            }
+
+
+        } else if (result == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE) {
             showToast("No biometric features, test passed.");
             getPassButton().setEnabled(true);
         } else if (result == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE) {
             showToast("Biometric unavailable, something is wrong with your device");
         } else if (result == BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED) {
             showToast("Error: " + result + " Please ensure you have a biometric enrolled");
-            Button startTestButton = findViewById(R.id.sec_start_test_button);
+
             startTestButton.setEnabled(false);
         }
     }
diff --git a/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
index b804b45..c111d30 100644
--- a/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
+++ b/apps/CtsVerifier/src/com/android/cts/verifier/security/FingerprintBoundKeysTest.java
@@ -83,6 +83,10 @@
         return R.string.sec_fingerprint_bound_key_test_info;
     }
 
+    protected String getTag() {
+        return TAG;
+    }
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -269,6 +273,7 @@
 
     protected void showToast(String message) {
         Toast.makeText(this, message, Toast.LENGTH_LONG).show();
+        Log.d(getTag(), message);
     }
 
     public static class FingerprintAuthDialogFragment extends DialogFragment {
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 16f7610..9a851f0 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,13 +15,18 @@
  */
 package com.android.compatibility.common.deviceinfo;
 
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.os.Bundle;
-
-import com.android.compatibility.common.deviceinfo.DeviceInfo;
+import android.content.pm.PermissionInfo;
+import android.os.Build;
+import android.os.Process;
 import com.android.compatibility.common.util.DeviceInfoStore;
 
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * PackageDeviceInfo collector.
  */
@@ -35,24 +40,116 @@
     private static final String MIN_SDK = "min_sdk";
     private static final String TARGET_SDK = "target_sdk";
 
+    private static final String REQUESTED_PERMISSIONS = "requested_permissions";
+    private static final String PERMISSION_NAME = "name";
+    private static final String PERMISSION_FLAGS = "flags";
+    private static final String PERMISSION_GROUP = "permission_group";
+    private static final String PERMISSION_PROTECTION = "protection_level";
+    private static final String PERMISSION_PROTECTION_FLAGS = "protection_level_flags";
+
+    private static final String HAS_SYSTEM_UID = "has_system_uid";
+
+    private static final String SHARES_INSTALL_PERMISSION = "shares_install_packages_permission";
+    private static final String INSTALL_PACKAGES_PERMISSION = "android.permission.INSTALL_PACKAGES";
+
+
     @Override
     protected void collectDeviceInfo(DeviceInfoStore store) throws Exception {
-        PackageManager pm = getContext().getPackageManager();
+        final PackageManager pm = getContext().getPackageManager();
+
+        final List<PackageInfo> allPackages =
+                pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
+
         store.startArray(PACKAGE);
-        for (PackageInfo pkg : pm.getInstalledPackages(0)) {
+        for (PackageInfo pkg : allPackages) {
             store.startGroup();
             store.addResult(NAME, pkg.packageName);
             store.addResult(VERSION_NAME, pkg.versionName);
 
-            if (pkg.applicationInfo != null) {
-                String dir = pkg.applicationInfo.sourceDir;
+            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();
+
+            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, pkg.applicationInfo.minSdkVersion);
-                store.addResult(TARGET_SDK, pkg.applicationInfo.targetSdkVersion);
+                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.endGroup();
         }
-        store.endArray(); // Package
+        store.endArray(); // "package"
+    }
+
+    private static boolean sharesUidWithInstallerPackage(PackageManager pm, int uid) {
+        final String[] sharesUidWith = pm.getPackagesForUid(uid);
+
+        if (sharesUidWith == null) {
+            return false;
+        }
+
+        // Approx 20 permissions per package for rough estimate of sizing
+        final int capacity = sharesUidWith.length * 20;
+        final List<String> sharedPermissions = new ArrayList<>(capacity);
+        for (String pkg :sharesUidWith){
+            try {
+                final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
+
+                if (info.requestedPermissions == null) {
+                    continue;
+                }
+
+                for (String p : info.requestedPermissions) {
+                    if (p != null) {
+                        sharedPermissions.add(p);
+                    }
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                // ignore, continue
+            }
+        }
+
+        return sharedPermissions.contains(PackageDeviceInfo.INSTALL_PACKAGES_PERMISSION);
+    }
+
+    private static void writePermissionsDetails(PermissionInfo pi, DeviceInfoStore store)
+            throws IOException {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            store.addResult(PERMISSION_FLAGS, pi.flags);
+        } else {
+            store.addResult(PERMISSION_FLAGS, 0);
+        }
+
+        store.addResult(PERMISSION_GROUP, pi.group);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+            store.addResult(PERMISSION_PROTECTION, pi.getProtection());
+            store.addResult(PERMISSION_PROTECTION_FLAGS, pi.getProtectionFlags());
+        } else {
+            store.addResult(PERMISSION_PROTECTION,
+                    pi.protectionLevel & PermissionInfo.PROTECTION_MASK_BASE);
+            store.addResult(PERMISSION_PROTECTION_FLAGS,
+                    pi.protectionLevel & ~PermissionInfo.PROTECTION_MASK_BASE);
+        }
     }
 }
diff --git a/common/device-side/util-axt/src/com/android/compatibility/common/util/RetryRule.java b/common/device-side/util-axt/src/com/android/compatibility/common/util/RetryRule.java
index 32dedea..6cefb5d 100644
--- a/common/device-side/util-axt/src/com/android/compatibility/common/util/RetryRule.java
+++ b/common/device-side/util-axt/src/com/android/compatibility/common/util/RetryRule.java
@@ -18,6 +18,8 @@
 
 import android.util.Log;
 
+import androidx.annotation.Nullable;
+
 import org.junit.rules.TestRule;
 import org.junit.runner.Description;
 import org.junit.runners.model.Statement;
@@ -29,6 +31,7 @@
 
     private static final String TAG = "RetryRule";
     private final int mMaxAttempts;
+    private final Runnable mCleaner;
 
     /**
      * Retries the underlying test when it catches a {@link RetryableException}.
@@ -38,10 +41,24 @@
      * @throws IllegalArgumentException if {@code retries} is less than {@code 0}.
      */
     public RetryRule(int retries) {
+        this(retries, null);
+    }
+
+    /**
+     * Retries the underlying test when it catches a {@link RetryableException}.
+     *
+     * @param retries number of retries. Use {@code 0} to disable rule.
+     * @param cleaner a {@link Runnable} to clean up the objects generated by the testing
+     *               between retries
+     *
+     * @throws IllegalArgumentException if {@code retries} is less than {@code 0}.
+     */
+    public RetryRule(int retries, @Nullable Runnable cleaner) {
         if (retries < 0) {
             throw new IllegalArgumentException("retries must be more than 0");
         }
         mMaxAttempts = retries + 1;
+        mCleaner = cleaner;
     }
 
     @Override
@@ -78,6 +95,9 @@
                                     + " to " + timeout.ms() + "ms");
                         }
                         caught = e;
+                        if (i != mMaxAttempts && mCleaner != null) {
+                            mCleaner.run();
+                        }
                     }
                     Log.w(TAG, "Arrrr! " + name + " failed at attempt " + i + "/" + mMaxAttempts
                             + ": " + caught);
diff --git a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/RetryRuleTest.java b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/RetryRuleTest.java
index 644d95f..571a1b8 100644
--- a/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/RetryRuleTest.java
+++ b/common/device-side/util-axt/tests/src/com/android/compatibility/common/util/RetryRuleTest.java
@@ -19,11 +19,15 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.testng.Assert.assertThrows;
 import static org.testng.Assert.expectThrows;
 
+import androidx.annotation.Nullable;
+
 import org.junit.Test;
 import org.junit.runner.Description;
 import org.junit.runner.RunWith;
@@ -43,15 +47,22 @@
         private final int mNumberFailures;
         private int mNumberCalls;
         private final T mException;
+        private final Runnable mCleaner;
 
         RetryableStatement(int numberFailures, T exception) {
+            this(numberFailures, exception, null);
+        }
+
+        RetryableStatement(int numberFailures, T exception, @Nullable Runnable cleaner) {
             mNumberFailures = numberFailures;
             mException = exception;
+            mCleaner = cleaner;
         }
 
         @Override
         public void evaluate() throws Throwable {
             mNumberCalls++;
+            verifyCleaner();
             if (mNumberCalls <= mNumberFailures) {
                 throw mException;
             }
@@ -61,6 +72,13 @@
         public String toString() {
             return "RetryableStatement: failures=" + mNumberFailures + ", calls=" + mNumberCalls;
         }
+
+        private void verifyCleaner() {
+            if (mCleaner == null) {
+                return;
+            }
+            verify(mCleaner, times(mNumberCalls - 1)).run();
+        }
     }
 
     private @Mock Statement mMockStatement;
@@ -68,6 +86,8 @@
     @Test
     public void testInvalidConstructor() throws Throwable {
         assertThrows(IllegalArgumentException.class, () -> new RetryRule(-1));
+        assertThrows(IllegalArgumentException.class, () -> new RetryRule(-1, null));
+        assertThrows(IllegalArgumentException.class, () -> new RetryRule(-1, () -> {}));
     }
 
     @Test
@@ -78,6 +98,57 @@
     }
 
     @Test
+    public void testDoCleanOnRetryableException() throws Throwable {
+        final Runnable cleaner = mock(Runnable.class);
+        final RetryRule rule = new RetryRule(2, cleaner);
+
+        rule.apply(new RetryableStatement<RetryableException>(2, sRetryableException, cleaner),
+                mDescription).evaluate();
+
+        verify(cleaner, times(2)).run();
+    }
+
+    @Test
+    public void testKeepLastStatusWhenFailOnRetryableException() throws Throwable {
+        final Runnable cleaner = mock(Runnable.class);
+        final RetryRule rule = new RetryRule(2, cleaner);
+
+        final RetryableException actualException = expectThrows(RetryableException.class,
+                () -> rule.apply(
+                        new RetryableStatement<RetryableException>(3, sRetryableException, cleaner),
+                        mDescription).evaluate());
+
+        assertThat(actualException).isSameAs(sRetryableException);
+        verify(cleaner, times(2)).run();
+    }
+
+    @Test
+    public void testNeverCleanWhenStatementPass() throws Throwable {
+        final Runnable cleaner = mock(Runnable.class);
+        final RetryRule rule = new RetryRule(2, cleaner);
+
+        rule.apply(mMockStatement, mDescription).evaluate();
+
+        verify(mMockStatement, times(1)).evaluate();
+        verify(cleaner, never()).run();
+    }
+
+    @Test
+    public void testNeverCleanWhenDisabledAndStatementThrowsRetryableException() throws Throwable {
+        final RetryableException exception = new RetryableException("Y U NO?");
+        final Runnable cleaner = mock(Runnable.class);
+        final RetryRule rule = new RetryRule(0, cleaner);
+        doThrow(exception).when(mMockStatement).evaluate();
+
+        final RetryableException actualException = expectThrows(RetryableException.class,
+                () -> rule.apply(mMockStatement, mDescription).evaluate());
+
+        assertThat(actualException).isSameAs(exception);
+        verify(mMockStatement, times(1)).evaluate();
+        verify(cleaner, never()).run();
+    }
+
+    @Test
     public void testPassOnRetryableExceptionWithTimeout() throws Throwable {
         final Timeout timeout = new Timeout("YOUR TIME IS GONE", 1, 2, 10);
         final RetryableException exception = new RetryableException(timeout, "Y U NO?");
diff --git a/hostsidetests/apex/src/android/apex/cts/ApexTest.java b/hostsidetests/apex/src/android/apex/cts/ApexTest.java
index 0347299..9bb09d8 100644
--- a/hostsidetests/apex/src/android/apex/cts/ApexTest.java
+++ b/hostsidetests/apex/src/android/apex/cts/ApexTest.java
@@ -33,7 +33,7 @@
   }
 
   private boolean isGSI() throws Exception {
-    String systemProduct = getDevice().getProperty("ro.product.system.name");
+    String systemProduct = getDevice().getProperty("ro.product.system_ext.name");
     return systemProduct.equals("aosp_arm")
       || systemProduct.equals("aosp_arm64")
       || systemProduct.equals("aosp_x86")
diff --git a/hostsidetests/appcompat/OWNERS b/hostsidetests/appcompat/OWNERS
new file mode 100644
index 0000000..4e3ec29
--- /dev/null
+++ b/hostsidetests/appcompat/OWNERS
@@ -0,0 +1,9 @@
+# Bug component: 610774
+
+# Use this reviewer by default.
+platform-compat-eng+reviews@google.com
+
+andreionea@google.com
+atrost@google.com
+mathewi@google.com
+satayev@google.com
\ No newline at end of file
diff --git a/hostsidetests/appcompat/compatchanges/Android.bp b/hostsidetests/appcompat/compatchanges/Android.bp
new file mode 100644
index 0000000..ca0867b
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/Android.bp
@@ -0,0 +1,31 @@
+// 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: "CtsAppCompatHostTestCases",
+    defaults: ["cts_defaults"],
+    srcs: ["src/**/*.java"],
+    libs: [
+        "cts-tradefed",
+        "tradefed",
+        "guava",
+    ],
+    static_libs:["CompatChangeGatingTestBase"],
+    // tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appcompat/compatchanges/AndroidTest.xml b/hostsidetests/appcompat/compatchanges/AndroidTest.xml
new file mode 100644
index 0000000..94f82cc
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/AndroidTest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Config for CTS appcompat host test cases">
+    <option name="test-suite-tag" value="cts" />
+    <option name="config-descriptor:metadata" key="component" value="framework" />
+    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+    <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+    <test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
+        <option name="jar" value="CtsAppCompatHostTestCases.jar" />
+    </test>
+</configuration>
diff --git a/hostsidetests/appcompat/compatchanges/app/Android.bp b/hostsidetests/appcompat/compatchanges/app/Android.bp
new file mode 100644
index 0000000..9ff5b17
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/app/Android.bp
@@ -0,0 +1,36 @@
+//
+// 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: "CtsHostsideCompatChangeTestsApp",
+    defaults: ["cts_support_defaults"],
+    platform_apis: true,
+    srcs: ["src/**/*.java"],
+    libs: [
+        "junit",
+    ],
+    static_libs: [
+        "ctstestrunner-axt",
+        "truth-prebuilt",
+    ],
+
+    // Tag this module as a cts test artifact
+    test_suites: [
+        "cts",
+        "vts",
+        "general-tests",
+    ],
+}
diff --git a/hostsidetests/appcompat/compatchanges/app/AndroidManifest.xml b/hostsidetests/appcompat/compatchanges/app/AndroidManifest.xml
new file mode 100644
index 0000000..28b33d2
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/app/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.cts.appcompat">
+    <!-- targetSdkVersion for this test must be below 1234 -->
+    <uses-sdk android:targetSdkVersion="29"/>
+    <application
+        android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.cts.appcompat" />
+
+</manifest>
diff --git a/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/CompatChangesTest.java b/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/CompatChangesTest.java
new file mode 100644
index 0000000..7025a96
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/app/src/com/android/cts/appcompat/CompatChangesTest.java
@@ -0,0 +1,98 @@
+/*
+ * 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.appcompat;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.Manifest;
+import android.app.compat.CompatChanges;
+import android.content.Context;
+import android.os.Process;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+/**
+ * Tests for the {@link android.app.compat.CompatChanges} SystemApi.
+ *
+ * These test methods have additional setup and post run checks done host side by
+ * {@link com.android.cts.appcompat.CompatChangesSystemApiTest}.
+ *
+ * The setup adds an override for the change id being tested, and the post run step checks if
+ * that change id has been logged to statsd.
+ */
+@RunWith(AndroidJUnit4.class)
+public final class CompatChangesTest {
+  private static final long CTS_SYSTEM_API_CHANGEID = 149391281;
+  @Before
+  public void setUp() {
+    InstrumentationRegistry.getInstrumentation().getUiAutomation()
+      .adoptShellPermissionIdentity(Manifest.permission.LOG_COMPAT_CHANGE,
+                                    Manifest.permission.READ_COMPAT_CHANGE_CONFIG);
+  }
+
+  @After
+  public void tearDown() {
+    InstrumentationRegistry.getInstrumentation().getUiAutomation().dropShellPermissionIdentity();
+  }
+
+  /* Test run by CompatChangesSystemApiTest.testIsChangeEnabled */
+  @Test
+  public void isChangeEnabled_changeEnabled() {
+    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isTrue();
+  }
+
+  /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledPackageName */
+  @Test
+  public void isChangeEnabledPackageName_changeEnabled() {
+    Context context = InstrumentationRegistry.getTargetContext();
+    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
+            context.getUser())).isTrue();
+  }
+
+  /* Test run by CompatChangesSystemApiTest.testIsChangeEnabledUid */
+  @Test
+  public void isChangeEnabledUid_changeEnabled() {
+    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isTrue();
+  }
+
+  /* Test run by CompatChangesSystemApiTest.testIsChangeDisabled */
+  @Test
+  public void isChangeEnabled_changeDisabled() {
+    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID)).isFalse();
+  }
+
+  /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledPackageName */
+  @Test
+  public void isChangeEnabledPackageName_changeDisabled() {
+    Context context = InstrumentationRegistry.getTargetContext();
+    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, context.getPackageName(),
+            context.getUser())).isFalse();
+  }
+
+  /* Test run by CompatChangesSystemApiTest.testIsChangeDisabledUid */
+  @Test
+  public void isChangeEnabledUid_changeDisabled() {
+    assertThat(CompatChanges.isChangeEnabled(CTS_SYSTEM_API_CHANGEID, Process.myUid())).isFalse();
+  }
+}
diff --git a/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSystemApiTest.java b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSystemApiTest.java
new file mode 100644
index 0000000..699af1a
--- /dev/null
+++ b/hostsidetests/appcompat/compatchanges/src/com/android/cts/appcompat/CompatChangesSystemApiTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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.appcompat;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.compat.cts.CompatChangeGatingTestCase;
+
+import com.google.common.collect.ImmutableSet;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * Tests for the {@link android.app.compat.CompatChanges} SystemApi.
+ */
+
+public class CompatChangesSystemApiTest extends CompatChangeGatingTestCase {
+
+    protected static final String TEST_APK = "CtsHostsideCompatChangeTestsApp.apk";
+    protected static final String TEST_PKG = "com.android.cts.appcompat";
+
+    private static final long CTS_SYSTEM_API_CHANGEID = 149391281;
+
+    @Override
+    protected void setUp() throws Exception {
+        installPackage(TEST_APK, true);
+    }
+
+    public void testIsChangeEnabled() throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".CompatChangesTest", "isChangeEnabled_changeEnabled",
+                /*enabledChanges*/ImmutableSet.of(CTS_SYSTEM_API_CHANGEID),
+                /*disabledChanges*/ ImmutableSet.of());
+    }
+
+    public void testIsChangeEnabledPackageName() throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".CompatChangesTest",
+                "isChangeEnabledPackageName_changeEnabled",
+                /*enabledChanges*/ImmutableSet.of(CTS_SYSTEM_API_CHANGEID),
+                /*disabledChanges*/ ImmutableSet.of());
+    }
+
+    public void testIsChangeEnabledUid() throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".CompatChangesTest", "isChangeEnabledUid_changeEnabled",
+                /*enabledChanges*/ImmutableSet.of(CTS_SYSTEM_API_CHANGEID),
+                /*disabledChanges*/ ImmutableSet.of());
+    }
+
+    public void testIsChangeDisabled() throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".CompatChangesTest", "isChangeEnabled_changeDisabled",
+                /*enabledChanges*/ImmutableSet.of(),
+                /*disabledChanges*/ ImmutableSet.of(CTS_SYSTEM_API_CHANGEID));
+    }
+
+    public void testIsChangeDisabledPackageName() throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".CompatChangesTest",
+                "isChangeEnabledPackageName_changeDisabled",
+                /*enabledChanges*/ImmutableSet.of(),
+                /*disabledChanges*/ ImmutableSet.of(CTS_SYSTEM_API_CHANGEID));
+    }
+
+    public void testIsChangeDisabledUid() throws Exception {
+        runDeviceCompatTest(TEST_PKG, ".CompatChangesTest", "isChangeEnabledUid_changeDisabled",
+                /*enabledChanges*/ImmutableSet.of(),
+                /*disabledChanges*/ ImmutableSet.of(CTS_SYSTEM_API_CHANGEID));
+    }
+}
diff --git a/hostsidetests/appsecurity/res/apexsigverify/OWNERS b/hostsidetests/appsecurity/res/apexsigverify/OWNERS
new file mode 100644
index 0000000..18ae217
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/OWNERS
@@ -0,0 +1,2 @@
+# Bug component: 494373
+dariofreni@google.com
diff --git a/hostsidetests/appsecurity/res/apexsigverify/README.md b/hostsidetests/appsecurity/res/apexsigverify/README.md
index c3842e1..34d9ba6 100644
--- a/hostsidetests/appsecurity/res/apexsigverify/README.md
+++ b/hostsidetests/appsecurity/res/apexsigverify/README.md
@@ -2,10 +2,20 @@
 
 ## AOSP Well known Key source path
 
+art/build/apex/com.android.art.avbpubkey
 art/build/apex/com.android.runtime.avbpubkey
+packages/services/Telephony/apex/com.android.telephony.avbpubkey
+packages/modules/Cronet/apex/com.android.cronet.avbpubkey
+packages/modules/DnsResolver/apex/com.android.resolv.avbpubkey
 external/conscrypt/apex/com.android.conscrypt.avbpubkey
+libcore/apex/com.android.i18n.avbpubkey
+bionic/apex/com.android.runtime.avbpubkey
 frameworks/av/apex/com.android.media.swcodec.avbpubkey
 frameworks/av/apex/com.android.media.avbpubkey
+frameworks/base/packages/Tethering/apex/com.android.tethering.avbpubkey
+frameworks/base/apex/sdkextensions/com.android.sdkext.avbpubkey
+frameworks/ml/nn/apex/com.android.neuralnetworks.avbpubkey
+frameworks/opt/net/ike/apex/com.android.ipsec.avbpubkey
 system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.avbpubkey
 system/apex/apexd/apexd_testdata/com.android.apex.test_package.no_inst_key.avbpubkey
 system/apex/apexd/apexd_testdata/com.android.apex.test_package.prepostinstall.fail.avbpubkey
@@ -15,10 +25,13 @@
 system/apex/tests/testdata/com.android.apex.test.avbpubkey
 system/apex/apexer/testdata/com.android.example.apex.avbpubkey
 system/apex/apexer/etc/com.android.support.apexer.avbpubkey
-system/timezone/apex/com.android.tzdata.avbpubkey
 system/netd/apex/com.android.resolv.avbpubkey
+system/timezone/apex/com.android.tzdata.avbpubkey
+system/bt/apex/com.android.bluetooth.updatable.avbpubkey
+system/core/adb/apex/com.android.adbd.avbpubkey
 system/core/rootdir/avb/s-gsi.avbpubkey
+system/core/rootdir/avb/q-developer-gsi.avbpubkey
 system/core/rootdir/avb/q-gsi.avbpubkey
 system/core/rootdir/avb/r-gsi.avbpubkey
 
-## Updating public keys
\ No newline at end of file
+## Updating public keys
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.adbd.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.adbd.avbpubkey
new file mode 100644
index 0000000..06235bd
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.adbd.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.art.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.art.avbpubkey
new file mode 100644
index 0000000..0d9cb49
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.art.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.bluetooth.updatable.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.bluetooth.updatable.avbpubkey
new file mode 100644
index 0000000..969211f
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.bluetooth.updatable.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.cronet.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.cronet.avbpubkey
new file mode 100644
index 0000000..38aebe0
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.cronet.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.i18n.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.i18n.avbpubkey
new file mode 100644
index 0000000..f1c97a0
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.i18n.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.ipsec.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.ipsec.avbpubkey
new file mode 100644
index 0000000..a6b43c8
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.ipsec.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.neuralnetworks.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.neuralnetworks.avbpubkey
new file mode 100644
index 0000000..3c96cd1
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.neuralnetworks.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.sdkext.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.sdkext.avbpubkey
new file mode 100644
index 0000000..8f47741
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.sdkext.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.telephony.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.telephony.avbpubkey
new file mode 100644
index 0000000..cba14427
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.telephony.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.tethering.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.tethering.avbpubkey
new file mode 100644
index 0000000..9a2c017
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.tethering.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/q-developer-gsi.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/q-developer-gsi.avbpubkey
new file mode 100644
index 0000000..0ace69d
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/q-developer-gsi.avbpubkey
Binary files differ
diff --git a/hostsidetests/hdmicec/OWNERS b/hostsidetests/hdmicec/OWNERS
index 254c58d..e7f5c32 100644
--- a/hostsidetests/hdmicec/OWNERS
+++ b/hostsidetests/hdmicec/OWNERS
@@ -1,3 +1,3 @@
-# Bug component: 141606867
+# Bug component: 114946
 nchalko@google.com
 amyjojo@google.com
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecDevice.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecDevice.java
index 3dac5d5..68c270a 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/CecDevice.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/CecDevice.java
@@ -21,12 +21,20 @@
 
 public enum CecDevice {
     TV(0x0),
-    RECORDING_1(0x1),
+    RECORDER_1(0x1),
+    RECORDER_2(0x2),
     TUNER_1(0x3),
     PLAYBACK_1(0x4),
     AUDIO_SYSTEM(0x5),
+    TUNER_2(0x6),
+    TUNER_3(0x7),
     PLAYBACK_2(0x8),
+    RECORDER_3(0x9),
+    TUNER_4(0xa),
     PLAYBACK_3(0xb),
+    RESERVED_1(0xc),
+    RESERVED_2(0xd),
+    SPECIFIC_USE(0xe),
     BROADCAST(0xf);
 
     private final int playerId;
@@ -53,8 +61,15 @@
                 return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_TV);
             case AUDIO_SYSTEM:
                 return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_AUDIO_SYSTEM);
-            case RECORDING_1:
-                return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_RECORDING_DEVICE);
+            case RECORDER_1:
+            case RECORDER_2:
+            case RECORDER_3:
+                return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_RECORDER);
+            case TUNER_1:
+            case TUNER_2:
+            case TUNER_3:
+            case TUNER_4:
+                return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_TUNER);
             default:
                 return Integer.toString(HdmiCecConstants.CEC_DEVICE_TYPE_RESERVED);
         }
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
index 6383a4a..0565eae 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/HdmiCecConstants.java
@@ -41,7 +41,7 @@
     public static final int UNRECOGNIZED_OPCODE = 0x0;
 
     public static final int CEC_DEVICE_TYPE_TV = 0;
-    public static final int CEC_DEVICE_TYPE_RECORDING_DEVICE = 1;
+    public static final int CEC_DEVICE_TYPE_RECORDER = 1;
     public static final int CEC_DEVICE_TYPE_RESERVED = 2;
     public static final int CEC_DEVICE_TYPE_TUNER = 3;
     public static final int CEC_DEVICE_TYPE_PLAYBACK_DEVICE = 4;
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
index e5ab1d9..c6df9f7 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemInformationTest.java
@@ -172,7 +172,7 @@
         final String originalLanguage = extractLanguage(locale);
         final String language = originalLanguage.equals("spa") ? "eng" : "spa";
         try {
-            hdmiCecClient.sendCecMessage(CecDevice.RECORDING_1, CecDevice.BROADCAST,
+            hdmiCecClient.sendCecMessage(CecDevice.RECORDER_1, CecDevice.BROADCAST,
                     CecMessage.SET_MENU_LANGUAGE, hdmiCecClient.convertStringToHexParams(language));
             assertEquals(originalLanguage, extractLanguage(getSystemLocale()));
         } finally {
diff --git a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java
index 3f51843..1aa8fe4 100644
--- a/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java
+++ b/hostsidetests/hdmicec/src/android/hdmicec/cts/playback/HdmiCecSystemStandbyTest.java
@@ -87,7 +87,7 @@
             checkDeviceAsleepAfterStandbySent(CecDevice.TV, CecDevice.BROADCAST);
             /* Wake up the TV */
             hdmiCecClient.sendConsoleMessage("on " + CecDevice.TV);
-            checkDeviceAsleepAfterStandbySent(CecDevice.RECORDING_1, CecDevice.BROADCAST);
+            checkDeviceAsleepAfterStandbySent(CecDevice.RECORDER_1, CecDevice.BROADCAST);
             /* Wake up the TV */
             hdmiCecClient.sendConsoleMessage("on " + CecDevice.TV);
             checkDeviceAsleepAfterStandbySent(CecDevice.AUDIO_SYSTEM, CecDevice.BROADCAST);
@@ -109,7 +109,7 @@
         getDevice().executeShellCommand("reboot");
         getDevice().waitForBootComplete(HdmiCecConstants.REBOOT_TIMEOUT);
         checkDeviceAsleepAfterStandbySent(CecDevice.TV, CecDevice.PLAYBACK_1);
-        checkDeviceAsleepAfterStandbySent(CecDevice.RECORDING_1, CecDevice.PLAYBACK_1);
+        checkDeviceAsleepAfterStandbySent(CecDevice.RECORDER_1, CecDevice.PLAYBACK_1);
         checkDeviceAsleepAfterStandbySent(CecDevice.AUDIO_SYSTEM, CecDevice.PLAYBACK_1);
         checkDeviceAsleepAfterStandbySent(CecDevice.PLAYBACK_2, CecDevice.PLAYBACK_1);
         checkDeviceAsleepAfterStandbySent(CecDevice.BROADCAST, CecDevice.PLAYBACK_1);
diff --git a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
index 6369470..bca2d47 100644
--- a/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
+++ b/hostsidetests/statsd/src/android/cts/statsd/validation/BatteryStatsValidationTests.java
@@ -122,67 +122,6 @@
                 bsPowerNas > ALLOWED_FRACTIONAL_DIFFERENCE * statsdPowerNas);
     }
 
-    public void testPowerBlameUid() throws Exception {
-        if (statsdDisabled()) {
-            return;
-        }
-        if (!hasFeature(FEATURE_LEANBACK_ONLY, false)) return;
-        resetBatteryStats();
-        unplugDevice();
-
-        final double ALLOWED_FRACTIONAL_DIFFERENCE = 0.8; // ratio that statsd and bs can differ
-
-        StatsdConfig.Builder config = getPulledConfig();
-        addGaugeAtomWithDimensions(config, Atom.DEVICE_CALCULATED_POWER_BLAME_UID_FIELD_NUMBER,
-                null);
-        uploadConfig(config);
-        unplugDevice();
-
-        Thread.sleep(WAIT_TIME_LONG);
-        runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSimpleCpu");
-        Thread.sleep(WAIT_TIME_LONG);
-
-        setAppBreadcrumbPredicate();
-        BatteryStatsProto batterystatsProto = getBatteryStatsProto();
-        Thread.sleep(WAIT_TIME_LONG);
-        List<Atom> atomList = getGaugeMetricDataList();
-
-        // Extract statsd data
-        boolean uidFound = false;
-        int uid = getUid();
-        long statsdUidPowerNas = 0;
-        for (Atom atom : atomList) {
-            DeviceCalculatedPowerBlameUid item = atom.getDeviceCalculatedPowerBlameUid();
-            if (item.getUid() == uid) {
-                assertFalse("Found multiple power values for uid " + uid, uidFound);
-                uidFound = true;
-                statsdUidPowerNas = item.getPowerNanoAmpSecs();
-            }
-        }
-        assertTrue("Statsd: No power value for uid " + uid, uidFound);
-        assertTrue("Statsd: Non-positive power value for uid " + uid, statsdUidPowerNas > 0);
-
-        // Extract batterystats data
-        double bsUidPowerNas = -1;
-        boolean hadUid = false;
-        for (UidProto uidProto : batterystatsProto.getUidsList()) {
-            if (uidProto.getUid() == uid) {
-                hadUid = true;
-                bsUidPowerNas = uidProto.getPowerUseItem().getComputedPowerMah()
-                        * 1_000_000L * 3600L; /* mAh to nAs */;
-            }
-        }
-        assertTrue("Batterystats: No power value for uid " + uid, hadUid);
-        assertTrue("BatteryStats: Non-positive power value for uid " + uid, bsUidPowerNas > 0);
-
-        assertTrue(
-                String.format("Statsd (%d) < Batterystats (%f).", statsdUidPowerNas, bsUidPowerNas),
-                statsdUidPowerNas > ALLOWED_FRACTIONAL_DIFFERENCE * bsUidPowerNas);
-        assertTrue(
-                String.format("Batterystats (%f) < Statsd (%d).", bsUidPowerNas, statsdUidPowerNas),
-                bsUidPowerNas > ALLOWED_FRACTIONAL_DIFFERENCE * statsdUidPowerNas);
-    }
-
     public void testServiceStartCount() throws Exception {
         final String fileName = "BATTERYSTATS_SERVICE_START_COUNT.pbtxt";
         StatsdConfig config = createValidationUtil().getConfig(fileName);
diff --git a/hostsidetests/theme/app/src/android/theme/app/TestConfiguration.java b/hostsidetests/theme/app/src/android/theme/app/TestConfiguration.java
index d6c9f1c..1d450cd 100644
--- a/hostsidetests/theme/app/src/android/theme/app/TestConfiguration.java
+++ b/hostsidetests/theme/app/src/android/theme/app/TestConfiguration.java
@@ -201,15 +201,17 @@
             new LayoutInfo(R.layout.radiobutton, "radiobutton"),
             new LayoutInfo(R.layout.radiogroup_horizontal, "radiogroup_horizontal"),
             new LayoutInfo(R.layout.radiogroup_vertical, "radiogroup_vertical"),
-            new LayoutInfo(R.layout.ratingbar_0, "ratingbar_0"),
-            new LayoutInfo(R.layout.ratingbar_2point5, "ratingbar_2point5"),
-            new LayoutInfo(R.layout.ratingbar_5, "ratingbar_5"),
-            new LayoutInfo(R.layout.ratingbar_0, "ratingbar_0_pressed",
-                    new ViewPressedModifier()),
-            new LayoutInfo(R.layout.ratingbar_2point5, "ratingbar_2point5_pressed",
-                    new ViewPressedModifier()),
-            new LayoutInfo(R.layout.ratingbar_5, "ratingbar_5_pressed",
-                    new ViewPressedModifier()),
+            // Temporarily remove tests for the RatingBar widget. It has indeterminate rendering
+            // behavior on 360dpi devices, but we don't know why yet.
+            //new LayoutInfo(R.layout.ratingbar_0, "ratingbar_0"),
+            //new LayoutInfo(R.layout.ratingbar_2point5, "ratingbar_2point5"),
+            //new LayoutInfo(R.layout.ratingbar_5, "ratingbar_5"),
+            //new LayoutInfo(R.layout.ratingbar_0, "ratingbar_0_pressed",
+            //        new ViewPressedModifier()),
+            //new LayoutInfo(R.layout.ratingbar_2point5, "ratingbar_2point5_pressed",
+            //        new ViewPressedModifier()),
+            //new LayoutInfo(R.layout.ratingbar_5, "ratingbar_5_pressed",
+            //        new ViewPressedModifier()),
             new LayoutInfo(R.layout.searchview, "searchview_query",
                     new SearchViewModifier(SearchViewModifier.QUERY)),
             new LayoutInfo(R.layout.searchview, "searchview_query_hint",
diff --git a/hostsidetests/userspacereboot/Android.bp b/hostsidetests/userspacereboot/Android.bp
index c3eb8b7..ba0715d 100644
--- a/hostsidetests/userspacereboot/Android.bp
+++ b/hostsidetests/userspacereboot/Android.bp
@@ -24,23 +24,11 @@
         "hamcrest-library",
     ],
     data: [
-        ":UserspaceRebootTest",
+        ":BasicUserspaceRebootTestApp",
+        ":BootCompletedUserspaceRebootTestApp",
     ],
     test_suites: [
         "cts",
         "general-tests",
     ],
 }
-
-android_test_helper_app {
-    name: "UserspaceRebootTest",
-    srcs:  ["app/src/**/*.java"],
-    manifest : "app/AndroidManifest.xml",
-    static_libs: [
-        "androidx.test.runner",
-        "androidx.test.core",
-        "testng",
-        "truth-prebuilt",
-    ],
-    sdk_version: "test_current",
-}
\ No newline at end of file
diff --git a/hostsidetests/userspacereboot/AndroidTest.xml b/hostsidetests/userspacereboot/AndroidTest.xml
index d62eb03..97f67af 100644
--- a/hostsidetests/userspacereboot/AndroidTest.xml
+++ b/hostsidetests/userspacereboot/AndroidTest.xml
@@ -20,10 +20,6 @@
     <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="not_secondary_user" />
-    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
-        <option name="cleanup-apks" value="true" />
-        <option name="test-file-name" value="UserspaceRebootTest.apk" />
-    </target_preparer>
     <test class="com.android.tradefed.testtype.HostTest" >
         <option name="class" value="com.android.cts.userspacereboot.host.UserspaceRebootHostTest" />
     </test>
diff --git a/hostsidetests/userspacereboot/TEST_MAPPING b/hostsidetests/userspacereboot/TEST_MAPPING
new file mode 100644
index 0000000..1105edf
--- /dev/null
+++ b/hostsidetests/userspacereboot/TEST_MAPPING
@@ -0,0 +1,8 @@
+{
+  "presubmit" : [
+    {
+      "name": "CtsUserspaceRebootHostSideTestCases"
+    }
+  ]
+}
+
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 9f3b4df..f953a1d 100644
--- a/hostsidetests/userspacereboot/src/com/android/cts/userspacereboot/host/UserspaceRebootHostTest.java
+++ b/hostsidetests/userspacereboot/src/com/android/cts/userspacereboot/host/UserspaceRebootHostTest.java
@@ -22,11 +22,13 @@
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
 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 org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -38,47 +40,58 @@
     private static final String USERSPACE_REBOOT_SUPPORTED_PROP =
             "ro.init.userspace_reboot.is_supported";
 
-    /**
-     * Runs the given {@code testName} of
-     * {@link com.android.cts.userspacereboot.UserspaceRebootTest}.
-     *
-     * @throws Exception if test phase fails.
-     */
-    private void runDeviceTest(String testName) throws Exception {
-        assertThat(runDeviceTests("com.android.cts.userspacereboot",
-                "com.android.cts.userspacereboot.UserspaceRebootTest",
-                testName)).isTrue();
+    private static final String BASIC_TEST_APP_APK = "BasicUserspaceRebootTestApp.apk";
+    private static final String BASIC_TEST_APP_PACKAGE_NAME =
+            "com.android.cts.userspacereboot.basic";
+
+    private static final String BOOT_COMPLETED_TEST_APP_APK =
+            "BootCompletedUserspaceRebootTestApp.apk";
+    private static final String BOOT_COMPLETED_TEST_APP_PACKAGE_NAME =
+            "com.android.cts.userspacereboot.bootcompleted";
+
+    private void runDeviceTest(String pkgName, String className, String testName) throws Exception {
+        assertThat(runDeviceTests(pkgName, pkgName + "." + className, testName)).isTrue();
+    }
+
+    private void installApk(String apkFileName) throws Exception {
+        CompatibilityBuildHelper helper = new CompatibilityBuildHelper(getBuild());
+        getDevice().installPackage(helper.getTestFile(apkFileName), false, true);
+    }
+
+    @After
+    public void cleanUp() throws Exception {
+        getDevice().uninstallPackage(BASIC_TEST_APP_PACKAGE_NAME);
+        getDevice().uninstallPackage(BOOT_COMPLETED_TEST_APP_PACKAGE_NAME);
     }
 
     @Test
     public void testOnlyFbeDevicesSupportUserspaceReboot() throws Exception {
         assumeTrue("Userspace reboot not supported on the device",
                 getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
+        installApk(BASIC_TEST_APP_APK);
         assertThat(getDevice().getProperty("ro.crypto.state")).isEqualTo("encrypted");
         assertThat(getDevice().getProperty("ro.crypto.type")).isEqualTo("file");
         // Also verify that PowerManager.isRebootingUserspaceSupported will return true
-        runDeviceTest("testUserspaceRebootIsSupported");
+        runDeviceTest(BASIC_TEST_APP_PACKAGE_NAME, "BasicUserspaceRebootTest",
+                "testUserspaceRebootIsSupported");
     }
 
     @Test
     public void testDeviceDoesNotSupportUserspaceReboot() throws Exception {
         assumeFalse("Userspace reboot supported on the device",
                 getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
+        installApk(BASIC_TEST_APP_APK);
         // Also verify that PowerManager.isRebootingUserspaceSupported will return true
-        runDeviceTest("testUserspaceRebootIsNotSupported");
+        runDeviceTest(BASIC_TEST_APP_PACKAGE_NAME, "BasicUserspaceRebootTest",
+                "testUserspaceRebootIsNotSupported");
     }
 
     @Test
     public void testUserspaceReboot() throws Exception {
         assumeTrue("Userspace reboot not supported on the device",
                 getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
-        getDevice().rebootUserspace();
-        assertWithMessage("Device did not boot within 2 minutes").that(
-                getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
-        // If userspace reboot fails and fallback to hard reboot is triggered then
-        // sys.init.userspace_reboot.last_finished won't be set.
-        assertWithMessage("Userspace reboot failed and fallback to full reboot was triggered").that(
-                getDevice().getProperty("sys.init.userspace_reboot.last_finished")).isNotEmpty();
+        rebootUserspaceAndWaitForBootComplete();
+        assertUserspaceRebootSucceed();
     }
 
     @Test
@@ -90,13 +103,32 @@
         Thread.sleep(500);
         assertWithMessage("Failed to start checkpoint : %s", result.getStderr()).that(
                 result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
-        getDevice().rebootUserspace();
-        assertWithMessage("Device did not boot withing 2 minutes").that(
-                getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
-        // If userspace reboot fails and fallback to hard reboot is triggered then
-        // sys.init.userspace_reboot.last_finished won't be set.
-        assertWithMessage("Userspace reboot failed and fallback to full reboot was triggered").that(
-                getDevice().getProperty("sys.init.userspace_reboot.last_finished")).isNotEmpty();
+        rebootUserspaceAndWaitForBootComplete();
+        assertUserspaceRebootSucceed();
+    }
+
+    // TODO(ioffe): this should also cover other lock scenarios.
+    @Test
+    public void testUserspaceReboot_verifyCeStorageIsUnlocked() throws Exception {
+        assumeTrue("Userspace reboot not supported on the device",
+                getDevice().getBooleanProperty(USERSPACE_REBOOT_SUPPORTED_PROP, false));
+        try {
+            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");
+        } finally {
+            getDevice().executeShellV2Command("cmd lock_settings clear --old 1543");
+        }
     }
 
     @Test
@@ -121,4 +153,18 @@
                 result.getStderr()).that(result.getStatus()).isEqualTo(CommandStatus.SUCCESS);
         return "true".equals(result.getStdout().trim());
     }
+
+    private void rebootUserspaceAndWaitForBootComplete() throws Exception {
+        getDevice().setProperty("test.userspace_reboot.requested", "1");
+        getDevice().rebootUserspace();
+        assertWithMessage("Device did not boot withing 2 minutes").that(
+                getDevice().waitForBootComplete(Duration.ofMinutes(2).toMillis())).isTrue();
+    }
+
+    private void assertUserspaceRebootSucceed() throws Exception {
+        // If userspace reboot fails and fallback to hard reboot is triggered then
+        // test.userspace_reboot.requested won't be set.
+        assertWithMessage("Userspace reboot failed and fallback to full reboot was triggered").that(
+                getDevice().getProperty("test.userspace_reboot.requested")).isEqualTo("1");
+    }
 }
diff --git a/hostsidetests/userspacereboot/testapps/BasicTestApp/Android.bp b/hostsidetests/userspacereboot/testapps/BasicTestApp/Android.bp
new file mode 100644
index 0000000..53ef0f1a
--- /dev/null
+++ b/hostsidetests/userspacereboot/testapps/BasicTestApp/Android.bp
@@ -0,0 +1,26 @@
+// 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: "BasicUserspaceRebootTestApp",
+    srcs:  ["src/**/*.java"],
+    manifest : "AndroidManifest.xml",
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.core",
+        "testng",
+        "truth-prebuilt",
+    ],
+    min_sdk_version: "30",
+}
\ No newline at end of file
diff --git a/hostsidetests/userspacereboot/app/AndroidManifest.xml b/hostsidetests/userspacereboot/testapps/BasicTestApp/AndroidManifest.xml
similarity index 86%
rename from hostsidetests/userspacereboot/app/AndroidManifest.xml
rename to hostsidetests/userspacereboot/testapps/BasicTestApp/AndroidManifest.xml
index 7eb103d..f003a71 100644
--- a/hostsidetests/userspacereboot/app/AndroidManifest.xml
+++ b/hostsidetests/userspacereboot/testapps/BasicTestApp/AndroidManifest.xml
@@ -16,13 +16,13 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-          package="com.android.cts.userspacereboot" >
+          package="com.android.cts.userspacereboot.basic" >
 
     <application>
         <uses-library android:name="android.test.runner" />
     </application>
 
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
-                     android:targetPackage="com.android.cts.userspacereboot"
-                     android:label="UserspaceReboot device side tests"/>
+                     android:targetPackage="com.android.cts.userspacereboot.basic"
+                     android:label="Basic userspace reboot device side tests"/>
 </manifest>
diff --git a/hostsidetests/userspacereboot/app/src/com/android/cts/userspacereboot/UserspaceRebootTest.java b/hostsidetests/userspacereboot/testapps/BasicTestApp/src/com/android/cts/userspacereboot/basic/BasicUserspaceRebootTest.java
similarity index 94%
rename from hostsidetests/userspacereboot/app/src/com/android/cts/userspacereboot/UserspaceRebootTest.java
rename to hostsidetests/userspacereboot/testapps/BasicTestApp/src/com/android/cts/userspacereboot/basic/BasicUserspaceRebootTest.java
index 341ab1c..f4616c2 100644
--- a/hostsidetests/userspacereboot/app/src/com/android/cts/userspacereboot/UserspaceRebootTest.java
+++ b/hostsidetests/userspacereboot/testapps/BasicTestApp/src/com/android/cts/userspacereboot/basic/BasicUserspaceRebootTest.java
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.cts.userspacereboot;
+package com.android.cts.userspacereboot.basic;
 
 import static com.google.common.truth.Truth.assertThat;
 
-
 import static org.testng.Assert.assertThrows;
 
 import android.content.Context;
@@ -31,7 +30,7 @@
 import org.junit.runners.JUnit4;
 
 @RunWith(JUnit4.class)
-public class UserspaceRebootTest {
+public class BasicUserspaceRebootTest {
 
     private final Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
 
@@ -48,4 +47,5 @@
         assertThrows(UnsupportedOperationException.class,
                 () -> powerManager.reboot("userspace"));
     }
+
 }
diff --git a/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/Android.bp b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/Android.bp
new file mode 100644
index 0000000..76357a6
--- /dev/null
+++ b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/Android.bp
@@ -0,0 +1,26 @@
+// 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: "BootCompletedUserspaceRebootTestApp",
+    srcs:  ["src/**/*.java"],
+    manifest : "AndroidManifest.xml",
+    static_libs: [
+        "androidx.test.runner",
+        "androidx.test.core",
+        "testng",
+        "truth-prebuilt",
+    ],
+    min_sdk_version: "30",
+}
\ No newline at end of file
diff --git a/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/AndroidManifest.xml b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/AndroidManifest.xml
new file mode 100644
index 0000000..bfa53f9
--- /dev/null
+++ b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/AndroidManifest.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2020 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.cts.userspacereboot.bootcompleted" >
+
+    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+    <application>
+        <activity android:name=".LauncherActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+        <uses-library android:name="android.test.runner" />
+        <receiver android:name=".BootCompletedUserspaceRebootTest$BootReceiver">
+            <intent-filter>
+                <action android:name="android.intent.action.BOOT_COMPLETED" />
+            </intent-filter>
+        </receiver>
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+                     android:targetPackage="com.android.cts.userspacereboot.bootcompleted"
+                     android:label="Boot Completed userspace reboot device side tests"/>
+</manifest>
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
new file mode 100644
index 0000000..8bb86ce
--- /dev/null
+++ b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/src/com/android/cts/userspacereboot/bootcompleted/BootCompletedUserspaceRebootTest.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 com.android.cts.userspacereboot.bootcompleted;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserManager;
+import android.util.Log;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.Scanner;
+
+@RunWith(JUnit4.class)
+public class BootCompletedUserspaceRebootTest {
+
+    private static final String TAG = "UserspaceRebootTest";
+
+    private static final String FILE_NAME = "secret.txt";
+    private static final String SECRET_MESSAGE = "wow, much secret";
+
+    private static final String RECEIVED_BROADCASTS_FILE = "received_broadcasts.txt";
+
+    private final Context mCeContext =
+            getInstrumentation().getContext().createCredentialProtectedStorageContext();
+    private final Context mDeContext =
+            getInstrumentation().getContext().createDeviceProtectedStorageContext();
+
+    @Test
+    public void prepareFile() throws Exception {
+        try (OutputStreamWriter writer = new OutputStreamWriter(
+                mCeContext.openFileOutput(FILE_NAME, Context.MODE_PRIVATE))) {
+            writer.write(SECRET_MESSAGE);
+        }
+    }
+
+    @Test
+    public void testVerifyCeStorageUnlocked() throws Exception {
+        UserManager um = getInstrumentation().getContext().getSystemService(UserManager.class);
+        assertThat(um.isUserUnlocked(0)).isTrue();
+        try (Scanner scanner = new Scanner(mCeContext.openFileInput(FILE_NAME))) {
+            final String content = scanner.nextLine();
+            assertThat(content).isEqualTo(SECRET_MESSAGE);
+        }
+    }
+
+    @Test
+    public void testVerifyReceivedBootCompletedBroadcast() throws Exception {
+        try (Scanner scanner = new Scanner(mDeContext.openFileInput(RECEIVED_BROADCASTS_FILE))) {
+            final String intent = scanner.nextLine();
+            assertThat(intent).isEqualTo(Intent.ACTION_BOOT_COMPLETED);
+        }
+    }
+
+    public static class BootReceiver extends BroadcastReceiver {
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.i(TAG, "Received! " + intent);
+            try (PrintWriter writer = new PrintWriter(new OutputStreamWriter(
+                    context.createDeviceProtectedStorageContext().openFileOutput(
+                            RECEIVED_BROADCASTS_FILE, Context.MODE_PRIVATE)))) {
+                writer.println(intent.getAction());
+            } catch (IOException e) {
+                Log.w(TAG, "Failed to append to " + RECEIVED_BROADCASTS_FILE, e);
+            }
+        }
+    }
+}
diff --git a/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/src/com/android/cts/userspacereboot/bootcompleted/LauncherActivity.java b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/src/com/android/cts/userspacereboot/bootcompleted/LauncherActivity.java
new file mode 100644
index 0000000..abaca60
--- /dev/null
+++ b/hostsidetests/userspacereboot/testapps/BootCompletedTestApp/src/com/android/cts/userspacereboot/bootcompleted/LauncherActivity.java
@@ -0,0 +1,22 @@
+/*
+ * 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.userspacereboot.bootcompleted;
+
+import android.app.Activity;
+
+public class LauncherActivity extends Activity {
+}
diff --git a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
index e01d682..d8ad039 100644
--- a/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
+++ b/hostsidetests/wifibroadcasts/src/android/wifibroadcasts/cts/WifiBroadcastsHostJUnit4Test.java
@@ -153,5 +153,7 @@
         } finally {
             in.close();
         }
+        //Re-enable Wi-Fi as part of CTS Pre-conditions
+        device.executeShellCommand("svc wifi enable; sleep 1");
     }
 }
diff --git a/tests/app/src/android/app/cts/DialogTest.java b/tests/app/src/android/app/cts/DialogTest.java
index 00837f6..2838830 100755
--- a/tests/app/src/android/app/cts/DialogTest.java
+++ b/tests/app/src/android/app/cts/DialogTest.java
@@ -485,7 +485,7 @@
         d.getWindow().getDecorView().getRootView().getLocationOnScreen(dialogLocation);
 
         final int touchSlop = ViewConfiguration.get(mActivity).getScaledWindowTouchSlop();
-        final int x = dialogLocation[0] - (touchSlop + 1);
+        final int x = dialogLocation[0];
         final int y = dialogLocation[1] - (touchSlop + 1);
 
         assertNull(d.onTouchEvent);
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
index 691ee97..9b3d9b9 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutoFillServiceTestCase.java
@@ -53,7 +53,6 @@
 import org.junit.Rule;
 import org.junit.rules.RuleChain;
 import org.junit.rules.TestRule;
-import org.junit.rules.TestWatcher;
 import org.junit.runner.Description;
 import org.junit.runner.RunWith;
 import org.junit.runners.model.Statement;
@@ -199,9 +198,15 @@
         protected static final RequiredFeatureRule sRequiredFeatureRule =
                 new RequiredFeatureRule(PackageManager.FEATURE_AUTOFILL);
 
-        private final TestWatcher mTestWatcher = new AutofillTestWatcher();
+        private final AutofillTestWatcher mTestWatcher = new AutofillTestWatcher();
 
-        private final RetryRule mRetryRule = new RetryRule(getNumberRetries());
+        private final RetryRule mRetryRule =
+                new RetryRule(getNumberRetries(), () -> {
+                    // Between testing and retries, clean all launched activities to avoid
+                    // exception:
+                    //     Could not launch intent Intent { ... } within 45 seconds.
+                    mTestWatcher.cleanAllActivities();
+                });
 
         private final AutofillLoggingTestRule mLoggingRule = new AutofillLoggingTestRule(TAG);
 
diff --git a/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java b/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
index 6879a8c..6c29197 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/AutofillTestWatcher.java
@@ -36,6 +36,18 @@
  */
 public final class AutofillTestWatcher extends TestWatcher {
 
+    /**
+     * Cleans up all launched activities between the tests and retries.
+     */
+    public void cleanAllActivities() {
+        try {
+            finishActivities();
+            waitUntilAllDestroyed();
+        } finally {
+            resetStaticState();
+        }
+    }
+
     private static final String TAG = "AutofillTestWatcher";
 
     @GuardedBy("sUnfinishedBusiness")
@@ -55,12 +67,7 @@
     @Override
     protected void finished(Description description) {
         final String testName = description.getDisplayName();
-        try {
-            finishActivities();
-            waitUntilAllDestroyed();
-        } finally {
-            resetStaticState();
-        }
+        cleanAllActivities();
         Log.i(TAG, "Finished " + testName);
         TestNameUtils.setCurrentTestName(null);
     }
diff --git a/tests/camera/libctscamera2jni/native-camera-jni.cpp b/tests/camera/libctscamera2jni/native-camera-jni.cpp
index b9fc256..5daccb7 100644
--- a/tests/camera/libctscamera2jni/native-camera-jni.cpp
+++ b/tests/camera/libctscamera2jni/native-camera-jni.cpp
@@ -2721,10 +2721,12 @@
             goto cleanup;
         }
 
+        StaticInfo staticInfo(chars);
         ACameraMetadata_const_entry sessionParamKeys{};
         ret = ACameraMetadata_getConstEntry(chars, ACAMERA_REQUEST_AVAILABLE_SESSION_KEYS,
                 &sessionParamKeys);
-        if ((ret != ACAMERA_OK) || (sessionParamKeys.count == 0)) {
+        if ((ret != ACAMERA_OK) || (sessionParamKeys.count == 0) ||
+                !staticInfo.isColorOutputSupported()) {
             ACameraMetadata_free(chars);
             chars = nullptr;
             continue;
@@ -3167,7 +3169,7 @@
         }
         StaticInfo staticInfo(chars);
 
-        usleep(100000); // sleep to give some time for callbacks to happen
+        usleep(200000); // sleep to give some time for callbacks to happen
 
         if (testCase.isCameraAvailable(cameraId)) {
             LOG_ERROR(errorString, "Camera %s should be unavailable now", cameraId);
@@ -3262,7 +3264,7 @@
         }
 
         // wait until last sequence complete
-        resultListener.getCaptureSequenceLastFrameNumber(lastSeqId, /*timeoutSec*/ 3);
+        resultListener.getCaptureSequenceLastFrameNumber(lastSeqId, /*timeoutSec*/ 5);
 
         std::vector<ACaptureRequest*> completedRequests;
         resultListener.getCompletedRequests(&completedRequests);
@@ -3333,7 +3335,7 @@
             goto cleanup;
         }
 
-        usleep(100000); // sleep to give some time for callbacks to happen
+        usleep(200000); // sleep to give some time for callbacks to happen
 
         if (!testCase.isCameraAvailable(cameraId)) {
             LOG_ERROR(errorString, "Camera %s should be available now", cameraId);
diff --git a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
index d108600e..3556954 100644
--- a/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/RobustnessTest.java
@@ -546,7 +546,7 @@
             MandatoryStreamCombination combination, boolean substituteY8,
             boolean substituteHeic) {
 
-        final int TIMEOUT_FOR_RESULT_MS = 3000;
+        final int TIMEOUT_FOR_RESULT_MS = 5000;
         final int NUM_REPROCESS_CAPTURES_PER_CONFIG = 3;
 
         List<SurfaceTexture> privTargets = new ArrayList<>();
diff --git a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
index 4cd0046..674dc96 100644
--- a/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
+++ b/tests/camera/src/android/hardware/camera2/cts/testcases/Camera2AndroidTestCase.java
@@ -61,7 +61,7 @@
 
     // Default capture size: VGA size is required by CDD.
     protected static final Size DEFAULT_CAPTURE_SIZE = new Size(640, 480);
-    protected static final int CAPTURE_WAIT_TIMEOUT_MS = 5000;
+    protected static final int CAPTURE_WAIT_TIMEOUT_MS = 7000;
 
     protected CameraManager mCameraManager;
     protected CameraDevice mCamera;
diff --git a/tests/contentcaptureservice/AndroidTest.xml b/tests/contentcaptureservice/AndroidTest.xml
index f8afb9a..13866c4 100644
--- a/tests/contentcaptureservice/AndroidTest.xml
+++ b/tests/contentcaptureservice/AndroidTest.xml
@@ -18,6 +18,7 @@
   <option name="config-descriptor:metadata" key="component" value="contentcapture" />
   <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
   <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+  <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
 
   <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
     <option name="cleanup-apks" value="true" />
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index a674c86..13f0f9b 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -152,7 +152,9 @@
 
         <activity android:name="android.server.wm.StartActivityTests$TestActivity2" />
 
-        <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivity" />
+        <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivity"
+                  android:resizeableActivity="true"
+                  android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen" />
         <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivity2" />
         <activity android:name="android.server.wm.MultiDisplaySystemDecorationTests$ImeTestActivityWithBrokenContextWrapper" />
 
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
index 07a8dba..3faa455 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplaySystemDecorationTests.java
@@ -44,6 +44,7 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.ContextWrapper;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
@@ -56,6 +57,7 @@
 import android.server.wm.TestJournalProvider.TestJournalContainer;
 import android.server.wm.WindowManagerState.Display;
 import android.server.wm.WindowManagerState.WindowState;
+import android.server.wm.intent.Activities;
 import android.text.TextUtils;
 import android.view.WindowManager;
 import android.view.inputmethod.EditorInfo;
@@ -587,6 +589,60 @@
         }
     }
 
+    @Test
+    public void testImeWindowCanShownWhenActivityMovedToDisplay() throws Exception {
+        try (final VirtualDisplaySession virtualDisplaySession = new VirtualDisplaySession();
+             // Launch a regular activity on default display at the test beginning to prevent the
+             // test may mis-touch the launcher icon that breaks the test expectation.
+             final TestActivitySession<Activities.RegularActivity> testActivitySession = new
+                     TestActivitySession<>();
+             final TestActivitySession<ImeTestActivity> imeTestActivitySession = new
+                     TestActivitySession<>();
+             // Leverage MockImeSession to ensure at least an IME exists as default.
+             final MockImeSession mockImeSession = MockImeSession.create(
+                     mContext, getInstrumentation().getUiAutomation(), new ImeSettings.Builder())) {
+
+            testActivitySession.launchTestActivityOnDisplaySync(Activities.RegularActivity.class,
+                    DEFAULT_DISPLAY);
+
+            // Create a virtual display and launch an activity on virtual display.
+            final ActivityDisplay newDisplay = virtualDisplaySession
+                    .setShowSystemDecorations(true)
+                    .setSimulateDisplay(true)
+                    .createDisplay();
+
+            imeTestActivitySession.launchTestActivityOnDisplaySync(ImeTestActivity.class,
+                    newDisplay.mId);
+
+            // Tap default display, assume a pointer-out-side event will happened to change the top
+            // display.
+            final Display defDisplay = mAmWmState.getWmState().getDisplay(DEFAULT_DISPLAY);
+            tapOnDisplayCenter(defDisplay.getDisplayId());
+
+            // Reparent ImeTestActivity from virtual display to default display.
+            getLaunchActivityBuilder()
+                    .setUseInstrumentation()
+                    .setTargetActivity(imeTestActivitySession.getActivity().getComponentName())
+                    .setIntentFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+                    .allowMultipleInstances(false)
+                    .setDisplayId(DEFAULT_DISPLAY).execute();
+            waitAndAssertTopResumedActivity(imeTestActivitySession.getActivity().getComponentName(),
+                    DEFAULT_DISPLAY, "Activity launched on default display and on top");
+            assertEquals("No stacks on virtual display", 0,
+                    mAmWmState.getAmState().getDisplay(newDisplay.mId).mStacks.size());
+
+            // Verify if tapping default display to request focus on EditText can show soft input.
+            final ImeEventStream stream = mockImeSession.openEventStream();
+            tapOnDisplayCenter(defDisplay.getDisplayId());
+            imeTestActivitySession.runOnMainSyncAndWait(
+                    imeTestActivitySession.getActivity()::showSoftInput);
+            waitOrderedImeEventsThenAssertImeShown(stream, defDisplay.getDisplayId(),
+                    editorMatcher("onStartInput",
+                            imeTestActivitySession.getActivity().mEditText.getPrivateImeOptions()),
+                    event -> "showSoftInput".equals(event.getEventName()));
+        }
+    }
+
     public static class ImeTestActivity extends Activity {
         ImeAwareEditText mEditText;
 
diff --git a/tests/media/Android.bp b/tests/media/Android.bp
index 889b123..b813fb2 100644
--- a/tests/media/Android.bp
+++ b/tests/media/Android.bp
@@ -26,6 +26,7 @@
     ],
     jni_libs: [
         "libctsmediav2muxer_jni",
+        "libctsmediav2extractor_jni",
     ],
     srcs: ["src/**/*.java"],
     // Tag this module as a cts test artifact
diff --git a/tests/media/OWNERS b/tests/media/OWNERS
index 8ffa99e..fad6041 100644
--- a/tests/media/OWNERS
+++ b/tests/media/OWNERS
@@ -1,12 +1,2 @@
 # Bug component: 1344
-andrewlewis@google.com
-chz@google.com
-dwkang@google.com
-elaurent@google.com
-essick@google.com
-gkasten@google.com
-hunga@google.com
-jmtrivi@google.com
-lajos@google.com
-marcone@google.com
-wjia@google.com
+include ../tests/media/OWNERS
diff --git a/tests/media/jni/Android.bp b/tests/media/jni/Android.bp
index e151d37..10f0f59 100644
--- a/tests/media/jni/Android.bp
+++ b/tests/media/jni/Android.bp
@@ -17,6 +17,25 @@
     srcs: [
         "NativeMediaConstants.cpp",
         "NativeMuxerTest.cpp",
+        "NativeMuxerUnitTest.cpp",
+    ],
+    shared_libs: [
+        "libmediandk",
+        "liblog",
+    ],
+    stl: "libc++_static",
+    cflags: [
+        "-Werror",
+        "-Wall",
+    ],
+    gtest: false,
+}
+
+cc_test_library {
+    name: "libctsmediav2extractor_jni",
+    srcs: [
+        "NativeMediaConstants.cpp",
+        "NativeExtractorTest.cpp",
     ],
     shared_libs: [
         "libmediandk",
diff --git a/tests/media/jni/NativeExtractorTest.cpp b/tests/media/jni/NativeExtractorTest.cpp
new file mode 100644
index 0000000..42876f0
--- /dev/null
+++ b/tests/media/jni/NativeExtractorTest.cpp
@@ -0,0 +1,735 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeExtractorTest"
+#include <log/log.h>
+
+#include <jni.h>
+#include <sys/stat.h>
+
+#include <cstdlib>
+#include <random>
+
+#include "media/NdkMediaExtractor.h"
+
+static bool isExtractorOKonEOS(AMediaExtractor* extractor) {
+    return AMediaExtractor_getSampleTrackIndex(extractor) < 0 &&
+           AMediaExtractor_getSampleSize(extractor) < 0 &&
+           (int)AMediaExtractor_getSampleFlags(extractor) < 0 &&
+           AMediaExtractor_getSampleTime(extractor) < 0;
+}
+
+static bool isSampleInfoIdentical(AMediaCodecBufferInfo* refSample,
+                                  AMediaCodecBufferInfo* testSample) {
+    return refSample->flags == testSample->flags && refSample->size == testSample->size &&
+           refSample->presentationTimeUs == testSample->presentationTimeUs;
+}
+
+static bool isSampleInfoValidAndIdentical(AMediaCodecBufferInfo* refSample,
+                                          AMediaCodecBufferInfo* testSample) {
+    return refSample->flags == testSample->flags && refSample->size == testSample->size &&
+           abs(refSample->presentationTimeUs - testSample->presentationTimeUs) <= 1 &&
+           (int)refSample->flags >= 0 && refSample->size >= 0 && refSample->presentationTimeUs >= 0;
+}
+
+static bool isFormatSimilar(AMediaFormat* refFormat, AMediaFormat* testFormat) {
+    const char *refMime = nullptr, *testMime = nullptr;
+    bool hasRefMime = AMediaFormat_getString(refFormat, AMEDIAFORMAT_KEY_MIME, &refMime);
+    bool hasTestMime = AMediaFormat_getString(testFormat, AMEDIAFORMAT_KEY_MIME, &testMime);
+
+    if (!hasRefMime || !hasTestMime || strcmp(refMime, testMime) != 0) return false;
+    if (!strncmp(refMime, "audio/", strlen("audio/"))) {
+        int32_t refSampleRate, testSampleRate, refNumChannels, testNumChannels;
+        bool hasRefSampleRate =
+                AMediaFormat_getInt32(refFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE, &refSampleRate);
+        bool hasTestSampleRate =
+                AMediaFormat_getInt32(testFormat, AMEDIAFORMAT_KEY_SAMPLE_RATE, &testSampleRate);
+        bool hasRefNumChannels =
+                AMediaFormat_getInt32(refFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &refNumChannels);
+        bool hasTestNumChannels =
+                AMediaFormat_getInt32(testFormat, AMEDIAFORMAT_KEY_CHANNEL_COUNT, &testNumChannels);
+        return hasRefSampleRate && hasTestSampleRate && hasRefNumChannels && hasTestNumChannels &&
+               refNumChannels == testNumChannels && refSampleRate == testSampleRate;
+    } else if (!strncmp(refMime, "video/", strlen("video/"))) {
+        int32_t refWidth, testWidth, refHeight, testHeight;
+        bool hasRefWidth = AMediaFormat_getInt32(refFormat, AMEDIAFORMAT_KEY_WIDTH, &refWidth);
+        bool hasTestWidth = AMediaFormat_getInt32(testFormat, AMEDIAFORMAT_KEY_WIDTH, &testWidth);
+        bool hasRefHeight = AMediaFormat_getInt32(refFormat, AMEDIAFORMAT_KEY_HEIGHT, &refHeight);
+        bool hasTestHeight =
+                AMediaFormat_getInt32(testFormat, AMEDIAFORMAT_KEY_HEIGHT, &testHeight);
+        return hasRefWidth && hasTestWidth && hasRefHeight && hasTestHeight &&
+               refWidth == testWidth && refHeight == testHeight;
+    }
+    return true;
+}
+
+static void inline setSampleInfo(AMediaExtractor* extractor, AMediaCodecBufferInfo* info) {
+    info->flags = AMediaExtractor_getSampleFlags(extractor);
+    info->offset = 0;
+    info->size = AMediaExtractor_getSampleSize(extractor);
+    info->presentationTimeUs = AMediaExtractor_getSampleTime(extractor);
+}
+
+static bool isMediaSimilar(AMediaExtractor* refExtractor, AMediaExtractor* testExtractor,
+                           const char* mime, int sampleLimit = INT32_MAX) {
+    const int maxSampleSize = (4 * 1024 * 1024);
+    auto refBuffer = new uint8_t[maxSampleSize];
+    auto testBuffer = new uint8_t[maxSampleSize];
+    int noOfTracksMatched = 0;
+    for (size_t refTrackID = 0; refTrackID < AMediaExtractor_getTrackCount(refExtractor);
+         refTrackID++) {
+        AMediaFormat* refFormat = AMediaExtractor_getTrackFormat(refExtractor, refTrackID);
+        const char* refMime = nullptr;
+        bool hasKey = AMediaFormat_getString(refFormat, AMEDIAFORMAT_KEY_MIME, &refMime);
+        if (!hasKey || (mime != nullptr && strcmp(refMime, mime) != 0)) {
+            AMediaFormat_delete(refFormat);
+            continue;
+        }
+        for (size_t testTrackID = 0; testTrackID < AMediaExtractor_getTrackCount(testExtractor);
+             testTrackID++) {
+            AMediaFormat* testFormat = AMediaExtractor_getTrackFormat(testExtractor, testTrackID);
+            if (!isFormatSimilar(refFormat, testFormat)) {
+                AMediaFormat_delete(testFormat);
+                continue;
+            }
+            AMediaExtractor_selectTrack(refExtractor, refTrackID);
+            AMediaExtractor_selectTrack(testExtractor, testTrackID);
+
+            AMediaCodecBufferInfo refSampleInfo, testSampleInfo;
+            bool areTracksIdentical = true;
+            for (int frameCount = 0;; frameCount++) {
+                setSampleInfo(refExtractor, &refSampleInfo);
+                setSampleInfo(testExtractor, &testSampleInfo);
+                if (!isSampleInfoValidAndIdentical(&refSampleInfo, &testSampleInfo)) {
+                    ALOGD(" Mime: %s mismatch for sample: %d", refMime, frameCount);
+                    ALOGD(" flags exp/got: %d / %d", refSampleInfo.flags, testSampleInfo.flags);
+                    ALOGD(" size exp/got: %d / %d ", refSampleInfo.size, testSampleInfo.size);
+                    ALOGD(" ts exp/got: %d / %d ", (int)refSampleInfo.presentationTimeUs,
+                          (int)testSampleInfo.presentationTimeUs);
+                    areTracksIdentical = false;
+                    break;
+                }
+                ssize_t refSz =
+                        AMediaExtractor_readSampleData(refExtractor, refBuffer, maxSampleSize);
+                if (refSz != refSampleInfo.size) {
+                    ALOGD("Mime: %s Size exp/got:  %d / %zd ", refMime, refSampleInfo.size, refSz);
+                    areTracksIdentical = false;
+                    break;
+                }
+                ssize_t testSz =
+                        AMediaExtractor_readSampleData(testExtractor, testBuffer, maxSampleSize);
+                if (testSz != testSampleInfo.size) {
+                    ALOGD("Mime: %s Size exp/got:  %d / %zd ", refMime, testSampleInfo.size,
+                          testSz);
+                    areTracksIdentical = false;
+                    break;
+                }
+                int trackIndex = AMediaExtractor_getSampleTrackIndex(refExtractor);
+                if (trackIndex != refTrackID) {
+                    ALOGD("Mime: %s TrackID exp/got: %zu / %d", refMime, refTrackID, trackIndex);
+                    areTracksIdentical = false;
+                    break;
+                }
+                trackIndex = AMediaExtractor_getSampleTrackIndex(testExtractor);
+                if (trackIndex != testTrackID) {
+                    ALOGD("Mime: %s  TrackID exp/got %zd / %d : ", refMime, testTrackID,
+                          trackIndex);
+                    areTracksIdentical = false;
+                    break;
+                }
+                bool haveRefSamples = AMediaExtractor_advance(refExtractor);
+                bool haveTestSamples = AMediaExtractor_advance(testExtractor);
+                if (haveRefSamples != haveTestSamples) {
+                    ALOGD("Mime: %s Mismatch in sampleCount", refMime);
+                    areTracksIdentical = false;
+                    break;
+                }
+
+                if (!haveRefSamples && !isExtractorOKonEOS(refExtractor)) {
+                    ALOGD("Mime: %s calls post advance() are not OK", refMime);
+                    areTracksIdentical = false;
+                    break;
+                }
+                if (!haveTestSamples && !isExtractorOKonEOS(testExtractor)) {
+                    ALOGD("Mime: %s calls post advance() are not OK", refMime);
+                    areTracksIdentical = false;
+                    break;
+                }
+                ALOGV("Mime: %s Sample: %d flags: %d size: %d ts: %d", refMime, frameCount,
+                      refSampleInfo.flags, refSampleInfo.size,
+                      (int)refSampleInfo.presentationTimeUs);
+                if (!haveRefSamples || frameCount >= sampleLimit) {
+                    break;
+                }
+            }
+            AMediaExtractor_unselectTrack(testExtractor, testTrackID);
+            AMediaExtractor_unselectTrack(refExtractor, refTrackID);
+            AMediaFormat_delete(testFormat);
+            if (areTracksIdentical) {
+                noOfTracksMatched++;
+                break;
+            }
+        }
+        AMediaFormat_delete(refFormat);
+        if (mime != nullptr && noOfTracksMatched > 0) break;
+    }
+    delete[] refBuffer;
+    delete[] testBuffer;
+    if (mime == nullptr) {
+        return noOfTracksMatched == AMediaExtractor_getTrackCount(refExtractor);
+    } else {
+        return noOfTracksMatched > 0;
+    }
+}
+
+static bool validateCachedDuration(AMediaExtractor* extractor, bool isNetworkSource) {
+    if (isNetworkSource) {
+        AMediaExtractor_selectTrack(extractor, 0);
+        for (unsigned cnt = 0;; cnt++) {
+            if ((cnt & (cnt - 1)) == 0) {
+                if (AMediaExtractor_getCachedDuration(extractor) < 0) {
+                    ALOGE("getCachedDuration is less than zero for network source");
+                    return false;
+                }
+            }
+            if (!AMediaExtractor_advance(extractor)) break;
+        }
+        AMediaExtractor_unselectTrack(extractor, 0);
+    } else {
+        if (AMediaExtractor_getCachedDuration(extractor) != -1) {
+            ALOGE("getCachedDuration != -1 for non-network source");
+            return false;
+        }
+    }
+    return true;
+}
+
+static AMediaExtractor* createExtractorFromFD(FILE* fp) {
+    AMediaExtractor* extractor = nullptr;
+    struct stat buf {};
+    if (fp && !fstat(fileno(fp), &buf)) {
+        extractor = AMediaExtractor_new();
+        media_status_t res = AMediaExtractor_setDataSourceFd(extractor, fileno(fp), 0, buf.st_size);
+        if (res != AMEDIA_OK) {
+            AMediaExtractor_delete(extractor);
+            extractor = nullptr;
+        }
+    }
+    return extractor;
+}
+
+// content necessary for testing seek are grouped in this class
+class SeekTestParams {
+  public:
+    SeekTestParams(AMediaCodecBufferInfo expected, int64_t timeStamp, SeekMode mode)
+        : mExpected{expected}, mTimeStamp{timeStamp}, mMode{mode} {}
+
+    AMediaCodecBufferInfo mExpected;
+    int64_t mTimeStamp;
+    SeekMode mMode;
+};
+
+static std::vector<AMediaCodecBufferInfo*> getSeekablePoints(const char* srcFile,
+                                                             const char* mime) {
+    std::vector<AMediaCodecBufferInfo*> bookmarks;
+    if (mime == nullptr) return bookmarks;
+    FILE* srcFp = fopen(srcFile, "rbe");
+    if (!srcFp) {
+        ALOGE("fopen failed for srcFile %s", srcFile);
+        return bookmarks;
+    }
+    AMediaExtractor* extractor = createExtractorFromFD(srcFp);
+    if (!extractor) {
+        if (srcFp) fclose(srcFp);
+        ALOGE("createExtractorFromFD failed");
+        return bookmarks;
+    }
+
+    for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) {
+        AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID);
+        const char* currMime = nullptr;
+        bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime);
+        if (!hasKey || strcmp(currMime, mime) != 0) {
+            AMediaFormat_delete(format);
+            continue;
+        }
+        AMediaExtractor_selectTrack(extractor, trackID);
+        do {
+            uint32_t sampleFlags = AMediaExtractor_getSampleFlags(extractor);
+            if ((sampleFlags & AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC) != 0) {
+                auto sampleInfo = new AMediaCodecBufferInfo;
+                setSampleInfo(extractor, sampleInfo);
+                bookmarks.push_back(sampleInfo);
+            }
+        } while (AMediaExtractor_advance(extractor));
+        AMediaExtractor_unselectTrack(extractor, trackID);
+        AMediaFormat_delete(format);
+        break;
+    }
+    AMediaExtractor_delete(extractor);
+    if (srcFp) fclose(srcFp);
+    return bookmarks;
+}
+
+static constexpr unsigned kSeed = 0x7ab7;
+
+static std::vector<SeekTestParams*> generateSeekTestArgs(const char* srcFile, const char* mime,
+                                                         bool isRandom) {
+    std::vector<SeekTestParams*> testArgs;
+    if (mime == nullptr) return testArgs;
+    const int MAX_SEEK_POINTS = 7;
+    std::srand(kSeed);
+    if (isRandom) {
+        FILE* srcFp = fopen(srcFile, "rbe");
+        if (!srcFp) {
+            ALOGE("fopen failed for srcFile %s", srcFile);
+            return testArgs;
+        }
+        AMediaExtractor* extractor = createExtractorFromFD(srcFp);
+        if (!extractor) {
+            if (srcFp) fclose(srcFp);
+            ALOGE("createExtractorFromFD failed");
+            return testArgs;
+        }
+
+        const int64_t maxEstDuration = 4000000;
+        for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) {
+            AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID);
+            const char* currMime = nullptr;
+            bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime);
+            if (!hasKey || strcmp(currMime, mime) != 0) {
+                AMediaFormat_delete(format);
+                continue;
+            }
+            AMediaExtractor_selectTrack(extractor, trackID);
+            for (int i = 0; i < MAX_SEEK_POINTS; i++) {
+                double r = ((double)rand() / (RAND_MAX));
+                long pts = (long)(r * maxEstDuration);
+
+                for (int mode = AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC;
+                     mode <= AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC; mode++) {
+                    AMediaExtractor_seekTo(extractor, pts, (SeekMode)mode);
+                    AMediaCodecBufferInfo currInfo;
+                    setSampleInfo(extractor, &currInfo);
+                    testArgs.push_back((new SeekTestParams(currInfo, pts, (SeekMode)mode)));
+                }
+            }
+            AMediaExtractor_unselectTrack(extractor, trackID);
+            AMediaFormat_delete(format);
+            break;
+        }
+        AMediaExtractor_delete(extractor);
+        if (srcFp) fclose(srcFp);
+    } else {
+        std::vector<AMediaCodecBufferInfo*> bookmarks = getSeekablePoints(srcFile, mime);
+        if (bookmarks.empty()) return testArgs;
+        int size = bookmarks.size();
+        int* indices;
+        int indexSize = 0;
+        if (size > MAX_SEEK_POINTS) {
+            indices = new int[MAX_SEEK_POINTS];
+            indexSize = MAX_SEEK_POINTS;
+            indices[0] = 0;
+            indices[MAX_SEEK_POINTS - 1] = size - 1;
+            for (int i = 1; i < MAX_SEEK_POINTS - 1; i++) {
+                double r = ((double)rand() / (RAND_MAX));
+                indices[i] = (int)(r * (MAX_SEEK_POINTS - 1) + 1);
+            }
+        } else {
+            indices = new int[size];
+            indexSize = size;
+            for (int i = 0; i < size; i++) indices[i] = i;
+        }
+        for (int i = 0; i < indexSize; i++) {
+            AMediaCodecBufferInfo currInfo = *bookmarks[i];
+            int64_t pts = currInfo.presentationTimeUs;
+            testArgs.push_back(
+                    (new SeekTestParams(currInfo, pts, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC)));
+            testArgs.push_back((new SeekTestParams(currInfo, pts, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC)));
+            testArgs.push_back(
+                    (new SeekTestParams(currInfo, pts, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC)));
+            if (i > 0) {
+                AMediaCodecBufferInfo prevInfo = *bookmarks[i - 1];
+                int64_t ptsMinus = prevInfo.presentationTimeUs;
+                ptsMinus = pts - ((pts - ptsMinus) >> 3);
+                testArgs.push_back((
+                        new SeekTestParams(currInfo, ptsMinus, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC)));
+                testArgs.push_back(
+                        (new SeekTestParams(currInfo, ptsMinus, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC)));
+                testArgs.push_back((new SeekTestParams(prevInfo, ptsMinus,
+                                                       AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC)));
+            }
+            if (i < size - 1) {
+                AMediaCodecBufferInfo nextInfo = *bookmarks[i + 1];
+                int64_t ptsPlus = nextInfo.presentationTimeUs;
+                ptsPlus = pts + ((ptsPlus - pts) >> 3);
+                testArgs.push_back(
+                        (new SeekTestParams(currInfo, ptsPlus, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC)));
+                testArgs.push_back(
+                        (new SeekTestParams(nextInfo, ptsPlus, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC)));
+                testArgs.push_back((
+                        new SeekTestParams(currInfo, ptsPlus, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC)));
+            }
+        }
+        for (auto bookmark : bookmarks) {
+            delete bookmark;
+        }
+        bookmarks.clear();
+        delete[] indices;
+    }
+    return testArgs;
+}
+
+static int checkSeekPoints(const char* srcFile, const char* mime,
+                           const std::vector<SeekTestParams*>& seekTestArgs) {
+    int errCnt = 0;
+    FILE* srcFp = fopen(srcFile, "rbe");
+    AMediaExtractor* extractor = createExtractorFromFD(srcFp);
+    if (!extractor) {
+        if (srcFp) fclose(srcFp);
+        ALOGE("createExtractorFromFD failed");
+        return -1;
+    }
+    for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) {
+        AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID);
+        const char* currMime = nullptr;
+        bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime);
+        if (!hasKey || strcmp(currMime, mime) != 0) {
+            continue;
+        }
+        AMediaExtractor_selectTrack(extractor, trackID);
+        AMediaCodecBufferInfo received;
+        for (auto arg : seekTestArgs) {
+            AMediaExtractor_seekTo(extractor, arg->mTimeStamp, arg->mMode);
+            setSampleInfo(extractor, &received);
+            if (!isSampleInfoIdentical(&arg->mExpected, &received)) {
+                ALOGE(" flags exp/got: %d / %d", arg->mExpected.flags, received.flags);
+                ALOGE(" size exp/got: %d / %d ", arg->mExpected.size, received.size);
+                ALOGE(" ts exp/got: %d / %d ", (int)arg->mExpected.presentationTimeUs,
+                      (int)received.presentationTimeUs);
+                errCnt++;
+            }
+        }
+        AMediaExtractor_unselectTrack(extractor, trackID);
+        AMediaFormat_delete(format);
+        break;
+    }
+    AMediaExtractor_delete(extractor);
+    if (srcFp) fclose(srcFp);
+    return errCnt;
+}
+
+static bool isFileFormatIdentical(AMediaExtractor* refExtractor, AMediaExtractor* testExtractor) {
+    bool result = false;
+    if (refExtractor && testExtractor) {
+        AMediaFormat* refFormat = AMediaExtractor_getFileFormat(refExtractor);
+        AMediaFormat* testFormat = AMediaExtractor_getFileFormat(testExtractor);
+        if (refFormat && testFormat) {
+            const char *refMime = nullptr, *testMime = nullptr;
+            bool hasRefKey = AMediaFormat_getString(refFormat, AMEDIAFORMAT_KEY_MIME, &refMime);
+            bool hasTestKey = AMediaFormat_getString(testFormat, AMEDIAFORMAT_KEY_MIME, &testMime);
+            /* TODO: Not Sure if we need to verify any other parameter of file format */
+            if (hasRefKey && hasTestKey && strcmp(refMime, testMime) == 0) {
+                result = true;
+            } else {
+                ALOGE("file format exp/got : %s/%s", refMime, testMime);
+            }
+        }
+        if (refFormat) AMediaFormat_delete(refFormat);
+        if (testFormat) AMediaFormat_delete(testFormat);
+    }
+    return result;
+}
+
+static bool isSeekOk(AMediaExtractor* refExtractor, AMediaExtractor* testExtractor) {
+    const long maxEstDuration = 14000000;
+    const int MAX_SEEK_POINTS = 7;
+    std::srand(kSeed);
+    AMediaCodecBufferInfo refSampleInfo, testSampleInfo;
+    bool result = true;
+    for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(refExtractor); trackID++) {
+        AMediaExtractor_selectTrack(refExtractor, trackID);
+        AMediaExtractor_selectTrack(testExtractor, trackID);
+        for (int i = 0; i < MAX_SEEK_POINTS && result; i++) {
+            double r = ((double)rand() / (RAND_MAX));
+            long pts = (long)(r * maxEstDuration);
+            for (int mode = AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC;
+                 mode <= AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC; mode++) {
+                AMediaExtractor_seekTo(refExtractor, pts, (SeekMode)mode);
+                AMediaExtractor_seekTo(testExtractor, pts, (SeekMode)mode);
+                setSampleInfo(refExtractor, &refSampleInfo);
+                setSampleInfo(testExtractor, &testSampleInfo);
+                result = isSampleInfoIdentical(&refSampleInfo, &testSampleInfo);
+                if (!result) {
+                    ALOGE(" flags exp/got: %d / %d", refSampleInfo.flags, testSampleInfo.flags);
+                    ALOGE(" size exp/got: %d / %d ", refSampleInfo.size, testSampleInfo.size);
+                    ALOGE(" ts exp/got: %d / %d ", (int)refSampleInfo.presentationTimeUs,
+                          (int)testSampleInfo.presentationTimeUs);
+                }
+                int refTrackIdx = AMediaExtractor_getSampleTrackIndex(refExtractor);
+                int testTrackIdx = AMediaExtractor_getSampleTrackIndex(testExtractor);
+                if (refTrackIdx != testTrackIdx) {
+                    ALOGE("trackIdx exp/got: %d/%d ", refTrackIdx, testTrackIdx);
+                    result = false;
+                }
+            }
+        }
+        AMediaExtractor_unselectTrack(refExtractor, trackID);
+        AMediaExtractor_unselectTrack(testExtractor, trackID);
+    }
+    return result;
+}
+
+static jboolean nativeTestExtract(JNIEnv* env, jobject, jstring jsrcPath, jstring jrefPath,
+                                  jstring jmime) {
+    bool isPass = false;
+    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+    const char* ctestPath = env->GetStringUTFChars(jrefPath, nullptr);
+    const char* cmime = env->GetStringUTFChars(jmime, nullptr);
+    FILE* srcFp = fopen(csrcPath, "rbe");
+    AMediaExtractor* srcExtractor = createExtractorFromFD(srcFp);
+    FILE* testFp = fopen(ctestPath, "rbe");
+    AMediaExtractor* testExtractor = createExtractorFromFD(testFp);
+    if (srcExtractor && testExtractor) {
+        isPass = isMediaSimilar(srcExtractor, testExtractor, cmime);
+        if (!isPass) {
+            ALOGE(" Src and test are different from extractor perspective");
+        }
+        AMediaExtractor_delete(srcExtractor);
+        AMediaExtractor_delete(testExtractor);
+    }
+    if (srcFp) fclose(srcFp);
+    if (testFp) fclose(testFp);
+    env->ReleaseStringUTFChars(jmime, cmime);
+    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+    env->ReleaseStringUTFChars(jrefPath, ctestPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSeek(JNIEnv* env, jobject, jstring jsrcPath, jstring jmime) {
+    bool isPass = false;
+    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+    const char* cmime = env->GetStringUTFChars(jmime, nullptr);
+    std::vector<SeekTestParams*> seekTestArgs = generateSeekTestArgs(csrcPath, cmime, false);
+    if (!seekTestArgs.empty()) {
+        std::shuffle(seekTestArgs.begin(), seekTestArgs.end(), std::default_random_engine(kSeed));
+        int seekAccErrCnt = checkSeekPoints(csrcPath, cmime, seekTestArgs);
+        if (seekAccErrCnt != 0) {
+            ALOGE("For %s seek chose inaccurate Sync point in: %d / %d", csrcPath, seekAccErrCnt,
+                  (int)seekTestArgs.size());
+            isPass = false;
+        } else {
+            isPass = true;
+        }
+        for (auto seekTestArg : seekTestArgs) {
+            delete seekTestArg;
+        }
+        seekTestArgs.clear();
+    } else {
+        ALOGE("No sync samples found.");
+    }
+    env->ReleaseStringUTFChars(jmime, cmime);
+    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSeekFlakiness(JNIEnv* env, jobject, jstring jsrcPath, jstring jmime) {
+    bool isPass = false;
+    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+    const char* cmime = env->GetStringUTFChars(jmime, nullptr);
+    std::vector<SeekTestParams*> seekTestArgs = generateSeekTestArgs(csrcPath, cmime, true);
+    if (!seekTestArgs.empty()) {
+        std::shuffle(seekTestArgs.begin(), seekTestArgs.end(), std::default_random_engine(kSeed));
+        int flakyErrCnt = checkSeekPoints(csrcPath, cmime, seekTestArgs);
+        if (flakyErrCnt != 0) {
+            ALOGE("No. of Samples where seek showed flakiness is: %d", flakyErrCnt);
+            isPass = false;
+        } else {
+            isPass = true;
+        }
+        for (auto seekTestArg : seekTestArgs) {
+            delete seekTestArg;
+        }
+        seekTestArgs.clear();
+    } else {
+        ALOGE("No sync samples found.");
+    }
+    env->ReleaseStringUTFChars(jmime, cmime);
+    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSeekToZero(JNIEnv* env, jobject, jstring jsrcPath, jstring jmime) {
+    bool isPass = false;
+    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+    const char* cmime = env->GetStringUTFChars(jmime, nullptr);
+    FILE* srcFp = fopen(csrcPath, "rbe");
+    AMediaExtractor* extractor = createExtractorFromFD(srcFp);
+    if (extractor) {
+        AMediaCodecBufferInfo sampleInfoAtZero;
+        AMediaCodecBufferInfo currInfo;
+        static long randomPts = 1 << 20;
+        for (size_t trackID = 0; trackID < AMediaExtractor_getTrackCount(extractor); trackID++) {
+            AMediaFormat* format = AMediaExtractor_getTrackFormat(extractor, trackID);
+            if (format) {
+                const char* currMime = nullptr;
+                bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &currMime);
+                if (!hasKey || strcmp(currMime, cmime) != 0) {
+                    AMediaFormat_delete(format);
+                    continue;
+                }
+                AMediaExtractor_selectTrack(extractor, trackID);
+                setSampleInfo(extractor, &sampleInfoAtZero);
+                AMediaExtractor_seekTo(extractor, randomPts, AMEDIAEXTRACTOR_SEEK_NEXT_SYNC);
+                AMediaExtractor_seekTo(extractor, 0, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
+                setSampleInfo(extractor, &currInfo);
+                isPass = isSampleInfoIdentical(&sampleInfoAtZero, &currInfo);
+                if (!isPass) {
+                    ALOGE("seen mismatch seekTo(0, SEEK_TO_CLOSEST_SYNC)");
+                    ALOGE(" flags exp/got: %d / %d", sampleInfoAtZero.flags, currInfo.flags);
+                    ALOGE(" size exp/got: %d / %d ", sampleInfoAtZero.size, currInfo.size);
+                    ALOGE(" ts exp/got: %d / %d ", (int)sampleInfoAtZero.presentationTimeUs,
+                          (int)currInfo.presentationTimeUs);
+                    AMediaFormat_delete(format);
+                    break;
+                }
+                AMediaExtractor_seekTo(extractor, -1L, AMEDIAEXTRACTOR_SEEK_CLOSEST_SYNC);
+                setSampleInfo(extractor, &currInfo);
+                isPass = isSampleInfoIdentical(&sampleInfoAtZero, &currInfo);
+                if (!isPass) {
+                    ALOGE("seen mismatch seekTo(-1, SEEK_TO_CLOSEST_SYNC)");
+                    ALOGE(" flags exp/got: %d / %d", sampleInfoAtZero.flags, currInfo.flags);
+                    ALOGE(" size exp/got: %d / %d ", sampleInfoAtZero.size, currInfo.size);
+                    ALOGE(" ts exp/got: %d / %d ", (int)sampleInfoAtZero.presentationTimeUs,
+                          (int)currInfo.presentationTimeUs);
+                    AMediaFormat_delete(format);
+                    break;
+                }
+                AMediaExtractor_unselectTrack(extractor, trackID);
+                AMediaFormat_delete(format);
+            }
+        }
+        AMediaExtractor_delete(extractor);
+    }
+    if (srcFp) fclose(srcFp);
+    env->ReleaseStringUTFChars(jmime, cmime);
+    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestFileFormat(JNIEnv* env, jobject, jstring jsrcPath) {
+    bool isPass = false;
+    const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+    FILE* srcFp = fopen(csrcPath, "rbe");
+    AMediaExtractor* extractor = createExtractorFromFD(srcFp);
+    if (extractor) {
+        AMediaFormat* format = AMediaExtractor_getFileFormat(extractor);
+        const char* mime = nullptr;
+        bool hasKey = AMediaFormat_getString(format, AMEDIAFORMAT_KEY_MIME, &mime);
+        /* TODO: Not Sure if we need to verify any other parameter of file format */
+        if (hasKey && mime && strlen(mime) > 0) {
+            isPass = true;
+        }
+        AMediaFormat_delete(format);
+        AMediaExtractor_delete(extractor);
+    }
+    if (srcFp) fclose(srcFp);
+    env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestDataSource(JNIEnv* env, jobject, jstring jsrcPath, jstring jsrcUrl) {
+    bool isPass = true;
+    const char* csrcUrl = env->GetStringUTFChars(jsrcUrl, nullptr);
+    AMediaExtractor* refExtractor = AMediaExtractor_new();
+    media_status_t status = AMediaExtractor_setDataSource(refExtractor, csrcUrl);
+    if (status == AMEDIA_OK) {
+        isPass &= validateCachedDuration(refExtractor, true);
+        const char* csrcPath = env->GetStringUTFChars(jsrcPath, nullptr);
+        AMediaDataSource* dataSource = AMediaDataSource_newUri(csrcUrl, 0, nullptr);
+        AMediaExtractor* testExtractor = AMediaExtractor_new();
+        status = AMediaExtractor_setDataSourceCustom(testExtractor, dataSource);
+        if (status != AMEDIA_OK) {
+            ALOGE("setDataSourceCustom failed");
+            isPass = false;
+        } else {
+            isPass &= validateCachedDuration(testExtractor, true);
+            if (!(isMediaSimilar(refExtractor, testExtractor, nullptr) &&
+                  isFileFormatIdentical(refExtractor, testExtractor) &&
+                  isSeekOk(refExtractor, testExtractor))) {
+                isPass = false;
+            }
+        }
+        if (testExtractor) AMediaExtractor_delete(testExtractor);
+        if (dataSource) AMediaDataSource_delete(dataSource);
+
+        FILE* testFp = fopen(csrcPath, "rbe");
+        testExtractor = createExtractorFromFD(testFp);
+        if (testExtractor == nullptr) {
+            ALOGE("createExtractorFromFD failed for test extractor");
+            isPass = false;
+        } else {
+            isPass &= validateCachedDuration(testExtractor, false);
+            if (!(isMediaSimilar(refExtractor, testExtractor, nullptr) &&
+                  isFileFormatIdentical(refExtractor, testExtractor) &&
+                  isSeekOk(refExtractor, testExtractor))) {
+                isPass = false;
+            }
+        }
+        if (testExtractor) AMediaExtractor_delete(testExtractor);
+        if (testFp) fclose(testFp);
+        env->ReleaseStringUTFChars(jsrcPath, csrcPath);
+    } else {
+        ALOGE("setDataSource failed");
+        isPass = false;
+    }
+    if (refExtractor) AMediaExtractor_delete(refExtractor);
+    env->ReleaseStringUTFChars(jsrcUrl, csrcUrl);
+    return static_cast<jboolean>(isPass);
+}
+
+int registerAndroidMediaV2CtsExtractorTestSetDS(JNIEnv* env) {
+    const JNINativeMethod methodTable[] = {
+            {"nativeTestDataSource", "(Ljava/lang/String;Ljava/lang/String;)Z",
+             (void*)nativeTestDataSource},
+    };
+    jclass c = env->FindClass("android/mediav2/cts/ExtractorTest$SetDataSourceTest");
+    return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
+}
+
+int registerAndroidMediaV2CtsExtractorTestFunc(JNIEnv* env) {
+    const JNINativeMethod methodTable[] = {
+            {"nativeTestExtract", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)Z",
+             (void*)nativeTestExtract},
+            {"nativeTestSeek", "(Ljava/lang/String;Ljava/lang/String;)Z", (void*)nativeTestSeek},
+            {"nativeTestSeekFlakiness", "(Ljava/lang/String;Ljava/lang/String;)Z",
+             (void*)nativeTestSeekFlakiness},
+            {"nativeTestSeekToZero", "(Ljava/lang/String;Ljava/lang/String;)Z",
+             (void*)nativeTestSeekToZero},
+            {"nativeTestFileFormat", "(Ljava/lang/String;)Z", (void*)nativeTestFileFormat},
+    };
+    jclass c = env->FindClass("android/mediav2/cts/ExtractorTest$FunctionalityTest");
+    return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
+}
+
+extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void*) {
+    JNIEnv* env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR;
+    if (registerAndroidMediaV2CtsExtractorTestSetDS(env) != JNI_OK) return JNI_ERR;
+    if (registerAndroidMediaV2CtsExtractorTestFunc(env) != JNI_OK) return JNI_ERR;
+    return JNI_VERSION_1_6;
+}
\ No newline at end of file
diff --git a/tests/media/jni/NativeMuxerTest.cpp b/tests/media/jni/NativeMuxerTest.cpp
index f9adbd7..62d1535 100644
--- a/tests/media/jni/NativeMuxerTest.cpp
+++ b/tests/media/jni/NativeMuxerTest.cpp
@@ -731,6 +731,8 @@
     return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
 }
 
+extern int registerAndroidMediaV2CtsMuxerUnitTestApi(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;
@@ -738,5 +740,6 @@
     if (registerAndroidMediaV2CtsMuxerTestMultiTrack(env) != JNI_OK) return JNI_ERR;
     if (registerAndroidMediaV2CtsMuxerTestOffsetPts(env) != JNI_OK) return JNI_ERR;
     if (registerAndroidMediaV2CtsMuxerTestSimpleMux(env) != JNI_OK) return JNI_ERR;
+    if (registerAndroidMediaV2CtsMuxerUnitTestApi(env) != JNI_OK) return JNI_ERR;
     return JNI_VERSION_1_6;
 }
\ No newline at end of file
diff --git a/tests/media/jni/NativeMuxerUnitTest.cpp b/tests/media/jni/NativeMuxerUnitTest.cpp
new file mode 100644
index 0000000..e267799
--- /dev/null
+++ b/tests/media/jni/NativeMuxerUnitTest.cpp
@@ -0,0 +1,509 @@
+/*
+ * 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.
+ */
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "NativeMuxerUnitTest"
+#include <log/log.h>
+#include <fcntl.h>
+#include <jni.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include <cmath>
+#include <cstring>
+#include <fstream>
+#include <map>
+#include <vector>
+
+#include "NativeMediaConstants.h"
+#include "media/NdkMediaExtractor.h"
+#include "media/NdkMediaFormat.h"
+#include "media/NdkMediaMuxer.h"
+
+static media_status_t insertPerFrameSubtitles(AMediaMuxer* muxer, long pts, size_t trackID) {
+    const char* greeting = "hello world";
+    auto* info = new AMediaCodecBufferInfo;
+    info->offset = 0;
+    info->size = strlen(greeting);
+    info->presentationTimeUs = pts;
+    info->flags = 0;
+    media_status_t status = AMediaMuxer_writeSampleData(muxer, trackID, (uint8_t*)greeting, info);
+    delete info;
+    return status;
+}
+
+static jboolean nativeTestIfInvalidFdIsRejected(JNIEnv*, jobject) {
+    AMediaMuxer* muxer = AMediaMuxer_new(-1, (OutputFormat)OUTPUT_FORMAT_THREE_GPP);
+    bool isPass = true;
+    if (muxer != nullptr) {
+        AMediaMuxer_delete(muxer);
+        ALOGE("error: muxer constructor accepts invalid file descriptor");
+        isPass = false;
+    }
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfReadOnlyFdIsRejected(JNIEnv* env, jobject, jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "rbe");
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_THREE_GPP);
+    bool isPass = true;
+    if (muxer != nullptr) {
+        AMediaMuxer_delete(muxer);
+        ALOGE("error: muxer constructor accepts read-only file descriptor");
+        isPass = false;
+    }
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfWriteOnlyFdIsRejected(JNIEnv* env, jobject, jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe");
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_WEBM);
+    bool isPass = true;
+    if (muxer != nullptr) {
+        AMediaMuxer_delete(muxer);
+        ALOGE("error: muxer constructor accepts write-only file descriptor");
+        isPass = false;
+    }
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfNonSeekableFdIsRejected(JNIEnv* env, jobject, jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    mkfifo(cdstPath, 0666);
+    int fd = open(cdstPath, O_WRONLY);
+    AMediaMuxer* muxer = AMediaMuxer_new(fd, (OutputFormat)OUTPUT_FORMAT_THREE_GPP);
+    bool isPass = true;
+    if (muxer != nullptr) {
+        AMediaMuxer_delete(muxer);
+        ALOGE("error: muxer constructor accepts non-seekable file descriptor");
+        isPass = false;
+    }
+    close(fd);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfInvalidOutputFormatIsRejected(JNIEnv* env, jobject, jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)(OUTPUT_FORMAT_LIST_END + 1));
+    bool isPass = true;
+    if (muxer != nullptr) {
+        AMediaMuxer_delete(muxer);
+        ALOGE("error: muxer constructor accepts invalid output format");
+        isPass = false;
+    }
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfInvalidMediaFormatIsRejected(JNIEnv* env, jobject, jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    bool isPass = true;
+    if (AMediaMuxer_addTrack(muxer, format) >= 0) {
+        ALOGE("error: muxer.addTrack succeeds with format that has no mime key");
+        isPass = false;
+    }
+
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "text/cea-608");
+    if (AMediaMuxer_addTrack(muxer, format) >= 0) {
+        ALOGE("error: muxer.addTrack succeeds with format whose mime is non-compliant");
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfCorruptMediaFormatIsRejected(JNIEnv* env, jobject, jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    bool isPass = true;
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, AMEDIA_MIMETYPE_AUDIO_AAC);
+    AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, -1);
+    if (AMediaMuxer_addTrack(muxer, format) >= 0) {
+        ALOGE("error: muxer.addTrack succeeds with erroneous key-value pairs in media format");
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfAddTrackSucceedsAfterStart(JNIEnv* env, jobject, jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    bool isPass = true;
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+    isPass &= AMediaMuxer_addTrack(muxer, format) >= 0;
+    isPass &= (AMediaMuxer_start(muxer) == AMEDIA_OK);
+    if (AMediaMuxer_addTrack(muxer, format) >= 0) {
+        ALOGE("error: muxer.addTrack succeeds after muxer.start");
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfAddTrackSucceedsAfterWriteSampleData(JNIEnv* env, jobject,
+                                                                 jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    bool isPass = true;
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+    ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+    isPass &= trackID >= 0;
+    isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+    isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+    if (AMediaMuxer_addTrack(muxer, format) >= 0) {
+        ALOGE("error: muxer.addTrack succeeds after muxer.writeSampleData");
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfAddTrackSucceedsAfterStop(JNIEnv* env, jobject, jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    bool isPass = true;
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+    ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+    isPass &= trackID >= 0;
+    isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+    isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+    isPass &= AMediaMuxer_stop(muxer) == AMEDIA_OK;
+    if (AMediaMuxer_addTrack(muxer, format) >= 0) {
+        ALOGE("error: muxer.addTrack succeeds after muxer.stop");
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfMuxerStartsBeforeAddTrack(JNIEnv* env, jobject, jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    bool isPass = true;
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    if (AMediaMuxer_start(muxer) == AMEDIA_OK) {
+        ALOGE("error: muxer.start succeeds before muxer.addTrack");
+        isPass = false;
+    }
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIdempotentStart(JNIEnv* env, jobject, jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    bool isPass = true;
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+    isPass &= AMediaMuxer_addTrack(muxer, format) >= 0;
+    isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+    if (AMediaMuxer_start(muxer) == AMEDIA_OK) {
+        ALOGE("error: double muxer.start succeeds");
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfMuxerStartsAfterWriteSampleData(JNIEnv* env, jobject,
+                                                            jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    bool isPass = true;
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+    ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+    isPass &= trackID >= 0;
+    isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+    isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+    if (AMediaMuxer_start(muxer) == AMEDIA_OK) {
+        ALOGE("error: muxer.start succeeds after muxer.writeSampleData");
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfMuxerStartsAfterStop(JNIEnv* env, jobject, jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    bool isPass = true;
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+    ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+    isPass &= trackID >= 0;
+    isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+    isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+    isPass &= AMediaMuxer_stop(muxer) == AMEDIA_OK;
+    if (AMediaMuxer_start(muxer) == AMEDIA_OK) {
+        ALOGE("error: muxer.start succeeds after muxer.stop");
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestStopOnANonStartedMuxer(JNIEnv* env, jobject, jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    bool isPass = true;
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+    isPass &= AMediaMuxer_addTrack(muxer, format) >= 0;
+    if (AMEDIA_OK == AMediaMuxer_stop(muxer)) {
+        ALOGE("error: muxer.stop succeeds before muxer.start");
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIdempotentStop(JNIEnv* env, jobject, jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    bool isPass = true;
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+    ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+    isPass &= trackID >= 0;
+    isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+    isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+    isPass &= AMediaMuxer_stop(muxer) == AMEDIA_OK;
+    if (AMEDIA_OK == AMediaMuxer_stop(muxer)) {
+        ALOGE("error: double muxer.stop succeeds");
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestSimpleStartStop(JNIEnv* env, jobject, jstring jdstPath) {
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    bool isPass = true;
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+    ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+    isPass &= trackID >= 0;
+    isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+    isPass &= AMediaMuxer_stop(muxer) == AMEDIA_OK;
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfWriteSampleDataRejectsInvalidTrackIndex(JNIEnv* env, jobject,
+                                                                    jstring jdstPath) {
+    bool isPass = true;
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+    ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+    isPass &= trackID >= 0;
+    isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+    isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+    if (AMEDIA_OK == insertPerFrameSubtitles(muxer, 22000, trackID + 1)) {
+        ALOGE("error: muxer.writeSampleData succeeds for invalid track ID");
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfWriteSampleDataRejectsInvalidPts(JNIEnv* env, jobject,
+                                                             jstring jdstPath) {
+    bool isPass = true;
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+    ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+    isPass &= trackID >= 0;
+    isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+    isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+    if (AMEDIA_OK == insertPerFrameSubtitles(muxer, -33000, trackID)) {
+        ALOGE("error: muxer.writeSampleData succeeds for invalid pts");
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfWriteSampleDataSucceedsBeforeStart(JNIEnv* env, jobject,
+                                                               jstring jdstPath) {
+    bool isPass = true;
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+    ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+    isPass &= trackID >= 0;
+    if (AMEDIA_OK == insertPerFrameSubtitles(muxer, 0, trackID)) {
+        ALOGE("error: muxer.writeSampleData succeeds before muxer.start");
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+static jboolean nativeTestIfWriteSampleDataSucceedsAfterStop(JNIEnv* env, jobject,
+                                                             jstring jdstPath) {
+    bool isPass = true;
+    const char* cdstPath = env->GetStringUTFChars(jdstPath, nullptr);
+    FILE* ofp = fopen(cdstPath, "wbe+");
+    AMediaMuxer* muxer = AMediaMuxer_new(fileno(ofp), (OutputFormat)OUTPUT_FORMAT_MPEG_4);
+    AMediaFormat* format = AMediaFormat_new();
+    AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, "application/x-subrip");
+    ssize_t trackID = AMediaMuxer_addTrack(muxer, format);
+    isPass &= trackID >= 0;
+    isPass &= AMediaMuxer_start(muxer) == AMEDIA_OK;
+    isPass &= insertPerFrameSubtitles(muxer, 0, trackID) == AMEDIA_OK;
+    isPass &= AMediaMuxer_stop(muxer) == AMEDIA_OK;
+    if (AMEDIA_OK == insertPerFrameSubtitles(muxer, 33000, trackID)) {
+        ALOGE("error: muxer.writeSampleData succeeds after muxer.stop");
+        isPass = false;
+    }
+    AMediaFormat_delete(format);
+    AMediaMuxer_delete(muxer);
+    fclose(ofp);
+    env->ReleaseStringUTFChars(jdstPath, cdstPath);
+    return static_cast<jboolean>(isPass);
+}
+
+int registerAndroidMediaV2CtsMuxerUnitTestApi(JNIEnv* env) {
+    const JNINativeMethod methodTable[] = {
+            {"nativeTestIfInvalidFdIsRejected", "()Z", (void*)nativeTestIfInvalidFdIsRejected},
+            {"nativeTestIfReadOnlyFdIsRejected", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfReadOnlyFdIsRejected},
+            {"nativeTestIfWriteOnlyFdIsRejected", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfWriteOnlyFdIsRejected},
+            {"nativeTestIfNonSeekableFdIsRejected", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfNonSeekableFdIsRejected},
+            {"nativeTestIfInvalidOutputFormatIsRejected", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfInvalidOutputFormatIsRejected},
+
+            {"nativeTestIfInvalidMediaFormatIsRejected", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfInvalidMediaFormatIsRejected},
+            {"nativeTestIfCorruptMediaFormatIsRejected", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfCorruptMediaFormatIsRejected},
+            {"nativeTestIfAddTrackSucceedsAfterStart", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfAddTrackSucceedsAfterStart},
+            {"nativeTestIfAddTrackSucceedsAfterWriteSampleData", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfAddTrackSucceedsAfterWriteSampleData},
+            {"nativeTestIfAddTrackSucceedsAfterStop", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfAddTrackSucceedsAfterStop},
+
+            {"nativeTestIfMuxerStartsBeforeAddTrack", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfMuxerStartsBeforeAddTrack},
+            {"nativeTestIdempotentStart", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIdempotentStart},
+            {"nativeTestIfMuxerStartsAfterWriteSampleData", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfMuxerStartsAfterWriteSampleData},
+            {"nativeTestIfMuxerStartsAfterStop", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfMuxerStartsAfterStop},
+
+            {"nativeTestStopOnANonStartedMuxer", "(Ljava/lang/String;)Z",
+             (void*)nativeTestStopOnANonStartedMuxer},
+            {"nativeTestIdempotentStop", "(Ljava/lang/String;)Z", (void*)nativeTestIdempotentStop},
+            {"nativeTestSimpleStartStop", "(Ljava/lang/String;)Z",
+             (void*)nativeTestSimpleStartStop},
+
+            {"nativeTestIfWriteSampleDataRejectsInvalidTrackIndex", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfWriteSampleDataRejectsInvalidTrackIndex},
+            {"nativeTestIfWriteSampleDataRejectsInvalidPts", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfWriteSampleDataRejectsInvalidPts},
+            {"nativeTestIfWriteSampleDataSucceedsBeforeStart", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfWriteSampleDataSucceedsBeforeStart},
+            {"nativeTestIfWriteSampleDataSucceedsAfterStop", "(Ljava/lang/String;)Z",
+             (void*)nativeTestIfWriteSampleDataSucceedsAfterStop},
+    };
+    jclass c = env->FindClass("android/mediav2/cts/MuxerUnitTest$TestApiNative");
+    return env->RegisterNatives(c, methodTable, sizeof(methodTable) / sizeof(JNINativeMethod));
+}
diff --git a/tests/media/src/android/mediav2/cts/CodecDecoderTest.java b/tests/media/src/android/mediav2/cts/CodecDecoderTest.java
new file mode 100644
index 0000000..19fe20a
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/CodecDecoderTest.java
@@ -0,0 +1,939 @@
+/*
+ * 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.mediav2.cts;
+
+import android.content.pm.PackageManager;
+import android.media.Image;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaExtractor;
+import android.media.MediaFormat;
+import android.util.Log;
+import android.util.Pair;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Assume;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.channels.FileChannel;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static android.media.MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Validate decode functionality of listed decoder components
+ */
+@RunWith(Parameterized.class)
+public class CodecDecoderTest extends CodecTestBase {
+    private static final String LOG_TAG = CodecDecoderTest.class.getSimpleName();
+
+    private final String mMime;
+    private final String mTestFile;
+    private final String mRefFile;
+    private final String mReconfigFile;
+    private final float mRmsError;
+
+    private ArrayList<ByteBuffer> mCsdBuffers;
+    private int mCurrCsdIdx;
+
+    private MediaExtractor mExtractor;
+
+    public CodecDecoderTest(String mime, String testFile, String refFile, String reconfigFile,
+            float rmsError) {
+        mMime = mime;
+        mTestFile = testFile;
+        mRefFile = refFile;
+        mReconfigFile = reconfigFile;
+        mRmsError = rmsError;
+        mAsyncHandle = new CodecAsyncHandler();
+        mCsdBuffers = new ArrayList<>();
+        mIsAudio = mMime.startsWith("audio/");
+    }
+
+    private static boolean isTv() {
+        return InstrumentationRegistry.getInstrumentation().getContext().getPackageManager()
+                .hasSystemFeature(PackageManager.FEATURE_LEANBACK);
+    }
+
+    private static boolean shouldRunTest(String mime) {
+        return !mime.equals(MediaFormat.MIMETYPE_VIDEO_AV1) &&
+                (!mime.equals(MediaFormat.MIMETYPE_VIDEO_MPEG2) || isTv());
+    }
+
+    private short[] setUpReference() throws IOException {
+        File refFile = new File(mInpPrefix + mRefFile);
+        short[] refData;
+        try (FileInputStream refStream = new FileInputStream(refFile)) {
+            FileChannel fileChannel = refStream.getChannel();
+            int length = (int) refFile.length();
+            ByteBuffer refBuffer = ByteBuffer.allocate(length);
+            refBuffer.order(ByteOrder.LITTLE_ENDIAN);
+            fileChannel.read(refBuffer);
+            refData = new short[length / 2];
+            refBuffer.position(0);
+            for (int i = 0; i < length / 2; i++) {
+                refData[i] = refBuffer.getShort();
+            }
+        }
+        return refData;
+    }
+
+    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);
+            if (mMime.equalsIgnoreCase(format.getString(MediaFormat.KEY_MIME))) {
+                mExtractor.selectTrack(trackID);
+                return mExtractor.getTrackFormat(trackID);
+            }
+        }
+        fail("No track with mime: " + mMime + " found in file: " + srcFile);
+        return null;
+    }
+
+    private boolean hasCSD(MediaFormat format) {
+        return format.containsKey("csd-0");
+    }
+
+    private void enqueueCodecConfig(int bufferIndex) {
+        ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex);
+        ByteBuffer csdBuffer = mCsdBuffers.get(mCurrCsdIdx);
+        inputBuffer.put((ByteBuffer) csdBuffer.rewind());
+        mCodec.queueInputBuffer(bufferIndex, 0, csdBuffer.limit(), 0,
+                MediaCodec.BUFFER_FLAG_CODEC_CONFIG);
+        if (ENABLE_LOGS) {
+            Log.v(LOG_TAG, "queued csd: id: " + bufferIndex + " size: " + csdBuffer.limit());
+        }
+    }
+
+    void enqueueInput(int bufferIndex) {
+        if (mExtractor.getSampleSize() < 0) {
+            enqueueEOS(bufferIndex);
+        } else {
+            ByteBuffer inputBuffer = mCodec.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;
+                mSawInputEOS = true;
+            }
+            if (ENABLE_LOGS) {
+                Log.v(LOG_TAG, "input: id: " + bufferIndex + " size: " + size + " pts: " + pts +
+                        " flags: " + codecFlags);
+            }
+            mCodec.queueInputBuffer(bufferIndex, 0, size, pts, codecFlags);
+            if (size > 0 && (codecFlags & (MediaCodec.BUFFER_FLAG_CODEC_CONFIG |
+                    MediaCodec.BUFFER_FLAG_PARTIAL_FRAME)) == 0) {
+                mInputCount++;
+            }
+        }
+    }
+
+    private void enqueueInput(int bufferIndex, ByteBuffer buffer, MediaCodec.BufferInfo info) {
+        ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex);
+        inputBuffer.put((ByteBuffer) buffer.rewind());
+        int flags = 0;
+        if ((info.flags & MediaExtractor.SAMPLE_FLAG_SYNC) != 0) {
+            flags |= MediaCodec.BUFFER_FLAG_KEY_FRAME;
+        }
+        if ((info.flags & MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME) != 0) {
+            flags |= MediaCodec.BUFFER_FLAG_PARTIAL_FRAME;
+        }
+        if (ENABLE_LOGS) {
+            Log.v(LOG_TAG, "input: id: " + bufferIndex + " flags: " + info.flags + " size: " +
+                    info.size + " timestamp: " + info.presentationTimeUs);
+        }
+        mCodec.queueInputBuffer(bufferIndex, info.offset, info.size, info.presentationTimeUs,
+                flags);
+        if (info.size > 0 && ((flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) &&
+                ((flags & MediaCodec.BUFFER_FLAG_PARTIAL_FRAME) == 0)) {
+            mInputCount++;
+        }
+    }
+
+    void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
+        if (info.size > 0 && mSaveToMem) {
+            if (mIsAudio) {
+                ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex);
+                mOutputBuff.saveToMemory(buf, info);
+            } else {
+                Image img = mCodec.getOutputImage(bufferIndex);
+                if (img != null) {
+                    mOutputBuff.checksum(img);
+                } else {
+                    ByteBuffer buf = mCodec.getOutputBuffer(bufferIndex);
+                    mOutputBuff.checksum(buf, info.size);
+                }
+            }
+        }
+        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) {
+            if (info.presentationTimeUs <= mPrevOutputPts) {
+                fail("Timestamp ordering check failed: last timestamp: " + mPrevOutputPts +
+                        " current timestamp:" + info.presentationTimeUs);
+            }
+            mPrevOutputPts = info.presentationTimeUs;
+            mOutputCount++;
+        }
+        mCodec.releaseOutputBuffer(bufferIndex, false);
+    }
+
+    private void doWork(ByteBuffer buffer, ArrayList<MediaCodec.BufferInfo> list)
+            throws InterruptedException {
+        int frameCount = 0;
+        if (mIsCodecInAsyncMode) {
+            // dequeue output after inputEOS is expected to be done in waitForAllOutputs()
+            while (!mAsyncHandle.hasSeenError() && !mSawInputEOS && frameCount < list.size()) {
+                Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork();
+                if (element != null) {
+                    int bufferID = element.first;
+                    MediaCodec.BufferInfo info = element.second;
+                    if (info != null) {
+                        dequeueOutput(bufferID, info);
+                    } else {
+                        enqueueInput(bufferID, buffer, list.get(frameCount));
+                        frameCount++;
+                    }
+                }
+            }
+        } else {
+            MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+            // dequeue output after inputEOS is expected to be done in waitForAllOutputs()
+            while (!mSawInputEOS && frameCount < list.size()) {
+                int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
+                if (outputBufferId >= 0) {
+                    dequeueOutput(outputBufferId, outInfo);
+                } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    mOutFormat = mCodec.getOutputFormat();
+                    mSignalledOutFormatChanged = true;
+                }
+                int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US);
+                if (inputBufferId != -1) {
+                    enqueueInput(inputBufferId, buffer, list.get(frameCount));
+                    frameCount++;
+                }
+            }
+        }
+    }
+
+    private ArrayList<MediaCodec.BufferInfo> createSubFrames(ByteBuffer buffer, int sfCount) {
+        int size = (int) mExtractor.getSampleSize();
+        if (size < 0) return null;
+        mExtractor.readSampleData(buffer, 0);
+        long pts = mExtractor.getSampleTime();
+        int flags = mExtractor.getSampleFlags();
+        if (size < sfCount) sfCount = size;
+        ArrayList<MediaCodec.BufferInfo> list = new ArrayList<>();
+        int offset = 0;
+        for (int i = 0; i < sfCount; i++) {
+            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+            info.offset = offset;
+            info.presentationTimeUs = pts;
+            info.flags = flags;
+            if (i != sfCount - 1) {
+                info.size = size / sfCount;
+                info.flags |= MediaExtractor.SAMPLE_FLAG_PARTIAL_FRAME;
+            } else {
+                info.size = size - offset;
+            }
+            list.add(info);
+            offset += info.size;
+        }
+        return list;
+    }
+
+    private void queueCodecConfig() throws InterruptedException {
+        if (mIsCodecInAsyncMode) {
+            for (mCurrCsdIdx = 0; !mAsyncHandle.hasSeenError() && mCurrCsdIdx < mCsdBuffers.size();
+                 mCurrCsdIdx++) {
+                Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getInput();
+                if (element != null) {
+                    enqueueCodecConfig(element.first);
+                }
+            }
+        } else {
+            for (mCurrCsdIdx = 0; mCurrCsdIdx < mCsdBuffers.size(); mCurrCsdIdx++) {
+                enqueueCodecConfig(mCodec.dequeueInputBuffer(-1));
+            }
+        }
+    }
+
+    private void decodeToMemory(String file, String decoder, long pts, int mode, int frameLimit)
+            throws IOException, InterruptedException {
+        mSaveToMem = true;
+        mOutputBuff = new OutputManager();
+        mCodec = MediaCodec.createByCodecName(decoder);
+        MediaFormat format = setUpSource(file);
+        configureCodec(format, false, true, false);
+        mCodec.start();
+        mExtractor.seekTo(pts, mode);
+        doWork(frameLimit);
+        queueEOS();
+        waitForAllOutputs();
+        mCodec.stop();
+        mCodec.release();
+        mExtractor.release();
+        mSaveToMem = false;
+    }
+
+    @Parameterized.Parameters
+    public static Collection<Object[]> input() {
+        return Arrays.asList(new Object[][]{
+                {MediaFormat.MIMETYPE_AUDIO_MPEG, "bbb_1ch_8kHz_lame_cbr.mp3",
+                        "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_44kHz_lame_vbr.mp3",
+                        91.022f * 1.05f},
+                {MediaFormat.MIMETYPE_AUDIO_MPEG, "bbb_1ch_16kHz_lame_vbr.mp3",
+                        "bbb_1ch_16kHz_s16le.raw", "bbb_2ch_44kHz_lame_vbr.mp3",
+                        119.256f * 1.05f},
+                {MediaFormat.MIMETYPE_AUDIO_MPEG, "bbb_2ch_44kHz_lame_cbr.mp3",
+                        "bbb_2ch_44kHz_s16le.raw", "bbb_1ch_16kHz_lame_vbr.mp3",
+                        103.60f * 1.05f},
+                {MediaFormat.MIMETYPE_AUDIO_MPEG, "bbb_2ch_44kHz_lame_vbr.mp3",
+                        "bbb_2ch_44kHz_s16le.raw", "bbb_1ch_8kHz_lame_cbr.mp3",
+                        53.066f * 1.05f},
+                {MediaFormat.MIMETYPE_AUDIO_AMR_WB, "bbb_1ch_16kHz_16kbps_amrwb.3gp",
+                        "bbb_1ch_16kHz_s16le.raw", "bbb_1ch_16kHz_23kbps_amrwb.3gp",
+                        2393.598f * 1.05f},
+                {MediaFormat.MIMETYPE_AUDIO_AMR_NB, "bbb_1ch_8kHz_10kbps_amrnb.3gp",
+                        "bbb_1ch_8kHz_s16le.raw", "bbb_1ch_8kHz_8kbps_amrnb.3gp", -1.0f},
+                {MediaFormat.MIMETYPE_AUDIO_FLAC, "bbb_1ch_16kHz_flac.mka",
+                        "bbb_1ch_16kHz_s16le.raw", "bbb_2ch_44kHz_flac.mka", 0.0f},
+                {MediaFormat.MIMETYPE_AUDIO_FLAC, "bbb_2ch_44kHz_flac.mka",
+                        "bbb_2ch_44kHz_s16le.raw", "bbb_1ch_16kHz_flac.mka", 0.0f},
+                {MediaFormat.MIMETYPE_AUDIO_RAW, "bbb_1ch_16kHz.wav", "bbb_1ch_16kHz_s16le.raw",
+                        "bbb_2ch_44kHz.wav", 0.0f},
+                {MediaFormat.MIMETYPE_AUDIO_RAW, "bbb_2ch_44kHz.wav", "bbb_2ch_44kHz_s16le.raw",
+                        "bbb_1ch_16kHz.wav", 0.0f},
+                {MediaFormat.MIMETYPE_AUDIO_VORBIS, "bbb_1ch_16kHz_vorbis.mka",
+                        "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_44kHz_vorbis.mka", -1.0f},
+                {MediaFormat.MIMETYPE_AUDIO_OPUS, "bbb_2ch_48kHz_opus.mka",
+                        "bbb_2ch_48kHz_s16le.raw", "bbb_1ch_48kHz_opus.mka", -1.0f},
+                {MediaFormat.MIMETYPE_AUDIO_AAC, "bbb_1ch_16kHz_aac.mp4",
+                        "bbb_1ch_8kHz_s16le.raw", "bbb_2ch_44kHz_aac.mp4", -1.0f},
+                {MediaFormat.MIMETYPE_VIDEO_MPEG2, "bbb_340x280_768kbps_30fps_mpeg2.mp4", null,
+                        "bbb_520x390_1mbps_30fps_mpeg2.mp4", -1.0f},
+                {MediaFormat.MIMETYPE_VIDEO_AVC, "bbb_340x280_768kbps_30fps_avc.mp4", null,
+                        "bbb_520x390_1mbps_30fps_avc.mp4", -1.0f},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, "bbb_520x390_1mbps_30fps_hevc.mp4", null,
+                        "bbb_340x280_768kbps_30fps_hevc.mp4", -1.0f},
+                {MediaFormat.MIMETYPE_VIDEO_MPEG4, "bbb_128x96_64kbps_12fps_mpeg4.mp4",
+                        null, "bbb_176x144_192kbps_15fps_mpeg4.mp4", -1.0f},
+                {MediaFormat.MIMETYPE_VIDEO_H263, "bbb_176x144_128kbps_15fps_h263.3gp",
+                        null, "bbb_176x144_192kbps_10fps_h263.3gp", -1.0f},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, "bbb_340x280_768kbps_30fps_vp8.webm", null,
+                        "bbb_520x390_1mbps_30fps_vp8.webm", -1.0f},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, "bbb_340x280_768kbps_30fps_vp9.webm", null,
+                        "bbb_520x390_1mbps_30fps_vp9.webm", -1.0f},
+                {MediaFormat.MIMETYPE_VIDEO_AV1, "bbb_340x280_768kbps_30fps_av1.mp4", null,
+                        "bbb_520x390_1mbps_30fps_av1.mp4", -1.0f},
+        });
+    }
+
+    /**
+     * Tests decoder for combinations:
+     * 1. Codec Sync Mode, Signal Eos with Last frame
+     * 2. Codec Sync Mode, Signal Eos Separately
+     * 3. Codec Async Mode, Signal Eos with Last frame
+     * 4. Codec Async Mode, Signal Eos Separately
+     * In all these scenarios, Timestamp ordering is verified, For audio the Rms of output has to be
+     * within the allowed tolerance. The output has to be consistent (not flaky) in all runs.
+     */
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testSimpleDecode() throws IOException, InterruptedException {
+        MediaFormat format = setUpSource(mTestFile);
+        if (!mIsAudio) {
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+        }
+        ArrayList<MediaFormat> formats = new ArrayList<>();
+        formats.add(format);
+        ArrayList<String> listOfDecoders = selectCodecs(mMime, formats, null, false);
+        if (listOfDecoders.isEmpty()) {
+            mExtractor.release();
+            if (shouldRunTest(mMime)) fail("no suitable codecs found for mime: " + mMime);
+            else Assume.assumeTrue("no suitable codecs found for mime: " + mMime, false);
+        }
+        boolean[] boolStates = {true, false};
+        mSaveToMem = true;
+        for (String decoder : listOfDecoders) {
+            mCodec = MediaCodec.createByCodecName(decoder);
+            OutputManager ref = mSaveToMem ? new OutputManager() : null;
+            OutputManager test = mSaveToMem ? new OutputManager() : null;
+            int loopCounter = 0;
+            for (boolean eosType : boolStates) {
+                for (boolean isAsync : boolStates) {
+                    boolean validateFormat = true;
+                    String log = String.format("codec: %s, file: %s, mode: %s, eos type: %s:: ",
+                            decoder, mTestFile, (isAsync ? "async" : "sync"),
+                            (eosType ? "eos with last frame" : "eos separate"));
+                    if (mSaveToMem) {
+                        mOutputBuff = loopCounter == 0 ? ref : test;
+                        mOutputBuff.reset();
+                    }
+                    mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+                    configureCodec(format, isAsync, eosType, false);
+                    MediaFormat defFormat = mCodec.getOutputFormat();
+                    if (isFormatSimilar(format, defFormat)) {
+                        if (ENABLE_LOGS) {
+                            Log.d("Input format is same as default for format for %s", decoder);
+                        }
+                        validateFormat = false;
+                    }
+                    mCodec.start();
+                    doWork(Integer.MAX_VALUE);
+                    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);
+                    if (!mIsAudio) {
+                        assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+                                " / " + mInputCount, mInputCount == mOutputCount);
+                    }
+                    if (mSaveToMem && loopCounter != 0) {
+                        assertTrue(log + "decoder output is flaky", ref.equals(test));
+                    }
+                    if (validateFormat) {
+                        assertTrue(log + "not received format change",
+                                mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
+                                        mSignalledOutFormatChanged);
+                        assertTrue(log + "configured format and output format are not similar",
+                                isFormatSimilar(format,
+                                        mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
+                                                mOutFormat));
+                    }
+                    loopCounter++;
+                }
+            }
+            mCodec.release();
+            if (mSaveToMem && mRefFile != null && mRmsError >= 0) {
+                short[] refData = setUpReference();
+                assertTrue(String.format("%s rms error too high", mTestFile),
+                        ref.getRmsError(refData) <= mRmsError);
+            }
+        }
+        mExtractor.release();
+    }
+
+    /**
+     * 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
+     */
+    @Ignore("TODO(b/147576107)")
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testFlush() throws IOException, InterruptedException {
+        MediaFormat format = setUpSource(mTestFile);
+        mExtractor.release();
+        if (!mIsAudio) {
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+        }
+        ArrayList<MediaFormat> formats = new ArrayList<>();
+        formats.add(format);
+        ArrayList<String> listOfDecoders = selectCodecs(mMime, formats, null, false);
+        if (listOfDecoders.isEmpty()) {
+            if (shouldRunTest(mMime)) fail("no suitable codecs found for mime: " + mMime);
+            else Assume.assumeTrue("no suitable codecs found for mime: " + mMime, false);
+        }
+        mCsdBuffers.clear();
+        for (int i = 0; ; i++) {
+            String csdKey = "csd-" + i;
+            if (format.containsKey(csdKey)) {
+                mCsdBuffers.add(format.getByteBuffer(csdKey));
+            } else break;
+        }
+        final long pts = 500000;
+        final int mode = MediaExtractor.SEEK_TO_CLOSEST_SYNC;
+        boolean[] boolStates = {true, false};
+        for (String decoder : listOfDecoders) {
+            decodeToMemory(mTestFile, decoder, pts, mode, Integer.MAX_VALUE);
+            OutputManager ref = mOutputBuff;
+            OutputManager test = new OutputManager();
+            mOutputBuff = test;
+            setUpSource(mTestFile);
+            mCodec = MediaCodec.createByCodecName(decoder);
+            for (boolean isAsync : boolStates) {
+                String log = String.format("decoder: %s, input file: %s, mode: %s:: ", decoder,
+                        mTestFile, (isAsync ? "async" : "sync"));
+                mExtractor.seekTo(0, mode);
+                configureCodec(format, isAsync, true, false);
+                MediaFormat defFormat = mCodec.getOutputFormat();
+                boolean validateFormat = true;
+                if (isFormatSimilar(format, defFormat)) {
+                    if (ENABLE_LOGS) {
+                        Log.d("Input format is same as default for format for %s", decoder);
+                    }
+                    validateFormat = false;
+                }
+                mCodec.start();
+
+                /* test flush in running state before queuing input */
+                flushCodec();
+                if (mIsCodecInAsyncMode) mCodec.start();
+                queueCodecConfig(); /* flushed codec too soon after start, resubmit csd */
+
+                doWork(1);
+                flushCodec();
+                if (mIsCodecInAsyncMode) mCodec.start();
+                queueCodecConfig(); /* flushed codec too soon after start, resubmit csd */
+
+                mExtractor.seekTo(0, mode);
+                doWork(23);
+
+                /* test flush in running state */
+                flushCodec();
+                if (mIsCodecInAsyncMode) mCodec.start();
+                mSaveToMem = true;
+                test.reset();
+                mExtractor.seekTo(pts, mode);
+                doWork(Integer.MAX_VALUE);
+                queueEOS();
+                waitForAllOutputs();
+                assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+                assertTrue(log + "no input sent", 0 != mInputCount);
+                assertTrue(log + "output received", 0 != mOutputCount);
+                if (!mIsAudio) {
+                    assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+                            " / " + mInputCount, mInputCount == mOutputCount);
+                }
+                assertTrue(log + "decoder output is flaky", ref.equals(test));
+
+                /* test flush in eos state */
+                flushCodec();
+                if (mIsCodecInAsyncMode) mCodec.start();
+                test.reset();
+                mExtractor.seekTo(pts, mode);
+                doWork(Integer.MAX_VALUE);
+                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);
+                if (!mIsAudio) {
+                    assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+                            " / " + mInputCount, mInputCount == mOutputCount);
+                }
+                assertTrue(log + "decoder output is flaky", ref.equals(test));
+                if (validateFormat) {
+                    assertTrue(log + "not received format change",
+                            mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
+                                    mSignalledOutFormatChanged);
+                    assertTrue(log + "configured format and output format are not similar",
+                            isFormatSimilar(format,
+                                    mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
+                                            mOutFormat));
+                }
+                mSaveToMem = false;
+            }
+            mCodec.release();
+            mExtractor.release();
+        }
+    }
+
+    /**
+     * 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
+     */
+    @Ignore("TODO(b/148523403)")
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testReconfigure() throws IOException, InterruptedException {
+        MediaFormat format = setUpSource(mTestFile);
+        mExtractor.release();
+        MediaFormat newFormat = setUpSource(mReconfigFile);
+        mExtractor.release();
+        if (!mIsAudio) {
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+            newFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+        }
+        ArrayList<MediaFormat> formats = new ArrayList<>();
+        formats.add(format);
+        formats.add(newFormat);
+        ArrayList<String> listOfDecoders = selectCodecs(mMime, formats, null, false);
+        if (listOfDecoders.isEmpty()) {
+            if (shouldRunTest(mMime)) fail("no suitable codecs found for mime: " + mMime);
+            else Assume.assumeTrue("no suitable codecs found for mime: " + mMime, false);
+        }
+        final long pts = 500000;
+        final int mode = MediaExtractor.SEEK_TO_CLOSEST_SYNC;
+        boolean[] boolStates = {true, false};
+        for (String decoder : listOfDecoders) {
+            decodeToMemory(mTestFile, decoder, pts, mode, Integer.MAX_VALUE);
+            OutputManager ref = mOutputBuff;
+            decodeToMemory(mReconfigFile, decoder, pts, mode, Integer.MAX_VALUE);
+            OutputManager configRef = mOutputBuff;
+            OutputManager test = new OutputManager();
+            mOutputBuff = test;
+            mCodec = MediaCodec.createByCodecName(decoder);
+            for (boolean isAsync : boolStates) {
+                setUpSource(mTestFile);
+                String log = String.format("decoder: %s, input file: %s, mode: %s:: ", decoder,
+                        mTestFile, (isAsync ? "async" : "sync"));
+                mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+                configureCodec(format, isAsync, true, false);
+                MediaFormat defFormat = mCodec.getOutputFormat();
+                boolean validateFormat = true;
+                if (isFormatSimilar(format, defFormat)) {
+                    if (ENABLE_LOGS) {
+                        Log.d("Input format is same as default for format for %s", decoder);
+                    }
+                    validateFormat = false;
+                }
+
+                /* test reconfigure in stopped state */
+                reConfigureCodec(format, !isAsync, false, false);
+                mCodec.start();
+
+                /* test reconfigure in running state before queuing input */
+                reConfigureCodec(format, !isAsync, false, false);
+                mCodec.start();
+                doWork(23);
+
+                if (validateFormat) {
+                    assertTrue(log + "not received format change",
+                            mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
+                                    mSignalledOutFormatChanged);
+                    assertTrue(log + "configured format and output format are not similar",
+                            isFormatSimilar(format,
+                                    mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
+                                            mOutFormat));
+                }
+
+                /* test reconfigure codec in running state */
+                reConfigureCodec(format, isAsync, true, false);
+                mCodec.start();
+                mSaveToMem = true;
+                test.reset();
+                mExtractor.seekTo(pts, mode);
+                doWork(Integer.MAX_VALUE);
+                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);
+                if (!mIsAudio) {
+                    assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+                            " / " + mInputCount, mInputCount == mOutputCount);
+                }
+                assertTrue(log + "decoder output is flaky", ref.equals(test));
+                if (validateFormat) {
+                    assertTrue(log + "not received format change",
+                            mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
+                                    mSignalledOutFormatChanged);
+                    assertTrue(log + "configured format and output format are not similar",
+                            isFormatSimilar(format,
+                                    mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
+                                            mOutFormat));
+                }
+
+                /* test reconfigure codec at eos state */
+                reConfigureCodec(format, !isAsync, false, false);
+                mCodec.start();
+                test.reset();
+                mExtractor.seekTo(pts, mode);
+                doWork(Integer.MAX_VALUE);
+                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);
+                if (!mIsAudio) {
+                    assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+                            " / " + mInputCount, mInputCount == mOutputCount);
+                }
+                assertTrue(log + "decoder output is flaky", ref.equals(test));
+                if (validateFormat) {
+                    assertTrue(log + "not received format change",
+                            mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
+                                    mSignalledOutFormatChanged);
+                    assertTrue(log + "configured format and output format are not similar",
+                            isFormatSimilar(format,
+                                    mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
+                                            mOutFormat));
+                }
+                mExtractor.release();
+
+                /* test reconfigure codec for new file */
+                setUpSource(mReconfigFile);
+                log = String.format("decoder: %s, input file: %s, mode: %s:: ", decoder,
+                        mReconfigFile, (isAsync ? "async" : "sync"));
+                reConfigureCodec(newFormat, isAsync, false, false);
+                if (isFormatSimilar(newFormat, defFormat)) {
+                    if (ENABLE_LOGS) {
+                        Log.d("Input format is same as default for format for %s", decoder);
+                    }
+                    validateFormat = false;
+                }
+                mCodec.start();
+                test.reset();
+                mExtractor.seekTo(pts, mode);
+                doWork(Integer.MAX_VALUE);
+                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);
+                if (!mIsAudio) {
+                    assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+                            " / " + mInputCount, mInputCount == mOutputCount);
+                }
+                assertTrue(log + "decoder output is flaky", configRef.equals(test));
+                if (validateFormat) {
+                    assertTrue(log + "not received format change",
+                            mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
+                                    mSignalledOutFormatChanged);
+                    assertTrue(log + "configured format and output format are not similar",
+                            isFormatSimilar(newFormat,
+                                    mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
+                                            mOutFormat));
+                }
+                mSaveToMem = false;
+                mExtractor.release();
+            }
+            mCodec.release();
+        }
+    }
+
+    /**
+     * Tests decoder for only EOS frame
+     */
+    @SmallTest
+    @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
+    public void testOnlyEos() throws IOException, InterruptedException {
+        MediaFormat format = setUpSource(mTestFile);
+        if (!mIsAudio) {
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+        }
+        ArrayList<MediaFormat> formats = new ArrayList<>();
+        formats.add(format);
+        ArrayList<String> listOfDecoders = selectCodecs(mMime, formats, null, false);
+        if (listOfDecoders.isEmpty()) {
+            mExtractor.release();
+            if (shouldRunTest(mMime)) fail("no suitable codecs found for mime: " + mMime);
+            else Assume.assumeTrue("no suitable codecs found for mime: " + mMime, false);
+        }
+        boolean[] boolStates = {true, false};
+        for (String decoder : listOfDecoders) {
+            mCodec = MediaCodec.createByCodecName(decoder);
+            for (boolean isAsync : boolStates) {
+                String log = String.format("decoder: %s, input file: %s, mode: %s:: ", decoder,
+                        mTestFile, (isAsync ? "async" : "sync"));
+                configureCodec(format, isAsync, false, false);
+                mCodec.start();
+                queueEOS();
+                waitForAllOutputs();
+                mCodec.stop();
+                assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+            }
+            mCodec.release();
+        }
+        mExtractor.release();
+    }
+
+    /**
+     * Test Decoder by Queuing CSD separately
+     */
+    @LargeTest
+    @Ignore("TODO(b/149031058)")
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testSimpleDecodeQueueCSD() throws IOException, InterruptedException {
+        MediaFormat format = setUpSource(mTestFile);
+        if (!mIsAudio) {
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+        }
+        Assume.assumeTrue("Format has no CSD, ignoring test for mime:" + mMime, hasCSD(format));
+        ArrayList<MediaFormat> formats = new ArrayList<>();
+        formats.add(format);
+        formats.add(new MediaFormat(format));
+        for (int i = 0; ; i++) {
+            String csdKey = "csd-" + i;
+            if (format.containsKey(csdKey)) {
+                mCsdBuffers.add(format.getByteBuffer(csdKey));
+                format.removeKey(csdKey);
+            } else break;
+        }
+        ArrayList<String> listOfDecoders = selectCodecs(mMime, formats, null, false);
+        if (listOfDecoders.isEmpty()) {
+            mExtractor.release();
+            if (shouldRunTest(mMime)) fail("no suitable codecs found for mime: " + mMime);
+            else Assume.assumeTrue("no suitable codecs found for mime: " + mMime, false);
+        }
+        boolean[] boolStates = {true, false};
+        mSaveToMem = true;
+        for (String decoder : listOfDecoders) {
+            mCodec = MediaCodec.createByCodecName(decoder);
+            OutputManager ref = mSaveToMem ? new OutputManager() : null;
+            OutputManager test = mSaveToMem ? new OutputManager() : null;
+            int loopCounter = 0;
+            for (MediaFormat fmt : formats) {
+                for (boolean eosMode : boolStates) {
+                    for (boolean isAsync : boolStates) {
+                        boolean validateFormat = true;
+                        String log = String.format("codec: %s, file: %s, mode: %s, eos type: %s:: ",
+                                decoder, mTestFile, (isAsync ? "async" : "sync"),
+                                (eosMode ? "eos with last frame" : "eos separate"));
+                        if (mSaveToMem) {
+                            mOutputBuff = loopCounter == 0 ? ref : test;
+                            mOutputBuff.reset();
+                        }
+                        mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+                        configureCodec(fmt, isAsync, eosMode, false);
+                        MediaFormat defFormat = mCodec.getOutputFormat();
+                        if (isFormatSimilar(defFormat, format)) {
+                            if (ENABLE_LOGS) {
+                                Log.d("Input format is same as default for format for %s", decoder);
+                            }
+                            validateFormat = false;
+                        }
+                        mCodec.start();
+                        queueCodecConfig();
+                        doWork(Integer.MAX_VALUE);
+                        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);
+                        if (!mIsAudio) {
+                            assertTrue(
+                                    log + "input count != output count, act/exp: " + mOutputCount +
+                                            " / " + mInputCount, mInputCount == mOutputCount);
+                        }
+                        if (mSaveToMem && loopCounter != 0) {
+                            assertTrue(log + "decoder output is flaky", ref.equals(test));
+                        }
+                        if (validateFormat) {
+                            assertTrue(log + "not received format change",
+                                    mIsCodecInAsyncMode ? mAsyncHandle.hasOutputFormatChanged() :
+                                            mSignalledOutFormatChanged);
+                            assertTrue(log + "configured format and output format are not similar",
+                                    isFormatSimilar(format,
+                                            mIsCodecInAsyncMode ? mAsyncHandle.getOutputFormat() :
+                                                    mOutFormat));
+                        }
+                        loopCounter++;
+                    }
+                }
+            }
+            mCodec.release();
+        }
+        mExtractor.release();
+    }
+
+    /**
+     * Test decoder for partial frame
+     */
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testDecodePartialFrame() throws IOException, InterruptedException {
+        MediaFormat format = setUpSource(mTestFile);
+        if (!mIsAudio) {
+            format.setInteger(MediaFormat.KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+        }
+        ArrayList<MediaFormat> formats = new ArrayList<>();
+        formats.add(format);
+        ArrayList<String> listOfDecoders = selectCodecs(mMime, formats,
+                new String[]{MediaCodecInfo.CodecCapabilities.FEATURE_PartialFrame}, false);
+        boolean[] boolStates = {true, false};
+        int frameLimit = 10;
+        ByteBuffer buffer = ByteBuffer.allocate(4 * 1024 * 1024);
+        for (String decoder : listOfDecoders) {
+            decodeToMemory(mTestFile, decoder, 0, MediaExtractor.SEEK_TO_CLOSEST_SYNC, frameLimit);
+            mCodec = MediaCodec.createByCodecName(decoder);
+            OutputManager ref = mOutputBuff;
+            OutputManager test = new OutputManager();
+            mSaveToMem = true;
+            mOutputBuff = test;
+            for (boolean isAsync : boolStates) {
+                String log = String.format("decoder: %s, input file: %s, mode: %s:: ", decoder,
+                        mTestFile, (isAsync ? "async" : "sync"));
+                mExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+                test.reset();
+                configureCodec(format, isAsync, true, false);
+                mCodec.start();
+                doWork(frameLimit - 1);
+                ArrayList<MediaCodec.BufferInfo> list = createSubFrames(buffer, 4);
+                assertTrue("no sub frames in list received for " + mTestFile,
+                        list != null && list.size() > 0);
+                doWork(buffer, list);
+                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);
+                if (!mIsAudio) {
+                    assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+                            " / " + mInputCount, mInputCount == mOutputCount);
+                }
+                assertTrue(log + "decoder output is not consistent with ref", ref.equals(test));
+            }
+            mCodec.release();
+        }
+        mExtractor.release();
+    }
+}
diff --git a/tests/media/src/android/mediav2/cts/CodecEncoderTest.java b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
new file mode 100644
index 0000000..447e470
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/CodecEncoderTest.java
@@ -0,0 +1,539 @@
+/*
+ * 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.mediav2.cts;
+
+import android.graphics.ImageFormat;
+import android.media.Image;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaFormat;
+import android.util.Log;
+
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Assert;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Validate encode functionality of available encoder components
+ */
+@RunWith(Parameterized.class)
+public class CodecEncoderTest extends CodecTestBase {
+    private static final String LOG_TAG = CodecEncoderTest.class.getSimpleName();
+    // files are in WorkDir.getMediaDirString();
+    private static final String mInputAudioFile = "bbb_2ch_44kHz_s16le.raw";
+    private static final String mInputVideoFile = "bbb_cif_yuv420p.yuv";
+
+    private final String mMime;
+    private final int[] mBitrates;
+    private final int[] mEncParamList1;
+    private final int[] mEncParamList2;
+    private final String mInputFile;
+    private ArrayList<MediaFormat> mFormats;
+    private byte[] mInputData;
+    private int mNumBytesSubmitted;
+    private long mInputOffsetPts;
+
+    private int mWidth, mHeight;
+    private int mChannels;
+    private int mRate;
+
+    public CodecEncoderTest(String mime, int[] bitrates, int[] encoderInfo1, int[] encoderInfo2) {
+        mMime = mime;
+        mBitrates = bitrates;
+        mEncParamList1 = encoderInfo1;
+        mEncParamList2 = encoderInfo2;
+        mAsyncHandle = new CodecAsyncHandler();
+        mFormats = new ArrayList<>();
+        mIsAudio = mMime.startsWith("audio/");
+        mInputFile = mIsAudio ? mInputAudioFile : mInputVideoFile;
+    }
+
+    @Override
+    void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) {
+        super.resetContext(isAsync, signalEOSWithLastFrame);
+        mNumBytesSubmitted = 0;
+        mInputOffsetPts = 0;
+    }
+
+    @Override
+    void flushCodec() {
+        super.flushCodec();
+        if (mIsAudio) {
+            mInputOffsetPts = (mNumBytesSubmitted + 1024) * 1000000L / (2 * mChannels * mRate);
+        } else {
+            mInputOffsetPts = (mInputCount + 5) * 1000000L / mRate;
+        }
+        mPrevOutputPts = mInputOffsetPts - 1;
+        mNumBytesSubmitted = 0;
+    }
+
+    private void setUpSource(String srcFile) throws IOException {
+        String inpPath = mInpPrefix + srcFile;
+        try (FileInputStream fInp = new FileInputStream(inpPath)) {
+            int size = (int) new File(inpPath).length();
+            mInputData = new byte[size];
+            fInp.read(mInputData, 0, size);
+        }
+    }
+
+    void fillImage(Image image) {
+        Assert.assertTrue(image.getFormat() == ImageFormat.YUV_420_888);
+        int imageWidth = image.getWidth();
+        int imageHeight = image.getHeight();
+        Image.Plane[] planes = image.getPlanes();
+        int offset = mNumBytesSubmitted;
+        for (int i = 0; i < planes.length; ++i) {
+            ByteBuffer buf = planes[i].getBuffer();
+            int width, height, rowStride, pixelStride, x, y;
+            rowStride = planes[i].getRowStride();
+            pixelStride = planes[i].getPixelStride();
+            if (i == 0) {
+                width = imageWidth;
+                height = imageHeight;
+            } else {
+                width = imageWidth / 2;
+                height = imageHeight / 2;
+            }
+            if (pixelStride == 1) {
+                if (width == rowStride) {
+                    buf.put(mInputData, offset, width * height);
+                } else {
+                    for (y = 0; y < height; ++y) {
+                        buf.put(mInputData, offset + y * width, width);
+                    }
+                }
+            } else {
+                // do it pixel-by-pixel
+                for (y = 0; y < height; ++y) {
+                    int lineOffset = y * rowStride;
+                    for (x = 0; x < width; ++x) {
+                        buf.position(lineOffset + x * pixelStride);
+                        buf.put(mInputData[offset + y * width + x]);
+                    }
+                }
+            }
+            offset += width * height;
+        }
+    }
+
+    void enqueueInput(int bufferIndex) {
+        ByteBuffer inputBuffer = mCodec.getInputBuffer(bufferIndex);
+        if (mNumBytesSubmitted >= mInputData.length) {
+            enqueueEOS(bufferIndex);
+        } else {
+            int size;
+            int flags = 0;
+            long pts = mInputOffsetPts;
+            if (mIsAudio) {
+                pts += mNumBytesSubmitted * 1000000L / (2 * mChannels * mRate);
+                size = Math.min(inputBuffer.capacity(), mInputData.length - mNumBytesSubmitted);
+                inputBuffer.put(mInputData, mNumBytesSubmitted, size);
+            } else {
+                pts += mInputCount * 1000000L / mRate;
+                size = mWidth * mHeight * 3 / 2;
+                if (mNumBytesSubmitted + size > mInputData.length) {
+                    fail("received partial frame to encode");
+                } else {
+                    Image img = mCodec.getInputImage(bufferIndex);
+                    if (img != null) {
+                        fillImage(img);
+                    } else {
+                        inputBuffer.put(mInputData, mNumBytesSubmitted, size);
+                    }
+                }
+            }
+            if (mNumBytesSubmitted + size >= mInputData.length && mSignalEOSWithLastFrame) {
+                flags |= MediaCodec.BUFFER_FLAG_END_OF_STREAM;
+                mSawInputEOS = true;
+            }
+            if (ENABLE_LOGS) {
+                Log.v(LOG_TAG, "input: id: " + bufferIndex + " size: " + size + " pts: " + pts +
+                        " flags: " + flags);
+            }
+            mCodec.queueInputBuffer(bufferIndex, 0, size, pts, flags);
+            mInputCount++;
+            mNumBytesSubmitted += size;
+        }
+    }
+
+    void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info) {
+        if (info.size > 0 && mSaveToMem &&
+                (info.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) == 0) {
+            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) {
+            /* TODO: skip pts check for video encoder with B frames */
+            if (info.presentationTimeUs <= mPrevOutputPts) {
+                fail("Timestamp ordering check failed: last timestamp: " + mPrevOutputPts +
+                        " current timestamp:" + info.presentationTimeUs);
+            }
+            mPrevOutputPts = info.presentationTimeUs;
+            mOutputCount++;
+        }
+        mCodec.releaseOutputBuffer(bufferIndex, false);
+    }
+
+    private void encodeToMemory(String file, String encoder, int frameLimit, MediaFormat format)
+            throws IOException, InterruptedException {
+        mSaveToMem = true;
+        mOutputBuff = new OutputManager();
+        mCodec = MediaCodec.createByCodecName(encoder);
+        setUpSource(file);
+        configureCodec(format, false, true, true);
+        if (mIsAudio) {
+            mRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
+            mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
+        } else {
+            mWidth = format.getInteger(MediaFormat.KEY_WIDTH, 352);
+            mHeight = format.getInteger(MediaFormat.KEY_HEIGHT, 288);
+            mRate = format.getInteger(MediaFormat.KEY_FRAME_RATE, 30);
+        }
+        mCodec.start();
+        doWork(frameLimit);
+        queueEOS();
+        waitForAllOutputs();
+        mCodec.stop();
+        mCodec.release();
+        mSaveToMem = false;
+    }
+
+    @Parameterized.Parameters
+    public static Collection<Object[]> input() {
+        return 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,
+                        22050, 44100, 48000}, new int[]{1, 2}},
+                {MediaFormat.MIMETYPE_AUDIO_OPUS, new int[]{6600, 8850, 12650, 14250, 15850,
+                        18250, 19850, 23050, 23850}, new int[]{16000}, new int[]{1}},
+                {MediaFormat.MIMETYPE_AUDIO_AMR_NB, new int[]{4750, 5150, 5900, 6700, 7400, 7950,
+                        10200, 12200}, new int[]{8000}, new int[]{1}},
+                {MediaFormat.MIMETYPE_AUDIO_AMR_WB, new int[]{6600, 8850, 12650, 14250, 15850,
+                        18250, 19850, 23050, 23850}, new int[]{16000}, new int[]{1}},
+                {MediaFormat.MIMETYPE_AUDIO_FLAC, new int[]{64000, 192000}, new int[]{8000, 48000
+                        , 96000, 192000}, new int[]{1, 2}},
+
+                // Video - CodecMime, arrays of bit-rates, height, width
+                {MediaFormat.MIMETYPE_VIDEO_AVC, new int[]{512000}, new int[]{352}, new int[]{288}},
+                {MediaFormat.MIMETYPE_VIDEO_HEVC, new int[]{512000}, new int[]{352},
+                        new int[]{288}},
+                {MediaFormat.MIMETYPE_VIDEO_VP8, new int[]{512000}, new int[]{352}, new int[]{288}},
+                {MediaFormat.MIMETYPE_VIDEO_VP9, new int[]{512000}, new int[]{352}, new int[]{288}},
+        });
+    }
+
+    public void setUpParams() {
+        for (int bitrate : mBitrates) {
+            if (mIsAudio) {
+                for (int rate : mEncParamList1) {
+                    for (int channels : mEncParamList2) {
+                        MediaFormat format = new MediaFormat();
+                        format.setString(MediaFormat.KEY_MIME, mMime);
+                        format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+                        format.setInteger(MediaFormat.KEY_SAMPLE_RATE, rate);
+                        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, channels);
+                        mFormats.add(format);
+                    }
+                }
+            } else {
+                assertTrue("Wrong number of height, width parameters",
+                        mEncParamList1.length == mEncParamList2.length);
+                for (int i = 0; i < mEncParamList1.length; i++) {
+                    MediaFormat format = new MediaFormat();
+                    format.setString(MediaFormat.KEY_MIME, mMime);
+                    format.setInteger(MediaFormat.KEY_BIT_RATE, bitrate);
+                    format.setInteger(MediaFormat.KEY_WIDTH, mEncParamList1[i]);
+                    format.setInteger(MediaFormat.KEY_HEIGHT, mEncParamList2[i]);
+                    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);
+                    mFormats.add(format);
+                }
+            }
+        }
+    }
+
+    /**
+     * Tests encoder for combinations:
+     * 1. Codec Sync Mode, Signal Eos with Last frame
+     * 2. Codec Sync Mode, Signal Eos Separately
+     * 3. Codec Async Mode, Signal Eos with Last frame
+     * 4. Codec Async Mode, Signal Eos Separately
+     * In all these scenarios, Timestamp ordering is verified. The output has to be
+     * consistent (not flaky) in all runs.
+     */
+    @LargeTest
+    @Ignore("TODO(b/149027258)")
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testSimpleEncode() throws IOException, InterruptedException {
+        setUpParams();
+        ArrayList<String> listOfEncoders = selectCodecs(mMime, mFormats, null, true);
+        assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+        boolean[] boolStates = {true, false};
+        mSaveToMem = true;
+        setUpSource(mInputFile);
+        for (String encoder : listOfEncoders) {
+            mCodec = MediaCodec.createByCodecName(encoder);
+            for (MediaFormat format : mFormats) {
+                OutputManager ref = mSaveToMem ? new OutputManager() : null;
+                OutputManager test = mSaveToMem ? new OutputManager() : null;
+                int loopCounter = 0;
+                if (mIsAudio) {
+                    mRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE, 44100);
+                    mChannels = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT, 2);
+                } else {
+                    mWidth = format.getInteger(MediaFormat.KEY_WIDTH, 352);
+                    mHeight = format.getInteger(MediaFormat.KEY_HEIGHT, 288);
+                    mRate = format.getInteger(MediaFormat.KEY_FRAME_RATE, 30);
+                }
+                for (boolean eosType : boolStates) {
+                    for (boolean isAsync : boolStates) {
+                        String log = String.format(
+                                "format: %s \n codec: %s, file: %s, mode: %s, eos type: %s:: ",
+                                format, encoder, mInputFile, (isAsync ? "async" : "sync"),
+                                (eosType ? "eos with last frame" : "eos separate"));
+                        if (mSaveToMem) {
+                            mOutputBuff = loopCounter == 0 ? ref : test;
+                            mOutputBuff.reset();
+                        }
+                        configureCodec(format, isAsync, eosType, true);
+                        mCodec.start();
+                        doWork(Integer.MAX_VALUE);
+                        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);
+                        if (!mIsAudio) {
+                            assertTrue(
+                                    log + "input count != output count, act/exp: " + mOutputCount +
+                                            " / " + mInputCount, mInputCount == mOutputCount);
+                        }
+                        if (mSaveToMem && loopCounter != 0) {
+                            assertTrue(log + "encoder output is flaky", ref.equals(test));
+                        }
+                        loopCounter++;
+                    }
+                }
+            }
+            mCodec.release();
+        }
+    }
+
+    /**
+     * 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
+     */
+    @Ignore("TODO(b/147576107, b/148652492, b/148651699)")
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testFlush() throws IOException, InterruptedException {
+        ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+        assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+        setUpSource(mInputFile);
+        setUpParams();
+        boolean[] boolStates = {true, false};
+        for (String encoder : listOfEncoders) {
+            MediaFormat inpFormat = mFormats.get(0);
+            if (mIsAudio) {
+                mRate = inpFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+                mChannels = inpFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+            } else {
+                mWidth = inpFormat.getInteger(MediaFormat.KEY_WIDTH);
+                mHeight = inpFormat.getInteger(MediaFormat.KEY_HEIGHT);
+                mRate = inpFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
+            }
+            mCodec = MediaCodec.createByCodecName(encoder);
+            for (boolean isAsync : boolStates) {
+                String log = String.format("encoder: %s, input file: %s, mode: %s:: ", encoder,
+                        mInputFile, (isAsync ? "async" : "sync"));
+                configureCodec(inpFormat, isAsync, true, true);
+                mCodec.start();
+
+                /* test flush in running state before queuing input */
+                flushCodec();
+                if (mIsCodecInAsyncMode) mCodec.start();
+                doWork(23);
+
+                /* test flush in running state */
+                flushCodec();
+                if (mIsCodecInAsyncMode) mCodec.start();
+                doWork(Integer.MAX_VALUE);
+                queueEOS();
+                waitForAllOutputs();
+                assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+                assertTrue(log + "no input sent", 0 != mInputCount);
+                assertTrue(log + "output received", 0 != mOutputCount);
+                if (!mIsAudio) {
+                    assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+                            " / " + mInputCount, mInputCount == mOutputCount);
+                }
+
+                /* test flush in eos state */
+                flushCodec();
+                if (mIsCodecInAsyncMode) mCodec.start();
+                doWork(Integer.MAX_VALUE);
+                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);
+                if (!mIsAudio) {
+                    assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+                            " / " + mInputCount, mInputCount == mOutputCount);
+                }
+            }
+            mCodec.release();
+        }
+    }
+
+    /**
+     * 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
+     */
+    @Ignore("TODO(b/148523403, b/149027258)")
+    @LargeTest
+    @Test(timeout = PER_TEST_TIMEOUT_LARGE_TEST_MS)
+    public void testReconfigure() throws IOException, InterruptedException {
+        ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+        assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+        setUpSource(mInputFile);
+        setUpParams();
+        boolean[] boolStates = {true, false};
+        for (String encoder : listOfEncoders) {
+            MediaFormat format = mFormats.get(0);
+            encodeToMemory(mInputFile, encoder, Integer.MAX_VALUE, format);
+            OutputManager ref = mOutputBuff;
+            OutputManager test = new OutputManager();
+            mOutputBuff = test;
+            mCodec = MediaCodec.createByCodecName(encoder);
+            for (boolean isAsync : boolStates) {
+                String log = String.format("encoder: %s, input file: %s, mode: %s:: ", encoder,
+                        mInputFile, (isAsync ? "async" : "sync"));
+                configureCodec(format, isAsync, true, true);
+
+                /* test reconfigure in stopped state */
+                reConfigureCodec(format, !isAsync, false, true);
+                mCodec.start();
+
+                /* test reconfigure in running state before queuing input */
+                reConfigureCodec(format, !isAsync, false, true);
+                mCodec.start();
+                doWork(23);
+
+                /* test reconfigure codec in running state */
+                reConfigureCodec(format, isAsync, true, true);
+                mCodec.start();
+                mSaveToMem = true;
+                test.reset();
+                doWork(Integer.MAX_VALUE);
+                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);
+                if (!mIsAudio) {
+                    assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+                            " / " + mInputCount, mInputCount == mOutputCount);
+                }
+                assertTrue(log + "encoder output is flaky", ref.equals(test));
+
+                /* test reconfigure codec at eos state */
+                reConfigureCodec(format, !isAsync, false, true);
+                mCodec.start();
+                test.reset();
+                doWork(Integer.MAX_VALUE);
+                queueEOS();
+                waitForAllOutputs();
+                mCodec.stop();
+                assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+                assertTrue(log + "no input sent", 0 != mInputCount);
+                assertTrue(log + "output received", 0 != mOutputCount);
+                if (!mIsAudio) {
+                    assertTrue(log + "input count != output count, act/exp: " + mOutputCount +
+                            " / " + mInputCount, mInputCount == mOutputCount);
+                }
+                assertTrue(log + "encoder output is flaky", ref.equals(test));
+                mSaveToMem = false;
+            }
+            mCodec.release();
+        }
+    }
+
+    /**
+     * Tests encoder for only EOS frame
+     */
+    @SmallTest
+    @Test(timeout = PER_TEST_TIMEOUT_SMALL_TEST_MS)
+    public void testOnlyEos() throws IOException, InterruptedException {
+        ArrayList<String> listOfEncoders = selectCodecs(mMime, null, null, true);
+        assertFalse("no suitable codecs found for mime: " + mMime, listOfEncoders.isEmpty());
+        setUpParams();
+        boolean[] boolStates = {true, false};
+        for (String encoder : listOfEncoders) {
+            mCodec = MediaCodec.createByCodecName(encoder);
+            for (boolean isAsync : boolStates) {
+                String log = String.format("encoder: %s, input file: %s, mode: %s:: ", encoder,
+                        mInputFile, (isAsync ? "async" : "sync"));
+                configureCodec(mFormats.get(0), isAsync, false, true);
+                mCodec.start();
+                queueEOS();
+                waitForAllOutputs();
+                /* TODO(b/147348711) */
+                if (false) mCodec.stop();
+                else mCodec.reset();
+                assertTrue(log + " unexpected error", !mAsyncHandle.hasSeenError());
+            }
+            mCodec.release();
+        }
+    }
+}
diff --git a/tests/media/src/android/mediav2/cts/CodecTestBase.java b/tests/media/src/android/mediav2/cts/CodecTestBase.java
new file mode 100644
index 0000000..a09cf5a
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/CodecTestBase.java
@@ -0,0 +1,595 @@
+/*
+ * 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.mediav2.cts;
+
+import android.graphics.ImageFormat;
+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.util.Log;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedList;
+import java.util.concurrent.locks.Condition;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.zip.CRC32;
+
+import static org.junit.Assert.assertTrue;
+
+class CodecAsyncHandler extends MediaCodec.Callback {
+    private static final String LOG_TAG = CodecAsyncHandler.class.getSimpleName();
+    private final Lock mLock = new ReentrantLock();
+    private final Condition mCondition = mLock.newCondition();
+    private CallBackQueue mCbInputQueue;
+    private CallBackQueue mCbOutputQueue;
+    private MediaFormat mOutFormat;
+    private boolean mSignalledOutFormatChanged;
+    private volatile boolean mSignalledError;
+
+    CodecAsyncHandler() {
+        mCbInputQueue = new CallBackQueue();
+        mCbOutputQueue = new CallBackQueue();
+        mSignalledError = false;
+        mSignalledOutFormatChanged = false;
+    }
+
+    void clearQueues() {
+        mCbInputQueue.clear();
+        mCbOutputQueue.clear();
+    }
+
+    void resetContext() {
+        clearQueues();
+        mOutFormat = null;
+        mSignalledOutFormatChanged = false;
+        mSignalledError = false;
+    }
+
+    @Override
+    public void onInputBufferAvailable(@NonNull MediaCodec codec, int bufferIndex) {
+        assertTrue(bufferIndex >= 0);
+        mCbInputQueue.push(new Pair<>(bufferIndex, (MediaCodec.BufferInfo) null));
+    }
+
+    @Override
+    public void onOutputBufferAvailable(@NonNull MediaCodec codec, int bufferIndex,
+            @NonNull MediaCodec.BufferInfo info) {
+        assertTrue(bufferIndex >= 0);
+        mCbOutputQueue.push(new Pair<>(bufferIndex, info));
+    }
+
+    @Override
+    public void onError(@NonNull MediaCodec codec, MediaCodec.CodecException e) {
+        mLock.lock();
+        mSignalledError = true;
+        mCondition.signalAll();
+        mLock.unlock();
+        Log.e(LOG_TAG, "received media codec error : " + e.getMessage());
+    }
+
+    @Override
+    public void onOutputFormatChanged(@NonNull MediaCodec codec, @NonNull MediaFormat format) {
+        mOutFormat = format;
+        mSignalledOutFormatChanged = true;
+        Log.i(LOG_TAG, "Output format changed: " + format.toString());
+    }
+
+    void setCallBack(MediaCodec codec, boolean isCodecInAsyncMode) {
+        if (isCodecInAsyncMode) {
+            codec.setCallback(this);
+        } else {
+            codec.setCallback(null);
+        }
+    }
+
+    Pair<Integer, MediaCodec.BufferInfo> getInput() throws InterruptedException {
+        Pair<Integer, MediaCodec.BufferInfo> element = null;
+        while (!mSignalledError) {
+            if (mCbInputQueue.isEmpty()) {
+                mLock.lock();
+                mCondition.await();
+                mLock.unlock();
+            } else {
+                element = mCbInputQueue.pop();
+                break;
+            }
+        }
+        return element;
+    }
+
+    Pair<Integer, MediaCodec.BufferInfo> getOutput() throws InterruptedException {
+        Pair<Integer, MediaCodec.BufferInfo> element = null;
+        while (!mSignalledError) {
+            if (mCbOutputQueue.isEmpty()) {
+                mLock.lock();
+                mCondition.await();
+                mLock.unlock();
+            } else {
+                element = mCbOutputQueue.pop();
+                break;
+            }
+        }
+        return element;
+    }
+
+    Pair<Integer, MediaCodec.BufferInfo> getWork() throws InterruptedException {
+        Pair<Integer, MediaCodec.BufferInfo> element = null;
+        while (!mSignalledError) {
+            if (mCbInputQueue.isEmpty() && mCbOutputQueue.isEmpty()) {
+                mLock.lock();
+                mCondition.await();
+                mLock.unlock();
+            }
+            if (!mCbOutputQueue.isEmpty()) {
+                element = mCbOutputQueue.pop();
+                break;
+            }
+            if (!mCbInputQueue.isEmpty()) {
+                element = mCbInputQueue.pop();
+                break;
+            }
+        }
+        return element;
+    }
+
+    boolean hasSeenError() {
+        return mSignalledError;
+    }
+
+    boolean hasOutputFormatChanged() {
+        return mSignalledOutFormatChanged;
+    }
+
+    MediaFormat getOutputFormat() {
+        return mOutFormat;
+    }
+
+    private class CallBackQueue {
+        private final LinkedList<Pair<Integer, MediaCodec.BufferInfo>> mQueue = new LinkedList<>();
+
+        void push(Pair<Integer, MediaCodec.BufferInfo> element) {
+            mLock.lock();
+            mQueue.add(element);
+            mCondition.signalAll();
+            mLock.unlock();
+        }
+
+        Pair<Integer, MediaCodec.BufferInfo> pop() {
+            Pair<Integer, MediaCodec.BufferInfo> element = null;
+            mLock.lock();
+            if (mQueue.size() != 0) element = mQueue.remove(0);
+            mLock.unlock();
+            return element;
+        }
+
+        boolean isEmpty() {
+            mLock.lock();
+            boolean isEmpty = (mQueue.size() == 0);
+            mLock.unlock();
+            return isEmpty;
+        }
+
+        void clear() {
+            mLock.lock();
+            mQueue.clear();
+            mLock.unlock();
+        }
+    }
+}
+
+class OutputManager {
+    private static final String LOG_TAG = OutputManager.class.getSimpleName();
+    private byte[] memory;
+    private int memIndex;
+    private ArrayList<Long> crc32List;
+
+    OutputManager() {
+        memory = new byte[1024];
+        memIndex = 0;
+        crc32List = new ArrayList<>();
+    }
+
+    void checksum(ByteBuffer buf, int size) {
+        int cap = buf.capacity();
+        assertTrue("checksum() params are invalid: size = " + size + " cap = " + cap,
+                size > 0 && size <= cap);
+        CRC32 crc = new CRC32();
+        if (buf.hasArray()) {
+            crc.update(buf.array(), buf.position() + buf.arrayOffset(), size);
+        } else {
+            int pos = buf.position();
+            final int rdsize = Math.min(4096, size);
+            byte[] bb = new byte[rdsize];
+            int chk;
+            for (int i = 0; i < size; i += chk) {
+                chk = Math.min(rdsize, size - i);
+                buf.get(bb, 0, chk);
+                crc.update(bb, 0, chk);
+            }
+            buf.position(pos);
+        }
+        crc32List.add(crc.getValue());
+    }
+
+    void checksum(Image image) {
+        int format = image.getFormat();
+        if (format != ImageFormat.YUV_420_888) {
+            crc32List.add(-1L);
+            return;
+        }
+        CRC32 crc = new CRC32();
+        int imageWidth = image.getWidth();
+        int imageHeight = image.getHeight();
+        Image.Plane[] planes = image.getPlanes();
+        for (int i = 0; i < planes.length; ++i) {
+            ByteBuffer buf = planes[i].getBuffer();
+            int width, height, rowStride, pixelStride, x, y;
+            rowStride = planes[i].getRowStride();
+            pixelStride = planes[i].getPixelStride();
+            if (i == 0) {
+                width = imageWidth;
+                height = imageHeight;
+            } else {
+                width = imageWidth / 2;
+                height = imageHeight / 2;
+            }
+            // local contiguous pixel buffer
+            byte[] bb = new byte[width * height];
+            if (buf.hasArray()) {
+                byte[] b = buf.array();
+                int offs = buf.arrayOffset();
+                if (pixelStride == 1) {
+                    for (y = 0; y < height; ++y) {
+                        System.arraycopy(bb, y * width, b, y * rowStride + offs, width);
+                    }
+                } else {
+                    // do it pixel-by-pixel
+                    for (y = 0; y < height; ++y) {
+                        int lineOffset = offs + y * rowStride;
+                        for (x = 0; x < width; ++x) {
+                            bb[y * width + x] = b[lineOffset + x * pixelStride];
+                        }
+                    }
+                }
+            } else { // almost always ends up here due to direct buffers
+                int pos = buf.position();
+                if (pixelStride == 1) {
+                    for (y = 0; y < height; ++y) {
+                        buf.position(pos + y * rowStride);
+                        buf.get(bb, y * width, width);
+                    }
+                } else {
+                    // local line buffer
+                    byte[] lb = new byte[rowStride];
+                    // do it pixel-by-pixel
+                    for (y = 0; y < height; ++y) {
+                        buf.position(pos + y * rowStride);
+                        // we're only guaranteed to have pixelStride * (width - 1) + 1 bytes
+                        buf.get(lb, 0, pixelStride * (width - 1) + 1);
+                        for (x = 0; x < width; ++x) {
+                            bb[y * width + x] = lb[x * pixelStride];
+                        }
+                    }
+                }
+                buf.position(pos);
+            }
+            crc.update(bb, 0, width * height);
+        }
+        crc32List.add(crc.getValue());
+    }
+
+    void saveToMemory(ByteBuffer buf, MediaCodec.BufferInfo info) {
+        if (memIndex + info.size >= memory.length) {
+            memory = Arrays.copyOf(memory, memIndex + info.size);
+        }
+        buf.position(info.offset);
+        buf.get(memory, memIndex, info.size);
+        memIndex += info.size;
+    }
+
+    void position(int index) {
+        if (index < 0 || index >= memory.length) index = 0;
+        memIndex = index;
+    }
+
+    void reset() {
+        position(0);
+        crc32List.clear();
+    }
+
+    float getRmsError(short[] refData) {
+        long totalErrorSquared = 0;
+        assertTrue(0 == (memory.length & 1));
+        short[] shortData = new short[memory.length / 2];
+        ByteBuffer.wrap(memory).order(ByteOrder.LITTLE_ENDIAN).asShortBuffer().get(shortData);
+        if (refData.length != shortData.length) return Float.MAX_VALUE;
+        for (int i = 0; i < shortData.length; i++) {
+            int d = shortData[i] - refData[i];
+            totalErrorSquared += d * d;
+        }
+        long avgErrorSquared = (totalErrorSquared / shortData.length);
+        return (float) Math.sqrt(avgErrorSquared);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        OutputManager that = (OutputManager) o;
+        boolean isEqual = true;
+        if (!crc32List.equals(that.crc32List)) {
+            isEqual = false;
+            Log.e(LOG_TAG, "ref and test crc32 checksums mismatch");
+        }
+        if (memIndex == that.memIndex) {
+            int count = 0;
+            for (int i = 0; i < memIndex; i++) {
+                if (memory[i] != that.memory[i]) {
+                    count++;
+                    if (count < 20) {
+                        Log.d(LOG_TAG, "sample at offset " + i + " exp/got:: " + memory[i] + '/' +
+                                that.memory[i]);
+                    }
+                }
+            }
+            if (count != 0) {
+                isEqual = false;
+                Log.e(LOG_TAG, "ref and test o/p samples mismatch " + count);
+            }
+        } else {
+            isEqual = false;
+            Log.e(LOG_TAG, "ref and test o/p sizes mismatch " + memIndex + '/' + that.memIndex);
+        }
+        return isEqual;
+    }
+}
+
+abstract class CodecTestBase {
+    private static final String LOG_TAG = CodecTestBase.class.getSimpleName();
+    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 String mInpPrefix = WorkDir.getMediaDirString();
+
+    CodecAsyncHandler mAsyncHandle;
+    boolean mIsCodecInAsyncMode;
+    boolean mSawInputEOS;
+    boolean mSawOutputEOS;
+    boolean mSignalEOSWithLastFrame;
+    int mInputCount;
+    int mOutputCount;
+    long mPrevOutputPts;
+    boolean mSignalledOutFormatChanged;
+    MediaFormat mOutFormat;
+    boolean mIsAudio;
+
+    boolean mSaveToMem;
+    OutputManager mOutputBuff;
+
+    MediaCodec mCodec;
+
+    abstract void enqueueInput(int bufferIndex) throws IOException;
+
+    abstract void dequeueOutput(int bufferIndex, MediaCodec.BufferInfo info);
+
+    void configureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame,
+            boolean isEncoder) {
+        resetContext(isAsync, signalEOSWithLastFrame);
+        mAsyncHandle.setCallBack(mCodec, isAsync);
+        mCodec.configure(format, null, null, isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0);
+        if (ENABLE_LOGS) {
+            Log.v(LOG_TAG, "codec configured");
+        }
+    }
+
+    void flushCodec() {
+        mCodec.flush();
+        mAsyncHandle.clearQueues();
+        mSawInputEOS = false;
+        mSawOutputEOS = false;
+        mInputCount = 0;
+        mOutputCount = 0;
+        mPrevOutputPts = Long.MIN_VALUE;
+        if (ENABLE_LOGS) {
+            Log.v(LOG_TAG, "codec flushed");
+        }
+    }
+
+    void reConfigureCodec(MediaFormat format, boolean isAsync, boolean signalEOSWithLastFrame,
+            boolean isEncoder) {
+        /* TODO(b/147348711) */
+        if (false) mCodec.stop();
+        else mCodec.reset();
+        configureCodec(format, isAsync, signalEOSWithLastFrame, isEncoder);
+    }
+
+    void resetContext(boolean isAsync, boolean signalEOSWithLastFrame) {
+        mAsyncHandle.resetContext();
+        mIsCodecInAsyncMode = isAsync;
+        mSawInputEOS = false;
+        mSawOutputEOS = false;
+        mSignalEOSWithLastFrame = signalEOSWithLastFrame;
+        mInputCount = 0;
+        mOutputCount = 0;
+        mPrevOutputPts = Long.MIN_VALUE;
+        mSignalledOutFormatChanged = false;
+    }
+
+    void enqueueEOS(int bufferIndex) {
+        if (!mSawInputEOS) {
+            mCodec.queueInputBuffer(bufferIndex, 0, 0, 0, MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+            mSawInputEOS = true;
+            if (ENABLE_LOGS) {
+                Log.v(LOG_TAG, "Queued End of Stream");
+            }
+        }
+    }
+
+    void doWork(int frameLimit) throws InterruptedException, IOException {
+        int frameCount = 0;
+        if (mIsCodecInAsyncMode) {
+            // dequeue output after inputEOS is expected to be done in waitForAllOutputs()
+            while (!mAsyncHandle.hasSeenError() && !mSawInputEOS && frameCount < frameLimit) {
+                Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getWork();
+                if (element != null) {
+                    int bufferID = element.first;
+                    MediaCodec.BufferInfo info = element.second;
+                    if (info != null) {
+                        dequeueOutput(bufferID, info);
+                    } else {
+                        enqueueInput(bufferID);
+                        frameCount++;
+                    }
+                }
+            }
+        } else {
+            MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+            // dequeue output after inputEOS is expected to be done in waitForAllOutputs()
+            while (!mSawInputEOS && frameCount < frameLimit) {
+                int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
+                if (outputBufferId >= 0) {
+                    dequeueOutput(outputBufferId, outInfo);
+                } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    mOutFormat = mCodec.getOutputFormat();
+                    mSignalledOutFormatChanged = true;
+                }
+                int inputBufferId = mCodec.dequeueInputBuffer(Q_DEQ_TIMEOUT_US);
+                if (inputBufferId != -1) {
+                    enqueueInput(inputBufferId);
+                    frameCount++;
+                }
+            }
+        }
+    }
+
+    void queueEOS() throws InterruptedException {
+        if (!mSawInputEOS) {
+            if (mIsCodecInAsyncMode) {
+                Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getInput();
+                if (element != null) {
+                    enqueueEOS(element.first);
+                }
+            } else {
+                enqueueEOS(mCodec.dequeueInputBuffer(-1));
+            }
+        }
+    }
+
+    void waitForAllOutputs() throws InterruptedException {
+        if (mIsCodecInAsyncMode) {
+            while (!mAsyncHandle.hasSeenError() && !mSawOutputEOS) {
+                Pair<Integer, MediaCodec.BufferInfo> element = mAsyncHandle.getOutput();
+                if (element != null) {
+                    dequeueOutput(element.first, element.second);
+                }
+            }
+        } else {
+            MediaCodec.BufferInfo outInfo = new MediaCodec.BufferInfo();
+            while (!mSawOutputEOS) {
+                int outputBufferId = mCodec.dequeueOutputBuffer(outInfo, Q_DEQ_TIMEOUT_US);
+                if (outputBufferId >= 0) {
+                    dequeueOutput(outputBufferId, outInfo);
+                } else if (outputBufferId == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+                    mOutFormat = mCodec.getOutputFormat();
+                    mSignalledOutFormatChanged = true;
+                }
+            }
+        }
+    }
+
+    static ArrayList<String> selectCodecs(String mime, ArrayList<MediaFormat> formats,
+            String[] features, boolean isEncoder) {
+        MediaCodecList codecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
+        MediaCodecInfo[] codecInfos = codecList.getCodecInfos();
+        ArrayList<String> listOfDecoders = new ArrayList<>();
+        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 (type.equalsIgnoreCase(mime)) {
+                    boolean isOk = true;
+                    MediaCodecInfo.CodecCapabilities codecCapabilities =
+                            codecInfo.getCapabilitiesForType(type);
+                    if (formats != null) {
+                        for (MediaFormat format : formats) {
+                            if (!codecCapabilities.isFormatSupported(format)) {
+                                isOk = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (features != null) {
+                        for (String feature : features) {
+                            if (!codecCapabilities.isFeatureSupported(feature)) {
+                                isOk = false;
+                                break;
+                            }
+                        }
+                    }
+                    if (isOk) listOfDecoders.add(codecInfo.getName());
+                }
+            }
+        }
+        return listOfDecoders;
+    }
+
+    static int getWidth(MediaFormat format) {
+        int width = format.getInteger(MediaFormat.KEY_WIDTH);
+        if (format.containsKey("crop-left") && format.containsKey("crop-right")) {
+            width = format.getInteger("crop-right") + 1 - format.getInteger("crop-left");
+        }
+        return width;
+    }
+
+    static int getHeight(MediaFormat format) {
+        int height = format.getInteger(MediaFormat.KEY_HEIGHT);
+        if (format.containsKey("crop-top") && format.containsKey("crop-bottom")) {
+            height = format.getInteger("crop-bottom") + 1 - format.getInteger("crop-top");
+        }
+        return height;
+    }
+
+    static boolean isFormatSimilar(MediaFormat inpFormat, MediaFormat outFormat) {
+        if (inpFormat == null || outFormat == null) return false;
+        String inpMime = inpFormat.getString(MediaFormat.KEY_MIME);
+        String outMime = inpFormat.getString(MediaFormat.KEY_MIME);
+        if (outMime.startsWith("audio/")) {
+            return inpFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) ==
+                    outFormat.getInteger(MediaFormat.KEY_CHANNEL_COUNT) &&
+                    inpFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) ==
+                            outFormat.getInteger(MediaFormat.KEY_SAMPLE_RATE) &&
+                    inpMime.startsWith("audio/");
+        } else if (outMime.startsWith("video/")) {
+            return getWidth(inpFormat) == getWidth(outFormat) &&
+                    getHeight(inpFormat) == getHeight(outFormat) && inpMime.startsWith("video/");
+        }
+        return true;
+    }
+}
diff --git a/tests/media/src/android/mediav2/cts/ExtractorTest.java b/tests/media/src/android/mediav2/cts/ExtractorTest.java
index b4c4d01..8a71d14 100644
--- a/tests/media/src/android/mediav2/cts/ExtractorTest.java
+++ b/tests/media/src/android/mediav2/cts/ExtractorTest.java
@@ -254,15 +254,25 @@
                                     '/' + testSampleInfo.presentationTimeUs);
                         }
                         areTracksIdentical = false;
+                        break;
                     }
                     int refSz = refExtractor.readSampleData(refBuffer, 0);
-                    int testSz = testExtractor.readSampleData(testBuffer, 0);
-                    if (refSz != testSz || refSz != refSampleInfo.size) {
+                    if (refSz != refSampleInfo.size) {
                         if (ENABLE_LOGS) {
-                            Log.d(LOG_TAG, "Mime: " + refMime + " Size exp/got/got: " +
-                                    refSz + '/' + testSz + '/' + refSampleInfo.size);
+                            Log.d(LOG_TAG, "Mime: " + refMime + " Size exp/got: " +
+                                    refSampleInfo.size + '/' + refSz);
                         }
                         areTracksIdentical = false;
+                        break;
+                    }
+                    int testSz = testExtractor.readSampleData(testBuffer, 0);
+                    if (testSz != testSampleInfo.size) {
+                        if (ENABLE_LOGS) {
+                            Log.d(LOG_TAG, "Mime: " + refMime + " Size exp/got: " +
+                                    testSampleInfo.size + '/' + testSz);
+                        }
+                        areTracksIdentical = false;
+                        break;
                     }
                     int trackIndex = refExtractor.getSampleTrackIndex();
                     if (trackIndex != refTrackID) {
@@ -271,6 +281,7 @@
                                     " TrackID exp/got: " + refTrackID + '/' + trackIndex);
                         }
                         areTracksIdentical = false;
+                        break;
                     }
                     trackIndex = testExtractor.getSampleTrackIndex();
                     if (trackIndex != testTrackID) {
@@ -279,6 +290,7 @@
                                     " TrackID exp/got: " + testTrackID + '/' + trackIndex);
                         }
                         areTracksIdentical = false;
+                        break;
                     }
                     boolean haveRefSamples = refExtractor.advance();
                     boolean haveTestSamples = testExtractor.advance();
@@ -287,6 +299,7 @@
                             Log.d(LOG_TAG, "Mime: " + refMime + " Mismatch in sampleCount");
                         }
                         areTracksIdentical = false;
+                        break;
                     }
 
                     if (!haveRefSamples && !isExtractorOKonEOS(refExtractor)) {
@@ -294,12 +307,14 @@
                             Log.d(LOG_TAG, "Mime: " + refMime + " calls post advance() are not OK");
                         }
                         areTracksIdentical = false;
+                        break;
                     }
                     if (!haveTestSamples && !isExtractorOKonEOS(testExtractor)) {
                         if (ENABLE_LOGS) {
                             Log.d(LOG_TAG, "Mime: " + refMime + " calls post advance() are not OK");
                         }
                         areTracksIdentical = false;
+                        break;
                     }
                     if (ENABLE_LOGS) {
                         Log.v(LOG_TAG, "Mime: " + refMime + " Sample: " + frameCount +
@@ -307,8 +322,7 @@
                                 " size: " + refSampleInfo.size +
                                 " ts: " + refSampleInfo.presentationTimeUs);
                     }
-                    if (!areTracksIdentical || !haveRefSamples || !haveTestSamples ||
-                            frameCount >= sampleLimit) {
+                    if (!haveRefSamples || frameCount >= sampleLimit) {
                         break;
                     }
                 }
@@ -343,6 +357,10 @@
 
         private MediaExtractor mRefExtractor;
 
+        static {
+            System.loadLibrary("ctsmediav2extractor_jni");
+        }
+
         @Before
         public void setUp() throws IOException {
             mRefExtractor = new MediaExtractor();
@@ -538,6 +556,14 @@
             testExtractor.unselectTrack(0);
             testExtractor.release();
         }
+
+        private native boolean nativeTestDataSource(String srcPath, String srcUrl);
+
+        @Test
+        public void testDataSourceNative() {
+            assertTrue(testName.getMethodName() + " failed ",
+                    nativeTestDataSource(mInpPrefix + mInpMedia, mInpMediaUrl));
+        }
     }
 
     /**
@@ -551,6 +577,10 @@
         private String[] mSrcFiles;
         private String mMime;
 
+        static {
+            System.loadLibrary("ctsmediav2extractor_jni");
+        }
+
         @Rule
         public TestName testName = new TestName();
 
@@ -618,6 +648,16 @@
             });
         }
 
+        private native boolean nativeTestExtract(String srcPath, String refPath, String mime);
+
+        private native boolean nativeTestSeek(String srcPath, String mime);
+
+        private native boolean nativeTestSeekFlakiness(String srcPath, String mime);
+
+        private native boolean nativeTestSeekToZero(String srcPath, String mime);
+
+        private native boolean nativeTestFileFormat(String srcPath);
+
         public FunctionalityTest(String mime, String[] srcFiles) {
             mMime = mime;
             mSrcFiles = srcFiles;
@@ -763,12 +803,13 @@
                     if (!isSampleInfoIdentical(arg.mExpected, received)) {
                         errCnt++;
                         if (ENABLE_LOGS) {
-                            Log.d(LOG_TAG, " flags exp/got: " +
-                                    received.flags + '/' + arg.mExpected.flags);
-                            Log.d(LOG_TAG, " size exp/got: " +
-                                    received.size + '/' + arg.mExpected.size);
-                            Log.d(LOG_TAG, " ts exp/got: " + received.presentationTimeUs +
-                                    '/' + arg.mExpected.presentationTimeUs);
+                            Log.d(LOG_TAG, " flags exp/got: " + arg.mExpected.flags + '/' +
+                                    received.flags);
+                            Log.d(LOG_TAG,
+                                    " size exp/got: " + arg.mExpected.size + '/' + received.size);
+                            Log.d(LOG_TAG,
+                                    " ts exp/got: " + arg.mExpected.presentationTimeUs + '/' +
+                                            received.presentationTimeUs);
                         }
                     }
                 }
@@ -798,7 +839,7 @@
             MediaExtractor refExtractor = new MediaExtractor();
             refExtractor.setDataSource(mInpPrefix + mSrcFiles[0]);
             boolean isOk = true;
-            for (int i = 1; i < mSrcFiles.length; i++) {
+            for (int i = 1; i < mSrcFiles.length && isOk; i++) {
                 MediaExtractor testExtractor = new MediaExtractor();
                 testExtractor.setDataSource(mInpPrefix + mSrcFiles[i]);
                 if (!isMediaSimilar(refExtractor, testExtractor, mMime, Integer.MAX_VALUE)) {
@@ -840,6 +881,7 @@
                     }
                     if (!codecListSupp.contains(mMime)) {
                         isOk = false;
+                        break;
                     }
                 }
             }
@@ -867,6 +909,7 @@
                     }
                     if (!codecListSupp.contains(mMime)) {
                         isOk = false;
+                        break;
                     }
                 }
             }
@@ -900,12 +943,42 @@
                     extractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
                     currInfo.set(0, (int) extractor.getSampleSize(),
                             extractor.getSampleTime(), extractor.getSampleFlags());
-                    extractor.unselectTrack(trackID);
                     if (!isSampleInfoIdentical(sampleInfoAtZero, currInfo)) {
                         if (!codecListSupp.contains(mMime)) {
+                            if (ENABLE_LOGS) {
+                                Log.d(LOG_TAG, "seen mismatch seekTo(0, SEEK_TO_CLOSEST_SYNC)");
+                                Log.d(LOG_TAG, " flags exp/got: " + sampleInfoAtZero.flags + '/' +
+                                        currInfo.flags);
+                                Log.d(LOG_TAG, " size exp/got: " + sampleInfoAtZero.size + '/' +
+                                        currInfo.size);
+                                Log.d(LOG_TAG,
+                                        " ts exp/got: " + sampleInfoAtZero.presentationTimeUs +
+                                                '/' + currInfo.presentationTimeUs);
+                            }
                             isOk = false;
+                            break;
                         }
                     }
+                    extractor.seekTo(-1L, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+                    currInfo.set(0, (int) extractor.getSampleSize(),
+                            extractor.getSampleTime(), extractor.getSampleFlags());
+                    if (!isSampleInfoIdentical(sampleInfoAtZero, currInfo)) {
+                        if (!codecListSupp.contains(mMime)) {
+                            if (ENABLE_LOGS) {
+                                Log.d(LOG_TAG, "seen mismatch seekTo(-1, SEEK_TO_CLOSEST_SYNC)");
+                                Log.d(LOG_TAG, " flags exp/got: " + sampleInfoAtZero.flags + '/' +
+                                        currInfo.flags);
+                                Log.d(LOG_TAG, " size exp/got: " + sampleInfoAtZero.size + '/' +
+                                        currInfo.size);
+                                Log.d(LOG_TAG,
+                                        " ts exp/got: " + sampleInfoAtZero.presentationTimeUs +
+                                                '/' + currInfo.presentationTimeUs);
+                            }
+                            isOk = false;
+                            break;
+                        }
+                    }
+                    extractor.unselectTrack(trackID);
                 }
                 extractor.release();
             }
@@ -928,5 +1001,94 @@
                 extractor.release();
             }
         }
+
+        @LargeTest
+        @Test
+        public void testExtractNative() {
+            assumeTrue(shouldRunTest(mMime));
+            assertTrue(mSrcFiles.length > 1);
+            assumeTrue("TODO(b/146925481)", mMime == MediaFormat.MIMETYPE_VIDEO_VP8 ||
+                    mMime == MediaFormat.MIMETYPE_VIDEO_VP9 ||
+                    mMime == MediaFormat.MIMETYPE_VIDEO_AV1 ||
+                    mMime == MediaFormat.MIMETYPE_AUDIO_FLAC);
+            boolean isOk = true;
+            for (int i = 1; i < mSrcFiles.length; i++) {
+                if (!nativeTestExtract(mInpPrefix + mSrcFiles[0], mInpPrefix + mSrcFiles[i],
+                        mMime)) {
+                    Log.d(LOG_TAG, "Files: " + mSrcFiles[0] + ", " + mSrcFiles[i] +
+                            " are different from extractor perpsective");
+                    if (!codecListSupp.contains(mMime)) {
+                        isOk = false;
+                        break;
+                    }
+                }
+            }
+            assertTrue(testName.getMethodName() + " failed for mime: " + mMime, isOk);
+        }
+
+        @LargeTest
+        @Test
+        @Ignore("TODO(b/146420831)")
+        public void testSeekNative() {
+            assumeTrue(shouldRunTest(mMime));
+            boolean isOk = true;
+            for (String srcFile : mSrcFiles) {
+                if (!nativeTestSeek(mInpPrefix + srcFile, mMime)) {
+                    if (!codecListSupp.contains(mMime)) {
+                        isOk = false;
+                        break;
+                    }
+                }
+            }
+            assertTrue(testName.getMethodName() + " failed for mime: " + mMime, isOk);
+        }
+
+        @LargeTest
+        @Test
+        public void testSeekFlakinessNative() {
+            assumeTrue(shouldRunTest(mMime));
+            boolean isOk = true;
+            for (String srcFile : mSrcFiles) {
+                if (!nativeTestSeekFlakiness(mInpPrefix + srcFile, mMime)) {
+                    if (!codecListSupp.contains(mMime)) {
+                        isOk = false;
+                        break;
+                    }
+                }
+            }
+            assertTrue(testName.getMethodName() + " failed for mime: " + mMime, isOk);
+        }
+
+        @SmallTest
+        @Test
+        public void testSeekToZeroNative() {
+            assumeTrue(shouldRunTest(mMime));
+            assumeTrue("TODO(b/146925481)", mMime != MediaFormat.MIMETYPE_AUDIO_MPEG &&
+                    mMime != MediaFormat.MIMETYPE_AUDIO_AAC);
+            boolean isOk = true;
+            for (String srcFile : mSrcFiles) {
+                if (!nativeTestSeekToZero(mInpPrefix + srcFile, mMime)) {
+                    if (!codecListSupp.contains(mMime)) {
+                        isOk = false;
+                        break;
+                    }
+                }
+            }
+            assertTrue(testName.getMethodName() + " failed for mime: " + mMime, isOk);
+        }
+
+        @SmallTest
+        @Test
+        public void testFileFormatNative() {
+            assumeTrue(shouldRunTest(mMime));
+            boolean isOk = true;
+            for (String srcFile : mSrcFiles) {
+                if (!nativeTestFileFormat(mInpPrefix + srcFile)) {
+                    isOk = false;
+                    break;
+                }
+                assertTrue(testName.getMethodName() + " failed for mime: " + mMime, isOk);
+            }
+        }
     }
 }
diff --git a/tests/media/src/android/mediav2/cts/ExtractorUnitTest.java b/tests/media/src/android/mediav2/cts/ExtractorUnitTest.java
new file mode 100644
index 0000000..84122cd
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/ExtractorUnitTest.java
@@ -0,0 +1,793 @@
+/*
+ * 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.mediav2.cts;
+
+import android.content.res.AssetFileDescriptor;
+import android.media.MediaDataSource;
+import android.media.MediaExtractor;
+import android.os.ParcelFileDescriptor;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+@RunWith(Enclosed.class)
+public class ExtractorUnitTest {
+    private static final int MAX_SAMPLE_SIZE = 4 * 1024 * 1024;
+    private static final String mInpPrefix = WorkDir.getMediaDirString();
+    private static final String mInpMedia = "ForBiggerEscapes.mp4";
+
+    @SmallTest
+    public static class TestApi {
+        @Rule
+        public TestName testName = new TestName();
+
+        @Test
+        public void testGetTrackCountBeforeSetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                assertTrue("received valid trackCount before setDataSource",
+                        extractor.getTrackCount() <= 0);
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testGetTrackCountAfterRelease() {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.release();
+            try {
+                extractor.getTrackCount();
+                fail("getTrackCount succeeds after release");
+            } catch (Exception e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testSelectTrackBeforeSetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                extractor.selectTrack(0);
+                fail("selectTrack succeeds before setDataSource");
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testSelectTrackForInvalidIndex() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            try {
+                try {
+                    extractor.selectTrack(-1);
+                    fail("selectTrack succeeds for track index: -1");
+                } catch (Exception e) {
+                    // expected
+                }
+                try {
+                    extractor.selectTrack(extractor.getTrackCount());
+                    fail("selectTrack succeeds for out of bounds track index: " +
+                            extractor.getTrackCount());
+                } catch (Exception e) {
+                    // expected
+                }
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testIdempotentSelectTrack() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            try {
+                extractor.selectTrack(0);
+                extractor.selectTrack(0);
+            } catch (Exception e) {
+                fail("multiple selection of same track has failed");
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testSelectTrackAfterRelease() {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.release();
+            try {
+                extractor.selectTrack(0);
+                fail("selectTrack succeeds after release");
+            } catch (Exception e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testUnselectTrackBeforeSetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                extractor.unselectTrack(0);
+                fail("unselectTrack succeeds before setDataSource");
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testUnselectTrackForInvalidIndex() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            try {
+                try {
+                    extractor.unselectTrack(-1);
+                    fail("unselectTrack succeeds for track index: -1");
+                } catch (Exception e) {
+                    // expected
+                }
+                try {
+                    extractor.unselectTrack(extractor.getTrackCount());
+                    fail("unselectTrack succeeds for out of bounds track index: " +
+                            extractor.getTrackCount());
+                } catch (Exception e) {
+                    // expected
+                }
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testUnselectTrackForUnSelectedTrackIndex() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            try {
+                extractor.unselectTrack(0);
+            } catch (Exception e) {
+                fail("un-selection of non-selected track has failed");
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testIdempotentUnselectTrack() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            try {
+                extractor.selectTrack(0);
+                extractor.unselectTrack(0);
+                extractor.unselectTrack(0);
+            } catch (Exception e) {
+                fail("multiple un-selection of selected track has failed");
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testUnselectTrackAfterRelease() {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.release();
+            try {
+                extractor.unselectTrack(0);
+                fail("unselectTrack succeeds after release");
+            } catch (Exception e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testSeekToBeforeSetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                extractor.seekTo(33000, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+                if (extractor.getSampleTime() != -1 || extractor.getSampleSize() != -1 ||
+                        extractor.getSampleFlags() != -1) {
+                    fail("seekTo() succeeds before setting data source, returns non-negative " +
+                            "sampleTime / sampleSize / sampleFlags");
+                }
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testSeekToBeforeSelectTrack() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            try {
+                extractor.seekTo(33000, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+                if (extractor.getSampleTime() != -1 || extractor.getSampleSize() != -1 ||
+                        extractor.getSampleFlags() != -1) {
+                    fail("seekTo() succeeds before selectTrack, returns non-negative " +
+                            "sampleTime / sampleSize / sampleFlags");
+                }
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testSeekToForInvalidMode() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            extractor.selectTrack(0);
+            long pts = 33000;
+            try {
+                extractor.seekTo(pts, (int) pts);
+                fail("seekTo() succeeds for invalid mode: " + pts);
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testSeekToAfterRelease() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            extractor.release();
+            try {
+                extractor.seekTo(33000, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
+                fail("seekTo() succeeds after release");
+            } catch (Exception e) {
+                // expected
+            }
+        }
+
+        @Test
+        @Ignore("TODO(b/148205432)")
+        public void testGetCachedDurationBeforeSetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                assertTrue("received valid cachedDuration before setDataSource",
+                        extractor.getCachedDuration() == -1);
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testGetCachedDurationAfterRelease() {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.release();
+            try {
+                extractor.getCachedDuration();
+                fail("cachedDuration succeeds after release");
+            } catch (Exception e) {
+                // expected
+            }
+        }
+
+        @Test
+        @Ignore("TODO(b/148204634)")
+        public void testHasCacheReachedEOSBeforeSetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                assertTrue("unexpected value received from hasCacheReachedEndOfStream before" +
+                        " setDataSource", !extractor.hasCacheReachedEndOfStream());
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testHasCacheReachedEOSAfterRelease() {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.release();
+            try {
+                extractor.hasCacheReachedEndOfStream();
+                fail("hasCacheReachedEndOfStream succeeds after release");
+            } catch (Exception e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testGetMetricsBeforeSetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                assertTrue("received valid metrics before setDataSource",
+                        extractor.getMetrics() == null);
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testGetMetricsAfterRelease() {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.release();
+            try {
+                extractor.getMetrics();
+                fail("getMetrics() succeeds after release");
+            } catch (Exception e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testIdempotentRelease() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                extractor.release();
+                extractor.release();
+            } catch (Exception e) {
+                fail(e.getMessage());
+            }
+        }
+
+        @Test
+        public void testAdvanceBeforeSetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                assertTrue("advance succeeds before setDataSource", !extractor.advance());
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testAdvanceBeforeSelectTrack() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            try {
+                assertTrue("advance succeeds without any active tracks", !extractor.advance());
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testAdvanceAfterRelease() {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.release();
+            try {
+                extractor.advance();
+                fail("advance succeeds after release");
+            } catch (Exception e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testGetSampleFlagsBeforeSetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                assertTrue("received valid sampleFlag before setDataSource",
+                        extractor.getSampleFlags() == -1);
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testGetSampleFlagsBeforeSelectTrack() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            try {
+                assertTrue("received valid sampleFlag without any active tracks",
+                        extractor.getSampleFlags() == -1);
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testGetSampleFlagsAfterRelease() {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.release();
+            try {
+                extractor.getSampleFlags();
+                fail("getSampleFlags succeeds after release");
+            } catch (Exception e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testGetSampleTimeBeforeSetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                assertTrue("received valid sampleTime before setDataSource",
+                        extractor.getSampleTime() == -1);
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testGetSampleTimeBeforeSelectTrack() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            try {
+                assertTrue("received valid sampleTime without any active tracks",
+                        extractor.getSampleTime() == -1);
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testGetSampleTimeAfterRelease() {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.release();
+            try {
+                extractor.getSampleTime();
+                fail("getSampleTime succeeds after release");
+            } catch (Exception e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testGetSampleSizeBeforeSetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                assertTrue("received valid sampleSize before setDataSource",
+                        extractor.getSampleSize() == -1);
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testGetSampleSizeBeforeSelectTrack() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            try {
+                assertTrue("received valid sampleSize without any active tracks",
+                        extractor.getSampleSize() == -1);
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testGetSampleSizeAfterRelease() {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.release();
+            try {
+                extractor.getSampleSize();
+                fail("getSampleSize succeeds after release");
+            } catch (Exception e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testGetSampleTrackIndexBeforeSetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                assertTrue("received valid sampleTrackIndex before setDataSource",
+                        extractor.getSampleTrackIndex() == -1);
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testGetSampleTrackIndexBeforeSelectTrack() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            try {
+                assertTrue("received valid sampleTrackIndex without any active tracks",
+                        extractor.getSampleTrackIndex() == -1);
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testGetSampleTrackIndexAfterRelease() {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.release();
+            try {
+                extractor.getSampleTrackIndex();
+                fail("getSampleTrackIndex succeeds after release");
+            } catch (Exception e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testGetTrackFormatBeforeSetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                extractor.getTrackFormat(0);
+                fail("getTrackFormat succeeds before setDataSource");
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testGetTrackFormatForInvalidIndex() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            try {
+                try {
+                    extractor.getTrackFormat(-1);
+                    fail("getTrackFormat succeeds for track index: -1");
+                } catch (Exception e) {
+                    // expected
+                }
+                try {
+                    extractor.getTrackFormat(extractor.getTrackCount());
+                    fail("getTrackFormat succeeds for out of bounds track index: " +
+                            extractor.getTrackCount());
+                } catch (Exception e) {
+                    // expected
+                }
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testGetTrackFormatAfterRelease() {
+            MediaExtractor extractor = new MediaExtractor();
+            extractor.release();
+            try {
+                extractor.getTrackFormat(0);
+                fail("getTrackFormat succeeds after release");
+            } catch (Exception e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testReadSampleDataBeforeSetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            ByteBuffer byteBuffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
+            try {
+                assertTrue("readSampleData returns val >= 0 before setDataSource",
+                        extractor.readSampleData(byteBuffer, 0) < 0);
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testReadSampleDataBeforeSelectTrack() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            ByteBuffer byteBuffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            try {
+                assertTrue("readSampleData returns val >= 0 without any active tracks",
+                        extractor.readSampleData(byteBuffer, 0) < 0);
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testIfInvalidOffsetIsRejectedByReadSampleData() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            ByteBuffer byteBuffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
+            extractor.setDataSource(mInpPrefix + mInpMedia);
+            extractor.selectTrack(0);
+            try {
+                // readSampleData with negative offset
+                try {
+                    extractor.readSampleData(byteBuffer, -1);
+                    fail("readSampleData succeeds with negative offset");
+                } catch (Exception e) {
+                    // expected
+                }
+
+                // readSampleData with byteBuffer's capacity - offset < frame size
+                int sampleSize = (int) extractor.getSampleSize();
+                try {
+                    extractor.readSampleData(byteBuffer, MAX_SAMPLE_SIZE - sampleSize + 1);
+                    fail("readSampleData succeeds when available size of byteBuffer is less than " +
+                            "frame size");
+                } catch (Exception e) {
+                    // expected
+                }
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testReadSampleDataAfterRelease() {
+            MediaExtractor extractor = new MediaExtractor();
+            ByteBuffer byteBuffer = ByteBuffer.allocate(MAX_SAMPLE_SIZE);
+            extractor.release();
+            try {
+                extractor.readSampleData(byteBuffer, 0);
+                fail("readSampleData succeeds after release");
+            } catch (Exception e) {
+                // expected
+            }
+        }
+
+        @Test
+        public void testIfInvalidDataSourceIsRejectedBySetDataSource() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            TestMediaDataSource dataSource =
+                    TestMediaDataSource.fromString(mInpPrefix + mInpMedia, true, false);
+            try {
+                try {
+                    extractor.setDataSource(dataSource);
+                    fail("setDataSource succeeds with malformed media data source");
+                } catch (Exception e) {
+                    // expected
+                }
+                assertTrue(dataSource.isClosed());
+                dataSource = TestMediaDataSource.fromString(mInpPrefix + mInpMedia, false, true);
+
+                try {
+                    extractor.setDataSource(dataSource);
+                    fail("setDataSource succeeds with malformed media data source");
+                } catch (Exception e) {
+                    // expected
+                }
+            } finally {
+                assertTrue(dataSource.isClosed());
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testIfNullFDIsRejectedBySetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                extractor.setDataSource((FileDescriptor) null);
+                fail("setDataSource succeeds with null fd");
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testIfWriteOnlyAssetFDIsRejectedBySetDataSource() throws IOException {
+            File inpFile = File.createTempFile("ExtractorTestApisetDSAFD", ".in");
+            MediaExtractor extractor = new MediaExtractor();
+            try (ParcelFileDescriptor parcelFD = ParcelFileDescriptor
+                    .open(inpFile, ParcelFileDescriptor.MODE_WRITE_ONLY);
+                 AssetFileDescriptor afd = new AssetFileDescriptor(parcelFD, 0,
+                         AssetFileDescriptor.UNKNOWN_LENGTH)) {
+                extractor.setDataSource(afd);
+                fail("setDataSource succeeds write only afd");
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+            inpFile.delete();
+        }
+
+        @Test
+        public void testIfWriteOnlyFDIsRejectedBySetDataSource() throws IOException {
+            MediaExtractor extractor = new MediaExtractor();
+            File inpFile = File.createTempFile("ExtractorTestApisetDSFD", ".in");
+            try (FileOutputStream fOut = new FileOutputStream(inpFile)) {
+                extractor.setDataSource(fOut.getFD());
+                fail("setDataSource succeeds write only fd");
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+            inpFile.delete();
+        }
+
+        @Test
+        public void testIfNullMediaDataSourceIsRejectedBySetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                extractor.setDataSource((MediaDataSource) null);
+                fail("setDataSource succeeds with null data source");
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testIfNullFileIsRejectedBySetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                extractor.setDataSource((String) null);
+                fail("setDataSource succeeds with null file path");
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+
+        @Test
+        public void testIfNullAssetFDIsRejectedBySetDataSource() {
+            MediaExtractor extractor = new MediaExtractor();
+            try {
+                extractor.setDataSource((AssetFileDescriptor) null);
+                fail("setDataSource succeeds with null asset fd");
+            } catch (Exception e) {
+                // expected
+            } finally {
+                extractor.release();
+            }
+        }
+    }
+}
diff --git a/tests/media/src/android/mediav2/cts/MuxerTest.java b/tests/media/src/android/mediav2/cts/MuxerTest.java
index b20ee08..45df10a 100644
--- a/tests/media/src/android/mediav2/cts/MuxerTest.java
+++ b/tests/media/src/android/mediav2/cts/MuxerTest.java
@@ -770,7 +770,6 @@
      * Add an offset to the presentation time of samples of a track. Mux with the added offset,
      * validate by re-extracting the muxer output file and compare with original.
      */
-    @Ignore("TODO(test fails for mp4, need to check if test is faulty)")
     @LargeTest
     @RunWith(Parameterized.class)
     public static class TestOffsetPts {
@@ -829,6 +828,10 @@
         public void testOffsetPresentationTime() throws IOException {
             final int OFFSET_TS = 111000;
             Assume.assumeTrue(shouldRunTest(mOutFormat));
+            Assume.assumeTrue("TODO(b/148978457)",
+                    mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            Assume.assumeTrue("TODO(b/148978457)",
+                    mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
             Assume.assumeTrue("TODO(b/146423022)",
                     mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM);
             Assume.assumeTrue("TODO(b/146421018)",
@@ -858,6 +861,10 @@
         @Test
         public void testOffsetPresentationTimeNative() {
             Assume.assumeTrue(shouldRunTest(mOutFormat));
+            Assume.assumeTrue("TODO(b/148978457)",
+                    mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            Assume.assumeTrue("TODO(b/148978457)",
+                    mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
             Assume.assumeTrue("TODO(b/146423022)",
                     mOutFormat != MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM);
             Assume.assumeTrue("TODO(b/146421018)",
diff --git a/tests/media/src/android/mediav2/cts/MuxerUnitTest.java b/tests/media/src/android/mediav2/cts/MuxerUnitTest.java
new file mode 100644
index 0000000..6942393
--- /dev/null
+++ b/tests/media/src/android/mediav2/cts/MuxerUnitTest.java
@@ -0,0 +1,906 @@
+/*
+ * 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.mediav2.cts;
+
+import android.media.MediaCodec;
+import android.media.MediaFormat;
+import android.media.MediaMuxer;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.runners.Enclosed;
+import org.junit.rules.TestName;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.charset.StandardCharsets;
+
+import static android.system.Os.pipe;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * Tests MediaMuxer API that are independent of MediaMuxer.OutputFormat. Constructors,
+ * addTrack, start, writeSampleData, stop, release are independent of OutputFormat selected.
+ * Legality of these APIs are tested in this class.
+ */
+@RunWith(Enclosed.class)
+public class MuxerUnitTest {
+    // duplicate definitions of hide fields of MediaMuxer.OutputFormat.
+    private static final int MUXER_OUTPUT_LAST = MediaMuxer.OutputFormat.MUXER_OUTPUT_OGG;
+
+    @SmallTest
+    public static class TestApi {
+        @Rule
+        public TestName testName = new TestName();
+
+        @Before
+        public void prologue() throws IOException {
+            mOutMedia = File.createTempFile(testName.getMethodName(), ".out");
+            mOutLoc = mOutMedia.getAbsolutePath();
+        }
+
+        @After
+        public void epilogue() {
+            new File(mOutLoc).delete();
+        }
+
+        private File mOutMedia;
+        private String mOutLoc;
+
+        // Insert one frame SubRip
+        static private void insertPerFrameSubtitles(MediaMuxer muxer, long presentationTimeUs,
+                int trackID) {
+            byte[] greeting = "hello world".getBytes(StandardCharsets.UTF_8);
+            ByteBuffer metaBuff = ByteBuffer.allocate(greeting.length);
+            metaBuff.put(greeting);
+            MediaCodec.BufferInfo metaInfo = new MediaCodec.BufferInfo();
+            metaInfo.offset = 0;
+            metaInfo.size = greeting.length;
+            metaInfo.presentationTimeUs = presentationTimeUs;
+            metaInfo.flags = 0;
+            muxer.writeSampleData(trackID, metaBuff, metaInfo);
+        }
+
+        @Test
+        public void testIfNullPathIsRejected() {
+            MediaMuxer muxer = null;
+            try {
+                String nullPath = null;
+                muxer = new MediaMuxer(nullPath, MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
+                fail("null destination path accepted by constructor");
+            } catch (IllegalArgumentException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                if (null != muxer) muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfNullFdIsRejected() {
+            MediaMuxer muxer = null;
+            try {
+                FileDescriptor fd = null;
+                muxer = new MediaMuxer(fd, MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
+                fail("null fd accepted by constructor");
+            } catch (IllegalArgumentException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                if (null != muxer) muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfInvalidFdIsRejected() {
+            MediaMuxer muxer = null;
+            try {
+                FileDescriptor fd = new FileDescriptor();
+                muxer = new MediaMuxer(fd, MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
+                fail("Invalid fd accepted by constructor");
+            } catch (IllegalArgumentException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                if (null != muxer) muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfReadOnlyFdIsRejected() {
+            MediaMuxer muxer = null;
+            try (FileInputStream fInp = new FileInputStream(mOutMedia)) {
+                muxer = new MediaMuxer(fInp.getFD(), MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
+                fail("fd with read-only attribute accepted by constructor");
+            } catch (IOException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                if (null != muxer) muxer.release();
+            }
+        }
+
+        @Test
+        @Ignore("TODO(b/146417874)")
+        public void testIfWriteOnlyFdIsRejected() {
+            MediaMuxer muxer = null;
+            try (FileOutputStream fOut = new FileOutputStream(mOutMedia)) {
+                muxer = new MediaMuxer(fOut.getFD(), MediaMuxer.OutputFormat.MUXER_OUTPUT_WEBM);
+                fail("fd with write only attribute accepted by constructor");
+            } catch (Exception e) {
+                // expected
+            } finally {
+                if (null != muxer) muxer.release();
+            }
+            assertTrue(mOutMedia.delete());
+        }
+
+        @Test
+        @Ignore("TODO(b/146417874)")
+        public void testIfNonSeekableFdIsRejected() {
+            MediaMuxer muxer = null;
+            try {
+                FileDescriptor[] fd = pipe();
+                muxer = new MediaMuxer(fd[1], MediaMuxer.OutputFormat.MUXER_OUTPUT_3GPP);
+                fail("pipe, a non-seekable fd accepted by constructor");
+            } catch (IOException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                if (null != muxer) muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfInvalidOutputFormatIsRejected() {
+            MediaMuxer muxer = null;
+            try {
+                muxer = new MediaMuxer(mOutLoc, MUXER_OUTPUT_LAST + 1);
+                fail("Invalid Media format accepted by constructor");
+            } catch (IllegalArgumentException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                if (null != muxer) muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfNullMediaFormatIsRejected() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            try {
+                muxer.addTrack(null);
+                fail("null media format accepted by addTrack");
+            } catch (IllegalArgumentException e) {
+                // expected
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfInvalidMediaFormatIsRejected() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            try {
+                // Invalid media format - no mime key
+                try {
+                    muxer.addTrack(new MediaFormat());
+                    fail("Invalid media format accepted by addTrack");
+                } catch (IllegalArgumentException e) {
+                    // expected
+                }
+
+                // metadata mime format shall start with "application/*"
+                try {
+                    MediaFormat format = new MediaFormat();
+                    format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_CEA_608);
+                    muxer.addTrack(format);
+                    fail("Invalid media format accepted by addTrack");
+                } catch (IllegalArgumentException | IllegalStateException e) {
+                    // Ideally check only for IllegalArgumentException.
+                    // expected
+                }
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        @Ignore("TODO(b/146923138)")
+        public void testIfCorruptMediaFormatIsRejected() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+
+             /* TODO: Audio/Video formats, have certain keys required to be set. It is noticed
+                that even when these keys are not set, no exceptions were raised. Do we need to
+                add fixtures for those cases. */
+            try {
+                MediaFormat format = new MediaFormat();
+                format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_AUDIO_AAC);
+                format.setInteger(MediaFormat.KEY_SAMPLE_RATE, -1);
+                muxer.addTrack(format);
+                muxer.start();
+                fail("muxer accepts media format with required key-value pairs missing");
+            } catch (Exception e) {
+                // expected
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        @Ignore("TODO(b/146423844)")
+        public void testIfAddTrackSucceedsAfterStart() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            try {
+                MediaFormat format = new MediaFormat();
+                format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+                muxer.addTrack(format);
+                muxer.start();
+                muxer.addTrack(format);
+                fail("muxer.addTrack() succeeded after muxer.start()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfAddTrackSucceedsAfterWriteSampleData() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            try {
+                MediaFormat format = new MediaFormat();
+                format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+                int trackID = muxer.addTrack(format);
+                muxer.start();
+                insertPerFrameSubtitles(muxer, 0, trackID);
+                muxer.addTrack(format);
+                fail("muxer.addTrack() succeeded after muxer.writeSampleData()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfAddTrackSucceedsAfterStop() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            try {
+                MediaFormat format = new MediaFormat();
+                format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+                int trackID = muxer.addTrack(format);
+                muxer.start();
+                insertPerFrameSubtitles(muxer, 0, trackID);
+                muxer.stop();
+                muxer.addTrack(format);
+                fail("muxer.addTrack() succeeded after muxer.stop()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfAddTrackSucceedsAfterRelease() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            try {
+                MediaFormat format = new MediaFormat();
+                format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+                muxer.release();
+                muxer.addTrack(format);
+                fail("muxer.addTrack() succeeded after muxer.release()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfMuxerStartsBeforeAddTrack() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+            try {
+                muxer.start();
+                fail("muxer.start() succeeded before muxer.addTrack()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        @Ignore("TODO(b/146423844)")
+        public void testIdempotentStart() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+            try {
+                muxer.addTrack(format);
+                muxer.start();
+                muxer.start();
+                fail("muxer.start() succeeded after muxer.start()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfMuxerStartsAfterWriteSampleData() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+            try {
+                int trackID = muxer.addTrack(format);
+                muxer.start();
+                insertPerFrameSubtitles(muxer, 0, trackID);
+                muxer.start();
+                fail("muxer.start() succeeded after muxer.writeSampleData()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfMuxerStartsAfterStop() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+            try {
+                int trackID = muxer.addTrack(format);
+                muxer.start();
+                insertPerFrameSubtitles(muxer, 0, trackID);
+                muxer.stop();
+                muxer.start();
+                fail("muxer.start() succeeded after muxer.stop()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfMuxerStartsAfterRelease() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+
+            try {
+                muxer.release();
+                muxer.start();
+                fail("muxer.start() succeeded after muxer.release()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testStopOnANonStartedMuxer() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+
+            try {
+                muxer.stop();
+                fail("muxer.stop() succeeded before muxer.start()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIdempotentStop() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+            try {
+                int trackID = muxer.addTrack(format);
+                muxer.start();
+                insertPerFrameSubtitles(muxer, 0, trackID);
+                muxer.stop();
+                muxer.stop();
+                fail("muxer.stop() succeeded after muxer.stop()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testStopAfterRelease() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            try {
+                muxer.release();
+                muxer.stop();
+                fail("muxer.stop() succeeded after muxer.release()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        @Ignore("TODO(b/146423844)")
+        public void testSimpleStartStopMuxer() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+            try {
+                muxer.addTrack(format);
+                muxer.start();
+                muxer.stop();
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfWriteSampleDataRejectsInvalidTrackIndex() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+            int trackID = muxer.addTrack(format);
+            muxer.start();
+            insertPerFrameSubtitles(muxer, 22000, trackID);
+            try {
+                insertPerFrameSubtitles(muxer, 0, trackID - 1);
+                fail("muxer.writeSampleData() succeeded with bad argument, trackIndex");
+            } catch (IllegalArgumentException e) {
+                // expected
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfWriteSampleDataRejectsNullByteBuffer() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+            int trackID = muxer.addTrack(format);
+            muxer.start();
+            insertPerFrameSubtitles(muxer, 22000, trackID);
+
+            MediaCodec.BufferInfo metaInfo = new MediaCodec.BufferInfo();
+            metaInfo.offset = 0;
+            metaInfo.size = 24;
+            metaInfo.presentationTimeUs = 0;
+            metaInfo.flags = 0;
+            try {
+                muxer.writeSampleData(trackID, null, metaInfo);
+                fail("muxer.writeSampleData() succeeded with bad argument, byteBuffer = null");
+            } catch (IllegalArgumentException e) {
+                // expected
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfWriteSampleDataRejectsNullBuffInfo() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+            int trackID = muxer.addTrack(format);
+            muxer.start();
+            insertPerFrameSubtitles(muxer, 22000, trackID);
+
+            byte[] greeting = "hello world".getBytes(StandardCharsets.UTF_8);
+            ByteBuffer metaBuff = ByteBuffer.allocate(greeting.length);
+            metaBuff.put(greeting);
+
+            try {
+                muxer.writeSampleData(trackID, metaBuff, null);
+                fail("muxer.writeSampleData() succeeded with bad argument, byteBuffer = null");
+            } catch (IllegalArgumentException e) {
+                // expected
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfWriteSampleDataRejectsInvalidBuffInfo() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+            int trackID = muxer.addTrack(format);
+            muxer.start();
+            insertPerFrameSubtitles(muxer, 22000, trackID);
+
+            byte[] greeting = "hello world".getBytes(StandardCharsets.UTF_8);
+            ByteBuffer metaBuff = ByteBuffer.allocate(greeting.length);
+            metaBuff.put(greeting);
+
+            MediaCodec.BufferInfo metaInfo = new MediaCodec.BufferInfo();
+
+            try {
+                // invalid metaData : buffer offset < 0
+                try {
+                    metaInfo.offset = -1;
+                    metaInfo.size = greeting.length;
+                    metaInfo.presentationTimeUs = 0;
+                    metaInfo.flags = 0;
+                    muxer.writeSampleData(trackID, metaBuff, metaInfo);
+                    fail("muxer.writeSampleData() succeeded with bad argument, bufInfo.offset < 0");
+                } catch (IllegalArgumentException e) {
+                    // expected
+                }
+
+                // invalid metaData : buffer size < 0
+                try {
+                    metaInfo.offset = 0;
+                    metaInfo.size = -1;
+                    metaInfo.presentationTimeUs = 0;
+                    metaInfo.flags = 0;
+                    muxer.writeSampleData(trackID, metaBuff, metaInfo);
+                    fail("muxer.writeSampleData() succeeded with bad argument, buffInfo.size < 0");
+                } catch (IllegalArgumentException e) {
+                    // expected
+                }
+
+                // invalid metaData : buffer size > capacity
+                try {
+                    metaInfo.offset = 0;
+                    metaInfo.size = greeting.length * 2;
+                    metaInfo.presentationTimeUs = 0;
+                    metaInfo.flags = 0;
+                    muxer.writeSampleData(trackID, metaBuff, metaInfo);
+                    fail("muxer.writeSampleData() succeeded with bad argument, buffInfo.size > " +
+                            "byteBuffer.capacity()");
+                } catch (IllegalArgumentException e) {
+                    // expected
+                }
+
+                // invalid metaData : buffer offset + size > capacity
+                try {
+                    metaInfo.offset = 1;
+                    metaInfo.size = greeting.length;
+                    metaInfo.presentationTimeUs = 0;
+                    metaInfo.flags = 0;
+                    muxer.writeSampleData(trackID, metaBuff, metaInfo);
+                    fail("muxer.writeSampleData() succeeded with bad argument, bufferInfo.offset " +
+                            "+ bufferInfo.size > byteBuffer.capacity()");
+                } catch (IllegalArgumentException e) {
+                    // expected
+                }
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        @Ignore("TODO(b/147128377)")
+        public void testIfWriteSampleDataRejectsInvalidPts() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+            int trackID = muxer.addTrack(format);
+            muxer.start();
+            insertPerFrameSubtitles(muxer, 22000, trackID);
+            try {
+                insertPerFrameSubtitles(muxer, -33000, trackID);
+                fail("muxer.writeSampleData() succeeded with bad argument, presentationTime");
+            } catch (IllegalArgumentException e) {
+                // expected
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfWriteSampleDataSucceedsBeforeStart() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+            try {
+                int trackID = muxer.addTrack(format);
+                insertPerFrameSubtitles(muxer, 0, trackID);
+                fail("muxer.WriteSampleData() succeeds before muxer.start()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfWriteSampleDataSucceedsAfterStop() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+            try {
+                int trackID = muxer.addTrack(format);
+                muxer.start();
+                insertPerFrameSubtitles(muxer, 0, trackID);
+                muxer.stop();
+                insertPerFrameSubtitles(muxer, 0, trackID);
+                fail("muxer.WriteSampleData() succeeds after muxer.stop()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIfWriteSampleDataSucceedsAfterRelease() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            MediaFormat format = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, MediaFormat.MIMETYPE_TEXT_SUBRIP);
+
+            try {
+                int trackID = muxer.addTrack(format);
+                muxer.start();
+                insertPerFrameSubtitles(muxer, 0, trackID);
+                muxer.release();
+                insertPerFrameSubtitles(muxer, 0, trackID);
+                fail("muxer.WriteSampleData() succeeds after muxer.release()");
+            } catch (IllegalStateException e) {
+                // expected
+            } catch (Exception e) {
+                fail(e.getMessage());
+            } finally {
+                muxer.release();
+            }
+        }
+
+        @Test
+        public void testIdempotentRelease() throws IOException {
+            MediaMuxer muxer = new MediaMuxer(mOutLoc, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
+            try {
+                muxer.release();
+                muxer.release();
+            } catch (Exception e) {
+                fail(e.getMessage());
+            }
+        }
+    }
+
+    @SmallTest
+    public static class TestApiNative {
+        @Rule
+        public TestName testName = new TestName();
+
+        static {
+            System.loadLibrary("ctsmediav2muxer_jni");
+        }
+
+        @Before
+        public void prologue() throws IOException {
+            File mOutMedia = File.createTempFile(testName.getMethodName(), ".out");
+            mOutLoc = mOutMedia.getAbsolutePath();
+        }
+
+        @After
+        public void epilogue() {
+            new File(mOutLoc).delete();
+        }
+
+        private String mOutLoc;
+
+        private native boolean nativeTestIfInvalidFdIsRejected();
+        private native boolean nativeTestIfReadOnlyFdIsRejected(String outPath);
+        private native boolean nativeTestIfWriteOnlyFdIsRejected(String outPath);
+        private native boolean nativeTestIfNonSeekableFdIsRejected(String outPath);
+        private native boolean nativeTestIfInvalidOutputFormatIsRejected(String outPath);
+
+        private native boolean nativeTestIfInvalidMediaFormatIsRejected(String outPath);
+        private native boolean nativeTestIfCorruptMediaFormatIsRejected(String outPath);
+        private native boolean nativeTestIfAddTrackSucceedsAfterStart(String outPath);
+        private native boolean nativeTestIfAddTrackSucceedsAfterWriteSampleData(String outPath);
+        private native boolean nativeTestIfAddTrackSucceedsAfterStop(String outPath);
+
+        private native boolean nativeTestIfMuxerStartsBeforeAddTrack(String outPath);
+        private native boolean nativeTestIdempotentStart(String outPath);
+        private native boolean nativeTestIfMuxerStartsAfterWriteSampleData(String outPath);
+        private native boolean nativeTestIfMuxerStartsAfterStop(String outPath);
+
+        private native boolean nativeTestStopOnANonStartedMuxer(String outPath);
+        private native boolean nativeTestIdempotentStop(String outPath);
+        private native boolean nativeTestSimpleStartStop(String outPath);
+
+        private native boolean nativeTestIfWriteSampleDataRejectsInvalidTrackIndex(String outPath);
+        private native boolean nativeTestIfWriteSampleDataRejectsInvalidPts(String outPath);
+        private native boolean nativeTestIfWriteSampleDataSucceedsBeforeStart(String outPath);
+        private native boolean nativeTestIfWriteSampleDataSucceedsAfterStop(String outPath);
+
+        @Test
+        @Ignore("TODO(b/146417874)")
+        public void testIfInvalidFdIsRejected() {
+            assertTrue(nativeTestIfInvalidFdIsRejected());
+        }
+
+        @Test
+        @Ignore("TODO(b/146417874)")
+        public void testIfReadOnlyFdIsRejected() {
+            assertTrue(nativeTestIfReadOnlyFdIsRejected(mOutLoc));
+        }
+
+        @Test
+        @Ignore("TODO(b/146417874)")
+        public void testIfWriteOnlyFdIsRejected() {
+            assertTrue(nativeTestIfWriteOnlyFdIsRejected(mOutLoc));
+        }
+
+        @Test
+        @Ignore("TODO(b/146417874)")
+        public void testIfNonSeekableFdIsRejected() {
+            assertTrue(nativeTestIfNonSeekableFdIsRejected(mOutLoc));
+        }
+
+        @Test
+        @Ignore("TODO(b/146417874)")
+        public void testIfInvalidOutputFormatIsRejected() {
+            assertTrue(nativeTestIfInvalidOutputFormatIsRejected(mOutLoc));
+        }
+
+        @Test
+        public void testIfInvalidMediaFormatIsRejected() {
+            assertTrue(nativeTestIfInvalidMediaFormatIsRejected(mOutLoc));
+        }
+
+        @Test
+        @Ignore("TODO(b/146923138)")
+        public void testIfCorruptMediaFormatIsRejected() {
+            assertTrue(nativeTestIfCorruptMediaFormatIsRejected(mOutLoc));
+        }
+
+        @Test
+        public void testIfAddTrackSucceedsAfterStart() {
+            assertTrue(nativeTestIfAddTrackSucceedsAfterStart(mOutLoc));
+        }
+
+        @Test
+        public void testIfAddTrackSucceedsAfterWriteSampleData() {
+            assertTrue(nativeTestIfAddTrackSucceedsAfterWriteSampleData(mOutLoc));
+        }
+
+        @Test
+        public void testIfAddTrackSucceedsAfterStop() {
+            assertTrue(nativeTestIfAddTrackSucceedsAfterStop(mOutLoc));
+        }
+
+        @Test
+        public void testIfMuxerStartsBeforeAddTrack() {
+            assertTrue(nativeTestIfMuxerStartsBeforeAddTrack(mOutLoc));
+        }
+
+        @Test
+        public void testIdempotentStart() {
+            assertTrue(nativeTestIdempotentStart(mOutLoc));
+        }
+
+        @Test
+        public void testIfMuxerStartsAfterWriteSampleData() {
+            assertTrue(nativeTestIfMuxerStartsAfterWriteSampleData(mOutLoc));
+        }
+
+        @Test
+        public void testIfMuxerStartsAfterStop() {
+            assertTrue(nativeTestIfMuxerStartsAfterStop(mOutLoc));
+        }
+
+        @Test
+        public void testStopOnANonStartedMuxer() {
+            assertTrue(nativeTestStopOnANonStartedMuxer(mOutLoc));
+        }
+
+        @Test
+        public void testIdempotentStop() {
+            assertTrue(nativeTestIdempotentStop(mOutLoc));
+        }
+
+        @Test
+        @Ignore("TODO(b/146423844)")
+        public void testSimpleStartStopMuxer() {
+            assertTrue(nativeTestSimpleStartStop(mOutLoc));
+        }
+
+        @Test
+        public void testIfWriteSampleDataRejectsInvalidTrackIndex() {
+            assertTrue(nativeTestIfWriteSampleDataRejectsInvalidTrackIndex(mOutLoc));
+        }
+
+        @Test
+        @Ignore("TODO(b/147128377)")
+        public void testIfWriteSampleDataRejectsInvalidPts() {
+            assertTrue(nativeTestIfWriteSampleDataRejectsInvalidPts(mOutLoc));
+        }
+
+        @Test
+        public void testIfWriteSampleDataSucceedsBeforeStart() {
+            assertTrue(nativeTestIfWriteSampleDataSucceedsBeforeStart(mOutLoc));
+        }
+
+        @Test
+        public void testIfWriteSampleDataSucceedsAfterStop() {
+            assertTrue(nativeTestIfWriteSampleDataSucceedsAfterStop(mOutLoc));
+        }
+    }
+}
diff --git a/tests/signature/lib/common/src/android/signature/cts/DexMethod.java b/tests/signature/lib/common/src/android/signature/cts/DexMethod.java
index dfe1934..967a5a8 100644
--- a/tests/signature/lib/common/src/android/signature/cts/DexMethod.java
+++ b/tests/signature/lib/common/src/android/signature/cts/DexMethod.java
@@ -23,10 +23,14 @@
 
 public class DexMethod extends DexMember {
   private final List<String> mParamTypeList;
-  private static final Pattern REGEX_SIGNATURE = Pattern.compile("^\\((.*)\\)(.*)$");
+
+  private enum ParseType {
+      DEX_TYPE_LIST,
+      DEX_RETURN_TYPE,
+  }
 
   public DexMethod(String className, String name, String signature, String[] flags) {
-      super(className, name, parseDexReturnType(signature), flags);
+      super(className, name, parseSignature(signature, ParseType.DEX_RETURN_TYPE), flags);
       mParamTypeList = parseDexTypeList(signature);
   }
 
@@ -52,20 +56,24 @@
               + "(" + String.join(", ", getJavaParameterTypes()) + ")";
   }
 
-  private static Matcher matchSignature(String signature) {
-      Matcher m = REGEX_SIGNATURE.matcher(signature);
-      if (!m.matches()) {
+  private static String parseSignature(String signature, ParseType type) {
+      // Form of signature:
+      //   (DexTypeList)DexReturnType
+      int length = signature.length();
+      int paren = signature.indexOf(')', 1);
+      if (length < 2 || signature.charAt(0) != '(' || paren == -1) {
           throw new RuntimeException("Could not parse method signature: " + signature);
       }
-      return m;
-  }
-
-  private static String parseDexReturnType(String signature) {
-      return matchSignature(signature).group(2);
+      if (type == ParseType.DEX_TYPE_LIST) {
+          return signature.substring(1, paren);
+      } else {
+          // ParseType.DEX_RETURN_TYPE
+          return signature.substring(paren + 1, length);
+      }
   }
 
   private static List<String> parseDexTypeList(String signature) {
-      String typeSequence = matchSignature(signature).group(1);
+      String typeSequence = parseSignature(signature, ParseType.DEX_TYPE_LIST);
       List<String> list = new ArrayList<String>();
       while (!typeSequence.isEmpty()) {
           String type = firstDexTypeFromList(typeSequence);
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/.hash b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/.hash
new file mode 100644
index 0000000..9c76fef
--- /dev/null
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/1/.hash
@@ -0,0 +1 @@
+8d903ce236a40b41624907c4d1d7a651eca9f763  -
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/.hash b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/.hash
new file mode 100644
index 0000000..ad2b69c3
--- /dev/null
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/aidl_api/libbinder_ndk_test_interface/2/.hash
@@ -0,0 +1 @@
+8359746d4317c0ef0f014821d362c4cfe96e2166  -
diff --git a/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp b/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
index 1b31a8c..cee0944 100644
--- a/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
+++ b/tests/tests/binder_ndk/libbinder_ndk_test/test_native_aidl_client.cpp
@@ -803,6 +803,17 @@
   }
 }
 
+TEST_P(NdkBinderTest_Aidl, GetInterfaceHash) {
+  std::string res;
+  EXPECT_OK(iface->getInterfaceHash(&res));
+  if (GetParam().shouldBeOld) {
+    // aidl_api/libbinder_ndk_test_interface/1/.hash
+    EXPECT_EQ("8d903ce236a40b41624907c4d1d7a651eca9f763", res);
+  } else {
+    EXPECT_EQ("notfrozen", res);
+  }
+}
+
 std::shared_ptr<ITest> getProxyLocalService() {
   std::shared_ptr<MyTest> test = SharedRefBase::make<MyTest>();
   SpAIBinder binder = test->asBinder();
@@ -820,7 +831,7 @@
   //
   // Warning: for testing purposes only. This parcels things within the same process for testing
   // purposes. In normal usage, this should just return SharedRefBase::make<MyTest> directly.
-  return (new BpTest(binder))->ref<ITest>();
+  return SharedRefBase::make<BpTest>(binder);
 }
 
 std::shared_ptr<ITest> getNdkBinderTestJavaService(const std::string& method) {
diff --git a/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java b/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
index 21c40c7..814e1e4 100644
--- a/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
+++ b/tests/tests/binder_ndk/src/android/binder/cts/JavaClientTest.java
@@ -177,6 +177,9 @@
     private static class Empty extends IEmpty.Stub {
         @Override
         public int getInterfaceVersion() { return Empty.VERSION; }
+
+        @Override
+        public String getInterfaceHash() { return Empty.HASH; }
     }
 
     @Test
diff --git a/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java b/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java
index 872c166..7b60483 100644
--- a/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java
+++ b/tests/tests/binder_ndk/src/android/binder/cts/TestImpl.java
@@ -37,6 +37,9 @@
   public int getInterfaceVersion() { return TestImpl.VERSION; }
 
   @Override
+  public String getInterfaceHash() { return TestImpl.HASH; }
+
+  @Override
   protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
     for (String arg : args) {
       pw.print(arg);
diff --git a/tests/tests/bionic/Android.build.copy.libs.mk b/tests/tests/bionic/Android.build.copy.libs.mk
index 5867547..8900a75 100644
--- a/tests/tests/bionic/Android.build.copy.libs.mk
+++ b/tests/tests/bionic/Android.build.copy.libs.mk
@@ -44,8 +44,13 @@
   libdlext_test_zip/libdlext_test_zip.so \
   libdlext_test_zip/libdlext_test_zip_zipaligned.zip \
   libgnu-hash-table-library.so \
-  librelr-new.so \
-  librelr-old.so \
+  libns_hidden_child_global.so \
+  libns_hidden_child_internal.so \
+  libns_hidden_child_public.so \
+  librelocations-fat.so \
+  librelocations-ANDROID_RELR.so \
+  librelocations-ANDROID_REL.so \
+  librelocations-RELR.so \
   libsegment_gap_inner.so \
   libsegment_gap_outer.so \
   libsysv-hash-table-library.so \
@@ -133,6 +138,8 @@
   ns_a/libnstest_ns_a_public1_internal.so \
   ns_b/libnstest_ns_b_public2.so \
   ns_b/libnstest_ns_b_public3.so \
+  ns_hidden_child_app/libns_hidden_child_app.so \
+  ns_hidden_child_helper/ns_hidden_child_helper \
   prebuilt-elf-files/libtest_invalid-empty_shdr_table.so \
   prebuilt-elf-files/libtest_invalid-rw_load_segment.so \
   prebuilt-elf-files/libtest_invalid-textrels.so \
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
index bee4729..b3bc692 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
@@ -74,7 +74,7 @@
 
     @Override
     public void setUp() {
-        if (!isBleSupported()) {
+        if (!TestUtils.isBleSupported(getContext())) {
             return;
         }
         BluetoothManager manager = (BluetoothManager) mContext.getSystemService(
@@ -84,7 +84,7 @@
             // Note it's not reliable to listen for Adapter.ACTION_STATE_CHANGED broadcast and check
             // bluetooth state.
             mBluetoothAdapter.enable();
-            sleep(ADAPTER_ENABLE_TIMEOUT);
+            TestUtils.sleep(ADAPTER_ENABLE_TIMEOUT);
         }
         mScanner = mBluetoothAdapter.getBluetoothLeScanner();
         mLocationOn = TestUtils.isLocationOn(getContext());
@@ -97,7 +97,7 @@
 
     @Override
     public void tearDown() {
-        if (!isBleSupported()) {
+        if (!TestUtils.isBleSupported(getContext())) {
           // mBluetoothAdapter == null.
           return;
         }
@@ -106,7 +106,7 @@
             TestUtils.disableLocation(getContext());
         }
         mBluetoothAdapter.disable();
-        sleep(ADAPTER_ENABLE_TIMEOUT);
+        TestUtils.sleep(ADAPTER_ENABLE_TIMEOUT);
     }
 
     /**
@@ -114,7 +114,7 @@
      */
     @MediumTest
     public void testBasicBleScan() {
-        if (!isBleSupported()) {
+        if (!TestUtils.isBleSupported(getContext())) {
             return;
         }
         long scanStartMillis = SystemClock.elapsedRealtime();
@@ -131,7 +131,7 @@
      */
     @MediumTest
     public void testScanFilter() {
-        if (!isBleSupported()) {
+        if (!TestUtils.isBleSupported(getContext())) {
             return;
         }
 
@@ -147,9 +147,9 @@
         ScanSettings settings = new ScanSettings.Builder().setScanMode(
                 ScanSettings.SCAN_MODE_LOW_LATENCY).build();
         mScanner.startScan(filters, settings, filterLeScanCallback);
-        sleep(SCAN_DURATION_MILLIS);
+        TestUtils.sleep(SCAN_DURATION_MILLIS);
         mScanner.stopScan(filterLeScanCallback);
-        sleep(SCAN_STOP_TIMEOUT);
+        TestUtils.sleep(SCAN_STOP_TIMEOUT);
         Collection<ScanResult> scanResults = filterLeScanCallback.getScanResults();
         for (ScanResult result : scanResults) {
             assertTrue(filter.matches(result));
@@ -195,7 +195,7 @@
 //     */
 //    @MediumTest
 //    public void testOpportunisticScan() {
-//        if (!isBleSupported()) {
+//        if (!TestUtils.isBleSupported(getContext())) {
 //            return;
 //        }
 //        ScanSettings opportunisticScanSettings = new ScanSettings.Builder()
@@ -245,7 +245,7 @@
      */
     @MediumTest
     public void testBatchScan() {
-        if (!isBleSupported() || !isBleBatchScanSupported()) {
+        if (!TestUtils.isBleSupported(getContext()) || !isBleBatchScanSupported()) {
             Log.d(TAG, "BLE or BLE batching not suppported");
             return;
         }
@@ -255,7 +255,7 @@
         BleScanCallback batchScanCallback = new BleScanCallback();
         mScanner.startScan(Collections.<ScanFilter>emptyList(), batchScanSettings,
                 batchScanCallback);
-        sleep(SCAN_DURATION_MILLIS);
+        TestUtils.sleep(SCAN_DURATION_MILLIS);
         mScanner.flushPendingScanResults(batchScanCallback);
         mFlushBatchScanLatch = new CountDownLatch(1);
         List<ScanResult> results = batchScanCallback.getBatchScanResults();
@@ -276,7 +276,7 @@
      */
     @MediumTest
     public void testStartScanPendingIntent_nullnull() throws Exception {
-        if (!isBleSupported() || !isBleBatchScanSupported()) {
+        if (!TestUtils.isBleSupported(getContext()) || !isBleBatchScanSupported()) {
             Log.d(TAG, "BLE or BLE batching not suppported");
             return;
         }
@@ -295,7 +295,7 @@
      */
     @MediumTest
     public void testStartScanPendingIntent() throws Exception {
-        if (!isBleSupported() || !isBleBatchScanSupported()) {
+        if (!TestUtils.isBleSupported(getContext()) || !isBleBatchScanSupported()) {
             Log.d(TAG, "BLE or BLE batching not suppported");
             return;
         }
@@ -384,27 +384,12 @@
     private Set<ScanResult> scan() {
         BleScanCallback regularLeScanCallback = new BleScanCallback();
         mScanner.startScan(regularLeScanCallback);
-        sleep(SCAN_DURATION_MILLIS);
+        TestUtils.sleep(SCAN_DURATION_MILLIS);
         mScanner.stopScan(regularLeScanCallback);
-        sleep(SCAN_STOP_TIMEOUT);
+        TestUtils.sleep(SCAN_STOP_TIMEOUT);
         return regularLeScanCallback.getScanResults();
     }
 
-    // Put the current thread to sleep.
-    private void sleep(int sleepMillis) {
-        try {
-            Thread.sleep(sleepMillis);
-        } catch (InterruptedException e) {
-            Log.e(TAG, "interrupted", e);
-        }
-    }
-
-    // Check if Bluetooth LE feature is supported on DUT.
-    private boolean isBleSupported() {
-        return getContext().getPackageManager()
-                .hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
-    }
-
     // Returns whether offloaded scan batching is supported.
     private boolean isBleBatchScanSupported() {
         return mBluetoothAdapter.isOffloadedScanBatchingSupported();
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java
new file mode 100644
index 0000000..433a39f
--- /dev/null
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.bluetooth.cts;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothServerSocket;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+
+import java.io.IOException;
+
+public class LeL2capSocketTest extends AndroidTestCase {
+
+    private static final int NUM_ITERATIONS_FOR_REPEATED_TEST = 100;
+    private static final int ADAPTER_TOGGLE_TIMEOUT_MS = 4000;
+
+    private BluetoothAdapter mAdapter = null;
+
+    @Override
+    public void setUp() throws Exception {
+        super.setUp();
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        assertNotNull("BluetoothAdapter.getDefaultAdapter() returned null. "
+                + "Does this device have a Bluetooth adapter?", mAdapter);
+        if (!mAdapter.isEnabled()) {
+            // Note: It's not reliable to listen for Adapter.ACTION_STATE_CHANGED broadcast and
+            // check bluetooth state per comments from BluetoothLeScanTest.java
+            mAdapter.enable();
+            TestUtils.sleep(ADAPTER_TOGGLE_TIMEOUT_MS);
+        }
+        assertTrue("Bluetooth failed to be enabled", mAdapter.isEnabled());
+    }
+
+    @Override
+    public void tearDown() throws Exception {
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+        mAdapter.disable();
+        TestUtils.sleep(ADAPTER_TOGGLE_TIMEOUT_MS);
+        assertFalse("Bluetooth failed to be disabled", mAdapter.isEnabled());
+        mAdapter = null;
+        super.tearDown();
+    }
+
+
+    @SmallTest
+    public void testOpenInsecureLeL2capServerSocketOnce() {
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+        assertTrue("Bluetooth is not enabled", mAdapter.isEnabled());
+        try {
+            final BluetoothServerSocket serverSocket = mAdapter.listenUsingInsecureL2capChannel();
+            assertNotNull("Failed to get server socket", serverSocket);
+            serverSocket.close();
+        } catch (IOException exp) {
+            fail("IOException while opening and closing server socket: " + exp);
+        }
+    }
+
+    @SmallTest
+    public void testOpenInsecureLeL2capServerSocketRepeatedly() {
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+        assertTrue("Bluetooth is not enabled", mAdapter.isEnabled());
+        try {
+            for (int i = 0; i < NUM_ITERATIONS_FOR_REPEATED_TEST; i++) {
+                final BluetoothServerSocket serverSocket =
+                        mAdapter.listenUsingInsecureL2capChannel();
+                assertNotNull("Failed to get server socket", serverSocket);
+                serverSocket.close();
+            }
+        } catch (IOException exp) {
+            fail("IOException while opening and closing server socket: " + exp);
+        }
+    }
+
+    @SmallTest
+    public void testOpenSecureLeL2capServerSocketOnce() {
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+        assertTrue("Bluetooth is not enabled", mAdapter.isEnabled());
+        try {
+            final BluetoothServerSocket serverSocket = mAdapter.listenUsingL2capChannel();
+            assertNotNull("Failed to get server socket", serverSocket);
+            serverSocket.close();
+        } catch (IOException exp) {
+            fail("IOException while opening and closing server socket: " + exp);
+        }
+    }
+
+    @SmallTest
+    public void testOpenSecureLeL2capServerSocketRepeatedly() {
+        if (!TestUtils.isBleSupported(getContext())) {
+            return;
+        }
+        assertTrue("Bluetooth is not enabled", mAdapter.isEnabled());
+        try {
+            for (int i = 0; i < NUM_ITERATIONS_FOR_REPEATED_TEST; i++) {
+                final BluetoothServerSocket serverSocket = mAdapter.listenUsingL2capChannel();
+                assertNotNull("Failed to get server socket", serverSocket);
+                serverSocket.close();
+            }
+        } catch (IOException exp) {
+            fail("IOException while opening and closing server socket: " + exp);
+        }
+    }
+}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java b/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
index 327a2b8..342266f 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/TestUtils.java
@@ -18,7 +18,9 @@
 
 import android.bluetooth.le.ScanRecord;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.provider.Settings;
+import android.util.Log;
 
 import junit.framework.Assert;
 
@@ -91,4 +93,25 @@
     static void disableLocation(Context context) {
         setLocationMode(context, Settings.Secure.LOCATION_MODE_OFF);
     }
+
+    /**
+     * Check if BLE is supported by this platform
+     * @param context current device context
+     * @return true if BLE is supported, false otherwise
+     */
+    static boolean isBleSupported(Context context) {
+        return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE);
+    }
+
+    /**
+     * Put the current thread to sleep.
+     * @param sleepMillis number of milliseconds to sleep for
+     */
+    static void sleep(int sleepMillis) {
+        try {
+            Thread.sleep(sleepMillis);
+        } catch (InterruptedException e) {
+            Log.e(TestUtils.class.getSimpleName(), "interrupted", e);
+        }
+    }
 }
\ No newline at end of file
diff --git a/tests/tests/dreams/OWNERS b/tests/tests/dreams/OWNERS
index 635efda..f70efe9 100644
--- a/tests/tests/dreams/OWNERS
+++ b/tests/tests/dreams/OWNERS
@@ -1,3 +1,3 @@
-# Bug component: 136515
+# Bug component: 345010
 michaelwr@google.com
 santoscordon@google.com
diff --git a/tests/tests/hardware/src/android/hardware/lights/cts/LightsManagerTest.java b/tests/tests/hardware/src/android/hardware/lights/cts/LightsManagerTest.java
new file mode 100755
index 0000000..a30ca46
--- /dev/null
+++ b/tests/tests/hardware/src/android/hardware/lights/cts/LightsManagerTest.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.lights.cts.tests;
+
+import static android.hardware.lights.LightsRequest.Builder;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Context;
+import android.hardware.lights.Light;
+import android.hardware.lights.LightState;
+import android.hardware.lights.LightsManager;
+
+import androidx.annotation.ColorInt;
+import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class LightsManagerTest {
+
+    private static final @ColorInt int TAN = 0xffd2b48c;
+    private static final @ColorInt int RED = 0xffff0000;
+
+    private LightsManager mManager;
+    private List<Light> mLights;
+
+    @Before
+    public void setUp() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .adoptShellPermissionIdentity(
+                        android.Manifest.permission.CONTROL_DEVICE_LIGHTS);
+
+        final Context context = InstrumentationRegistry.getTargetContext();
+        mManager = context.getSystemService(LightsManager.class);
+        mLights = mManager.getLights();
+    }
+
+    @After
+    public void tearDown() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+    }
+
+    @Test
+    public void testControlLightsPermissionIsRequiredToUseLights() {
+        InstrumentationRegistry.getInstrumentation().getUiAutomation()
+                .dropShellPermissionIdentity();
+        try {
+            mManager.getLights();
+            fail("Expected SecurityException to be thrown for getLights()");
+        } catch (SecurityException expected) {
+        }
+
+        try (LightsManager.LightsSession session = mManager.openSession()) {
+            fail("Expected SecurityException to be thrown for openSession()");
+        } catch (SecurityException expected) {
+        }
+    }
+
+    @Test
+    public void testControlSingleLight() {
+        assumeTrue(mLights.size() >= 1);
+
+        try (LightsManager.LightsSession session = mManager.openSession()) {
+            // When the session requests to turn a single light on:
+            session.setLights(new Builder()
+                    .setLight(mLights.get(0), new LightState(RED))
+                    .build());
+
+            // Then the light should turn on.
+            assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(RED);
+        }
+    }
+
+    @Test
+    public void testControlMultipleLights() {
+        assumeTrue(mLights.size() >= 2);
+
+        try (LightsManager.LightsSession session = mManager.openSession()) {
+            // When the session requests to turn two of the lights on:
+            session.setLights(new Builder()
+                    .setLight(mLights.get(0), new LightState(0xffaaaaff))
+                    .setLight(mLights.get(1), new LightState(0xffbbbbff))
+                    .build());
+
+            // Then both should turn on.
+            assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(0xffaaaaff);
+            assertThat(mManager.getLightState(mLights.get(1)).getColor()).isEqualTo(0xffbbbbff);
+
+            // Any others should remain off.
+            for (int i = 2; i < mLights.size(); i++) {
+                assertThat(mManager.getLightState(mLights.get(i)).getColor()).isEqualTo(0x00);
+            }
+        }
+    }
+
+    @Test
+    public void testControlLights_onlyEffectiveForLifetimeOfClient() {
+        assumeTrue(mLights.size() >= 1);
+
+        // The light should begin by being off.
+        assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(0x00);
+
+        try (LightsManager.LightsSession session = mManager.openSession()) {
+            // When a session commits changes:
+            session.setLights(new Builder().setLight(mLights.get(0), new LightState(TAN)).build());
+            // Then the light should turn on.
+            assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(TAN);
+
+            // When the session goes away:
+            session.close();
+            // Then the light should turn off.
+            assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(0x00);
+        }
+    }
+
+    @Test
+    public void testControlLights_firstCallerWinsContention() {
+        assumeTrue(mLights.size() >= 1);
+
+        try (LightsManager.LightsSession session1 = mManager.openSession();
+                LightsManager.LightsSession session2 = mManager.openSession()) {
+
+            // When session1 and session2 both request the same light:
+            session1.setLights(new Builder().setLight(mLights.get(0), new LightState(TAN)).build());
+            session2.setLights(new Builder().setLight(mLights.get(0), new LightState(RED)).build());
+            // Then session1 should win because it was created first.
+            assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(TAN);
+
+            // When session1 goes away:
+            session1.close();
+            // Then session2 should have its request go into effect.
+            assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(RED);
+
+            // When session2 goes away:
+            session2.close();
+            // Then the light should turn off because there are no more sessions.
+            assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(0);
+        }
+    }
+
+    @Test
+    public void testClearLight() {
+        assumeTrue(mLights.size() >= 1);
+
+        try (LightsManager.LightsSession session = mManager.openSession()) {
+            // When the session turns a light on:
+            session.setLights(new Builder().setLight(mLights.get(0), new LightState(RED)).build());
+            // And then the session clears it again:
+            session.setLights(new Builder().clearLight(mLights.get(0)).build());
+            // Then the light should turn back off.
+            assertThat(mManager.getLightState(mLights.get(0)).getColor()).isEqualTo(0);
+        }
+    }
+}
diff --git a/tests/tests/identity/src/android/security/identity/cts/AttestationTest.java b/tests/tests/identity/src/android/security/identity/cts/AttestationTest.java
new file mode 100644
index 0000000..34b0cf2
--- /dev/null
+++ b/tests/tests/identity/src/android/security/identity/cts/AttestationTest.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.identity.cts;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+
+import android.security.identity.IdentityCredential;
+import android.security.identity.IdentityCredentialStore;
+import androidx.test.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.Ignore;
+
+import com.google.common.primitives.Bytes;
+
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.util.Collection;
+
+import java.util.Optional;
+
+public class AttestationTest {
+    private static final String TAG = "AttestationTest";
+
+    // The subset of Keymaster tags we care about. See
+    //
+    //   https://source.android.com/security/keystore/tags
+    //
+    // for a list of known tags.
+    //
+    public static final int KM_TAG_ATTESTATION_APPLICATION_ID = 709;
+    public static final int KM_TAG_IDENTITY_CREDENTIAL_KEY = 721;
+
+    @Ignore("Not ready until SW implementation produces correct attestations")
+    @Test
+    public void attestationTest() throws Exception {
+        Context appContext = InstrumentationRegistry.getTargetContext();
+        IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+
+        // Use a random challenge of length between 16 and 32.
+        SecureRandom random = new SecureRandom();
+        int challengeLength = 16 + random.nextInt(16);
+        byte[] challenge = new byte[challengeLength];
+        random.nextBytes(challenge);
+
+        String credentialName = "test";
+
+        store.deleteCredentialByName(credentialName);
+        Collection<X509Certificate> certChain = ProvisioningTest.createCredentialWithChallenge(
+            store, credentialName, challenge);
+        store.deleteCredentialByName("test");
+
+        // Check that each certificate in the chain isn't expired and also that it's signed by the
+        // next one.
+        assertTrue(verifyCertificateChain(certChain));
+
+        // Parse the attestation record... Identity Credential uses standard KeyMaster-style
+        // attestation with only a few differences:
+        //
+        // - Tag::IDENTITY_CREDENTIAL_KEY (boolean) must be set to identify the key as an Identity
+        //   Credential key.
+        //
+        // - the KeyMaster version and security-level should be set to that of Identity Credential
+        //
+        // In this test we only test for these two things and a) the attestationApplicationId
+        // matches the calling application; and b) the given challenge matches what was sent.
+        //
+        // We could test for all the other expected attestation records but currently we assume
+        // this is already tested by Keymaster/Android Keystore tests since the TA is expected to
+        // re-use the same infrastructure.
+        //
+        X509Certificate cert = certChain.iterator().next();
+        ParsedAttestationRecord record = new ParsedAttestationRecord(cert);
+
+        // Need at least attestation version 3 and attestations to be done in either the TEE
+        // or in a StrongBox.
+        assertTrue(record.getAttestationVersion() >= 3);
+
+        // Also per the IC HAL, the keymasterSecurityLevel field is used as the securityLevel of
+        // the IC TA and this must be either TEE or in a StrongBox... except we specifically
+        // allow our software implementation to report Software here.
+        //
+        // Since we cannot get the implementation name or author at this layer, we can't test for
+        // it. This can be tested for in the VTS test, however.
+
+        // As per the IC HAL, the keymasterVersion field should be the version of the Identity
+        // Credential HAL - 1.0 - and this is encoded as major*10 + minor. This field is used by
+        // Keymaster which is known to report integers less than or equal to 4 (for KM up to 4.0)
+        // and integers greater or equal than 41 (for KM starting with 4.1).
+        //
+        // Since we won't get to version 4.0 of the IC HAL for a while, let's also check that a KM
+        // version isn't errornously returned.
+        assertTrue(record.getKeymasterVersion() >= 10);
+        assertTrue(record.getKeymasterVersion() < 40);
+
+        // Check that the challenge we passed in, is in fact in the attestation record.
+        assertArrayEquals(challenge, record.getAttestationChallenge());
+
+        // Tag::ATTESTATION_APPLICATION_ID is used to identify the set of possible applications of
+        // which one has initiated a key attestation. This is complicated ASN.1 which we don't want
+        // to parse and we don't need to [1].. we can however easily check that our applicationId
+        // is appearing as a substring somewhere in this blob.
+        //
+        // [1] : and the point of this test isn't to verify that attestations are done correctly,
+        // that's tested elsewhere in e.g. KM and Android Keystore.
+        //
+        Optional<byte[]> attestationApplicationId =
+                record.getSoftwareAuthorizationByteString(KM_TAG_ATTESTATION_APPLICATION_ID);
+        assertTrue(attestationApplicationId.isPresent());
+        String appId = appContext.getPackageName();
+        assertTrue(Bytes.indexOf(attestationApplicationId.get(), appId.getBytes()) != -1);
+
+        // Tag::IDENTITY_CREDENTIAL_KEY is used in attestations produced by the Identity Credential
+        // HAL when that HAL attests to Credential Keys.
+        boolean isIdentityCredentialKey =
+                record.getTeeAuthorizationBoolean(KM_TAG_IDENTITY_CREDENTIAL_KEY);
+        assertTrue(isIdentityCredentialKey);
+    }
+
+    // This only verifies each cert hasn't expired and signed by the next one.
+    private static boolean verifyCertificateChain(Collection<X509Certificate> certChain) {
+        X509Certificate[] certs = new X509Certificate[certChain.size()];
+        certs = certChain.toArray(certs);
+        X509Certificate parent = certs[certs.length - 1];
+        for (int i = certs.length - 1; i >= 0; i--) {
+            X509Certificate cert = certs[i];
+            try {
+                cert.checkValidity();
+                cert.verify(parent.getPublicKey());
+            } catch (Exception e) {
+                e.printStackTrace();
+                return false;
+            }
+            parent = cert;
+        }
+        return true;
+    }
+}
diff --git a/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java b/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java
index 6d11c7b..ac34c36 100644
--- a/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/DynamicAuthTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 
@@ -61,6 +62,9 @@
     public void dynamicAuthTest() throws Exception {
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
 
         String credentialName = "test";
 
diff --git a/tests/tests/identity/src/android/security/identity/cts/EphemeralKeyTest.java b/tests/tests/identity/src/android/security/identity/cts/EphemeralKeyTest.java
index 29e7311..a474729 100644
--- a/tests/tests/identity/src/android/security/identity/cts/EphemeralKeyTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/EphemeralKeyTest.java
@@ -21,6 +21,7 @@
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 import android.security.keystore.KeyProperties;
@@ -61,6 +62,9 @@
     public void createEphemeralKey() throws IdentityCredentialException {
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
 
         String credentialName = "ephemeralKeyTest";
 
@@ -131,8 +135,8 @@
                 PublicKey holderEphemeralPublicKey) throws IdentityCredentialException {
             mCipherSuite = cipherSuite;
             mHolderEphemeralPublicKey = holderEphemeralPublicKey;
-            mCounter = 0;
-            mMdlExpectedCounter = 0;
+            mCounter = 1;
+            mMdlExpectedCounter = 1;
 
             try {
                 KeyPairGenerator kpg = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_EC);
diff --git a/tests/tests/identity/src/android/security/identity/cts/ParsedAttestationRecord.java b/tests/tests/identity/src/android/security/identity/cts/ParsedAttestationRecord.java
new file mode 100644
index 0000000..e4297d7
--- /dev/null
+++ b/tests/tests/identity/src/android/security/identity/cts/ParsedAttestationRecord.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.security.identity.cts;
+
+import java.io.IOException;
+import java.security.cert.X509Certificate;
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Set;
+import java.util.Optional;
+
+import org.bouncycastle.asn1.ASN1InputStream;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Sequence;
+import org.bouncycastle.asn1.ASN1Boolean;
+import org.bouncycastle.asn1.ASN1Encodable;
+import org.bouncycastle.asn1.ASN1Enumerated;
+import org.bouncycastle.asn1.ASN1Integer;
+import org.bouncycastle.asn1.ASN1OctetString;
+import org.bouncycastle.asn1.ASN1Primitive;
+import org.bouncycastle.asn1.ASN1Set;
+import org.bouncycastle.asn1.ASN1TaggedObject;
+import org.bouncycastle.asn1.DEROctetString;
+
+// This code is loosely based on https://github.com/google/android-key-attestation
+
+class ParsedAttestationRecord {
+    private static final String KEY_DESCRIPTION_OID = "1.3.6.1.4.1.11129.2.1.17";
+
+    enum SecurityLevel {
+        SOFTWARE,
+        TRUSTED_ENVIRONMENT,
+        STRONG_BOX
+    }
+
+    private static final int ATTESTATION_VERSION_INDEX = 0;
+    private static final int ATTESTATION_SECURITY_LEVEL_INDEX = 1;
+    private static final int KEYMASTER_VERSION_INDEX = 2;
+    private static final int KEYMASTER_SECURITY_LEVEL_INDEX = 3;
+    private static final int ATTESTATION_CHALLENGE_INDEX = 4;
+    private static final int UNIQUE_ID_INDEX = 5;
+    private static final int SW_ENFORCED_INDEX = 6;
+    private static final int TEE_ENFORCED_INDEX = 7;
+
+    // Some security values. The complete list is in this AOSP file:
+    // hardware/libhardware/include/hardware/keymaster_defs.h
+    private static final int KM_SECURITY_LEVEL_SOFTWARE = 0;
+    private static final int KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT = 1;
+    private static final int KM_SECURITY_LEVEL_STRONG_BOX = 2;
+
+    private int attestationVersion;
+    private SecurityLevel attestationSecurityLevel;
+    private int keymasterVersion;
+    private SecurityLevel keymasterSecurityLevel;
+    private byte[] attestationChallenge;
+    private byte[] uniqueId;
+
+    private Map<Integer, ASN1Primitive> softwareEnforcedAuthorizations;
+    private Map<Integer, ASN1Primitive> teeEnforcedAuthorizations;
+
+    public int getAttestationVersion() {
+        return attestationVersion;
+    }
+
+    public SecurityLevel getAttestationSecurityLevel() {
+        return attestationSecurityLevel;
+    }
+
+    public int getKeymasterVersion() {
+        return keymasterVersion;
+    }
+
+    public SecurityLevel getKeymasterSecurityLevel() {
+        return attestationSecurityLevel;
+    }
+
+    public byte[] getAttestationChallenge() {
+        return attestationChallenge;
+    }
+
+    public byte[] getUniqueId() {
+        return uniqueId;
+    }
+
+    public Set<Integer> getSoftwareEnforcedAuthorizationTags() {
+        return softwareEnforcedAuthorizations.keySet();
+    }
+
+    public Set<Integer> getTeeEnforcedAuthorizationTags() {
+        return teeEnforcedAuthorizations.keySet();
+    }
+
+    private static ASN1Primitive findAuthorizationListEntry(
+        Map<Integer, ASN1Primitive> authorizationMap, int tag) {
+        return authorizationMap.getOrDefault(tag, null);
+    }
+
+    public Optional<Integer> getSoftwareAuthorizationInteger(int tag) {
+        ASN1Primitive entry = findAuthorizationListEntry(softwareEnforcedAuthorizations, tag);
+        return Optional.ofNullable(entry).map(ParsedAttestationRecord::getIntegerFromAsn1);
+    }
+
+    public Optional<Integer> getTeeAuthorizationInteger(int tag) {
+        ASN1Primitive entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag);
+        return Optional.ofNullable(entry).map(ParsedAttestationRecord::getIntegerFromAsn1);
+    }
+    public boolean getSoftwareAuthorizationBoolean(int tag) {
+        ASN1Primitive entry = findAuthorizationListEntry(softwareEnforcedAuthorizations, tag);
+        return entry != null;
+    }
+
+    public boolean getTeeAuthorizationBoolean(int tag) {
+        ASN1Primitive entry = findAuthorizationListEntry(teeEnforcedAuthorizations, tag);
+        return entry != null;
+    }
+
+    public Optional<byte[]> getSoftwareAuthorizationByteString(int tag) {
+        ASN1OctetString entry = (ASN1OctetString) findAuthorizationListEntry(softwareEnforcedAuthorizations, tag);
+        return Optional.ofNullable(entry).map(ASN1OctetString::getOctets);
+    }
+
+    public Optional<byte[]> getTeeAuthorizationByteString(int tag) {
+        ASN1OctetString entry = (ASN1OctetString) findAuthorizationListEntry(teeEnforcedAuthorizations, tag);
+        return Optional.ofNullable(entry).map(ASN1OctetString::getOctets);
+    }
+
+    private static boolean getBooleanFromAsn1(ASN1Encodable asn1Value) {
+        if (asn1Value instanceof ASN1Boolean) {
+            return ((ASN1Boolean) asn1Value).isTrue();
+        } else {
+            throw new RuntimeException(
+                "Boolean value expected; found " + asn1Value.getClass().getName() + " instead.");
+        }
+    }
+
+    private static int getIntegerFromAsn1(ASN1Encodable asn1Value) {
+        if (asn1Value instanceof ASN1Integer) {
+            return ((ASN1Integer) asn1Value).getValue().intValue();
+        } else if (asn1Value instanceof ASN1Enumerated) {
+            return ((ASN1Enumerated) asn1Value).getValue().intValue();
+        } else {
+            throw new IllegalArgumentException(
+                "Integer value expected; found " + asn1Value.getClass().getName() + " instead.");
+        }
+    }
+
+    private static Map<Integer, ASN1Primitive> getAuthorizationMap(
+        ASN1Encodable[] authorizationList) {
+        Map<Integer, ASN1Primitive> authorizationMap = new HashMap<>();
+        for (ASN1Encodable entry : authorizationList) {
+            ASN1TaggedObject taggedEntry = (ASN1TaggedObject) entry;
+            authorizationMap.put(taggedEntry.getTagNo(), taggedEntry.getObject());
+        }
+        return authorizationMap;
+    }
+
+    public ParsedAttestationRecord(X509Certificate cert) throws IOException {
+        byte[] attestationExtensionBytes = cert.getExtensionValue(KEY_DESCRIPTION_OID);
+        if (attestationExtensionBytes == null || attestationExtensionBytes.length == 0) {
+            throw new IllegalArgumentException("Couldn't find keystore attestation extension.");
+        }
+
+        ASN1Sequence seq;
+        try (ASN1InputStream asn1InputStream = new ASN1InputStream(attestationExtensionBytes)) {
+            // The extension contains one object, a sequence, in the
+            // Distinguished Encoding Rules (DER)-encoded form. Get the DER
+            // bytes.
+            byte[] derSequenceBytes = ((ASN1OctetString) asn1InputStream.readObject()).getOctets();
+            // Decode the bytes as an ASN1 sequence object.
+            try (ASN1InputStream seqInputStream = new ASN1InputStream(derSequenceBytes)) {
+                seq = (ASN1Sequence) seqInputStream.readObject();
+            }
+        }
+
+        this.attestationVersion = getIntegerFromAsn1(seq.getObjectAt(ATTESTATION_VERSION_INDEX));
+        this.attestationSecurityLevel =
+                securityLevelToEnum(getIntegerFromAsn1(
+                    seq.getObjectAt(ATTESTATION_SECURITY_LEVEL_INDEX)));
+        this.keymasterVersion = getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_VERSION_INDEX));
+        this.keymasterSecurityLevel = securityLevelToEnum(
+            getIntegerFromAsn1(seq.getObjectAt(KEYMASTER_SECURITY_LEVEL_INDEX)));
+        this.attestationChallenge =
+                ((ASN1OctetString) seq.getObjectAt(ATTESTATION_CHALLENGE_INDEX)).getOctets();
+        this.uniqueId = ((ASN1OctetString) seq.getObjectAt(UNIQUE_ID_INDEX)).getOctets();
+
+        this.softwareEnforcedAuthorizations = getAuthorizationMap(
+            ((ASN1Sequence) seq.getObjectAt(SW_ENFORCED_INDEX)).toArray());
+
+        this.teeEnforcedAuthorizations = getAuthorizationMap(
+            ((ASN1Sequence) seq.getObjectAt(TEE_ENFORCED_INDEX)).toArray());
+    }
+
+    private static SecurityLevel securityLevelToEnum(int securityLevel) {
+        switch (securityLevel) {
+            case KM_SECURITY_LEVEL_SOFTWARE:
+                return SecurityLevel.SOFTWARE;
+            case KM_SECURITY_LEVEL_TRUSTED_ENVIRONMENT:
+                return SecurityLevel.TRUSTED_ENVIRONMENT;
+            case KM_SECURITY_LEVEL_STRONG_BOX:
+                return SecurityLevel.STRONG_BOX;
+            default:
+                throw new IllegalArgumentException("Invalid security level.");
+        }
+    }
+}
diff --git a/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java b/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java
index d903fac..48cc405 100644
--- a/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/ProvisioningTest.java
@@ -27,6 +27,7 @@
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 
@@ -106,11 +107,17 @@
 
     static Collection<X509Certificate> createCredential(IdentityCredentialStore store,
             String credentialName) throws IdentityCredentialException {
+        return createCredentialWithChallenge(store, credentialName, "SomeChallenge".getBytes());
+    }
+
+    static Collection<X509Certificate> createCredentialWithChallenge(IdentityCredentialStore store,
+            String credentialName,
+            byte[] challenge) throws IdentityCredentialException {
         WritableIdentityCredential wc = null;
         wc = store.createCredential(credentialName, "org.iso.18013-5.2019.mdl");
 
         Collection<X509Certificate> certificateChain =
-                wc.getCredentialKeyCertificateChain("SomeChallenge".getBytes());
+                wc.getCredentialKeyCertificateChain(challenge);
         // TODO: inspect cert-chain
 
         // Profile 0 (no authentication)
@@ -348,6 +355,9 @@
     public void alreadyPersonalized() throws IdentityCredentialException {
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
 
         store.deleteCredentialByName("test");
         createCredential(store, "test");
@@ -365,6 +375,9 @@
     public void nonExistent() throws IdentityCredentialException {
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
 
         store.deleteCredentialByName("test");
         IdentityCredential credential = store.getCredentialByName("test",
@@ -376,6 +389,10 @@
     public void defaultStoreSupportsAnyDocumentType() throws IdentityCredentialException {
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
+
         String[] supportedDocTypes = store.getSupportedDocTypes();
         assertEquals(0, supportedDocTypes.length);
     }
@@ -385,6 +402,9 @@
             throws IdentityCredentialException, CborException, CertificateEncodingException {
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
 
         store.deleteCredentialByName("test");
         assertNull(store.deleteCredentialByName("test"));
@@ -420,6 +440,9 @@
     public void testProvisionAndRetrieve() throws IdentityCredentialException, CborException {
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
 
         store.deleteCredentialByName("test");
         Collection<X509Certificate> certChain = createCredential(store, "test");
@@ -508,6 +531,9 @@
             InvalidKeyException {
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
 
         // This checks we can do multiple getEntries() calls
 
@@ -569,6 +595,9 @@
     public void testProvisionAndRetrieveWithFiltering() throws IdentityCredentialException {
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
 
         store.deleteCredentialByName("test");
         Collection<X509Certificate> certChain = createCredential(store, "test");
@@ -620,6 +649,9 @@
     public void testProvisionAndRetrieveElementWithNoACP() throws IdentityCredentialException {
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
 
         store.deleteCredentialByName("test");
         Collection<X509Certificate> certChain = createCredential(store, "test");
@@ -659,6 +691,9 @@
     public void testProvisionAndRetrieveWithEntryNotInRequest() throws IdentityCredentialException {
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
 
         store.deleteCredentialByName("test");
         Collection<X509Certificate> certChain = createCredential(store, "test");
@@ -713,6 +748,9 @@
     public void nonExistentEntries() throws IdentityCredentialException {
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
 
         store.deleteCredentialByName("test");
         Collection<X509Certificate> certChain = createCredential(store, "test");
@@ -756,6 +794,9 @@
     public void multipleNamespaces() throws IdentityCredentialException, CborException {
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
 
         store.deleteCredentialByName("test");
         Collection<X509Certificate> certChain = createCredentialMultipleNamespaces(
diff --git a/tests/tests/identity/src/android/security/identity/cts/ReaderAuthTest.java b/tests/tests/identity/src/android/security/identity/cts/ReaderAuthTest.java
index 88abce4..1abc0ae 100644
--- a/tests/tests/identity/src/android/security/identity/cts/ReaderAuthTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/ReaderAuthTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 import android.security.keystore.KeyGenParameterSpec;
@@ -155,6 +156,10 @@
         // Provision the credential.
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
+
         String credentialName = "readerAuthTestCredential";
         store.deleteCredentialByName(credentialName);
         WritableIdentityCredential wc = store.createCredential(credentialName,
diff --git a/tests/tests/identity/src/android/security/identity/cts/UserAuthTest.java b/tests/tests/identity/src/android/security/identity/cts/UserAuthTest.java
index 244c636..295fdaf 100644
--- a/tests/tests/identity/src/android/security/identity/cts/UserAuthTest.java
+++ b/tests/tests/identity/src/android/security/identity/cts/UserAuthTest.java
@@ -31,11 +31,11 @@
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
 
 import android.content.Context;
 import android.os.SystemClock;
 import android.util.Log;
-import android.test.AndroidTestCase;
 
 import androidx.test.InstrumentationRegistry;
 
@@ -92,7 +92,7 @@
 import android.security.keystore.UserNotAuthenticatedException;
 
 
-public class UserAuthTest extends AndroidTestCase {
+public class UserAuthTest {
     private static final String TAG = "UserAuthTest";
 
     private class DeviceLockSession extends ActivityManagerTestBase implements AutoCloseable {
@@ -100,14 +100,14 @@
         private LockScreenSession mLockCredential;
 
         public DeviceLockSession() throws Exception {
-            setUp();
             mLockCredential = new LockScreenSession();
             mLockCredential.setLockCredential();
         }
 
         public void performDeviceLock() {
             mLockCredential.sleepDevice();
-            KeyguardManager keyguardManager = (KeyguardManager)getContext().
+            Context appContext = InstrumentationRegistry.getTargetContext();
+            KeyguardManager keyguardManager = (KeyguardManager)appContext.
                                               getSystemService(Context.KEYGUARD_SERVICE);
             for (int i = 0; i < 25 && !keyguardManager.isDeviceLocked(); i++) {
                 SystemClock.sleep(200);
@@ -123,7 +123,6 @@
         @Override
         public void close() throws Exception {
             mLockCredential.close();
-            tearDown();
         }
     }
 
@@ -170,14 +169,18 @@
         }
     }
 
+    @Test
     public void testUserAuth() throws Exception {
         String alias = "authbound";
 
         try (DeviceLockSession dl = new DeviceLockSession()) {
-            KeyguardManager keyguardManager = (KeyguardManager)getContext()
+            Context appContext = InstrumentationRegistry.getTargetContext();
+            KeyguardManager keyguardManager = (KeyguardManager)appContext
                                               .getSystemService(Context.KEYGUARD_SERVICE);
 
             doTestUserAuth(dl, keyguardManager);
+        } catch (org.junit.AssumptionViolatedException e) {
+            /* do nothing */
         }
     }
 
@@ -196,6 +199,10 @@
         // Provision the credential.
         Context appContext = InstrumentationRegistry.getTargetContext();
         IdentityCredentialStore store = IdentityCredentialStore.getInstance(appContext);
+        if (Util.isHalOptional()) {
+            assumeTrue("IC HAL not found on device", store != null);
+        }
+
         store.deleteCredentialByName("test");
         WritableIdentityCredential wc = store.createCredential("test",
                 "org.iso.18013-5.2019.mdl");
@@ -318,7 +325,7 @@
         dl.performDeviceLock();
         assertTrue(keyguardManager.isDeviceLocked());
         dl.performDeviceUnlock();
-        assertFalse(keyguardManager.isDeviceLocked());
+        assertTrue(!keyguardManager.isDeviceLocked());
 
         credential = store.getCredentialByName("test",
                 CIPHERSUITE_ECDHE_HKDF_ECDSA_WITH_AES_256_GCM_SHA256);
diff --git a/tests/tests/identity/src/android/security/identity/cts/Util.java b/tests/tests/identity/src/android/security/identity/cts/Util.java
index 52649f1..b950b89 100644
--- a/tests/tests/identity/src/android/security/identity/cts/Util.java
+++ b/tests/tests/identity/src/android/security/identity/cts/Util.java
@@ -18,8 +18,9 @@
 
 import android.security.identity.ResultData;
 
-import android.util.Log;
+import android.os.SystemProperties;
 import android.security.keystore.KeyProperties;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -1224,4 +1225,24 @@
             throw new RuntimeException("Error generating ephemeral key-pair", e);
         }
     }
+
+    // Returns true if, and only if, the Identity Credential HAL (and credstore) is optional for
+    // the device under test.
+    static boolean isHalOptional() {
+        int firstApiLevel = SystemProperties.getInt("ro.product.first_api_level", 0);
+        if (firstApiLevel == 0) {
+            // ro.product.first_api_level is not set and per
+            //
+            //  https://source.android.com/compatibility/cts/setup
+            //
+            // this is optional to set. In this case we simply just require the HAL.
+            return false;
+        }
+        if (firstApiLevel >= 30) {
+            // Device launched with R or later, Identity Credential HAL is not optional.
+            return false;
+        }
+        // Device launched before R, Identity Credential HAL is optional.
+        return true;
+    }
 }
diff --git a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
index 1493430..6353fed 100644
--- a/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
+++ b/tests/tests/keystore/src/android/keystore/cts/KeyPairGeneratorTest.java
@@ -35,6 +35,7 @@
 import java.security.Principal;
 import java.security.PrivateKey;
 import java.security.Provider;
+import java.security.PublicKey;
 import java.security.SecureRandom;
 import java.security.Security;
 import java.security.Provider.Service;
@@ -709,6 +710,64 @@
                 NOW_PLUS_10_YEARS);
     }
 
+    public void testGenerate_EC_Different_Keys() throws Exception {
+        testGenerate_EC_Different_KeysHelper(false /* useStrongbox */);
+        if (TestUtils.hasStrongBox(getContext())) {
+            testGenerate_EC_Different_KeysHelper(true /* useStrongbox */);
+        }
+    }
+
+    private void testGenerate_EC_Different_KeysHelper(boolean useStrongbox) throws Exception {
+        KeyPairGenerator generator = getEcGenerator();
+        generator.initialize(new KeyGenParameterSpec.Builder(
+                TEST_ALIAS_1,
+                KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                .setIsStrongBoxBacked(useStrongbox)
+                .build());
+        KeyPair keyPair1 = generator.generateKeyPair();
+        PublicKey pub1 = keyPair1.getPublic();
+
+        generator.initialize(new KeyGenParameterSpec.Builder(
+                TEST_ALIAS_2,
+                KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                .setIsStrongBoxBacked(useStrongbox)
+                .build());
+        KeyPair keyPair2 = generator.generateKeyPair();
+        PublicKey pub2 = keyPair2.getPublic();
+        if(Arrays.equals(pub1.getEncoded(), pub2.getEncoded())) {
+            fail("The same EC key pair was generated twice");
+        }
+    }
+
+    public void testGenerate_RSA_Different_Keys() throws Exception {
+        testGenerate_RSA_Different_KeysHelper(false /* useStrongbox */);
+        if (TestUtils.hasStrongBox(getContext())) {
+            testGenerate_RSA_Different_KeysHelper(true /* useStrongbox */);
+        }
+    }
+
+    private void testGenerate_RSA_Different_KeysHelper(boolean useStrongbox) throws Exception {
+        KeyPairGenerator generator = getRsaGenerator();
+        generator.initialize(new KeyGenParameterSpec.Builder(
+                TEST_ALIAS_1,
+                KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                .setIsStrongBoxBacked(useStrongbox)
+                .build());
+        KeyPair keyPair1 = generator.generateKeyPair();
+        PublicKey pub1 = keyPair1.getPublic();
+
+        generator.initialize(new KeyGenParameterSpec.Builder(
+                TEST_ALIAS_2,
+                KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
+                .setIsStrongBoxBacked(useStrongbox)
+                .build());
+        KeyPair keyPair2 = generator.generateKeyPair();
+        PublicKey pub2 = keyPair2.getPublic();
+        if(Arrays.equals(pub1.getEncoded(), pub2.getEncoded())) {
+            fail("The same RSA key pair was generated twice");
+        }
+    }
+
     public void testGenerate_EC_ModernSpec_Defaults() throws Exception {
         testGenerate_EC_ModernSpec_DefaultsHelper(false /* useStrongbox */);
         if (TestUtils.hasStrongBox(getContext())) {
diff --git a/tests/tests/location/src/android/location/cts/LocationManagerTest.java b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
index 89ee843..27dd4ef 100644
--- a/tests/tests/location/src/android/location/cts/LocationManagerTest.java
+++ b/tests/tests/location/src/android/location/cts/LocationManagerTest.java
@@ -86,7 +86,7 @@
     /**
      * Helper method to add a test provider with given name.
      */
-    private void addTestProvider(final String providerName) {
+    private void addTestProvider(final String providerName) throws Exception {
         mManager.addTestProvider(providerName, true, //requiresNetwork,
                 false, // requiresSatellite,
                 true,  // requiresCell,
@@ -97,6 +97,7 @@
                 Criteria.POWER_MEDIUM, // powerRequirement
                 Criteria.ACCURACY_FINE); // accuracy
         mManager.setTestProviderEnabled(providerName, true);
+	Thread.sleep(500);
     }
 
     @Override
@@ -146,7 +147,7 @@
         }
     }
 
-    public void testGetProviders() {
+    public void testGetProviders() throws Exception {
         List<String> providers = mManager.getAllProviders();
         assertTrue(providers.size() >= 2);
         assertTrue(hasTestProvider(providers));
@@ -165,6 +166,8 @@
         int oldSizeTrueProviders = providers.size();
 
         mManager.setTestProviderEnabled(TEST_MOCK_PROVIDER_NAME, false);
+	Thread.sleep(500);
+
         providers = mManager.getProviders(true);
         assertEquals(oldSizeTrueProviders - 1, providers.size());
         assertFalse(hasTestProvider(providers));
@@ -272,7 +275,7 @@
      * throws an {@link java.lang.IllegalArgumentException} if there is no such test provider,
      * so we have to add it before we clear it.
      */
-    private void forceRemoveTestProvider(String provider) {
+    private void forceRemoveTestProvider(String provider) throws Exception {
         addTestProvider(provider);
         mManager.removeTestProvider(provider);
     }
@@ -353,7 +356,7 @@
      * known way to determine if a given provider is a test provider.
      * @throws InterruptedException
      */
-    public void testReplaceRealProvidersWithMocks() throws InterruptedException {
+    public void testReplaceRealProvidersWithMocks() throws Exception {
         for (String providerName : mManager.getAllProviders()) {
             if (!providerName.equals(TEST_MOCK_PROVIDER_NAME) &&
                 !providerName.equals(LocationManager.PASSIVE_PROVIDER)) {
@@ -530,7 +533,7 @@
         }
     }
 
-    public void testLocationUpdatesWithCriteriaAndPendingIntent() throws InterruptedException {
+    public void testLocationUpdatesWithCriteriaAndPendingIntent() throws Exception {
         double latitude1 = 10;
         double longitude1 = 20;
         double latitude2 = 30;
@@ -586,7 +589,7 @@
         unmockFusedLocation();
     }
 
-    public void testSingleUpdateWithCriteriaAndPendingIntent() throws InterruptedException {
+    public void testSingleUpdateWithCriteriaAndPendingIntent() throws Exception {
         double latitude1 = 10;
         double longitude1 = 20;
         double latitude2 = 30;
@@ -650,7 +653,7 @@
     }
 
     public void testLocationUpdatesWithCriteriaAndLocationListenerAndLooper()
-            throws InterruptedException {
+            throws Exception {
         double latitude1 = 40;
         double longitude1 = 10;
         double latitude2 = 20;
@@ -706,7 +709,7 @@
     }
 
     public void testSingleUpdateWithCriteriaAndLocationListenerAndLooper()
-            throws InterruptedException {
+            throws Exception {
         double latitude1 = 40;
         double longitude1 = 10;
         double latitude2 = 20;
@@ -850,7 +853,7 @@
         mManager.removeNmeaListener((OnNmeaMessageListener) null);
     }
 
-    public void testIsProviderEnabled() {
+    public void testIsProviderEnabled() throws Exception {
         // this test assumes enabled TEST_MOCK_PROVIDER_NAME was created in setUp.
         assertNotNull(mManager.getProvider(TEST_MOCK_PROVIDER_NAME));
         assertTrue(mManager.isProviderEnabled(TEST_MOCK_PROVIDER_NAME));
@@ -867,7 +870,7 @@
 
         mManager.setTestProviderEnabled(TEST_MOCK_PROVIDER_NAME, true);
         try {
-            Thread.sleep(100);
+            Thread.sleep(500);
         } catch (Exception e) {
             Log.e(TAG, "fail in testIsProviderEnabled");
         }
@@ -1183,7 +1186,7 @@
         return criteria;
      }
 
-    private void mockFusedLocation() {
+    private void mockFusedLocation() throws Exception {
         addTestProvider(FUSED_PROVIDER_NAME);
     }
 
diff --git a/tests/tests/media/OWNERS b/tests/tests/media/OWNERS
index 4b97bcb..6614de9 100644
--- a/tests/tests/media/OWNERS
+++ b/tests/tests/media/OWNERS
@@ -3,6 +3,7 @@
 chz@google.com
 dwkang@google.com
 elaurent@google.com
+essick@google.com
 etalvala@google.com
 gkasten@google.com
 hdmoon@google.com
diff --git a/tests/tests/media/res/raw/a_4_haptic.ogg b/tests/tests/media/res/raw/a_4_haptic.ogg
index e8287a8..3c94c8d 100644
--- a/tests/tests/media/res/raw/a_4_haptic.ogg
+++ b/tests/tests/media/res/raw/a_4_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/b_5_haptic.ogg b/tests/tests/media/res/raw/b_5_haptic.ogg
index cb4df5a..90ab939 100644
--- a/tests/tests/media/res/raw/b_5_haptic.ogg
+++ b/tests/tests/media/res/raw/b_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/c_sharp_5_haptic.ogg b/tests/tests/media/res/raw/c_sharp_5_haptic.ogg
index 7950552..57c46ea 100644
--- a/tests/tests/media/res/raw/c_sharp_5_haptic.ogg
+++ b/tests/tests/media/res/raw/c_sharp_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/e_5_haptic.ogg b/tests/tests/media/res/raw/e_5_haptic.ogg
index 7c7f4cd..98289dc 100644
--- a/tests/tests/media/res/raw/e_5_haptic.ogg
+++ b/tests/tests/media/res/raw/e_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/res/raw/g_sharp_5_haptic.ogg b/tests/tests/media/res/raw/g_sharp_5_haptic.ogg
index ecf9439..6fb7388 100644
--- a/tests/tests/media/res/raw/g_sharp_5_haptic.ogg
+++ b/tests/tests/media/res/raw/g_sharp_5_haptic.ogg
Binary files differ
diff --git a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
index 4c234ce..7e353ea 100644
--- a/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
+++ b/tests/tests/media/src/android/media/cts/AudioPlaybackCaptureTest.java
@@ -422,7 +422,7 @@
         // As a result, read() should fail after at most the total buffer size read.
         // Even if the projection is stopped, the policy unregisteration is async,
         // so double that to be on the conservative side.
-        final int MAX_READ_SIZE = 2 * nativeBufferSize;
+        final int MAX_READ_SIZE = 8 * nativeBufferSize;
         int readSize = 0;
         ByteBuffer buffer = ByteBuffer.allocateDirect(BUFFER_SIZE);
         int status;
diff --git a/tests/tests/media/src/android/media/cts/CodecState.java b/tests/tests/media/src/android/media/cts/CodecState.java
index 5f1d322..2384704 100644
--- a/tests/tests/media/src/android/media/cts/CodecState.java
+++ b/tests/tests/media/src/android/media/cts/CodecState.java
@@ -19,6 +19,7 @@
 import android.media.MediaCodec;
 import android.media.MediaExtractor;
 import android.media.MediaFormat;
+import android.os.Handler;
 import android.util.Log;
 import android.view.Surface;
 import java.nio.ByteBuffer;
@@ -50,6 +51,7 @@
     private MediaFormat mFormat;
     private MediaFormat mOutputFormat;
     private NonBlockingAudioTrack mAudioTrack;
+    private OnFrameRenderedListener mOnFrameRenderedListener;
 
     /**
      * Manages audio and video playback using MediaCodec and AudioTrack.
@@ -84,6 +86,11 @@
         String mime = mFormat.getString(MediaFormat.KEY_MIME);
         Log.d(TAG, "CodecState::CodecState " + mime);
         mIsAudio = mime.startsWith("audio/");
+
+        if (mTunneled && !mIsAudio) {
+            mOnFrameRenderedListener = new OnFrameRenderedListener();
+            codec.setOnFrameRenderedListener(mOnFrameRenderedListener, new Handler());
+        }
     }
 
     public void release() {
@@ -100,6 +107,11 @@
         mAvailableOutputBufferIndices = null;
         mAvailableOutputBufferInfos = null;
 
+        if (mOnFrameRenderedListener != null) {
+            mCodec.setOnFrameRenderedListener(null, null);
+            mOnFrameRenderedListener = null;
+        }
+
         mCodec.release();
         mCodec = null;
 
@@ -218,11 +230,6 @@
                 Log.d(TAG, "sampleSize: " + sampleSize + " trackIndex:" + trackIndex +
                         " sampleTime:" + sampleTime + " sampleFlags:" + sampleFlags);
                 mSawInputEOS = true;
-                // FIX-ME: in tunneled mode we currently use input EOS as output EOS indicator
-                // we should use stream duration
-                if (mTunneled && !mIsAudio) {
-                    mSawOutputEOS = true;
-                }
                 return false;
             }
 
@@ -231,9 +238,6 @@
                     mSampleBaseTimeUs = sampleTime;
                 }
                 sampleTime -= mSampleBaseTimeUs;
-                // FIX-ME: in tunneled mode we currently use input buffer time
-                // as video presentation time. This is not accurate and should be fixed
-                mPresentationTimeUs = sampleTime;
             }
 
             if ((sampleFlags & MediaExtractor.SAMPLE_FLAG_ENCRYPTED) != 0) {
@@ -255,11 +259,6 @@
             Log.d(TAG, "saw input EOS on track " + mTrackIndex);
 
             mSawInputEOS = true;
-            // FIX-ME: in tunneled mode we currently use input EOS as output EOS indicator
-            // we should use stream duration
-            if (mTunneled && !mIsAudio) {
-                mSawOutputEOS = true;
-            }
 
             mCodec.queueInputBuffer(
                     index, 0 /* offset */, 0 /* sampleSize */,
@@ -375,6 +374,23 @@
         }
     }
 
+    /** Callback called by the renderer in tunneling mode. */
+    private class OnFrameRenderedListener implements MediaCodec.OnFrameRenderedListener {
+        private static final long TUNNELING_EOS_PRESENTATION_TIME_US = Long.MAX_VALUE;
+
+        @Override
+        public void onFrameRendered(MediaCodec codec, long presentationTimeUs, long nanoTime) {
+            if (this != mOnFrameRenderedListener) {
+                return; // stale event
+            }
+            if (presentationTimeUs == TUNNELING_EOS_PRESENTATION_TIME_US) {
+                 mSawOutputEOS = true;
+            } else {
+                 mPresentationTimeUs = presentationTimeUs;
+            }
+        }
+    }
+
     public long getAudioTimeUs() {
         if (mAudioTrack == null) {
             return 0;
diff --git a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
index 90c6572..e0ec13a 100755
--- a/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
+++ b/tests/tests/media/src/android/media/cts/EncodeVirtualDisplayTest.java
@@ -44,6 +44,7 @@
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.nio.ByteBuffer;
+import java.util.concurrent.CountDownLatch;
 
 /**
  * Tests connecting a virtual display to the input of a MediaCodec encoder.
@@ -540,6 +541,7 @@
         private void showPresentation(final int color) {
             final TestPresentation[] presentation = new TestPresentation[1];
             try {
+                final CountDownLatch latch = new CountDownLatch(1);
                 runOnUiThread(new Runnable() {
                     @Override
                     public void run() {
@@ -548,13 +550,17 @@
                         presentation[0] = new TestPresentation(getContext(), mDisplay, color);
                         if (VERBOSE) Log.d(TAG, "showing color=0x" + Integer.toHexString(color));
                         presentation[0].show();
+                        latch.countDown();
                     }
                 });
 
                 // Give the presentation an opportunity to render.  We don't have a way to
                 // monitor the output, so we just sleep for a bit.
-                try { Thread.sleep(UI_RENDER_PAUSE_MS); }
-                catch (InterruptedException ignore) {}
+                try {
+                    // wait for the UI thread execution to finish
+                    latch.await();
+                    Thread.sleep(UI_RENDER_PAUSE_MS);
+                } catch (InterruptedException ignore) {}
             } finally {
                 if (presentation[0] != null) {
                     runOnUiThread(new Runnable() {
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
index f0c3d1d..cad25ed 100644
--- a/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecCapabilitiesTest.java
@@ -15,6 +15,8 @@
  */
 package android.media.cts;
 
+import android.app.ActivityManager;
+import android.content.Context;
 import android.content.pm.PackageManager;
 import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
@@ -33,6 +35,7 @@
 import static android.media.MediaFormat.MIMETYPE_VIDEO_VP9;
 import android.media.MediaPlayer;
 import android.os.Build;
+import android.os.SystemProperties;
 import android.platform.test.annotations.AppModeFull;
 import android.util.Log;
 import android.util.Range;
@@ -691,13 +694,16 @@
         return format;
     }
 
-    private static int getActualMax(
+    private int getActualMax(
             boolean isEncoder, String name, String mime, CodecCapabilities caps, int max) {
         int flag = isEncoder ? MediaCodec.CONFIGURE_FLAG_ENCODE : 0;
         MediaFormat format = createMinFormat(mime, caps);
         Log.d(TAG, "Test format " + format);
         Vector<MediaCodec> codecs = new Vector<MediaCodec>();
         MediaCodec codec = null;
+        ActivityManager am = (ActivityManager)
+                mContext.getSystemService(Context.ACTIVITY_SERVICE);
+        ActivityManager.MemoryInfo outInfo = new ActivityManager.MemoryInfo();
         for (int i = 0; i < max; ++i) {
             try {
                 Log.d(TAG, "Create codec " + name + " #" + i);
@@ -706,6 +712,12 @@
                 codec.start();
                 codecs.add(codec);
                 codec = null;
+
+                am.getMemoryInfo(outInfo);
+                if (outInfo.lowMemory) {
+                    Log.d(TAG, "System is in low memory condition, stopping. max: " + i);
+                    break;
+                }
             } catch (IllegalArgumentException e) {
                 fail("Got unexpected IllegalArgumentException " + e.getMessage());
             } catch (IOException e) {
diff --git a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
index 6a10e40..1b4d4e2 100644
--- a/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaPlayerTest.java
@@ -1255,49 +1255,6 @@
         mMediaPlayer.stop();
     }
 
-    public void testMkvWithoutCueSeek() throws Exception {
-        if (!checkLoadResource(
-                R.raw.video_1280x720_mkv_h265_500kbps_25fps_aac_stereo_128kbps_44100hz_withoutcues)) {
-            return; // skip
-        }
-
-        mMediaPlayer.setOnSeekCompleteListener(new MediaPlayer.OnSeekCompleteListener() {
-            @Override
-            public void onSeekComplete(MediaPlayer mp) {
-                mOnSeekCompleteCalled.signal();
-            }
-        });
-        mMediaPlayer.setDisplay(mActivity.getSurfaceHolder());
-        mMediaPlayer.prepare();
-        mOnSeekCompleteCalled.reset();
-        mMediaPlayer.start();
-
-        final int seekPosMs = 3000;
-        final int syncTime2Ms = 5960;
-
-        int cp = runSeekMode(MediaPlayer.SEEK_CLOSEST, seekPosMs);
-        Log.d(LOG_TAG, "runSeekMode SEEK_CLOSEST cp: " + cp);
-        assertTrue("MediaPlayer seek fail with SEEK_CLOSEST mode.",
-                  cp > seekPosMs && cp < syncTime2Ms);
-
-        cp = runSeekMode(MediaPlayer.SEEK_PREVIOUS_SYNC, seekPosMs);
-        Log.d(LOG_TAG, "runSeekMode SEEK_PREVIOUS_SYNC cp: " + cp);
-        assertTrue("MediaPlayer seek fail with SEEK_PREVIOUS_SYNC mode.",
-                cp >= syncTime2Ms);
-
-        cp = runSeekMode(MediaPlayer.SEEK_NEXT_SYNC, seekPosMs);
-        Log.d(LOG_TAG, "runSeekMode SEEK_NEXT_SYNC cp: " + cp);
-        assertTrue("MediaPlayer seek fail with SEEK_NEXT_SYNC mode.",
-                cp >= syncTime2Ms);
-
-        cp = runSeekMode(MediaPlayer.SEEK_CLOSEST_SYNC, seekPosMs);
-        Log.d(LOG_TAG, "runSeekMode SEEK_CLOSEST_SYNC cp: " + cp);
-        assertTrue("MediaPlayer seek fail with SEEK_CLOSEST_SYNC mode.",
-                cp >= syncTime2Ms);
-
-        mMediaPlayer.stop();
-    }
-
     public void testSeekModes() throws Exception {
         // This clip has 2 I frames at 66687us and 4299687us.
         if (!checkLoadResource(
diff --git a/tests/tests/media/src/android/media/cts/SoundPoolTest.java b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
index 2c56acd..e9b8fd1 100644
--- a/tests/tests/media/src/android/media/cts/SoundPoolTest.java
+++ b/tests/tests/media/src/android/media/cts/SoundPoolTest.java
@@ -287,8 +287,8 @@
             }
 
             // wait for all sounds to load,
-            // it usually takes about 33 seconds and 50 seconds is used to have some headroom.
-            final long LOAD_TIMEOUT_IN_MS = 50000;
+            // it usually takes about 33 seconds and 100 seconds is used to have some headroom.
+            final long LOAD_TIMEOUT_IN_MS = 100000;
             final long startTime = System.currentTimeMillis();
             synchronized(done) {
                 while (loaded[0] != soundIds.length) {
diff --git a/tests/tests/net/Android.bp b/tests/tests/net/Android.bp
index b00455d..624d149 100644
--- a/tests/tests/net/Android.bp
+++ b/tests/tests/net/Android.bp
@@ -12,8 +12,8 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-android_test {
-    name: "CtsNetTestCases",
+java_defaults {
+    name: "CtsNetTestCasesDefaults",
     defaults: ["cts_defaults"],
 
     // Include both the 32 and 64 bit versions
@@ -32,7 +32,6 @@
         "libnativehelper_compat_libc++",
     ],
 
-    // include CtsTestServer as a temporary hack to free net.cts from cts.stub.
     srcs: [
         "src/**/*.java",
         "src/**/*.kt",
@@ -54,13 +53,33 @@
     // uncomment when b/13249961 is fixed
     // sdk_version: "current",
     platform_apis: true,
+}
 
-    // Tag this module as a cts test artifact
+// Networking CTS tests for development and release. These tests always target the platform SDK
+// version, and are subject to all the restrictions appropriate to that version. Before SDK
+// finalization, these tests have a min_sdk_version of 10000, and cannot be installed on release
+// devices.
+android_test {
+    name: "CtsNetTestCases",
+    defaults: ["CtsNetTestCasesDefaults"],
     test_suites: [
         "cts",
         "vts",
         "general-tests",
+    ],
+    test_config_template: "AndroidTestTemplate.xml",
+}
+
+// Networking CTS tests that have a min_sdk_version of the latest released SDK. These tests can
+// be installed on release devices at any point in the release cycle and are useful for qualifying
+// mainline modules on release devices.
+android_test {
+    name: "CtsNetTestCasesLatestSdk",
+    defaults: ["CtsNetTestCasesDefaults"],
+    min_sdk_version: "29",
+    target_sdk_version: "29",
+    test_suites: [
         "mts",
     ],
-
+    test_config_template: "AndroidTestTemplate.xml",
 }
diff --git a/tests/tests/net/AndroidTest.xml b/tests/tests/net/AndroidTestTemplate.xml
similarity index 92%
rename from tests/tests/net/AndroidTest.xml
rename to tests/tests/net/AndroidTestTemplate.xml
index 3ff019e..1f75da1 100644
--- a/tests/tests/net/AndroidTest.xml
+++ b/tests/tests/net/AndroidTestTemplate.xml
@@ -12,7 +12,7 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<configuration description="Config for CTS Net test cases">
+<configuration description="Test config for {MODULE}">
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="networking" />
     <option name="config-descriptor:metadata" key="token" value="SIM_CARD" />
@@ -22,7 +22,7 @@
     <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="CtsNetTestCases.apk" />
+        <option name="test-file-name" value="{MODULE}.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="android.net.cts" />
diff --git a/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java b/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
new file mode 100644
index 0000000..0a80047
--- /dev/null
+++ b/tests/tests/net/src/android/net/cts/ConnectivityDiagnosticsManagerTest.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net.cts;
+
+import static android.net.ConnectivityDiagnosticsManager.ConnectivityDiagnosticsCallback;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import android.content.Context;
+import android.net.ConnectivityDiagnosticsManager;
+import android.net.NetworkRequest;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.Executor;
+
+@RunWith(AndroidJUnit4.class)
+public class ConnectivityDiagnosticsManagerTest {
+    private static final Executor INLINE_EXECUTOR = x -> x.run();
+    private static final NetworkRequest DEFAULT_REQUEST = new NetworkRequest.Builder().build();
+
+    private Context mContext;
+    private ConnectivityDiagnosticsManager mCdm;
+    private ConnectivityDiagnosticsCallback mCallback;
+
+    @Before
+    public void setUp() throws Exception {
+        mContext = InstrumentationRegistry.getContext();
+        mCdm = mContext.getSystemService(ConnectivityDiagnosticsManager.class);
+
+        mCallback = new ConnectivityDiagnosticsCallback() {};
+    }
+
+    @Test
+    public void testRegisterConnectivityDiagnosticsCallback() {
+        mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+    }
+
+    @Test
+    public void testRegisterDuplicateConnectivityDiagnosticsCallback() {
+        mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+
+        try {
+            mCdm.registerConnectivityDiagnosticsCallback(
+                    DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+            fail("Registering the same callback twice should throw an IllegalArgumentException");
+        } catch (IllegalArgumentException expected) {
+        }
+    }
+
+    @Test
+    public void testUnregisterConnectivityDiagnosticsCallback() {
+        mCdm.registerConnectivityDiagnosticsCallback(DEFAULT_REQUEST, INLINE_EXECUTOR, mCallback);
+        mCdm.unregisterConnectivityDiagnosticsCallback(mCallback);
+    }
+
+    @Test
+    public void testUnregisterUnknownConnectivityDiagnosticsCallback() {
+        // Expected to silently ignore the unregister() call
+        mCdm.unregisterConnectivityDiagnosticsCallback(mCallback);
+    }
+}
diff --git a/tests/tests/net/src/android/net/wifi/cts/ConcurrencyTest.java b/tests/tests/net/src/android/net/wifi/cts/ConcurrencyTest.java
index 628571c..c557338 100644
--- a/tests/tests/net/src/android/net/wifi/cts/ConcurrencyTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/ConcurrencyTest.java
@@ -272,6 +272,14 @@
                     + " needs Location enabled.");
         }
 
+        mWifiP2pManager =
+                (WifiP2pManager) getContext().getSystemService(Context.WIFI_P2P_SERVICE);
+        mWifiP2pChannel = mWifiP2pManager.initialize(
+                getContext(), getContext().getMainLooper(), null);
+
+        assertNotNull(mWifiP2pManager);
+        assertNotNull(mWifiP2pChannel);
+
         long timeout = System.currentTimeMillis() + TIMEOUT_MSEC;
         while (!mWifiManager.isWifiEnabled() && System.currentTimeMillis() < timeout) {
             try {
@@ -288,14 +296,6 @@
         assertEquals(WifiManager.WIFI_STATE_ENABLED, mMySync.expectedWifiState);
         assertEquals(WifiP2pManager.WIFI_P2P_STATE_ENABLED, mMySync.expectedP2pState);
 
-        mWifiP2pManager =
-                (WifiP2pManager) getContext().getSystemService(Context.WIFI_P2P_SERVICE);
-        mWifiP2pChannel = mWifiP2pManager.initialize(
-                getContext(), getContext().getMainLooper(), null);
-
-        assertNotNull(mWifiP2pManager);
-        assertNotNull(mWifiP2pChannel);
-
         assertTrue(waitForBroadcasts(MySync.NETWORK_INFO));
         // wait for changing to EnabledState
         assertNotNull(mMySync.expectedNetworkInfo);
diff --git a/tests/tests/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java b/tests/tests/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
index b592c10..e4c4b00 100644
--- a/tests/tests/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
+++ b/tests/tests/net/src/android/net/wifi/cts/WifiEnterpriseConfigTest.java
@@ -687,6 +687,9 @@
     @Override
     protected void setUp() throws Exception {
         super.setUp();
+        if(!hasWifi()) {
+            return;
+        }
         mWifiManager = (WifiManager) mContext
                 .getSystemService(Context.WIFI_SERVICE);
         assertNotNull(mWifiManager);
@@ -778,6 +781,9 @@
     }
 
     public void testEnterpriseConfigDoesNotPrintPassword() {
+        if(!hasWifi()) {
+            return;
+        }
         WifiEnterpriseConfig enterpriseConfig = new WifiEnterpriseConfig();
         final String identity = "IdentityIsOkayToBeDisplayedHere";
         final String password = "PasswordIsNotOkayToBeDisplayedHere";
diff --git a/tests/tests/neuralnetworks/tflite_delegate/Android.mk b/tests/tests/neuralnetworks/tflite_delegate/Android.mk
index e9ea001..1b6e7b3 100644
--- a/tests/tests/neuralnetworks/tflite_delegate/Android.mk
+++ b/tests/tests/neuralnetworks/tflite_delegate/Android.mk
@@ -20,7 +20,11 @@
 LOCAL_SRC_FILES := \
     tensorflow/lite/delegates/nnapi/nnapi_delegate_test.cc \
     tensorflow/lite/kernels/test_util.cc \
-    tensorflow/core/platform/default/logging.cc
+    tensorflow/core/platform/default/logging.cc \
+    tensorflow/lite/kernels/acceleration_test_util.cc \
+    tensorflow/lite/kernels/acceleration_test_util_internal.cc \
+    tensorflow/lite/delegates/nnapi/acceleration_test_list.cc \
+    tensorflow/lite/delegates/nnapi/acceleration_test_util.cc
 LOCAL_CPP_EXTENSION := .cc
 
 LOCAL_C_INCLUDES += external/flatbuffers/include
@@ -38,6 +42,7 @@
 
 LOCAL_SHARED_LIBRARIES := libandroid liblog libneuralnetworks
 LOCAL_STATIC_LIBRARIES := libgtest_ndk_c++ libgmock_ndk libtflite_static
+LOCAL_HEADER_LIBRARIES := libeigen gemmlowp_headers
 LOCAL_SDK_VERSION := current
 LOCAL_NDK_STL_VARIANT := c++_static
 include $(BUILD_STATIC_LIBRARY)
diff --git a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/ConditionProviderServiceTest.java b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/ConditionProviderServiceTest.java
index 6e87253..b999cfe 100644
--- a/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/ConditionProviderServiceTest.java
+++ b/tests/tests/notificationlegacy/notificationlegacy27/src/android/app/notification/legacy/cts/ConditionProviderServiceTest.java
@@ -39,6 +39,7 @@
 import android.net.Uri;
 import android.os.ParcelFileDescriptor;
 import android.service.notification.Condition;
+import android.service.notification.ZenModeConfig;
 import android.util.ArraySet;
 import android.util.Log;
 
@@ -175,6 +176,7 @@
         // trigger a change in the bound service's condition
         ((LegacyConditionProviderService) LegacyConditionProviderService.getInstance())
                 .toggleDND(false);
+        sleep(500);
 
         // verify that the unbound service maintains it's DND vote
         assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter());
@@ -293,8 +295,13 @@
     }
 
     private void addRule(ComponentName cn, int filter, boolean enabled) {
+        final Uri conditionId = new Uri.Builder()
+                .scheme(Condition.SCHEME)
+                .authority(ZenModeConfig.SYSTEM_AUTHORITY)
+                .appendPath(cn.toString())
+                .build();
         String id = mNm.addAutomaticZenRule(new AutomaticZenRule("name",
-                cn, Uri.EMPTY, filter, enabled));
+                cn, conditionId, filter, enabled));
         Log.d(TAG, "Created rule with id " + id);
         ids.add(id);
     }
diff --git a/tests/tests/permission/src/android/permission/cts/TvPermissionTest.java b/tests/tests/permission/src/android/permission/cts/TvPermissionTest.java
index 6a3290f..e61b266 100644
--- a/tests/tests/permission/src/android/permission/cts/TvPermissionTest.java
+++ b/tests/tests/permission/src/android/permission/cts/TvPermissionTest.java
@@ -50,6 +50,8 @@
             fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
         } catch (SecurityException e) {
             // Expected exception
+        } catch (IllegalArgumentException e) {
+            // TvProvider is not visable for instant app
         }
     }
 
@@ -60,6 +62,8 @@
             fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
         } catch (SecurityException e) {
             // Expected exception
+        } catch (IllegalArgumentException e) {
+            // TvProvider is not visable for instant app
         }
     }
 
@@ -69,6 +73,8 @@
             fail("Accessing " + tableName + " table should require WRITE_EPG_DATA permission.");
         } catch (SecurityException e) {
             // Expected exception
+        } catch (IllegalArgumentException e) {
+            // TvProvider is not visable for instant app
         }
     }
 
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index 0f1eba7..613b3df 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -2484,7 +2484,7 @@
          <p>Not for use by third-party applications.
          @hide
      -->
-    <permission android:name="android.permission.SUGGEST_PHONE_TIME_AND_ZONE"
+    <permission android:name="android.permission.SUGGEST_TELEPHONY_TIME_AND_ZONE"
         android:protectionLevel="signature|telephony" />
 
     <!-- Allows applications like settings to suggest the user's manually chosen time / time zone.
@@ -3569,6 +3569,13 @@
     <permission android:name="android.permission.CONFIGURE_DISPLAY_COLOR_MODE"
         android:protectionLevel="signature" />
 
+    <!-- Allows an application to control the lights on the device.
+         @hide
+         @SystemApi
+         @TestApi -->
+    <permission android:name="android.permission.CONTROL_DEVICE_LIGHTS"
+        android:protectionLevel="signature|privileged" />
+
     <!-- Allows an application to control the color saturation of the display.
          @hide
          @SystemApi -->
@@ -4629,15 +4636,15 @@
     <!-- Allows an app to log compat change usage.
          @hide  <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.LOG_COMPAT_CHANGE"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|privileged" />
     <!-- Allows an app to read compat change config.
          @hide  <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|privileged" />
     <!-- Allows an app to override compat change config.
          @hide  <p>Not for use by third-party applications.</p> -->
     <permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"
-                android:protectionLevel="signature" />
+                android:protectionLevel="signature|privileged" />
 
     <!-- Allows input events to be monitored. Very dangerous!  @hide -->
     <permission android:name="android.permission.MONITOR_INPUT"
diff --git a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
index be9d1d2..b8391d7 100644
--- a/tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
+++ b/tests/tests/provider/src/android/provider/cts/contacts/ContactsContractIntentsTest.java
@@ -50,7 +50,7 @@
 
     public void testGetContentContactDir() {
         Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
-        intent.setData(ContactsContract.Contacts.CONTENT_URI);
+        intent.setType(ContactsContract.Contacts.CONTENT_ITEM_TYPE);
         assertCanBeHandled(intent);
     }
 }
diff --git a/tests/tests/security/AndroidTest.xml b/tests/tests/security/AndroidTest.xml
index 45220d9..4dfec99 100644
--- a/tests/tests/security/AndroidTest.xml
+++ b/tests/tests/security/AndroidTest.xml
@@ -17,7 +17,7 @@
     <option name="test-suite-tag" value="cts" />
     <option name="config-descriptor:metadata" key="component" value="security" />
     <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
-    <option name="config-descriptor:metadata" key="parameter" value="instant_app" />
+    <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
     <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" />
diff --git a/tests/tests/security/src/android/security/cts/ServicePermissionsTest.java b/tests/tests/security/src/android/security/cts/ServicePermissionsTest.java
index 60e27e6..bccbe59 100644
--- a/tests/tests/security/src/android/security/cts/ServicePermissionsTest.java
+++ b/tests/tests/security/src/android/security/cts/ServicePermissionsTest.java
@@ -107,6 +107,7 @@
             } catch (SecurityException e) {
                 String msg = e.getMessage();
                 if ((msg == null) || msg.contains("android.permission.DUMP")) {
+                    Log.d(TAG, "Service " + service + " correctly checked permission");
                     // Service correctly checked for DUMP permission, yay
                 } else {
                     // Service is throwing about something else; they're
@@ -115,7 +116,9 @@
                     continue;
                 }
             } catch (TransactionTooLargeException | DeadObjectException e) {
-                // SELinux likely prevented the dump - assume safe
+                // SELinux likely prevented the dump - assume safe, but log anywasy
+                // (as the exception might happens in some devices but not on others)
+                Log.w(TAG, "Service " + service + " threw exception: " + e);
                 continue;
             } finally {
                 out.close();
diff --git a/tests/tests/security/src/android/security/cts/StagefrightTest.java b/tests/tests/security/src/android/security/cts/StagefrightTest.java
index d1daa5a..fdf5676 100644
--- a/tests/tests/security/src/android/security/cts/StagefrightTest.java
+++ b/tests/tests/security/src/android/security/cts/StagefrightTest.java
@@ -1240,8 +1240,10 @@
             Pattern.compile("android\\.hardware\\.drm@\\d+?\\.\\d+?-service"),
             Pattern.compile("android\\.hardware\\.drm@\\d+?\\.\\d+?-service\\.clearkey"),
             Pattern.compile("android\\.hardware\\.drm@\\d+?\\.\\d+?-service\\.widevine"),
+            Pattern.compile("omx@\\d+?\\.\\d+?-service"),  // name:omx@1.0-service
             Pattern.compile("android\\.process\\.media"),
             Pattern.compile("mediadrmserver"),
+            Pattern.compile("mediaextractor"),
             Pattern.compile("media\\.extractor"),
             Pattern.compile("media\\.metrics"),
             Pattern.compile("mediaserver"),
diff --git a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
index 116cc12..a6fe2bf 100644
--- a/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/CallDetailsTest.java
@@ -283,8 +283,8 @@
         mConnection.setConnectionProperties(Connection.PROPERTY_IS_RTT);
         assertCallProperties(mCall, Call.Details.PROPERTY_RTT);
 
-        mConnection.setConnectionProperties(Connection.PROPERTY_ASSISTED_DIALING_USED);
-        assertCallProperties(mCall, Call.Details.PROPERTY_ASSISTED_DIALING_USED);
+        mConnection.setConnectionProperties(Connection.PROPERTY_ASSISTED_DIALING);
+        assertCallProperties(mCall, Call.Details.PROPERTY_ASSISTED_DIALING);
 
         mConnection.setConnectionProperties(Connection.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL);
         assertCallProperties(mCall, Call.Details.PROPERTY_NETWORK_IDENTIFIED_EMERGENCY_CALL);
@@ -870,16 +870,26 @@
     }
 
     /**
-     * Tests whether the CallLogManager logs the features of a call(HD call and Wifi call)
+     * Tests whether the CallLogManager logs the features of a call(HD call, Wifi call, VoLTE)
      * correctly.
      */
-    public void testLogHdAndWifi() throws Exception {
+    public void testLogFeatures() throws Exception {
         if (!mShouldTestTelecom) {
             return;
         }
 
         // Register content observer on call log and get latch
         CountDownLatch callLogEntryLatch = getCallLogEntryLatch();
+
+        Bundle testBundle = new Bundle();
+        testBundle.putInt(TelecomManager.EXTRA_CALL_NETWORK_TYPE,
+                          TelephonyManager.NETWORK_TYPE_LTE);
+        mConnection.putExtras(testBundle);
+        // Wait for the 2nd invocation; setExtras is called in the setup method.
+        mOnExtrasChangedCounter.waitForCount(2, WAIT_FOR_STATE_CHANGE_TIMEOUT_MS);
+
+        Bundle extra = mCall.getDetails().getExtras();
+
         mCall.disconnect();
 
         // Wait on the call log latch.
@@ -887,11 +897,13 @@
 
         // Verify the contents of the call log
         Cursor callsCursor = mContext.getContentResolver().query(CallLog.Calls.CONTENT_URI, null,
-                "features", null, null);
-        int features = callsCursor.getColumnIndex(CallLog.Calls.FEATURES);
+                null, null, "_id DESC");
+        callsCursor.moveToFirst();
+        int features = callsCursor.getInt(callsCursor.getColumnIndex("features"));
         assertEquals(CallLog.Calls.FEATURES_HD_CALL,
                 features & CallLog.Calls.FEATURES_HD_CALL);
         assertEquals(CallLog.Calls.FEATURES_WIFI, features & CallLog.Calls.FEATURES_WIFI);
+        assertEquals(CallLog.Calls.FEATURES_VOLTE, features & CallLog.Calls.FEATURES_VOLTE);
     }
 
     /**
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java b/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
index 771f09b..cb0d769 100644
--- a/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConferenceTest.java
@@ -199,7 +199,7 @@
         // Consumed internally in Telecom; no verifiable manner to see the end point of this data
         // through public APIs.
         mConferenceObject.setConnectionTime(0);
-        mConferenceObject.setConnectionStartElapsedRealTime(0);
+        mConferenceObject.setConnectionStartElapsedRealtimeMillis(0);
 
         Bundle extras = new Bundle();
         extras.putString(TelecomManager.EXTRA_CALL_DISCONNECT_MESSAGE, "Test");
diff --git a/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java b/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
index e0bb29e..08931eb 100755
--- a/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/ConnectionServiceTest.java
@@ -272,8 +272,8 @@
         connection.setCallDirection(Call.Details.DIRECTION_OUTGOING);
         connection.setConnectTimeMillis(1000L);
         assertEquals(1000L, connection.getConnectTimeMillis());
-        connection.setConnectionStartElapsedRealTime(100L);
-        assertEquals(100L, connection.getConnectElapsedTimeMillis());
+        connection.setConnectionStartElapsedRealtimeMillis(100L);
+        assertEquals(100L, connection.getConnectionStartElapsedRealtimeMillis());
 
         CtsConnectionService.addExistingConnectionToTelecom(TEST_PHONE_ACCOUNT_HANDLE, connection);
         assertNumCalls(mInCallCallbacks.getService(), 2);
diff --git a/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java b/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java
index ec0a280..9bdf6f7 100644
--- a/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/DefaultDialerOperationsTest.java
@@ -97,7 +97,7 @@
         assertEquals(TestUtils.PACKAGE, mTelecomManager.getDefaultDialerPackage());
         assertEquals(mTelecomManager.getDefaultDialerPackage(),
                 ShellIdentityUtils.invokeMethodWithShellPermissions(mTelecomManager,
-                        tm -> tm.getDefaultDialerPackage(Process.myUserHandle().getIdentifier())));
+                        tm -> tm.getDefaultDialerPackage(Process.myUserHandle())));
     }
 
     /** Default dialer should be the default package handling ACTION_DIAL. */
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
index 4ba93db..92cd25b 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CarrierConfigManagerTest.java
@@ -35,6 +35,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 
+
 import android.app.UiAutomation;
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -83,7 +84,7 @@
     @After
     public void tearDown() throws Exception {
         try {
-            setOpMode("android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_ALLOWED);
+            setOpMode("--uid android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_ALLOWED);
         } catch (IOException e) {
             fail();
         }
@@ -166,7 +167,7 @@
         PersistableBundle config;
 
         try {
-            setOpMode("android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_IGNORED);
+            setOpMode("--uid android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_IGNORED);
         } catch (IOException e) {
             fail();
         }
@@ -175,7 +176,7 @@
         assertTrue(config.isEmptyParcel());
 
         try {
-            setOpMode("android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_ALLOWED);
+            setOpMode("--uid android.telephony.cts", OPSTR_READ_PHONE_STATE, MODE_ALLOWED);
         } catch (IOException e) {
             fail();
         }
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 4035372..4c4d93d 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastIntentsTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellBroadcastIntentsTest.java
@@ -44,6 +44,7 @@
     private static final SmsCbLocation TEST_LOCATION = new SmsCbLocation(TEST_PLMN, -1, -1);
     private static final int TEST_SERVICE_CATEGORY = 4097;
     private static final String TEST_LANGUAGE = "en";
+    private static final int TEST_DCS = 0;
     private static final String TEST_BODY = "test body";
     private static final int TEST_PRIORITY = 0;
     private static final int TEST_ETWS_WARNING_TYPE =
@@ -61,9 +62,9 @@
     public void testGetIntentForBackgroundReceivers() {
         try {
             SmsCbMessage message = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE,
-                    TEST_SERIAL, TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
-                    TEST_PRIORITY, TEST_ETWS_INFO, null, TEST_MAX_WAIT_TIME, TEST_GEOS,
-                    TEST_RECEIVED_TIME, TEST_SLOT, TEST_SUB_ID);
+                    TEST_SERIAL, TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_DCS,
+                    TEST_BODY, TEST_PRIORITY, TEST_ETWS_INFO, null,
+                    TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME, TEST_SLOT, TEST_SUB_ID);
 
             CellBroadcastIntents.sendSmsCbReceivedBroadcast(
                     InstrumentationRegistry.getContext(), UserHandle.ALL, message,
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
index ab6937f..e1589b2 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/CellInfoTest.java
@@ -324,8 +324,9 @@
         assertTrue("Invalid timestamp in CellInfo: " + info.getTimeStamp(),
                 info.getTimeStamp() > 0 && info.getTimeStamp() < Long.MAX_VALUE);
 
-        assertTrue("Invalid timestamp in CellInfo: " + info.getTimestampNanos(),
-                info.getTimestampNanos() > 0 && info.getTimestampNanos() < Long.MAX_VALUE);
+        long curTime = SystemClock.elapsedRealtime();
+        assertTrue("Invalid timestamp in CellInfo: " + info.getTimestampMillis(),
+                info.getTimestampMillis() > 0 && info.getTimestampMillis() <= curTime);
 
         if (mRadioHalVersion >= RADIO_HAL_VERSION_1_2) {
             // In HAL 1.2 or greater, the connection status must be reported
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/NetworkRegistrationInfoTest.java b/tests/tests/telephony/current/src/android/telephony/cts/NetworkRegistrationInfoTest.java
index 427ede3..a5e75c5 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/NetworkRegistrationInfoTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/NetworkRegistrationInfoTest.java
@@ -15,17 +15,18 @@
  */
 package android.telephony.cts;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotSame;
-
 import android.telephony.AccessNetworkConstants;
 import android.telephony.NetworkRegistrationInfo;
 import android.telephony.TelephonyManager;
 
-import java.util.Arrays;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertTrue;
 
 import org.junit.Test;
 
+import java.util.Arrays;
+
 public class NetworkRegistrationInfoTest {
 
     @Test
@@ -87,6 +88,30 @@
     }
 
     @Test
+    public void testGetEmergencyServices() {
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setAvailableServices(Arrays.asList(NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY,
+                        NetworkRegistrationInfo.SERVICE_TYPE_VOICE))
+                .build();
+        assertEquals(Arrays.asList(NetworkRegistrationInfo.SERVICE_TYPE_EMERGENCY,
+                NetworkRegistrationInfo.SERVICE_TYPE_VOICE), nri.getAvailableServices());
+    }
+
+     /**
+     * Basic test to ensure {@link NetworkRegistrationInfo#isSearching()} does not throw any
+     * exception.
+     */
+    @Test
+    public void testNetworkRegistrationInfoIsSearching() {
+        NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
+                .setRegistrationState(
+                    NetworkRegistrationInfo.REGISTRATION_STATE_NOT_REGISTERED_SEARCHING)
+                .build();
+        assertTrue(nri.isSearching());
+    }
+
+
+    @Test
     public void testGetDomain() {
         NetworkRegistrationInfo nri = new NetworkRegistrationInfo.Builder()
                 .setDomain(NetworkRegistrationInfo.DOMAIN_CS)
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 b2c532a..5f85093 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/PhoneStateListenerTest.java
@@ -292,6 +292,105 @@
     }
 
     @Test
+    public void testOnAlwaysReportedSignalStrengthChanged() throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+
+        TestThread t = new TestThread(new Runnable() {
+            public void run() {
+                Looper.prepare();
+
+                mListener = new PhoneStateListener() {
+                    @Override
+                    public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+                        synchronized (mLock) {
+                            mSignalStrength = signalStrength;
+                            mLock.notify();
+                        }
+                    }
+                };
+                ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
+                        (tm) -> tm.listen(mListener,
+                                PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH));
+
+                Looper.loop();
+            }
+        });
+
+        assertTrue(mSignalStrength == null);
+        t.start();
+
+        synchronized (mLock) {
+            if (mSignalStrength == null) {
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        t.checkException();
+        assertTrue(mSignalStrength != null);
+
+        // Call SignalStrength methods to make sure they do not throw any exceptions
+        mSignalStrength.getCdmaDbm();
+        mSignalStrength.getCdmaEcio();
+        mSignalStrength.getEvdoDbm();
+        mSignalStrength.getEvdoEcio();
+        mSignalStrength.getEvdoSnr();
+        mSignalStrength.getGsmBitErrorRate();
+        mSignalStrength.getGsmSignalStrength();
+        mSignalStrength.isGsm();
+        mSignalStrength.getLevel();
+    }
+
+    /**
+     * Validate that SecurityException should be thrown when listen
+     * with LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH without LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH
+     * permission.
+     */
+    @Test
+    public void testOnAlwaysReportedSignalStrengthChangedWithoutPermission() throws Throwable {
+        if (mCm.getNetworkInfo(ConnectivityManager.TYPE_MOBILE) == null) {
+            Log.d(TAG, "Skipping test that requires ConnectivityManager.TYPE_MOBILE");
+            return;
+        }
+
+        TestThread t = new TestThread(new Runnable() {
+            public void run() {
+                Looper.prepare();
+
+                mListener = new PhoneStateListener() {
+                    @Override
+                    public void onSignalStrengthsChanged(SignalStrength signalStrength) {
+                        synchronized (mLock) {
+                            mSignalStrength = signalStrength;
+                            mLock.notify();
+                        }
+                    }
+                };
+                try {
+                    mTelephonyManager.listen(mListener,
+                            PhoneStateListener.LISTEN_ALWAYS_REPORTED_SIGNAL_STRENGTH);
+                } catch (SecurityException se) {
+                    mSecurityExceptionThrown = true;
+                    mLock.notify();
+                }
+                Looper.loop();
+            }
+        });
+
+        assertTrue(mSignalStrength == null);
+        t.start();
+
+        synchronized (mLock) {
+            if (!mSecurityExceptionThrown) {
+                mLock.wait(WAIT_TIME);
+            }
+        }
+        assertThat(mSecurityExceptionThrown).isTrue();
+        assertTrue(mSignalStrength == null);
+    }
+
+    @Test
     public void testOnSignalStrengthsChanged() 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/SignalStrengthTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthTest.java
index 3077bbb..0849b64 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SignalStrengthTest.java
@@ -38,13 +38,13 @@
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import org.junit.Before;
+import org.junit.Test;
+
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 
-import org.junit.Before;
-import org.junit.Test;
-
 /**
  * Test SignalStrength to ensure that valid data is being reported and that invalid data is
  * not reported.
@@ -81,10 +81,10 @@
         SignalStrength ss = mTm.getSignalStrength();
         assertNotNull("TelephonyManager.getSignalStrength() returned NULL!", ss);
 
-        long curTime = SystemClock.elapsedRealtimeNanos();
-        assertTrue("Invalid timestamp in SignalStrength: " + ss.getTimestampNanos(),
-                ss.getTimestampNanos() > 0 && ss.getTimestampNanos() < curTime);
-        Log.d(TAG, "Timestamp of SignalStrength: " + Long.toString(ss.getTimestampNanos()));
+        long curTime = SystemClock.elapsedRealtime();
+        assertTrue("Invalid timestamp in SignalStrength: " + ss.getTimestampMillis(),
+                ss.getTimestampMillis() > 0 && ss.getTimestampMillis() <= curTime);
+        Log.d(TAG, "Timestamp of SignalStrength: " + Long.toString(ss.getTimestampMillis()));
 
         List<CellSignalStrength> signalStrengths = ss.getCellSignalStrengths();
 
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsCbMessageTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsCbMessageTest.java
index 6b1ca39..c74332c 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsCbMessageTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsCbMessageTest.java
@@ -74,7 +74,7 @@
             }
         });
         mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
-                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY, TEST_PRIORITY,
+                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY, TEST_PRIORITY,
                 TEST_ETWS_INFO, null, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
                 TEST_SLOT, TEST_SUB_ID);
     }
@@ -154,7 +154,7 @@
                         SmsCbCmasInfo.CMAS_URGENCY_EXPECTED, SmsCbCmasInfo.CMAS_CERTAINTY_LIKELY);
 
         mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
-                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY, TEST_PRIORITY,
+                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY, TEST_PRIORITY,
                 null, info, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
                 TEST_SLOT, TEST_SUB_ID);
         assertEquals(info, mSmsCbMessage.getCmasWarningInfo());
@@ -163,14 +163,14 @@
     @Test
     public void testEmergency() {
         mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
-                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
+                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY,
                 SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
                 TEST_ETWS_INFO, null, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
                 TEST_SLOT, TEST_SUB_ID);
         assertEquals(true, mSmsCbMessage.isEmergencyMessage());
 
         mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
-                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
+                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY,
                 SmsCbMessage.MESSAGE_PRIORITY_NORMAL,
                 TEST_ETWS_INFO, null, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
                 TEST_SLOT, TEST_SUB_ID);
@@ -180,14 +180,14 @@
     @Test
     public void testIsEtws() {
         mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
-                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
+                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY,
                 SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
                 null, TEST_CMAS_INFO, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
                 TEST_SLOT, TEST_SUB_ID);
         assertEquals(false, mSmsCbMessage.isEtwsMessage());
 
         mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
-                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
+                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY,
                 SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
                 TEST_ETWS_INFO, null, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
                 TEST_SLOT, TEST_SUB_ID);
@@ -197,14 +197,14 @@
     @Test
     public void testIsCmas() {
         mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
-                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
+                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY,
                 SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
                 TEST_ETWS_INFO, null, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
                 TEST_SLOT, TEST_SUB_ID);
         assertEquals(false, mSmsCbMessage.isCmasMessage());
 
         mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
-                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
+                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY,
                 SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
                 null, TEST_CMAS_INFO, TEST_MAX_WAIT_TIME, TEST_GEOS, TEST_RECEIVED_TIME,
                 TEST_SLOT, TEST_SUB_ID);
@@ -216,7 +216,7 @@
         assertEquals(false, mSmsCbMessage.needGeoFencingCheck());
 
         mSmsCbMessage = new SmsCbMessage(TEST_MESSAGE_FORMAT, TEST_GEO_SCOPE, TEST_SERIAL,
-                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, TEST_BODY,
+                TEST_LOCATION, TEST_SERVICE_CATEGORY, TEST_LANGUAGE, 0, TEST_BODY,
                 SmsCbMessage.MESSAGE_PRIORITY_EMERGENCY,
                 null, TEST_CMAS_INFO, 100, TEST_GEOS, TEST_RECEIVED_TIME,
                 TEST_SLOT, TEST_SUB_ID);
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java b/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
index 8c855db..7c8c9d46 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SmsMessageTest.java
@@ -23,7 +23,6 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
 
 import android.content.Context;
 import android.content.pm.PackageManager;
@@ -241,26 +240,22 @@
             return;
         }
 
+        SmsMessage.SubmitPdu smsPdu;
         String scAddress = null, destinationAddress = null;
         String message = null;
         boolean statusReportRequested = false;
 
-        try {
-            // null message, null destination
-            SmsMessage.getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested);
-            fail("Should throw NullPointerException");
-        } catch (NullPointerException expected) {
-            // expected
-        }
+        // Null message, null destination
+        smsPdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress, message,
+                statusReportRequested);
+        assertNull(smsPdu);
 
         message = "This is a test message";
-        try {
-            // non-null message
-            SmsMessage.getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested);
-            fail("Should throw NullPointerException");
-        } catch (NullPointerException expected) {
-            // expected
-        }
+
+        // Non-null message, null destination
+        smsPdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress, message,
+                statusReportRequested);
+        assertNull(smsPdu);
 
         if (mTelephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA) {
             // TODO: temp workaround, OCTET encoding for EMS not properly supported
@@ -271,8 +266,8 @@
         destinationAddress = "18004664411";
         message = "This is a test message";
         statusReportRequested = false;
-        SmsMessage.SubmitPdu smsPdu =
-            SmsMessage.getSubmitPdu(scAddress, destinationAddress, message, statusReportRequested);
+        smsPdu = SmsMessage.getSubmitPdu(
+                scAddress, destinationAddress, message, statusReportRequested);
         assertNotNull(smsPdu);
 
         smsPdu = SmsMessage.getSubmitPdu(scAddress, destinationAddress, (short)80,
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 7614ffe..718f261 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -68,6 +68,7 @@
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 
+
 public class SubscriptionManagerTest {
     private SubscriptionManager mSm;
 
@@ -650,12 +651,12 @@
         if (!isSupported()) return;
         boolean enabled = executeWithShellPermissionAndDefault(false, mSm,
                 (sm) -> sm.isSubscriptionEnabled(mSubId));
-        if (isDSDS()) {
-            // Change it to a different value
-            changeAndVerifySubscriptionEnabledValue(mSubId, !enabled);
-            // Reset it back to original
-            changeAndVerifySubscriptionEnabledValue(mSubId, enabled);
-        }
+        // Enable or disable subscription may require users UX confirmation or may not be supported.
+        // Call APIs to make sure there's no crash.
+        executeWithShellPermissionAndDefault(false, mSm,
+                (sm) -> sm.setSubscriptionEnabled(mSubId, !enabled));
+        executeWithShellPermissionAndDefault(false, mSm,
+                (sm) -> sm.setSubscriptionEnabled(mSubId, enabled));
     }
 
     @Test
@@ -732,18 +733,6 @@
         }
     }
 
-    private void changeAndVerifySubscriptionEnabledValue(int subId, boolean targetValue) {
-        boolean changeSuccessfully = executeWithShellPermissionAndDefault(false, mSm,
-                (sm) -> sm.setSubscriptionEnabled(subId, targetValue));
-        if (!changeSuccessfully) {
-            fail("Cannot change subscription " + subId
-                    + " from " + !targetValue + " to " + targetValue);
-        }
-        boolean res = executeWithShellPermissionAndDefault(targetValue, mSm,
-                (sm) -> sm.isSubscriptionEnabled(subId));
-        assertEquals(targetValue, res);
-    }
-
     private <T, U> T executeWithShellPermissionAndDefault(T defaultValue, U targetObject,
             ShellIdentityUtils.ShellPermissionMethodHelper<T, U> helper) {
         try {
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 e8812cc..f48cf47 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -53,6 +53,7 @@
 import android.telephony.Annotation.RadioPowerState;
 import android.telephony.AvailableNetworkInfo;
 import android.telephony.CallAttributes;
+import android.telephony.CallForwardingInfo;
 import android.telephony.CallQuality;
 import android.telephony.CarrierConfigManager;
 import android.telephony.CellLocation;
@@ -164,6 +165,8 @@
     private static final int MAX_FPLMN_NUM = 100;
     private static final int MIN_FPLMN_NUM = 3;
 
+    private static final String TEST_FORWARD_NUMBER = "54321";
+
     static {
         EMERGENCY_NUMBER_SOURCE_SET = new HashSet<Integer>();
         EMERGENCY_NUMBER_SOURCE_SET.add(EmergencyNumber.EMERGENCY_NUMBER_SOURCE_NETWORK_SIGNALING);
@@ -513,6 +516,8 @@
         mTelephonyManager.getSubscriptionId(defaultAccount);
         mTelephonyManager.getCarrierConfig();
         ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.isDataConnectionEnabled());
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
                 (tm) -> tm.isAnyRadioPoweredOn());
         ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mTelephonyManager,
                 (tm) -> tm.resetIms(tm.getSlotIndex()));
@@ -527,6 +532,101 @@
         }
 
         TelephonyManager.getDefaultRespondViaMessageApplication(getContext(), false);
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.getPhoneCapability());
+    }
+
+    @Test
+    public void testGetCallForwarding() {
+        List<Integer> callForwardingReasons = new ArrayList<>();
+        callForwardingReasons.add(CallForwardingInfo.REASON_UNCONDITIONAL);
+        callForwardingReasons.add(CallForwardingInfo.REASON_BUSY);
+        callForwardingReasons.add(CallForwardingInfo.REASON_NO_REPLY);
+        callForwardingReasons.add(CallForwardingInfo.REASON_NOT_REACHABLE);
+        callForwardingReasons.add(CallForwardingInfo.REASON_ALL);
+        callForwardingReasons.add(CallForwardingInfo.REASON_ALL_CONDITIONAL);
+
+        Set<Integer> callForwardingStatus = new HashSet<Integer>();
+        callForwardingStatus.add(CallForwardingInfo.STATUS_INACTIVE);
+        callForwardingStatus.add(CallForwardingInfo.STATUS_ACTIVE);
+        callForwardingStatus.add(CallForwardingInfo.STATUS_FDN_CHECK_FAILURE);
+        callForwardingStatus.add(CallForwardingInfo.STATUS_UNKNOWN_ERROR);
+        callForwardingStatus.add(CallForwardingInfo.STATUS_NOT_SUPPORTED);
+
+        for (int callForwardingReasonToGet : callForwardingReasons) {
+            Log.d(TAG, "[testGetCallForwarding] callForwardingReasonToGet: "
+                    + callForwardingReasonToGet);
+            CallForwardingInfo callForwardingInfo =
+                    ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                            (tm) -> tm.getCallForwarding(callForwardingReasonToGet));
+
+            assertNotNull(callForwardingInfo);
+            assertTrue(callForwardingStatus.contains(callForwardingInfo.getStatus()));
+            assertTrue(callForwardingReasons.contains(callForwardingInfo.getReason()));
+            callForwardingInfo.getNumber();
+            assertTrue(callForwardingInfo.getTimeoutSeconds() >= 0);
+        }
+    }
+
+    @Test
+    public void testSetCallForwarding() {
+        List<Integer> callForwardingReasons = new ArrayList<>();
+        callForwardingReasons.add(CallForwardingInfo.REASON_UNCONDITIONAL);
+        callForwardingReasons.add(CallForwardingInfo.REASON_BUSY);
+        callForwardingReasons.add(CallForwardingInfo.REASON_NO_REPLY);
+        callForwardingReasons.add(CallForwardingInfo.REASON_NOT_REACHABLE);
+        callForwardingReasons.add(CallForwardingInfo.REASON_ALL);
+        callForwardingReasons.add(CallForwardingInfo.REASON_ALL_CONDITIONAL);
+
+        // Enable Call Forwarding
+        for (int callForwardingReasonToEnable : callForwardingReasons) {
+            final CallForwardingInfo callForwardingInfoToEnable = new CallForwardingInfo(
+                    CallForwardingInfo.STATUS_ACTIVE,
+                    callForwardingReasonToEnable,
+                    TEST_FORWARD_NUMBER,
+                    1 /** time seconds */);
+            Log.d(TAG, "[testSetCallForwarding] Enable Call Forwarding. Status: "
+                    + CallForwardingInfo.STATUS_ACTIVE + " Reason: " + callForwardingReasonToEnable
+                    + " Number: " + TEST_FORWARD_NUMBER + " Time Seconds: 1");
+            ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    (tm) -> tm.setCallForwarding(callForwardingInfoToEnable));
+        }
+
+        // Disable Call Forwarding
+        for (int callForwardingReasonToDisable : callForwardingReasons) {
+            final CallForwardingInfo callForwardingInfoToDisable = new CallForwardingInfo(
+                    CallForwardingInfo.STATUS_INACTIVE,
+                    callForwardingReasonToDisable,
+                    TEST_FORWARD_NUMBER,
+                    1 /** time seconds */);
+            Log.d(TAG, "[testSetCallForwarding] Disable Call Forwarding. Status: "
+                    + CallForwardingInfo.STATUS_INACTIVE + " Reason: "
+                    + callForwardingReasonToDisable + " Number: " + TEST_FORWARD_NUMBER
+                    + " Time Seconds: 1");
+            ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                    (tm) -> tm.setCallForwarding(callForwardingInfoToDisable));
+        }
+    }
+
+    @Test
+    public void testGetCallWaitingStatus() {
+        Set<Integer> callWaitingStatus = new HashSet<Integer>();
+        callWaitingStatus.add(TelephonyManager.CALL_WAITING_STATUS_ACTIVE);
+        callWaitingStatus.add(TelephonyManager.CALL_WAITING_STATUS_INACTIVE);
+        callWaitingStatus.add(TelephonyManager.CALL_WAITING_STATUS_UNKNOWN_ERROR);
+        callWaitingStatus.add(TelephonyManager.CALL_WAITING_STATUS_NOT_SUPPORTED);
+
+        int status = ShellIdentityUtils.invokeMethodWithShellPermissions(
+                mTelephonyManager, (tm) -> tm.getCallWaitingStatus());
+        assertTrue(callWaitingStatus.contains(status));
+    }
+
+    @Test
+    public void testSetCallWaitingStatus() {
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.setCallWaitingStatus(true));
+        ShellIdentityUtils.invokeMethodWithShellPermissions(mTelephonyManager,
+                (tm) -> tm.setCallWaitingStatus(false));
     }
 
     @Test
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java
index 5b0492f..aa60c8e 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/EuiccServiceTest.java
@@ -33,6 +33,7 @@
 import android.service.euicc.IDownloadSubscriptionCallback;
 import android.service.euicc.IEraseSubscriptionsCallback;
 import android.service.euicc.IEuiccService;
+import android.service.euicc.IEuiccServiceDumpResultCallback;
 import android.service.euicc.IGetDefaultDownloadableSubscriptionListCallback;
 import android.service.euicc.IGetDownloadableSubscriptionMetadataCallback;
 import android.service.euicc.IGetEidCallback;
@@ -432,4 +433,25 @@
 
         assertTrue(mCallback.isMethodCalled());
     }
+
+    @Test
+    public void testDump() throws Exception {
+        mCountDownLatch = new CountDownLatch(1);
+
+        mEuiccServiceBinder.dump(new IEuiccServiceDumpResultCallback.Stub() {
+            @Override
+            public void onComplete(String logs) {
+                assertEquals(MockEuiccService.MOCK_DUMPED_LOG, logs);
+                mCountDownLatch.countDown();
+            }
+        });
+
+        try {
+            mCountDownLatch.await(CALLBACK_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e) {
+            fail(e.toString());
+        }
+
+        assertTrue(mCallback.isMethodCalled());
+    }
 }
diff --git a/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java b/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java
index c952446..bfd1601 100644
--- a/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java
+++ b/tests/tests/telephony/current/src/android/telephony/euicc/cts/MockEuiccService.java
@@ -29,10 +29,13 @@
 import android.telephony.euicc.EuiccManager;
 import android.telephony.euicc.EuiccManager.OtaStatus;
 
+import java.io.PrintWriter;
+
 /** Dummy implementation of {@link EuiccService} for testing. */
 public class MockEuiccService extends EuiccService {
     static String MOCK_EID = "89000000000000000000000000000000";
     static String MOCK_OS_VERSION = "1.0";
+    static final String MOCK_DUMPED_LOG = "log messages";
 
     interface IMockEuiccServiceCallback {
         void setMethodCalled();
@@ -61,6 +64,12 @@
     }
 
     @Override
+    public void dump(PrintWriter printWriter) {
+        sMockEuiccServiceCallback.setMethodCalled();
+        printWriter.append(MOCK_DUMPED_LOG);
+    }
+
+    @Override
     public void onStartOtaIfNecessary(int slotId, OtaStatusChangedCallback statusChangedCallback) {
         sMockEuiccServiceCallback.setMethodCalled();
         statusChangedCallback.onOtaStatusChanged(EuiccManager.EUICC_OTA_SUCCEEDED);
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 d5a61c8..fc478ef 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
@@ -66,6 +66,7 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.BeforeClass;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -780,6 +781,7 @@
 
     }
 
+    @Ignore("RCS APIs not public yet")
     @Test
     public void testRcsManagerRegistrationCallback() throws Exception {
         if (!ImsUtils.shouldTestImsService()) {
@@ -949,6 +951,7 @@
                 (m) -> m.unregisterImsRegistrationCallback(callback));
     }
 
+    @Ignore("RCS APIs not public yet")
     @Test
     public void testRcsManagerRegistrationState() throws Exception {
         if (!ImsUtils.shouldTestImsService()) {
@@ -1163,6 +1166,7 @@
         }
     }
 
+    @Ignore("RCS APIs not public yet")
     @Test
     public void testRcsCapabilityStatusCallback() throws Exception {
         if (!ImsUtils.shouldTestImsService()) {
@@ -1560,10 +1564,10 @@
                 .build()));
         // The RcsFeature is created when the ImsService is bound. If it wasn't created, then the
         // Framework did not call it.
-        sServiceConnector.getCarrierService().waitForLatchCountdown(
-                TestImsService.LATCH_CREATE_RCS);
-        sServiceConnector.getCarrierService().waitForLatchCountdown(
-                TestImsService.LATCH_RCS_READY);
+        assertTrue("Did not receive createRcsFeature", sServiceConnector.getCarrierService()
+                .waitForLatchCountdown(TestImsService.LATCH_CREATE_RCS));
+        assertTrue("Did not receive RcsFeature#onReady", sServiceConnector.getCarrierService()
+                .waitForLatchCountdown(TestImsService.LATCH_RCS_READY));
         // Make sure the RcsFeature was created in the test service.
         assertNotNull("Device ImsService created, but TestDeviceImsService#createRcsFeature was not"
                 + "called!", sServiceConnector.getCarrierService().getRcsFeature());
@@ -1576,10 +1580,10 @@
                 .build()));
         // The MmTelFeature is created when the ImsService is bound. If it wasn't created, then the
         // Framework did not call it.
-        sServiceConnector.getCarrierService().waitForLatchCountdown(
-                TestImsService.LATCH_CREATE_MMTEL);
-        assertTrue(sServiceConnector.getCarrierService().waitForLatchCountdown(
-                TestImsService.LATCH_MMTEL_READY));
+        assertTrue("Did not receive createMmTelFeature", sServiceConnector.getCarrierService()
+                .waitForLatchCountdown(TestImsService.LATCH_CREATE_MMTEL));
+        assertTrue("Did not receive MmTelFeature#onReady", sServiceConnector.getCarrierService()
+                .waitForLatchCountdown(TestImsService.LATCH_MMTEL_READY));
         assertNotNull("ImsService created, but ImsService#createMmTelFeature was not called!",
                 sServiceConnector.getCarrierService().getMmTelFeature());
     }
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java
index 73e3b05..2eb16cd 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/RcsContactUceCapabilityTest.java
@@ -25,6 +25,7 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -39,6 +40,7 @@
             + "-application.ims.iari.rcs.mnc000.mcc000.testService\"";
 
     @Test
+    @Ignore("RCS APIs not public yet")
     public void createParcelUnparcel() {
         if (!ImsUtils.shouldTestImsService()) {
             return;
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 8ab1f78..755d439 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
@@ -123,53 +123,9 @@
         ImsManager imsManager = getContext().getSystemService(ImsManager.class);
         RcsUceAdapter uceAdapter = imsManager.getImsRcsManager(sTestSub).getUceAdapter();
         assertNotNull("UCE adapter should not be null!", uceAdapter);
-
         // requestCapabilities
         ArrayList<Uri> numbers = new ArrayList<>(1);
         numbers.add(TEST_NUMBER_URI);
-        try {
-            ShellIdentityUtils.invokeThrowableMethodWithShellPermissionsNoReturn(uceAdapter,
-                    (m) -> m.requestCapabilities(Runnable::run, numbers,
-                            new RcsUceAdapter.CapabilitiesCallback() {
-                            }), ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-        } catch (SecurityException e) {
-            fail("requestCapabilities should succeed with READ_PRIVILEGED_PHONE_STATE.");
-        } catch (ImsException e) {
-            // unsupported is a valid fail cause.
-            if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
-                fail("request capabilities failed with code " + e.getCode());
-            }
-        }
-
-        try {
-            uceAdapter.requestCapabilities(Runnable::run, numbers,
-                    new RcsUceAdapter.CapabilitiesCallback() {});
-            fail("requestCapabilities should require READ_PRIVILEGED_PHONE_STATE.");
-        } catch (SecurityException e) {
-            //expected
-        }
-
-        // getUcePublishState
-        try {
-            Integer result = ShellIdentityUtils.invokeThrowableMethodWithShellPermissions(
-                    uceAdapter, RcsUceAdapter::getUcePublishState, ImsException.class,
-                    "android.permission.READ_PRIVILEGED_PHONE_STATE");
-            assertNotNull("result from getUcePublishState should not be null", result);
-        } catch (SecurityException e) {
-            fail("getUcePublishState should succeed with READ_PRIVILEGED_PHONE_STATE.");
-        } catch (ImsException e) {
-            // unsupported is a valid fail cause.
-            if (e.getCode() != ImsException.CODE_ERROR_UNSUPPORTED_OPERATION) {
-                fail("getUcePublishState failed with code " + e.getCode());
-            }
-        }
-        try {
-            uceAdapter.getUcePublishState();
-            fail("requestCapabilities should require READ_PRIVILEGED_PHONE_STATE.");
-        } catch (SecurityException e) {
-            //expected
-        }
 
         //isUceSettingEnabled
         Boolean isUceSettingEnabledResult = null;
diff --git a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
index 9752258..3354158 100644
--- a/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
+++ b/tests/tests/telephony/current/src/android/telephony/ims/cts/TestRcsFeature.java
@@ -16,13 +16,9 @@
 
 package android.telephony.ims.cts;
 
-import android.telephony.ims.feature.CapabilityChangeRequest;
 import android.telephony.ims.feature.RcsFeature;
-import android.telephony.ims.stub.ImsRegistrationImplBase;
 import android.util.Log;
 
-import java.util.List;
-
 public class TestRcsFeature extends RcsFeature {
     private static final String TAG = "CtsTestImsService";
 
@@ -30,9 +26,6 @@
     private final TestImsService.RemovedListener mRemovedListener;
     private final TestImsService.CapabilitiesSetListener mCapSetListener;
 
-    private RcsImsCapabilities mCapabilities =
-            new RcsImsCapabilities(RcsImsCapabilities.CAPABILITY_TYPE_NONE);
-
     TestRcsFeature(TestImsService.ReadyListener readyListener,
             TestImsService.RemovedListener listener,
             TestImsService.CapabilitiesSetListener setListener) {
@@ -58,35 +51,4 @@
         }
         mRemovedListener.onRemoved();
     }
-
-
-    @Override
-    public boolean queryCapabilityConfiguration(int capability, int radioTech) {
-        if (ImsUtils.VDBG) {
-            Log.d(TAG, "TestRcsFeature.queryCapabilityConfiguration called with capability: "
-                    + capability);
-        }
-        return mCapabilities.isCapable(capability);
-    }
-
-    @Override
-    public void changeEnabledCapabilities(CapabilityChangeRequest request,
-            CapabilityCallbackProxy c) {
-        if (ImsUtils.VDBG) {
-            Log.d(TAG, "TestRcsFeature.changeEnabledCapabilities");
-        }
-        List<CapabilityChangeRequest.CapabilityPair> pairs = request.getCapabilitiesToEnable();
-        for (CapabilityChangeRequest.CapabilityPair pair : pairs) {
-            if (pair.getRadioTech() == ImsRegistrationImplBase.REGISTRATION_TECH_LTE) {
-                mCapabilities.addCapabilities(pair.getCapability());
-            }
-        }
-        pairs = request.getCapabilitiesToDisable();
-        for (CapabilityChangeRequest.CapabilityPair pair : pairs) {
-            if (pair.getRadioTech() == ImsRegistrationImplBase.REGISTRATION_TECH_LTE) {
-                mCapabilities.removeCapabilities(pair.getCapability());
-            }
-        }
-        mCapSetListener.onSet();
-    }
 }
diff --git a/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java b/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
index 043d04f..98dbe52 100644
--- a/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
+++ b/tests/tests/tethering/src/android/tethering/cts/TetheringManagerTest.java
@@ -15,13 +15,16 @@
  */
 package android.tethering.test;
 
+import static android.net.TetheringManager.TETHERING_WIFI;
+
 import static org.junit.Assert.fail;
 
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.net.ConnectivityManager;
+import android.net.TetheringManager;
+import android.net.TetheringManager.TetheringRequest;
 import android.os.ConditionVariable;
 
 import androidx.test.InstrumentationRegistry;
@@ -35,6 +38,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
+import java.util.concurrent.Executor;
 import java.util.concurrent.LinkedBlockingQueue;
 import java.util.concurrent.TimeUnit;
 
@@ -43,7 +47,7 @@
 
     private Context mContext;
 
-    private ConnectivityManager mCM;
+    private TetheringManager mTM;
 
     private TetherChangeReceiver mTetherChangeReceiver;
 
@@ -57,10 +61,10 @@
                 .getUiAutomation()
                 .adoptShellPermissionIdentity();
         mContext = InstrumentationRegistry.getContext();
-        mCM = (ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE);
+        mTM = (TetheringManager) mContext.getSystemService(Context.TETHERING_SERVICE);
         mTetherChangeReceiver = new TetherChangeReceiver();
         final IntentFilter filter = new IntentFilter(
-                ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
+                TetheringManager.ACTION_TETHER_STATE_CHANGED);
         final Intent intent = mContext.registerReceiver(mTetherChangeReceiver, filter);
         if (intent != null) mTetherChangeReceiver.onReceive(null, intent);
     }
@@ -81,36 +85,37 @@
 
             TetherState(Intent intent) {
                 mAvailable = intent.getStringArrayListExtra(
-                        ConnectivityManager.EXTRA_AVAILABLE_TETHER);
+                        TetheringManager.EXTRA_AVAILABLE_TETHER);
                 mActive = intent.getStringArrayListExtra(
-                        ConnectivityManager.EXTRA_ACTIVE_TETHER);
+                        TetheringManager.EXTRA_ACTIVE_TETHER);
                 mErrored = intent.getStringArrayListExtra(
-                        ConnectivityManager.EXTRA_ERRORED_TETHER);
+                        TetheringManager.EXTRA_ERRORED_TETHER);
             }
         }
 
         @Override
         public void onReceive(Context content, Intent intent) {
             String action = intent.getAction();
-            if (action.equals(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)) {
+            if (action.equals(TetheringManager.ACTION_TETHER_STATE_CHANGED)) {
                 mResult.add(new TetherState(intent));
             }
         }
 
         public final LinkedBlockingQueue<TetherState> mResult = new LinkedBlockingQueue<>();
 
-        // This method expects either an event where one of the interfaces is active, or an event
-        // where one of the interface is available followed by one where one of the interfaces is
-        // active.
+        // This method expects either an event where one of the interfaces is active, or events
+        // where the interfaces are available followed by one event where one of the interfaces is
+        // active. Here is a typical example for wifi tethering:
+        // AVAILABLE(wlan0) -> AVAILABLE(wlan1) -> ACTIVATE(wlan1).
         public void expectActiveTethering(String[] ifaceRegexs) {
-            TetherState state = pollAndAssertNoError(DEFAULT_TIMEOUT_MS);
-            if (state == null) fail("Do not receive tethering state change broadcast");
-
-            if (isIfaceActive(ifaceRegexs, state)) return;
-
-            if (isIfaceAvailable(ifaceRegexs, state)) {
+            TetherState state = null;
+            while (true) {
                 state = pollAndAssertNoError(DEFAULT_TIMEOUT_MS);
+                if (state == null) fail("Do not receive active state change broadcast");
+
                 if (isIfaceActive(ifaceRegexs, state)) return;
+
+                if (!isIfaceAvailable(ifaceRegexs, state)) break;
             }
 
             fail("Tethering is not actived, available ifaces: " + state.mAvailable.toString()
@@ -175,16 +180,15 @@
         }
     }
 
-    private class OnStartTetheringCallback extends
-            ConnectivityManager.OnStartTetheringCallback {
+    private class StartTetheringCallback extends TetheringManager.StartTetheringCallback {
         @Override
         public void onTetheringStarted() {
             // Do nothing, TetherChangeReceiver will wait until it receives the broadcast.
         }
 
         @Override
-        public void onTetheringFailed() {
-            fail("startTethering fail");
+        public void onTetheringFailed(final int resultCode) {
+            fail("startTethering fail: " + resultCode);
         }
     }
 
@@ -206,18 +210,19 @@
 
     @Test
     public void testStartTetheringWithStateChangeBroadcast() throws Exception {
-        if (!mCM.isTetheringSupported()) return;
+        if (!mTM.isTetheringSupported()) return;
 
-        final String[] wifiRegexs = mCM.getTetherableWifiRegexs();
+        final String[] wifiRegexs = mTM.getTetherableWifiRegexs();
         if (wifiRegexs.length == 0) return;
 
         mTetherChangeReceiver.expectNoActiveTethering(0 /** timeout */);
 
-        final OnStartTetheringCallback startTetheringCallback = new OnStartTetheringCallback();
-        mCM.startTethering(ConnectivityManager.TETHERING_WIFI, true, startTetheringCallback);
+        final StartTetheringCallback startTetheringCallback = new StartTetheringCallback();
+        mTM.startTethering(new TetheringRequest.Builder(TETHERING_WIFI).build(), c -> c.run(),
+                startTetheringCallback);
         mTetherChangeReceiver.expectActiveTethering(wifiRegexs);
 
-        mCM.stopTethering(ConnectivityManager.TETHERING_WIFI);
+        mTM.stopTethering(TETHERING_WIFI);
         mTetherChangeReceiver.expectNoActiveTethering(DEFAULT_TIMEOUT_MS);
     }
 }